mirror of https://github.com/inolen/redream.git
x64 emitter first pass
This commit is contained in:
parent
0cbb175853
commit
4beeeb52f5
|
@ -52,6 +52,7 @@ set(DREAVM_SOURCES
|
||||||
src/cpu/backend/interpreter/interpreter_callbacks.cc
|
src/cpu/backend/interpreter/interpreter_callbacks.cc
|
||||||
src/cpu/backend/x64/x64_backend.cc
|
src/cpu/backend/x64/x64_backend.cc
|
||||||
src/cpu/backend/x64/x64_block.cc
|
src/cpu/backend/x64/x64_block.cc
|
||||||
|
src/cpu/backend/x64/x64_emitter.cc
|
||||||
src/cpu/frontend/sh4/sh4_builder.cc
|
src/cpu/frontend/sh4/sh4_builder.cc
|
||||||
src/cpu/frontend/sh4/sh4_emit.cc
|
src/cpu/frontend/sh4/sh4_emit.cc
|
||||||
src/cpu/frontend/sh4/sh4_frontend.cc
|
src/cpu/frontend/sh4/sh4_frontend.cc
|
||||||
|
@ -268,6 +269,7 @@ set(DREAVM_TEST_SOURCES
|
||||||
src/cpu/backend/interpreter/interpreter_callbacks.cc
|
src/cpu/backend/interpreter/interpreter_callbacks.cc
|
||||||
src/cpu/backend/x64/x64_backend.cc
|
src/cpu/backend/x64/x64_backend.cc
|
||||||
src/cpu/backend/x64/x64_block.cc
|
src/cpu/backend/x64/x64_block.cc
|
||||||
|
src/cpu/backend/x64/x64_emitter.cc
|
||||||
src/cpu/frontend/sh4/sh4_builder.cc
|
src/cpu/frontend/sh4/sh4_builder.cc
|
||||||
src/cpu/frontend/sh4/sh4_emit.cc
|
src/cpu/frontend/sh4/sh4_emit.cc
|
||||||
src/cpu/frontend/sh4/sh4_frontend.cc
|
src/cpu/frontend/sh4/sh4_frontend.cc
|
||||||
|
|
|
@ -8,7 +8,6 @@ template <typename T>
|
||||||
T align(T v, T alignment) {
|
T align(T v, T alignment) {
|
||||||
return (v + alignment - 1) & -alignment;
|
return (v + alignment - 1) & -alignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,46 +1,28 @@
|
||||||
|
#include "core/core.h"
|
||||||
#include "cpu/backend/x64/x64_backend.h"
|
#include "cpu/backend/x64/x64_backend.h"
|
||||||
#include "cpu/backend/x64/x64_block.h"
|
#include "cpu/backend/x64/x64_block.h"
|
||||||
|
|
||||||
|
using namespace dreavm::core;
|
||||||
using namespace dreavm::cpu;
|
using namespace dreavm::cpu;
|
||||||
using namespace dreavm::cpu::backend;
|
using namespace dreavm::cpu::backend;
|
||||||
using namespace dreavm::cpu::backend::x64;
|
using namespace dreavm::cpu::backend::x64;
|
||||||
using namespace dreavm::cpu::ir;
|
using namespace dreavm::cpu::ir;
|
||||||
using namespace dreavm::emu;
|
using namespace dreavm::emu;
|
||||||
|
|
||||||
static Register x64_registers[] = {{"rax", VALUE_INT_MASK},
|
static Register x64_registers[] = {
|
||||||
{"rbx", VALUE_INT_MASK},
|
{"rbx", VALUE_INT_MASK}, {"rbp", VALUE_INT_MASK},
|
||||||
{"rcx", VALUE_INT_MASK},
|
{"r12", VALUE_INT_MASK}, {"r13", VALUE_INT_MASK},
|
||||||
{"rdx", VALUE_INT_MASK},
|
{"r14", VALUE_INT_MASK}, {"r15", VALUE_INT_MASK},
|
||||||
{"rsi", VALUE_INT_MASK},
|
{"xmm2", VALUE_FLOAT_MASK}, {"xmm3", VALUE_FLOAT_MASK},
|
||||||
{"rdi", VALUE_INT_MASK},
|
{"xmm4", VALUE_FLOAT_MASK}, {"xmm5", VALUE_FLOAT_MASK},
|
||||||
{"rbp", VALUE_INT_MASK},
|
{"xmm6", VALUE_FLOAT_MASK}, {"xmm7", VALUE_FLOAT_MASK}};
|
||||||
{"rsp", VALUE_INT_MASK},
|
|
||||||
{"r8", VALUE_INT_MASK},
|
|
||||||
{"r9", VALUE_INT_MASK},
|
|
||||||
{"r10", VALUE_INT_MASK},
|
|
||||||
{"r11", VALUE_INT_MASK},
|
|
||||||
{"r12", VALUE_INT_MASK},
|
|
||||||
{"r13", VALUE_INT_MASK},
|
|
||||||
{"r14", VALUE_INT_MASK},
|
|
||||||
{"r15", VALUE_INT_MASK},
|
|
||||||
{"mm0", VALUE_FLOAT_MASK},
|
|
||||||
{"mm1", VALUE_FLOAT_MASK},
|
|
||||||
{"mm2", VALUE_FLOAT_MASK},
|
|
||||||
{"mm3", VALUE_FLOAT_MASK},
|
|
||||||
{"mm4", VALUE_FLOAT_MASK},
|
|
||||||
{"mm5", VALUE_FLOAT_MASK},
|
|
||||||
{"mm6", VALUE_FLOAT_MASK},
|
|
||||||
{"mm7", VALUE_FLOAT_MASK}};
|
|
||||||
|
|
||||||
static const Xbyak::Reg *reg_map[] = {
|
X64Backend::X64Backend(emu::Memory &memory)
|
||||||
&Xbyak::util::rax, &Xbyak::util::rbx, &Xbyak::util::rcx, &Xbyak::util::rdx,
|
: Backend(memory),
|
||||||
&Xbyak::util::rsi, &Xbyak::util::rdi, &Xbyak::util::rbp, &Xbyak::util::rsp,
|
// TODO allocate a 32mb buffer for code for now, this needs to be managed
|
||||||
&Xbyak::util::r8, &Xbyak::util::r9, &Xbyak::util::r10, &Xbyak::util::r11,
|
// soon. Freed from when blocks are freed, etc.
|
||||||
&Xbyak::util::r12, &Xbyak::util::r13, &Xbyak::util::r14, &Xbyak::util::r15,
|
codegen_(1024 * 1024 * 32),
|
||||||
&Xbyak::util::mm0, &Xbyak::util::mm1, &Xbyak::util::mm2, &Xbyak::util::mm3,
|
emitter_(codegen_) {}
|
||||||
&Xbyak::util::mm4, &Xbyak::util::mm5, &Xbyak::util::mm6, &Xbyak::util::mm7};
|
|
||||||
|
|
||||||
X64Backend::X64Backend(emu::Memory &memory) : Backend(memory) {}
|
|
||||||
|
|
||||||
X64Backend::~X64Backend() {}
|
X64Backend::~X64Backend() {}
|
||||||
|
|
||||||
|
@ -53,36 +35,12 @@ int X64Backend::num_registers() const {
|
||||||
bool X64Backend::Init() { return true; }
|
bool X64Backend::Init() { return true; }
|
||||||
|
|
||||||
std::unique_ptr<RuntimeBlock> X64Backend::AssembleBlock(IRBuilder &builder) {
|
std::unique_ptr<RuntimeBlock> X64Backend::AssembleBlock(IRBuilder &builder) {
|
||||||
int guest_cycles = 0;
|
X64Fn fn = emitter_.Emit(builder);
|
||||||
|
|
||||||
// 0. LOAD_CONTEXT 40 %0
|
// get number of guest cycles for this block of code
|
||||||
// 1. LOAD_CONTEXT 36 %1
|
const Value *md_guest_cycles = builder.GetMetadata(MD_GUEST_CYCLES);
|
||||||
// 2. ADD %0 %1 %2 <--- ideally %0 and %2 should re-use the same register
|
CHECK(md_guest_cycles);
|
||||||
// 3. STORE_CONTEXT 40 %2
|
int guest_cycles = md_guest_cycles->value<int32_t>();
|
||||||
// 4. LOAD_CONTEXT 16 %3
|
|
||||||
// 5. BRANCH %3
|
|
||||||
|
|
||||||
// RuntimeContext * is at RCX on Windows, RDI on OSX
|
|
||||||
|
|
||||||
for (auto block : builder.blocks()) {
|
|
||||||
for (auto instr : block->instrs()) {
|
|
||||||
if (instr->op() == OP_LOAD_CONTEXT) {
|
|
||||||
if (instr->arg0()->value<int32_t>() == 40) {
|
|
||||||
gen_.mov(*reg_map[instr->result()->reg()], gen_.dword[gen_.rdi + 40]);
|
|
||||||
} else if (instr->arg0()->value<int32_t>() == 36) {
|
|
||||||
gen_.mov(*reg_map[instr->result()->reg()], gen_.dword[gen_.rdi + 36]);
|
|
||||||
}
|
|
||||||
} else if (instr->op() == OP_ADD) {
|
|
||||||
gen_.add(*reg_map[instr->arg0()->reg()],
|
|
||||||
*reg_map[instr->arg1()->reg()]);
|
|
||||||
} else if (instr->op() == OP_STORE_CONTEXT) {
|
|
||||||
gen_.mov(gen_.dword[gen_.rdi + 40], *reg_map[instr->arg1()->reg()]);
|
|
||||||
} else if (instr->op() == OP_BRANCH) {
|
|
||||||
gen_.ret();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
X64Fn fn = gen_.getCode<X64Fn>();
|
|
||||||
return std::unique_ptr<RuntimeBlock>(new X64Block(guest_cycles, fn));
|
return std::unique_ptr<RuntimeBlock>(new X64Block(guest_cycles, fn));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <xbyak/xbyak.h>
|
#include <xbyak/xbyak.h>
|
||||||
#include "cpu/backend/backend.h"
|
#include "cpu/backend/backend.h"
|
||||||
|
#include "cpu/backend/x64/x64_emitter.h"
|
||||||
#include "cpu/runtime.h"
|
#include "cpu/runtime.h"
|
||||||
|
|
||||||
namespace dreavm {
|
namespace dreavm {
|
||||||
|
@ -22,7 +23,8 @@ class X64Backend : public Backend {
|
||||||
std::unique_ptr<RuntimeBlock> AssembleBlock(ir::IRBuilder &builder);
|
std::unique_ptr<RuntimeBlock> AssembleBlock(ir::IRBuilder &builder);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Xbyak::CodeGenerator gen_;
|
Xbyak::CodeGenerator codegen_;
|
||||||
|
X64Emitter emitter_;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#include <iomanip>
|
||||||
|
#include <beaengine/BeaEngine.h>
|
||||||
#include "cpu/backend/x64/x64_backend.h"
|
#include "cpu/backend/x64/x64_backend.h"
|
||||||
#include "cpu/backend/x64/x64_block.h"
|
#include "cpu/backend/x64/x64_block.h"
|
||||||
#include "emu/profiler.h"
|
#include "emu/profiler.h"
|
||||||
|
@ -12,6 +14,32 @@ X64Block::X64Block(int guest_cycles, X64Fn fn)
|
||||||
X64Block::~X64Block() {}
|
X64Block::~X64Block() {}
|
||||||
|
|
||||||
uint32_t X64Block::Call(emu::Memory *memory, void *guest_ctx) {
|
uint32_t X64Block::Call(emu::Memory *memory, void *guest_ctx) {
|
||||||
fn_(guest_ctx);
|
return fn_(guest_ctx, memory);
|
||||||
return 0xdeadbeef;
|
}
|
||||||
|
|
||||||
|
void X64Block::Dump() {
|
||||||
|
DISASM dsm;
|
||||||
|
dsm.Archi = 64;
|
||||||
|
dsm.EIP = (uintptr_t)fn_;
|
||||||
|
dsm.SecurityBlock = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int len = Disasm(&dsm);
|
||||||
|
if (len == OUT_OF_BLOCK) {
|
||||||
|
LOG(INFO) << "Disasm engine is not allowed to read more memory";
|
||||||
|
break;
|
||||||
|
} else if (len == UNKNOWN_OPCODE) {
|
||||||
|
LOG(INFO) << "Unknown opcode";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(INFO) << std::setw(2) << std::hex << std::setfill('0')
|
||||||
|
<< (int)dsm.VirtualAddr << " " << dsm.CompleteInstr;
|
||||||
|
|
||||||
|
if (dsm.Instruction.BranchType == RetType) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
dsm.EIP = dsm.EIP + len;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "cpu/ir/ir_builder.h"
|
#include "cpu/ir/ir_builder.h"
|
||||||
#include "cpu/runtime.h"
|
#include "cpu/runtime.h"
|
||||||
|
#include "emu/memory.h"
|
||||||
|
|
||||||
namespace dreavm {
|
namespace dreavm {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
namespace backend {
|
namespace backend {
|
||||||
namespace x64 {
|
namespace x64 {
|
||||||
|
|
||||||
typedef void (*X64Fn)(void *guest_ctx);
|
typedef uint32_t (*X64Fn)(void *guest_ctx, emu::Memory *memory);
|
||||||
|
|
||||||
class X64Block : public RuntimeBlock {
|
class X64Block : public RuntimeBlock {
|
||||||
public:
|
public:
|
||||||
|
@ -18,6 +19,7 @@ class X64Block : public RuntimeBlock {
|
||||||
~X64Block();
|
~X64Block();
|
||||||
|
|
||||||
uint32_t Call(emu::Memory *memory, void *guest_ctx);
|
uint32_t Call(emu::Memory *memory, void *guest_ctx);
|
||||||
|
void Dump();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
X64Fn fn_;
|
X64Fn fn_;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,49 @@
|
||||||
|
#ifndef X64_EMITTER_H
|
||||||
|
#define X64_EMITTER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <xbyak/xbyak.h>
|
||||||
|
#include "core/arena.h"
|
||||||
|
#include "cpu/backend/x64/x64_block.h"
|
||||||
|
#include "cpu/runtime.h"
|
||||||
|
|
||||||
|
namespace dreavm {
|
||||||
|
namespace cpu {
|
||||||
|
namespace backend {
|
||||||
|
namespace x64 {
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STACK_OFFSET_GUEST_CONTEXT = 0,
|
||||||
|
STACK_OFFSET_MEMORY = 8,
|
||||||
|
STACK_OFFSET_LOCALS = 16
|
||||||
|
};
|
||||||
|
|
||||||
|
class X64Emitter {
|
||||||
|
public:
|
||||||
|
X64Emitter(Xbyak::CodeGenerator &codegen);
|
||||||
|
|
||||||
|
X64Fn Emit(ir::IRBuilder &builder);
|
||||||
|
|
||||||
|
// helpers for the emitter callbacks
|
||||||
|
const Xbyak::Operand &GetOperand(const ir::Value *v, int size = -1);
|
||||||
|
const Xbyak::Operand &GetOperand(const ir::Value *v,
|
||||||
|
const Xbyak::Operand &tmp);
|
||||||
|
const Xbyak::Reg &GetRegister(const ir::Value *v, const Xbyak::Reg &tmp);
|
||||||
|
const Xbyak::Xmm &GetXMMRegister(const ir::Value *v, const Xbyak::Xmm &tmp);
|
||||||
|
const Xbyak::Xmm &GetXMMRegister(const ir::Value *v,
|
||||||
|
const Xbyak::Operand &prefered,
|
||||||
|
const Xbyak::Xmm &tmp);
|
||||||
|
void CopyOperand(const Xbyak::Operand &from, const Xbyak::Operand &to);
|
||||||
|
void CopyOperand(const ir::Value *v, const Xbyak::Operand &to);
|
||||||
|
bool CanEncodeAsImmediate(const ir::Value *v);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Xbyak::CodeGenerator &c_;
|
||||||
|
core::Arena operand_arena_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -11,31 +11,6 @@ const char *dreavm::cpu::ir::Opnames[NUM_OPCODES] = {
|
||||||
#include "cpu/ir/ir_ops.inc"
|
#include "cpu/ir/ir_ops.inc"
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline bool IsFloatType(ValueTy type) {
|
|
||||||
return type == VALUE_F32 || type == VALUE_F64;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool IsIntType(ValueTy type) { return !IsFloatType(type); }
|
|
||||||
|
|
||||||
static inline int SizeForType(ValueTy type) {
|
|
||||||
switch (type) {
|
|
||||||
case VALUE_I8:
|
|
||||||
return 1;
|
|
||||||
case VALUE_I16:
|
|
||||||
return 2;
|
|
||||||
case VALUE_I32:
|
|
||||||
return 4;
|
|
||||||
case VALUE_I64:
|
|
||||||
return 8;
|
|
||||||
case VALUE_F32:
|
|
||||||
return 4;
|
|
||||||
case VALUE_F64:
|
|
||||||
return 8;
|
|
||||||
case VALUE_BLOCK:
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Value
|
// Value
|
||||||
//
|
//
|
||||||
|
@ -175,7 +150,7 @@ void IRBuilder::Dump() const {
|
||||||
auto res = value_vars.insert(std::make_pair((intptr_t)v, name));
|
auto res = value_vars.insert(std::make_pair((intptr_t)v, name));
|
||||||
it = res.first;
|
it = res.first;
|
||||||
}
|
}
|
||||||
ss << it->second;
|
ss << it->second << " (" << v->reg() << ")";
|
||||||
};
|
};
|
||||||
auto DumpValue = [&](std::stringstream &ss, const Value *v) {
|
auto DumpValue = [&](std::stringstream &ss, const Value *v) {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
|
@ -324,6 +299,9 @@ void IRBuilder::Store(Value *addr, Value *v) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Value *IRBuilder::Cast(Value *v, ValueTy dest_type) {
|
Value *IRBuilder::Cast(Value *v, ValueTy dest_type) {
|
||||||
|
CHECK((IsIntType(v->type()) && IsFloatType(dest_type)) ||
|
||||||
|
(IsFloatType(v->type()) && IsIntType(dest_type)));
|
||||||
|
|
||||||
Instr *instr = AppendInstr(OP_CAST);
|
Instr *instr = AppendInstr(OP_CAST);
|
||||||
Value *result = AllocDynamic(dest_type);
|
Value *result = AllocDynamic(dest_type);
|
||||||
instr->set_arg0(v);
|
instr->set_arg0(v);
|
||||||
|
|
|
@ -87,6 +87,31 @@ class Block;
|
||||||
class Instr;
|
class Instr;
|
||||||
class ValueRef;
|
class ValueRef;
|
||||||
|
|
||||||
|
static inline bool IsFloatType(ValueTy type) {
|
||||||
|
return type == VALUE_F32 || type == VALUE_F64;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool IsIntType(ValueTy type) { return !IsFloatType(type); }
|
||||||
|
|
||||||
|
static inline int SizeForType(ValueTy type) {
|
||||||
|
switch (type) {
|
||||||
|
case VALUE_I8:
|
||||||
|
return 1;
|
||||||
|
case VALUE_I16:
|
||||||
|
return 2;
|
||||||
|
case VALUE_I32:
|
||||||
|
return 4;
|
||||||
|
case VALUE_I64:
|
||||||
|
return 8;
|
||||||
|
case VALUE_F32:
|
||||||
|
return 4;
|
||||||
|
case VALUE_F64:
|
||||||
|
return 8;
|
||||||
|
case VALUE_BLOCK:
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Value {
|
class Value {
|
||||||
public:
|
public:
|
||||||
Value(ValueTy ty);
|
Value(ValueTy ty);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "cpu/ir/passes/register_allocation_pass.h"
|
#include "cpu/ir/passes/register_allocation_pass.h"
|
||||||
|
|
||||||
using namespace dreavm;
|
using namespace dreavm;
|
||||||
|
using namespace dreavm::cpu::backend;
|
||||||
using namespace dreavm::cpu::ir;
|
using namespace dreavm::cpu::ir;
|
||||||
using namespace dreavm::cpu::ir::passes;
|
using namespace dreavm::cpu::ir::passes;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ void RegisterAllocationPass::Run(IRBuilder &builder) {
|
||||||
Value *result = instr->result();
|
Value *result = instr->result();
|
||||||
|
|
||||||
// only allocate registers for results, assume constants can always be
|
// only allocate registers for results, assume constants can always be
|
||||||
// encoded by immediates or that the backend has registers reserved
|
// encoded as immediates or that the backend has registers reserved
|
||||||
// for storing the constants
|
// for storing the constants
|
||||||
if (!result) {
|
if (!result) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -47,36 +48,16 @@ void RegisterAllocationPass::Run(IRBuilder &builder) {
|
||||||
// expire any old intervals, freeing up the registers they claimed
|
// expire any old intervals, freeing up the registers they claimed
|
||||||
ExpireOldIntervals(start);
|
ExpireOldIntervals(start);
|
||||||
|
|
||||||
// if the last argument isn't used after this instruction, its register
|
// first, try and reuse the register of one of the incoming arguments
|
||||||
// can be reused to take advantage of many architectures supporting
|
int reg = ReuuseArgRegister(instr, start, end);
|
||||||
// operations where the destination is the last source argument
|
|
||||||
// FIXME could reorder arguments and do this with any source arguments
|
|
||||||
// meeting the criteria
|
|
||||||
Value *last_arg = instr->arg2()
|
|
||||||
? instr->arg2()
|
|
||||||
: (instr->arg1() ? instr->arg1() : instr->arg0());
|
|
||||||
if (last_arg && !last_arg->constant()) {
|
|
||||||
// get the current interval for this register
|
|
||||||
int last_reg = last_arg->reg();
|
|
||||||
|
|
||||||
if (last_reg != NO_REGISTER) {
|
|
||||||
const std::multiset<Interval>::iterator &it = live_[last_reg];
|
|
||||||
|
|
||||||
// if the argument isn't used after this instruction, reuse its
|
|
||||||
// register for the result
|
|
||||||
if (GetOrdinal(it->end) <= GetOrdinal(start)) {
|
|
||||||
UpdateInterval(it, result, start, end);
|
|
||||||
result->set_reg(last_reg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// else, allocate a new register
|
|
||||||
int reg = AllocFreeRegister(result, start, end);
|
|
||||||
if (reg == NO_REGISTER) {
|
if (reg == NO_REGISTER) {
|
||||||
reg = AllocBlockedRegister(builder, result, start, end);
|
// else, allocate a new register for the result
|
||||||
CHECK_NE(reg, NO_REGISTER);
|
reg = AllocFreeRegister(result, start, end);
|
||||||
|
if (reg == NO_REGISTER) {
|
||||||
|
// if a register couldn't be allocated, spill a register and try again
|
||||||
|
reg = AllocBlockedRegister(builder, result, start, end);
|
||||||
|
CHECK_NE(reg, NO_REGISTER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result->set_reg(reg);
|
result->set_reg(reg);
|
||||||
|
@ -143,9 +124,6 @@ void RegisterAllocationPass::UpdateInterval(
|
||||||
Instr *end) {
|
Instr *end) {
|
||||||
int reg = it->reg;
|
int reg = it->reg;
|
||||||
|
|
||||||
// printf("UpdateRegister %d (%p) -> %d (%p) : (%p)\n", GetOrdinal(start),
|
|
||||||
// start, GetOrdinal(end), end, value);
|
|
||||||
|
|
||||||
// remove the old interval
|
// remove the old interval
|
||||||
intervals_.erase(it);
|
intervals_.erase(it);
|
||||||
|
|
||||||
|
@ -160,19 +138,60 @@ void RegisterAllocationPass::UpdateInterval(
|
||||||
live_[reg] = intervals_.insert(interval);
|
live_[reg] = intervals_.insert(interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
int RegisterAllocationPass::AllocFreeRegister(Value *value, Instr *start,
|
// If the first argument isn't used after this instruction, its register
|
||||||
|
// can be reused to take advantage of many architectures supporting
|
||||||
|
// operations where the destination is the first argument.
|
||||||
|
// TODO could reorder arguments for communicative binary ops and do this
|
||||||
|
// with the second argument as well
|
||||||
|
int RegisterAllocationPass::ReuuseArgRegister(Instr *instr, Instr *start,
|
||||||
Instr *end) {
|
Instr *end) {
|
||||||
if (!num_free_) {
|
if (!instr->arg0() || instr->arg0()->constant()) {
|
||||||
// LOG(WARNING) << "AllocFreeRegister failed for " << GetOrdinal(start);
|
|
||||||
return NO_REGISTER;
|
return NO_REGISTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// printf("AllocFreeRegister %d (%p) -> %d (%p) : (%p)\n", GetOrdinal(start),
|
int last_reg = instr->arg0()->reg();
|
||||||
// start, GetOrdinal(end), end, value);
|
if (last_reg == NO_REGISTER) {
|
||||||
|
return NO_REGISTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the register can hold the result type
|
||||||
|
const Register &r = registers_[last_reg];
|
||||||
|
if (!(r.value_types & 1 << (instr->result()->type()))) {
|
||||||
|
return NO_REGISTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the argument's register is used after this instruction, it can't be
|
||||||
|
// reused
|
||||||
|
const std::multiset<Interval>::iterator &it = live_[last_reg];
|
||||||
|
if (GetOrdinal(it->end) > GetOrdinal(start)) {
|
||||||
|
return NO_REGISTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the argument's register isn't used afterwards, update its interval and
|
||||||
|
// reuse
|
||||||
|
UpdateInterval(it, instr->result(), start, end);
|
||||||
|
|
||||||
|
return last_reg;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RegisterAllocationPass::AllocFreeRegister(Value *value, Instr *start,
|
||||||
|
Instr *end) {
|
||||||
|
// find the first free register that can store this value type
|
||||||
|
// TODO split up free queue into int / float to avoid this scan
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < num_free_; i++) {
|
||||||
|
const Register &r = registers_[free_[i]];
|
||||||
|
if (r.value_types & 1 << (value->type())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == num_free_) {
|
||||||
|
return NO_REGISTER;
|
||||||
|
}
|
||||||
|
|
||||||
// remove register from free queue
|
// remove register from free queue
|
||||||
int reg = free_[0];
|
int reg = free_[i];
|
||||||
free_[0] = free_[--num_free_];
|
free_[i] = free_[--num_free_];
|
||||||
|
|
||||||
// add interval
|
// add interval
|
||||||
Interval interval;
|
Interval interval;
|
||||||
|
@ -191,24 +210,31 @@ int RegisterAllocationPass::AllocFreeRegister(Value *value, Instr *start,
|
||||||
int RegisterAllocationPass::AllocBlockedRegister(IRBuilder &builder,
|
int RegisterAllocationPass::AllocBlockedRegister(IRBuilder &builder,
|
||||||
Value *value, Instr *start,
|
Value *value, Instr *start,
|
||||||
Instr *end) {
|
Instr *end) {
|
||||||
CHECK_EQ(num_free_, 0);
|
// TODO no longer valid due to type masks
|
||||||
CHECK_EQ(num_registers_, (int)intervals_.size());
|
// CHECK_EQ(num_free_, 0);
|
||||||
|
// CHECK_EQ(num_registers_, (int)intervals_.size());
|
||||||
|
|
||||||
|
// spill the register that ends furthest away that can store this type
|
||||||
|
auto it = intervals_.rbegin();
|
||||||
|
auto e = intervals_.rend();
|
||||||
|
for (; it != e; ++it) {
|
||||||
|
const Register &r = registers_[it->reg];
|
||||||
|
|
||||||
|
if (r.value_types & 1 << (value->type())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(it != e);
|
||||||
|
|
||||||
// spill the register that ends furthest away, or possibly this register
|
|
||||||
// itself
|
|
||||||
auto it = --intervals_.end();
|
|
||||||
const Interval &to_spill = *it;
|
const Interval &to_spill = *it;
|
||||||
|
|
||||||
// point spilled value to use stack
|
// point spilled value to use stack
|
||||||
to_spill.value->set_reg(NO_REGISTER);
|
to_spill.value->set_reg(NO_REGISTER);
|
||||||
to_spill.value->set_local(builder.AllocLocal(to_spill.value->type()));
|
to_spill.value->set_local(builder.AllocLocal(to_spill.value->type()));
|
||||||
|
|
||||||
// printf("Spilling %d (%p) -> %d (%p) : (%p)\n", GetOrdinal(to_spill.start),
|
|
||||||
// to_spill.start, GetOrdinal(to_spill.end), to_spill.end, to_spill.value);
|
|
||||||
|
|
||||||
// remove interval
|
// remove interval
|
||||||
free_[num_free_++] = to_spill.reg;
|
free_[num_free_++] = to_spill.reg;
|
||||||
intervals_.erase(it);
|
intervals_.erase(--it.base());
|
||||||
|
|
||||||
return AllocFreeRegister(value, start, end);
|
return AllocFreeRegister(value, start, end);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ class RegisterAllocationPass : public Pass {
|
||||||
void ExpireOldIntervals(Instr *start);
|
void ExpireOldIntervals(Instr *start);
|
||||||
void UpdateInterval(const std::multiset<Interval>::iterator &it, Value *value,
|
void UpdateInterval(const std::multiset<Interval>::iterator &it, Value *value,
|
||||||
Instr *start, Instr *end);
|
Instr *start, Instr *end);
|
||||||
|
int ReuuseArgRegister(Instr *instr, Instr *start, Instr *end);
|
||||||
int AllocFreeRegister(Value *value, Instr *start, Instr *end);
|
int AllocFreeRegister(Value *value, Instr *start, Instr *end);
|
||||||
int AllocBlockedRegister(IRBuilder &builder, Value *value, Instr *start,
|
int AllocBlockedRegister(IRBuilder &builder, Value *value, Instr *start,
|
||||||
Instr *end);
|
Instr *end);
|
||||||
|
|
|
@ -79,7 +79,7 @@ void Runtime::ResetBlocks() { pending_reset_ = true; }
|
||||||
RuntimeBlock *Runtime::CompileBlock(uint32_t addr) {
|
RuntimeBlock *Runtime::CompileBlock(uint32_t addr) {
|
||||||
PROFILER_SCOPE_F("runtime");
|
PROFILER_SCOPE_F("runtime");
|
||||||
|
|
||||||
// LOG(INFO) << "Compiling block 0x" << std::hex << addr;
|
LOG(INFO) << "Compiling block 0x" << std::hex << addr;
|
||||||
|
|
||||||
std::unique_ptr<IRBuilder> builder = frontend_->BuildBlock(addr);
|
std::unique_ptr<IRBuilder> builder = frontend_->BuildBlock(addr);
|
||||||
if (!builder) {
|
if (!builder) {
|
||||||
|
|
|
@ -256,8 +256,8 @@ void SH4::InitMemory() {
|
||||||
void SH4::InitContext() {
|
void SH4::InitContext() {
|
||||||
memset(&ctx_, 0, sizeof(ctx_));
|
memset(&ctx_, 0, sizeof(ctx_));
|
||||||
ctx_.sh4 = this;
|
ctx_.sh4 = this;
|
||||||
// ctx_.pc = 0xa0000000;
|
ctx_.pc = 0xa0000000;
|
||||||
ctx_.pc = 0x0c010000;
|
// ctx_.pc = 0x0c010000;
|
||||||
ctx_.pr = 0xdeadbeef;
|
ctx_.pr = 0xdeadbeef;
|
||||||
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
#define SH4_REG(addr, name, flags, default, reset, sleep, standby, type) \
|
||||||
if (default != HELD) { \
|
if (default != HELD) { \
|
||||||
|
|
|
@ -24,8 +24,8 @@ Emulator::Emulator(System &sys)
|
||||||
processor_(scheduler_, memory_),
|
processor_(scheduler_, memory_),
|
||||||
holly_(scheduler_, memory_, processor_) {
|
holly_(scheduler_, memory_, processor_) {
|
||||||
rt_frontend_ = new SH4Frontend(memory_);
|
rt_frontend_ = new SH4Frontend(memory_);
|
||||||
rt_backend_ = new InterpreterBackend(memory_);
|
// rt_backend_ = new InterpreterBackend(memory_);
|
||||||
// rt_backend_ = new X64Backend(*memory_);
|
rt_backend_ = new X64Backend(memory_);
|
||||||
rb_ = new GLBackend(sys);
|
rb_ = new GLBackend(sys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ void RunSH4Test(const SH4Test &test) {
|
||||||
|
|
||||||
// initialize runtime
|
// initialize runtime
|
||||||
frontend::sh4::SH4Frontend rt_frontend(memory);
|
frontend::sh4::SH4Frontend rt_frontend(memory);
|
||||||
// backend::x64::X64Backend rt_backend(memory);
|
backend::x64::X64Backend rt_backend(memory);
|
||||||
backend::interpreter::InterpreterBackend rt_backend(memory);
|
// backend::interpreter::InterpreterBackend rt_backend(memory);
|
||||||
Runtime runtime(memory);
|
Runtime runtime(memory);
|
||||||
ASSERT_TRUE(runtime.Init(&rt_frontend, &rt_backend));
|
ASSERT_TRUE(runtime.Init(&rt_frontend, &rt_backend));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue