diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 28361060..f8a40f57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,9 +15,11 @@ set(SRCS frontend/arm_types.cpp frontend/disassembler/disassembler_arm.cpp frontend/disassembler/disassembler_thumb.cpp - frontend/ir/ir.cpp + frontend/ir/basic_block.cpp frontend/ir/ir_emitter.cpp + frontend/ir/microinstruction.cpp frontend/ir/opcodes.cpp + frontend/ir/value.cpp frontend/translate/translate.cpp frontend/translate/translate_arm.cpp frontend/translate/translate_arm/branch.cpp @@ -63,9 +65,12 @@ set(HEADERS frontend/decoder/thumb32.h frontend/decoder/vfp2.h frontend/disassembler/disassembler.h - frontend/ir/ir.h + frontend/ir/basic_block.h frontend/ir/ir_emitter.h + frontend/ir/microinstruction.h frontend/ir/opcodes.h + frontend/ir/terminal.h + frontend/ir/value.h frontend/translate/translate.h frontend/translate/translate_arm/translate_arm.h interface/interface.h diff --git a/src/backend_x64/emit_x64.h b/src/backend_x64/emit_x64.h index 2e292d04..d5fe7e5a 100644 --- a/src/backend_x64/emit_x64.h +++ b/src/backend_x64/emit_x64.h @@ -8,13 +8,17 @@ #include #include +#include #include #include "backend_x64/block_of_code.h" #include "backend_x64/reg_alloc.h" #include "common/x64/emitter.h" -#include "frontend/ir/ir.h" +#include "frontend/arm_types.h" +#include "frontend/ir/basic_block.h" +#include "frontend/ir/microinstruction.h" +#include "frontend/ir/terminal.h" #include "interface/interface.h" namespace Dynarmic { diff --git a/src/backend_x64/reg_alloc.h b/src/backend_x64/reg_alloc.h index 1555b467..cfd829a1 100644 --- a/src/backend_x64/reg_alloc.h +++ b/src/backend_x64/reg_alloc.h @@ -6,13 +6,18 @@ #pragma once +#include #include +#include + +#include #include "backend_x64/block_of_code.h" #include "backend_x64/jitstate.h" #include "common/common_types.h" #include "common/x64/emitter.h" -#include "frontend/ir/ir.h" +#include "frontend/ir/microinstruction.h" +#include "frontend/ir/value.h" namespace Dynarmic { namespace BackendX64 { diff --git a/src/frontend/ir/basic_block.cpp b/src/frontend/ir/basic_block.cpp new file mode 100644 index 00000000..7bb13937 --- /dev/null +++ b/src/frontend/ir/basic_block.cpp @@ -0,0 +1,85 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include +#include + +#include "common/string_util.h" +#include "frontend/ir/basic_block.h" + +namespace Dynarmic { +namespace IR { + +std::string DumpBlock(const IR::Block& block) { + std::string ret; + + const auto loc_to_string = [](Arm::LocationDescriptor loc) -> std::string { + return Common::StringFromFormat("{%u,%s,%s,%u}", + loc.PC(), + loc.TFlag() ? "T" : "!T", + loc.EFlag() ? "E" : "!E", + loc.FPSCR()); + }; + + ret += Common::StringFromFormat("Block: location=%s\n", loc_to_string(block.location).c_str()); + ret += Common::StringFromFormat("cycles=%zu", block.cycle_count); + ret += Common::StringFromFormat(", entry_cond=%s", Arm::CondToString(block.cond, true)); + if (block.cond != Arm::Cond::AL) { + ret += Common::StringFromFormat(", cond_fail=%s", loc_to_string(block.cond_failed.get()).c_str()); + } + ret += "\n"; + + std::map inst_to_index; + size_t index = 0; + + const auto arg_to_string = [&inst_to_index](const IR::Value& arg) -> std::string { + if (arg.IsEmpty()) { + return ""; + } else if (!arg.IsImmediate()) { + return Common::StringFromFormat("%%%zu", inst_to_index.at(arg.GetInst())); + } + switch (arg.GetType()) { + case Type::U1: + return Common::StringFromFormat("#%s", arg.GetU1() ? "1" : "0"); + case Type::U8: + return Common::StringFromFormat("#%u", arg.GetU8()); + case Type::U32: + return Common::StringFromFormat("#%#x", arg.GetU32()); + case Type::RegRef: + return Arm::RegToString(arg.GetRegRef()); + case Type::ExtRegRef: + return Arm::ExtRegToString(arg.GetExtRegRef()); + default: + return ""; + } + }; + + for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) { + const Opcode op = inst->GetOpcode(); + + if (GetTypeOf(op) != Type::Void) { + ret += Common::StringFromFormat("%%%-5zu = ", index); + } else { + ret += " "; // '%00000 = ' -> 1 + 5 + 3 = 9 spaces + } + + ret += GetNameOf(op); + + const size_t arg_count = GetNumArgsOf(op); + for (size_t arg_index = 0; arg_index < arg_count; arg_index++) { + ret += arg_index != 0 ? ", " : " "; + ret += arg_to_string(inst->GetArg(arg_index)); + } + + ret += "\n"; + inst_to_index[&*inst] = index++; + } + + return ret; +} + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/basic_block.h b/src/frontend/ir/basic_block.h new file mode 100644 index 00000000..155a4adb --- /dev/null +++ b/src/frontend/ir/basic_block.h @@ -0,0 +1,56 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include +#include + +#include + +#include "common/common_types.h" +#include "common/intrusive_list.h" +#include "common/memory_pool.h" +#include "frontend/arm_types.h" +#include "frontend/ir/microinstruction.h" +#include "frontend/ir/terminal.h" + +namespace Dynarmic { +namespace IR { + +/** + * A basic block. It consists of zero or more instructions followed by exactly one terminal. + * Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to + * the microinstructions. This only matters before chaining is done in order to correctly + * order memory accesses. + */ +class Block final { +public: + explicit Block(const Arm::LocationDescriptor& location) : location(location) {} + + /// Description of the starting location of this block + Arm::LocationDescriptor location; + /// Conditional to pass in order to execute this block + Arm::Cond cond = Arm::Cond::AL; + /// Block to execute next if `cond` did not pass. + boost::optional cond_failed = {}; + + /// List of instructions in this block. + Common::IntrusiveList instructions; + /// Memory pool for instruction list + std::unique_ptr instruction_alloc_pool = std::make_unique(sizeof(Inst), 4096); + /// Terminal instruction of this block. + Terminal terminal = Term::Invalid{}; + + /// Number of cycles this block takes to execute. + size_t cycle_count = 0; +}; + +/// Returns a string representation of the contents of block. Intended for debugging. +std::string DumpBlock(const IR::Block& block); + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/ir.cpp b/src/frontend/ir/ir.cpp deleted file mode 100644 index 88fbba88..00000000 --- a/src/frontend/ir/ir.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* This file is part of the dynarmic project. - * Copyright (c) 2016 MerryMage - * This software may be used and distributed according to the terms of the GNU - * General Public License version 2 or any later version. - */ - -#include -#include - -#include "common/assert.h" -#include "common/string_util.h" -#include "frontend/ir/ir.h" -#include "frontend/ir/opcodes.h" - -namespace Dynarmic { -namespace IR { - -// Value class member definitions - -Value::Value(Inst* value) : type(Type::Opaque) { - inner.inst = value; -} - -Value::Value(Arm::Reg value) : type(Type::RegRef) { - inner.imm_regref = value; -} - -Value::Value(Arm::ExtReg value) : type(Type::ExtRegRef) { - inner.imm_extregref = value; -} - -Value::Value(bool value) : type(Type::U1) { - inner.imm_u1 = value; -} - -Value::Value(u8 value) : type(Type::U8) { - inner.imm_u8 = value; -} - -Value::Value(u32 value) : type(Type::U32) { - inner.imm_u32 = value; -} - -Value::Value(u64 value) : type(Type::U64) { - inner.imm_u64 = value; -} - -bool Value::IsImmediate() const { - if (type == Type::Opaque) - return inner.inst->GetOpcode() == Opcode::Identity ? inner.inst->GetArg(0).IsImmediate() : false; - return true; -} - -bool Value::IsEmpty() const { - return type == Type::Void; -} - -Type Value::GetType() const { - if (type == Type::Opaque) { - if (inner.inst->GetOpcode() == Opcode::Identity) { - return inner.inst->GetArg(0).GetType(); - } else { - return inner.inst->GetType(); - } - } - return type; -} - -Arm::Reg Value::GetRegRef() const { - DEBUG_ASSERT(type == Type::RegRef); - return inner.imm_regref; -} - -Arm::ExtReg Value::GetExtRegRef() const { - DEBUG_ASSERT(type == Type::ExtRegRef); - return inner.imm_extregref; -} - -Inst* Value::GetInst() const { - DEBUG_ASSERT(type == Type::Opaque); - return inner.inst; -} - -bool Value::GetU1() const { - if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) - return inner.inst->GetArg(0).GetU1(); - DEBUG_ASSERT(type == Type::U1); - return inner.imm_u1; -} - -u8 Value::GetU8() const { - if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) - return inner.inst->GetArg(0).GetU8(); - DEBUG_ASSERT(type == Type::U8); - return inner.imm_u8; -} - -u32 Value::GetU32() const { - if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) - return inner.inst->GetArg(0).GetU32(); - DEBUG_ASSERT(type == Type::U32); - return inner.imm_u32; -} - -u64 Value::GetU64() const { - if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) - return inner.inst->GetArg(0).GetU64(); - DEBUG_ASSERT(type == Type::U64); - return inner.imm_u64; -} - -// Inst class member definitions - -Value Inst::GetArg(size_t index) const { - DEBUG_ASSERT(index < GetNumArgsOf(op)); - DEBUG_ASSERT(!args[index].IsEmpty()); - - return args[index]; -} - -void Inst::SetArg(size_t index, Value value) { - DEBUG_ASSERT(index < GetNumArgsOf(op)); - DEBUG_ASSERT(value.GetType() == GetArgTypeOf(op, index) || Type::Opaque == GetArgTypeOf(op, index)); - - if (!args[index].IsImmediate()) { - UndoUse(args[index]); - } - if (!value.IsImmediate()) { - Use(value); - } - - args[index] = value; -} - -void Inst::Invalidate() { - for (auto& value : args) { - if (!value.IsImmediate()) { - UndoUse(value); - } - } -} - -void Inst::ReplaceUsesWith(Value& replacement) { - Invalidate(); - - op = Opcode::Identity; - - if (!replacement.IsImmediate()) { - Use(replacement); - } - - args[0] = replacement; -} - -void Inst::Use(Value& value) { - value.GetInst()->use_count++; - - switch (op){ - case Opcode::GetCarryFromOp: - value.GetInst()->carry_inst = this; - break; - case Opcode::GetOverflowFromOp: - value.GetInst()->overflow_inst = this; - break; - default: - break; - } -} - -void Inst::UndoUse(Value& value) { - value.GetInst()->use_count--; - - switch (op){ - case Opcode::GetCarryFromOp: - value.GetInst()->carry_inst = nullptr; - break; - case Opcode::GetOverflowFromOp: - value.GetInst()->overflow_inst = nullptr; - break; - default: - break; - } -} - -std::string DumpBlock(const IR::Block& block) { - std::string ret; - - const auto loc_to_string = [](Arm::LocationDescriptor loc) -> std::string { - return Common::StringFromFormat("{%u,%s,%s,%u}", - loc.PC(), - loc.TFlag() ? "T" : "!T", - loc.EFlag() ? "E" : "!E", - loc.FPSCR()); - }; - - ret += Common::StringFromFormat("Block: location=%s\n", loc_to_string(block.location).c_str()); - ret += Common::StringFromFormat("cycles=%zu", block.cycle_count); - ret += Common::StringFromFormat(", entry_cond=%s", Arm::CondToString(block.cond, true)); - if (block.cond != Arm::Cond::AL) { - ret += Common::StringFromFormat(", cond_fail=%s", loc_to_string(block.cond_failed.get()).c_str()); - } - ret += "\n"; - - std::map inst_to_index; - size_t index = 0; - - const auto arg_to_string = [&inst_to_index](const IR::Value& arg) -> std::string { - if (arg.IsEmpty()) { - return ""; - } else if (!arg.IsImmediate()) { - return Common::StringFromFormat("%%%zu", inst_to_index.at(arg.GetInst())); - } - switch (arg.GetType()) { - case Type::U1: - return Common::StringFromFormat("#%s", arg.GetU1() ? "1" : "0"); - case Type::U8: - return Common::StringFromFormat("#%u", arg.GetU8()); - case Type::U32: - return Common::StringFromFormat("#%#x", arg.GetU32()); - case Type::RegRef: - return Arm::RegToString(arg.GetRegRef()); - case Type::ExtRegRef: - return Arm::ExtRegToString(arg.GetExtRegRef()); - default: - return ""; - } - }; - - for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) { - const Opcode op = inst->GetOpcode(); - - if (GetTypeOf(op) != Type::Void) { - ret += Common::StringFromFormat("%%%-5zu = ", index); - } else { - ret += " "; // '%00000 = ' -> 1 + 5 + 3 = 9 spaces - } - - ret += GetNameOf(op); - - const size_t arg_count = GetNumArgsOf(op); - for (size_t arg_index = 0; arg_index < arg_count; arg_index++) { - ret += arg_index != 0 ? ", " : " "; - ret += arg_to_string(inst->GetArg(arg_index)); - } - - ret += "\n"; - inst_to_index[&*inst] = index++; - } - - return ret; -} - -} // namespace IR -} // namespace Dynarmic diff --git a/src/frontend/ir/ir.h b/src/frontend/ir/ir.h deleted file mode 100644 index 14abb50e..00000000 --- a/src/frontend/ir/ir.h +++ /dev/null @@ -1,238 +0,0 @@ -/* This file is part of the dynarmic project. - * Copyright (c) 2016 MerryMage - * This software may be used and distributed according to the terms of the GNU - * General Public License version 2 or any later version. - */ - -#pragma once - -#include -#include -#include - -#include -#include - -#include "common/assert.h" -#include "common/common_types.h" -#include "common/intrusive_list.h" -#include "common/memory_pool.h" -#include "frontend/arm_types.h" -#include "frontend/ir/opcodes.h" - -namespace Dynarmic { -namespace IR { - -// ARM JIT Microinstruction Intermediate Representation -// -// This intermediate representation is an SSA IR. It is designed primarily for analysis, -// though it can be lowered into a reduced form for interpretation. Each IR node (Value) -// is a microinstruction of an idealised ARM CPU. The choice of microinstructions is made -// not based on any existing microarchitecture but on ease of implementation. -// -// A basic block is represented as an IR::Block. - -// Type declarations - -struct Value; -class Inst; - -/** - * A representation of a value in the IR. - * A value may either be an immediate or the result of a microinstruction. - */ -struct Value final { -public: - Value() : type(Type::Void) {} - explicit Value(Inst* value); - explicit Value(Arm::Reg value); - explicit Value(Arm::ExtReg value); - explicit Value(bool value); - explicit Value(u8 value); - explicit Value(u32 value); - explicit Value(u64 value); - - bool IsEmpty() const; - bool IsImmediate() const; - Type GetType() const; - - Inst* GetInst() const; - Arm::Reg GetRegRef() const; - Arm::ExtReg GetExtRegRef() const; - bool GetU1() const; - u8 GetU8() const; - u32 GetU32() const; - u64 GetU64() const; - -private: - Type type; - - union { - Inst* inst; // type == Type::Opaque - Arm::Reg imm_regref; - Arm::ExtReg imm_extregref; - bool imm_u1; - u8 imm_u8; - u32 imm_u32; - u64 imm_u64; - } inner; -}; - -/** - * A representation of a microinstruction. A single ARM/Thumb instruction may be - * converted into zero or more microinstructions. - */ -class Inst final : public Common::IntrusiveListNode { -public: - Inst(Opcode op) : op(op) {} - - bool HasUses() const { return use_count > 0; } - - /// Get the microop this microinstruction represents. - Opcode GetOpcode() const { return op; } - /// Get the type this instruction returns. - Type GetType() const { return GetTypeOf(op); } - /// Get the number of arguments this instruction has. - size_t NumArgs() const { return GetNumArgsOf(op); } - - Value GetArg(size_t index) const; - void SetArg(size_t index, Value value); - - void Invalidate(); - - void ReplaceUsesWith(Value& replacement); - - size_t use_count = 0; - Inst* carry_inst = nullptr; - Inst* overflow_inst = nullptr; - -private: - void Use(Value& value); - void UndoUse(Value& value); - - Opcode op; - std::array args; -}; - -namespace Term { - -struct Invalid {}; - -/** - * This terminal instruction calls the interpreter, starting at `next`. - * The interpreter must interpret exactly one instruction. - */ -struct Interpret { - explicit Interpret(const Arm::LocationDescriptor& next_) : next(next_) {} - Arm::LocationDescriptor next; ///< Location at which interpretation starts. -}; - -/** - * This terminal instruction returns control to the dispatcher. - * The dispatcher will use the value in R15 to determine what comes next. - */ -struct ReturnToDispatch {}; - -/** - * This terminal instruction jumps to the basic block described by `next` if we have enough - * cycles remaining. If we do not have enough cycles remaining, we return to the - * dispatcher, which will return control to the host. - */ -struct LinkBlock { - explicit LinkBlock(const Arm::LocationDescriptor& next_) : next(next_) {} - Arm::LocationDescriptor next; ///< Location descriptor for next block. -}; - -/** - * This terminal instruction jumps to the basic block described by `next` unconditionally. - * This is an optimization and MUST only be emitted when this is guaranteed not to result - * in hanging, even in the face of other optimizations. (In practice, this means that only - * forward jumps to short-ish blocks would use this instruction.) - * A backend that doesn't support this optimization may choose to implement this exactly - * as LinkBlock. - */ -struct LinkBlockFast { - explicit LinkBlockFast(const Arm::LocationDescriptor& next_) : next(next_) {} - Arm::LocationDescriptor next; ///< Location descriptor for next block. -}; - -/** - * This terminal instruction checks the top of the Return Stack Buffer against R15. - * If RSB lookup fails, control is returned to the dispatcher. - * This is an optimization for faster function calls. A backend that doesn't support - * this optimization or doesn't have a RSB may choose to implement this exactly as - * ReturnToDispatch. - */ -struct PopRSBHint {}; - -struct If; -struct CheckHalt; -/// A Terminal is the terminal instruction in a MicroBlock. -using Terminal = boost::variant< - Invalid, - Interpret, - ReturnToDispatch, - LinkBlock, - LinkBlockFast, - PopRSBHint, - boost::recursive_wrapper, - boost::recursive_wrapper ->; - -/** - * This terminal instruction conditionally executes one terminal or another depending - * on the run-time state of the ARM flags. - */ -struct If { - If(Arm::Cond if_, Terminal then_, Terminal else_) : if_(if_), then_(then_), else_(else_) {} - Arm::Cond if_; - Terminal then_; - Terminal else_; -}; - -/** - * This terminal instruction checks if a halt was requested. If it wasn't, else_ is - * executed. - */ -struct CheckHalt { - CheckHalt(Terminal else_) : else_(else_) {} - Terminal else_; -}; - -} // namespace Term - -using Term::Terminal; - -/** - * A basic block. It consists of zero or more instructions followed by exactly one terminal. - * Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to - * the microinstructions. This only matters before chaining is done in order to correctly - * order memory accesses. - */ -class Block final { -public: - explicit Block(const Arm::LocationDescriptor& location) : location(location) {} - - /// Description of the starting location of this block - Arm::LocationDescriptor location; - /// Conditional to pass in order to execute this block - Arm::Cond cond = Arm::Cond::AL; - /// Block to execute next if `cond` did not pass. - boost::optional cond_failed = {}; - - /// List of instructions in this block. - Common::IntrusiveList instructions; - /// Memory pool for instruction list - std::unique_ptr instruction_alloc_pool = std::make_unique(sizeof(Inst), 4096); - /// Terminal instruction of this block. - Terminal terminal = Term::Invalid{}; - - /// Number of cycles this block takes to execute. - size_t cycle_count = 0; -}; - -/// Returns a string representation of the contents of block. Intended for debugging. -std::string DumpBlock(const IR::Block& block); - -} // namespace IR -} // namespace Dynarmic diff --git a/src/frontend/ir/ir_emitter.h b/src/frontend/ir/ir_emitter.h index e559c18c..3de5512e 100644 --- a/src/frontend/ir/ir_emitter.h +++ b/src/frontend/ir/ir_emitter.h @@ -6,9 +6,21 @@ #pragma once +#include + +#include "common/common_types.h" #include "frontend/arm_types.h" -#include "frontend/ir/ir.h" +#include "frontend/ir/basic_block.h" #include "frontend/ir/opcodes.h" +#include "frontend/ir/terminal.h" +#include "frontend/ir/value.h" + +// ARM JIT Microinstruction Intermediate Representation +// +// This intermediate representation is an SSA IR. It is designed primarily for analysis, +// though it can be lowered into a reduced form for interpretation. Each IR node (Value) +// is a microinstruction of an idealised ARM CPU. The choice of microinstructions is made +// not based on any existing microarchitecture but on ease of implementation. namespace Dynarmic { namespace Arm { diff --git a/src/frontend/ir/microinstruction.cpp b/src/frontend/ir/microinstruction.cpp new file mode 100644 index 00000000..2a0f5c98 --- /dev/null +++ b/src/frontend/ir/microinstruction.cpp @@ -0,0 +1,85 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include "common/assert.h" +#include "frontend/ir/microinstruction.h" + +namespace Dynarmic { +namespace IR { + +Value Inst::GetArg(size_t index) const { + DEBUG_ASSERT(index < GetNumArgsOf(op)); + DEBUG_ASSERT(!args[index].IsEmpty()); + + return args[index]; +} + +void Inst::SetArg(size_t index, Value value) { + DEBUG_ASSERT(index < GetNumArgsOf(op)); + DEBUG_ASSERT(value.GetType() == GetArgTypeOf(op, index) || Type::Opaque == GetArgTypeOf(op, index)); + + if (!args[index].IsImmediate()) { + UndoUse(args[index]); + } + if (!value.IsImmediate()) { + Use(value); + } + + args[index] = value; +} + +void Inst::Invalidate() { + for (auto& value : args) { + if (!value.IsImmediate()) { + UndoUse(value); + } + } +} + +void Inst::ReplaceUsesWith(Value& replacement) { + Invalidate(); + + op = Opcode::Identity; + + if (!replacement.IsImmediate()) { + Use(replacement); + } + + args[0] = replacement; +} + +void Inst::Use(Value& value) { + value.GetInst()->use_count++; + + switch (op){ + case Opcode::GetCarryFromOp: + value.GetInst()->carry_inst = this; + break; + case Opcode::GetOverflowFromOp: + value.GetInst()->overflow_inst = this; + break; + default: + break; + } +} + +void Inst::UndoUse(Value& value) { + value.GetInst()->use_count--; + + switch (op){ + case Opcode::GetCarryFromOp: + value.GetInst()->carry_inst = nullptr; + break; + case Opcode::GetOverflowFromOp: + value.GetInst()->overflow_inst = nullptr; + break; + default: + break; + } +} + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/microinstruction.h b/src/frontend/ir/microinstruction.h new file mode 100644 index 00000000..730e1b2d --- /dev/null +++ b/src/frontend/ir/microinstruction.h @@ -0,0 +1,56 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/intrusive_list.h" +#include "frontend/ir/opcodes.h" +#include "frontend/ir/value.h" + +namespace Dynarmic { +namespace IR { + +/** + * A representation of a microinstruction. A single ARM/Thumb instruction may be + * converted into zero or more microinstructions. + */ +class Inst final : public Common::IntrusiveListNode { +public: + Inst(Opcode op) : op(op) {} + + bool HasUses() const { return use_count > 0; } + + /// Get the microop this microinstruction represents. + Opcode GetOpcode() const { return op; } + /// Get the type this instruction returns. + Type GetType() const { return GetTypeOf(op); } + /// Get the number of arguments this instruction has. + size_t NumArgs() const { return GetNumArgsOf(op); } + + Value GetArg(size_t index) const; + void SetArg(size_t index, Value value); + + void Invalidate(); + + void ReplaceUsesWith(Value& replacement); + + size_t use_count = 0; + Inst* carry_inst = nullptr; + Inst* overflow_inst = nullptr; + +private: + void Use(Value& value); + void UndoUse(Value& value); + + Opcode op; + std::array args; +}; + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/terminal.h b/src/frontend/ir/terminal.h new file mode 100644 index 00000000..13142751 --- /dev/null +++ b/src/frontend/ir/terminal.h @@ -0,0 +1,106 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include + +#include "common/common_types.h" +#include "frontend/arm_types.h" + +namespace Dynarmic { +namespace IR { +namespace Term { + +struct Invalid {}; + +/** + * This terminal instruction calls the interpreter, starting at `next`. + * The interpreter must interpret exactly one instruction. + */ +struct Interpret { + explicit Interpret(const Arm::LocationDescriptor& next_) : next(next_) {} + Arm::LocationDescriptor next; ///< Location at which interpretation starts. +}; + +/** + * This terminal instruction returns control to the dispatcher. + * The dispatcher will use the value in R15 to determine what comes next. + */ +struct ReturnToDispatch {}; + +/** + * This terminal instruction jumps to the basic block described by `next` if we have enough + * cycles remaining. If we do not have enough cycles remaining, we return to the + * dispatcher, which will return control to the host. + */ +struct LinkBlock { + explicit LinkBlock(const Arm::LocationDescriptor& next_) : next(next_) {} + Arm::LocationDescriptor next; ///< Location descriptor for next block. +}; + +/** + * This terminal instruction jumps to the basic block described by `next` unconditionally. + * This is an optimization and MUST only be emitted when this is guaranteed not to result + * in hanging, even in the face of other optimizations. (In practice, this means that only + * forward jumps to short-ish blocks would use this instruction.) + * A backend that doesn't support this optimization may choose to implement this exactly + * as LinkBlock. + */ +struct LinkBlockFast { + explicit LinkBlockFast(const Arm::LocationDescriptor& next_) : next(next_) {} + Arm::LocationDescriptor next; ///< Location descriptor for next block. +}; + +/** + * This terminal instruction checks the top of the Return Stack Buffer against R15. + * If RSB lookup fails, control is returned to the dispatcher. + * This is an optimization for faster function calls. A backend that doesn't support + * this optimization or doesn't have a RSB may choose to implement this exactly as + * ReturnToDispatch. + */ +struct PopRSBHint {}; + +struct If; +struct CheckHalt; +/// A Terminal is the terminal instruction in a MicroBlock. +using Terminal = boost::variant< + Invalid, + Interpret, + ReturnToDispatch, + LinkBlock, + LinkBlockFast, + PopRSBHint, + boost::recursive_wrapper, + boost::recursive_wrapper +>; + +/** + * This terminal instruction conditionally executes one terminal or another depending + * on the run-time state of the ARM flags. + */ +struct If { + If(Arm::Cond if_, Terminal then_, Terminal else_) : if_(if_), then_(then_), else_(else_) {} + Arm::Cond if_; + Terminal then_; + Terminal else_; +}; + +/** + * This terminal instruction checks if a halt was requested. If it wasn't, else_ is + * executed. + */ +struct CheckHalt { + CheckHalt(Terminal else_) : else_(else_) {} + Terminal else_; +}; + +} // namespace Term + +using Term::Terminal; + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/value.cpp b/src/frontend/ir/value.cpp new file mode 100644 index 00000000..1e2b6444 --- /dev/null +++ b/src/frontend/ir/value.cpp @@ -0,0 +1,107 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#include "common/assert.h" +#include "frontend/ir/microinstruction.h" +#include "frontend/ir/value.h" + +namespace Dynarmic { +namespace IR { + +Value::Value(Inst* value) : type(Type::Opaque) { + inner.inst = value; +} + +Value::Value(Arm::Reg value) : type(Type::RegRef) { + inner.imm_regref = value; +} + +Value::Value(Arm::ExtReg value) : type(Type::ExtRegRef) { + inner.imm_extregref = value; +} + +Value::Value(bool value) : type(Type::U1) { + inner.imm_u1 = value; +} + +Value::Value(u8 value) : type(Type::U8) { + inner.imm_u8 = value; +} + +Value::Value(u32 value) : type(Type::U32) { + inner.imm_u32 = value; +} + +Value::Value(u64 value) : type(Type::U64) { + inner.imm_u64 = value; +} + +bool Value::IsImmediate() const { + if (type == Type::Opaque) + return inner.inst->GetOpcode() == Opcode::Identity ? inner.inst->GetArg(0).IsImmediate() : false; + return true; +} + +bool Value::IsEmpty() const { + return type == Type::Void; +} + +Type Value::GetType() const { + if (type == Type::Opaque) { + if (inner.inst->GetOpcode() == Opcode::Identity) { + return inner.inst->GetArg(0).GetType(); + } else { + return inner.inst->GetType(); + } + } + return type; +} + +Arm::Reg Value::GetRegRef() const { + DEBUG_ASSERT(type == Type::RegRef); + return inner.imm_regref; +} + +Arm::ExtReg Value::GetExtRegRef() const { + DEBUG_ASSERT(type == Type::ExtRegRef); + return inner.imm_extregref; +} + +Inst* Value::GetInst() const { + DEBUG_ASSERT(type == Type::Opaque); + return inner.inst; +} + +bool Value::GetU1() const { + if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) + return inner.inst->GetArg(0).GetU1(); + DEBUG_ASSERT(type == Type::U1); + return inner.imm_u1; +} + +u8 Value::GetU8() const { + if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) + return inner.inst->GetArg(0).GetU8(); + DEBUG_ASSERT(type == Type::U8); + return inner.imm_u8; +} + +u32 Value::GetU32() const { + if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) + return inner.inst->GetArg(0).GetU32(); + DEBUG_ASSERT(type == Type::U32); + return inner.imm_u32; +} + +u64 Value::GetU64() const { + if (type == Type::Opaque && inner.inst->GetOpcode() == Opcode::Identity) + return inner.inst->GetArg(0).GetU64(); + DEBUG_ASSERT(type == Type::U64); + return inner.imm_u64; +} + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/ir/value.h b/src/frontend/ir/value.h new file mode 100644 index 00000000..13b6efa9 --- /dev/null +++ b/src/frontend/ir/value.h @@ -0,0 +1,59 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * This software may be used and distributed according to the terms of the GNU + * General Public License version 2 or any later version. + */ + +#pragma once + +#include "common/common_types.h" +#include "frontend/arm_types.h" + +namespace Dynarmic { +namespace IR { + +class Inst; + +/** + * A representation of a value in the IR. + * A value may either be an immediate or the result of a microinstruction. + */ +struct Value final { +public: + Value() : type(Type::Void) {} + explicit Value(Inst* value); + explicit Value(Arm::Reg value); + explicit Value(Arm::ExtReg value); + explicit Value(bool value); + explicit Value(u8 value); + explicit Value(u32 value); + explicit Value(u64 value); + + bool IsEmpty() const; + bool IsImmediate() const; + Type GetType() const; + + Inst* GetInst() const; + Arm::Reg GetRegRef() const; + Arm::ExtReg GetExtRegRef() const; + bool GetU1() const; + u8 GetU8() const; + u32 GetU32() const; + u64 GetU64() const; + +private: + Type type; + + union { + Inst* inst; // type == Type::Opaque + Arm::Reg imm_regref; + Arm::ExtReg imm_extregref; + bool imm_u1; + u8 imm_u8; + u32 imm_u32; + u64 imm_u64; + } inner; +}; + +} // namespace IR +} // namespace Dynarmic diff --git a/src/frontend/translate/translate.cpp b/src/frontend/translate/translate.cpp index 2dc33755..537aa776 100644 --- a/src/frontend/translate/translate.cpp +++ b/src/frontend/translate/translate.cpp @@ -5,8 +5,8 @@ */ #include "frontend/arm_types.h" -#include "frontend/ir/ir.h" -#include "translate.h" +#include "frontend/ir/basic_block.h" +#include "frontend/translate/translate.h" namespace Dynarmic { namespace Arm { diff --git a/src/frontend/translate/translate.h b/src/frontend/translate/translate.h index 9667ca3f..9739fa09 100644 --- a/src/frontend/translate/translate.h +++ b/src/frontend/translate/translate.h @@ -5,10 +5,15 @@ */ #pragma once +#include "common/common_types.h" #include "frontend/arm_types.h" -#include "frontend/ir/ir.h" namespace Dynarmic { + +namespace IR { + class Block; +} // namespace IR + namespace Arm { using MemoryRead32FuncType = u32 (*)(u32 vaddr); diff --git a/src/frontend/translate/translate_arm.cpp b/src/frontend/translate/translate_arm.cpp index 69c7a168..fdb3a2ee 100644 --- a/src/frontend/translate/translate_arm.cpp +++ b/src/frontend/translate/translate_arm.cpp @@ -8,7 +8,7 @@ #include "frontend/arm_types.h" #include "frontend/decoder/arm.h" #include "frontend/decoder/vfp2.h" -#include "frontend/ir/ir.h" +#include "frontend/ir/basic_block.h" #include "frontend/translate/translate.h" #include "frontend/translate/translate_arm/translate_arm.h" diff --git a/src/ir_opt/dead_code_elimination_pass.cpp b/src/ir_opt/dead_code_elimination_pass.cpp index abfb2a03..419b5fe3 100644 --- a/src/ir_opt/dead_code_elimination_pass.cpp +++ b/src/ir_opt/dead_code_elimination_pass.cpp @@ -5,7 +5,8 @@ */ #include "common/assert.h" -#include "frontend/ir/ir.h" +#include "frontend/ir/basic_block.h" +#include "frontend/ir/opcodes.h" #include "ir_opt/passes.h" namespace Dynarmic { diff --git a/src/ir_opt/get_set_elimination_pass.cpp b/src/ir_opt/get_set_elimination_pass.cpp index 1d7d7f2a..161088b1 100644 --- a/src/ir_opt/get_set_elimination_pass.cpp +++ b/src/ir_opt/get_set_elimination_pass.cpp @@ -4,8 +4,12 @@ * General Public License version 2 or any later version. */ +#include + #include "common/assert.h" -#include "frontend/ir/ir.h" +#include "common/common_types.h" +#include "frontend/ir/basic_block.h" +#include "frontend/ir/value.h" #include "ir_opt/passes.h" namespace Dynarmic { diff --git a/src/ir_opt/verification_pass.cpp b/src/ir_opt/verification_pass.cpp index 2de47a5a..983b9892 100644 --- a/src/ir_opt/verification_pass.cpp +++ b/src/ir_opt/verification_pass.cpp @@ -7,7 +7,9 @@ #include #include "common/assert.h" -#include "frontend/ir/ir.h" +#include "common/common_types.h" +#include "frontend/ir/basic_block.h" +#include "frontend/ir/microinstruction.h" #include "ir_opt/passes.h" namespace Dynarmic { diff --git a/tests/arm/fuzz_arm.cpp b/tests/arm/fuzz_arm.cpp index 7ab91c3f..b53208f9 100644 --- a/tests/arm/fuzz_arm.cpp +++ b/tests/arm/fuzz_arm.cpp @@ -4,9 +4,14 @@ * General Public License version 2 or any later version. */ +#include +#include #include +#include #include #include +#include +#include #include @@ -14,7 +19,7 @@ #include "common/common_types.h" #include "frontend/arm_types.h" #include "frontend/disassembler/disassembler.h" -#include "frontend/ir/ir.h" +#include "frontend/ir/basic_block.h" #include "frontend/translate/translate.h" #include "interface/interface.h" #include "ir_opt/passes.h" diff --git a/tests/arm/fuzz_thumb.cpp b/tests/arm/fuzz_thumb.cpp index d1c200cf..739c9444 100644 --- a/tests/arm/fuzz_thumb.cpp +++ b/tests/arm/fuzz_thumb.cpp @@ -4,23 +4,27 @@ * General Public License version 2 or any later version. */ +#include +#include #include +#include #include #include +#include #include #include "common/bit_util.h" #include "common/common_types.h" #include "frontend/disassembler/disassembler.h" +#include "frontend/ir/basic_block.h" +#include "frontend/translate/translate.h" #include "interface/interface.h" +#include "ir_opt/passes.h" #include "rand_int.h" #include "skyeye_interpreter/dyncom/arm_dyncom_interpreter.h" #include "skyeye_interpreter/skyeye_common/armstate.h" -#include "frontend/translate/translate.h" -#include "ir_opt/passes.h" - struct WriteRecord { size_t size; u32 address;