Moving alloy/ into xenia/cpu/ to start simplifying things.

This commit is contained in:
Ben Vanik
2015-03-24 07:46:18 -07:00
parent 59395318f3
commit 29912f44c0
519 changed files with 2246 additions and 2296 deletions

View File

@@ -0,0 +1,26 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/assembler.h"
namespace xe {
namespace cpu {
namespace backend {
Assembler::Assembler(Backend* backend) : backend_(backend) {}
Assembler::~Assembler() { Reset(); }
int Assembler::Initialize() { return 0; }
void Assembler::Reset() {}
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,58 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_ASSEMBLER_H_
#define XENIA_BACKEND_ASSEMBLER_H_
#include <memory>
namespace xe {
namespace cpu {
namespace hir {
class HIRBuilder;
} // namespace hir
namespace runtime {
class DebugInfo;
class Function;
class FunctionInfo;
class Runtime;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
class Backend;
class Assembler {
public:
Assembler(Backend* backend);
virtual ~Assembler();
virtual int Initialize();
virtual void Reset();
virtual int Assemble(runtime::FunctionInfo* symbol_info,
hir::HIRBuilder* builder, uint32_t debug_info_flags,
std::unique_ptr<runtime::DebugInfo> debug_info,
uint32_t trace_flags,
runtime::Function** out_function) = 0;
protected:
Backend* backend_;
};
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_ASSEMBLER_H_

View File

@@ -0,0 +1,32 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/backend.h"
namespace xe {
namespace cpu {
namespace backend {
using xe::cpu::runtime::Runtime;
Backend::Backend(Runtime* runtime) : runtime_(runtime) {
memset(&machine_info_, 0, sizeof(machine_info_));
}
Backend::~Backend() = default;
int Backend::Initialize() { return 0; }
void* Backend::AllocThreadData() { return nullptr; }
void Backend::FreeThreadData(void* thread_data) {}
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,55 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_BACKEND_H_
#define XENIA_BACKEND_BACKEND_H_
#include <memory>
#include "xenia/cpu/backend/machine_info.h"
namespace xe {
namespace cpu {
namespace runtime {
class Runtime;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
class Assembler;
class Backend {
public:
Backend(runtime::Runtime* runtime);
virtual ~Backend();
runtime::Runtime* runtime() const { return runtime_; }
const MachineInfo* machine_info() const { return &machine_info_; }
virtual int Initialize();
virtual void* AllocThreadData();
virtual void FreeThreadData(void* thread_data);
virtual std::unique_ptr<Assembler> CreateAssembler() = 0;
protected:
runtime::Runtime* runtime_;
MachineInfo machine_info_;
};
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_BACKEND_H_

View File

@@ -0,0 +1,37 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_MACHINE_INFO_H_
#define XENIA_BACKEND_MACHINE_INFO_H_
#include <cstdint>
namespace xe {
namespace cpu {
namespace backend {
struct MachineInfo {
struct RegisterSet {
enum Types {
INT_TYPES = (1 << 1),
FLOAT_TYPES = (1 << 2),
VEC_TYPES = (1 << 3),
};
uint8_t id;
char name[4];
uint32_t types;
uint32_t count;
} register_sets[8];
};
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_MACHINE_INFO_H_

View File

@@ -0,0 +1,14 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'assembler.cc',
'assembler.h',
'backend.cc',
'backend.h',
'machine_info.h',
],
'includes': [
'x64/sources.gypi',
],
}

View File

@@ -0,0 +1,34 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'x64_assembler.cc',
'x64_assembler.h',
'x64_backend.cc',
'x64_backend.h',
'x64_code_cache.h',
'x64_emitter.cc',
'x64_emitter.h',
'x64_function.cc',
'x64_function.h',
'x64_sequence.inl',
'x64_sequences.cc',
'x64_sequences.h',
'x64_thunk_emitter.cc',
'x64_thunk_emitter.h',
'x64_tracers.cc',
'x64_tracers.h',
],
'conditions': [
['OS == "mac" or OS == "linux"', {
'sources': [
'x64_code_cache_posix.cc',
],
}],
['OS == "win"', {
'sources': [
'x64_code_cache_win.cc',
],
}],
],
}

View File

@@ -0,0 +1,135 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_assembler.h"
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/backend/x64/x64_emitter.h"
#include "xenia/cpu/backend/x64/x64_function.h"
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/hir/label.h"
#include "xenia/cpu/runtime/runtime.h"
#include "poly/reset_scope.h"
#include "xenia/profiling.h"
namespace BE {
#include <beaengine/BeaEngine.h>
} // namespace BE
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::runtime;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::runtime::DebugInfo;
using xe::cpu::runtime::Function;
using xe::cpu::runtime::FunctionInfo;
X64Assembler::X64Assembler(X64Backend* backend)
: Assembler(backend), x64_backend_(backend) {}
X64Assembler::~X64Assembler() {
// Emitter must be freed before the allocator.
emitter_.reset();
allocator_.reset();
}
int X64Assembler::Initialize() {
int result = Assembler::Initialize();
if (result) {
return result;
}
allocator_.reset(new XbyakAllocator());
emitter_.reset(new X64Emitter(x64_backend_, allocator_.get()));
return result;
}
void X64Assembler::Reset() {
string_buffer_.Reset();
Assembler::Reset();
}
int X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<DebugInfo> debug_info,
uint32_t trace_flags, Function** out_function) {
SCOPE_profile_cpu_f("cpu");
// Reset when we leave.
poly::make_reset_scope(this);
// Lower HIR -> x64.
void* machine_code = 0;
size_t code_size = 0;
int result = emitter_->Emit(builder, debug_info_flags, debug_info.get(),
trace_flags, machine_code, code_size);
if (result) {
return result;
}
// Stash generated machine code.
if (debug_info_flags & DebugInfoFlags::DEBUG_INFO_MACHINE_CODE_DISASM) {
DumpMachineCode(debug_info.get(), machine_code, code_size, &string_buffer_);
debug_info->set_machine_code_disasm(string_buffer_.ToString());
string_buffer_.Reset();
}
{
X64Function* fn = new X64Function(symbol_info);
fn->set_debug_info(std::move(debug_info));
fn->Setup(machine_code, code_size);
*out_function = fn;
}
return 0;
}
void X64Assembler::DumpMachineCode(DebugInfo* debug_info, void* machine_code,
size_t code_size, poly::StringBuffer* str) {
BE::DISASM disasm = {0};
disasm.Archi = 64;
disasm.Options = BE::Tabulation + BE::MasmSyntax + BE::PrefixedNumeral;
disasm.EIP = (BE::UIntPtr)machine_code;
BE::UIntPtr eip_end = disasm.EIP + code_size;
uint64_t prev_source_offset = 0;
while (disasm.EIP < eip_end) {
// Look up source offset.
auto map_entry =
debug_info->LookupCodeOffset(disasm.EIP - (BE::UIntPtr)machine_code);
if (map_entry) {
if (map_entry->source_offset == prev_source_offset) {
str->Append(" ");
} else {
str->Append("%.8X ", map_entry->source_offset);
prev_source_offset = map_entry->source_offset;
}
} else {
str->Append("? ");
}
size_t len = BE::Disasm(&disasm);
if (len == BE::UNKNOWN_OPCODE) {
break;
}
str->Append("%p %s\n", disasm.EIP, disasm.CompleteInstr);
disasm.EIP += len;
}
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,58 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_ASSEMBLER_H_
#define XENIA_BACKEND_X64_X64_ASSEMBLER_H_
#include <memory>
#include "xenia/cpu/backend/assembler.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64Backend;
class X64Emitter;
class XbyakAllocator;
class X64Assembler : public Assembler {
public:
X64Assembler(X64Backend* backend);
~X64Assembler() override;
int Initialize() override;
void Reset() override;
int Assemble(runtime::FunctionInfo* symbol_info, hir::HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<runtime::DebugInfo> debug_info,
uint32_t trace_flags, runtime::Function** out_function) override;
private:
void DumpMachineCode(runtime::DebugInfo* debug_info, void* machine_code,
size_t code_size, poly::StringBuffer* str);
private:
X64Backend* x64_backend_;
std::unique_ptr<X64Emitter> emitter_;
std::unique_ptr<XbyakAllocator> allocator_;
poly::StringBuffer string_buffer_;
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_ASSEMBLER_H_

View File

@@ -0,0 +1,67 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/backend/x64/x64_assembler.h"
#include "xenia/cpu/backend/x64/x64_code_cache.h"
#include "xenia/cpu/backend/x64/x64_sequences.h"
#include "xenia/cpu/backend/x64/x64_thunk_emitter.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
using xe::cpu::runtime::Runtime;
X64Backend::X64Backend(Runtime* runtime) : Backend(runtime), code_cache_(0) {}
X64Backend::~X64Backend() { delete code_cache_; }
int X64Backend::Initialize() {
int result = Backend::Initialize();
if (result) {
return result;
}
RegisterSequences();
machine_info_.register_sets[0] = {
0, "gpr", MachineInfo::RegisterSet::INT_TYPES, X64Emitter::GPR_COUNT,
};
machine_info_.register_sets[1] = {
1, "xmm", MachineInfo::RegisterSet::FLOAT_TYPES |
MachineInfo::RegisterSet::VEC_TYPES,
X64Emitter::XMM_COUNT,
};
code_cache_ = new X64CodeCache();
result = code_cache_->Initialize();
if (result) {
return result;
}
// Generate thunks used to transition between jitted code and host code.
auto allocator = std::make_unique<XbyakAllocator>();
auto thunk_emitter = std::make_unique<X64ThunkEmitter>(this, allocator.get());
host_to_guest_thunk_ = thunk_emitter->EmitHostToGuestThunk();
guest_to_host_thunk_ = thunk_emitter->EmitGuestToHostThunk();
return result;
}
std::unique_ptr<Assembler> X64Backend::CreateAssembler() {
return std::make_unique<X64Assembler>(this);
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,51 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_BACKEND_H_
#define XENIA_BACKEND_X64_X64_BACKEND_H_
#include "xenia/cpu/backend/backend.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64CodeCache;
#define XENIA_HAS_X64_BACKEND 1
typedef void* (*HostToGuestThunk)(void* target, void* arg0, void* arg1);
typedef void* (*GuestToHostThunk)(void* target, void* arg0, void* arg1);
class X64Backend : public Backend {
public:
X64Backend(runtime::Runtime* runtime);
~X64Backend() override;
X64CodeCache* code_cache() const { return code_cache_; }
HostToGuestThunk host_to_guest_thunk() const { return host_to_guest_thunk_; }
GuestToHostThunk guest_to_host_thunk() const { return guest_to_host_thunk_; }
int Initialize() override;
std::unique_ptr<Assembler> CreateAssembler() override;
private:
X64CodeCache* code_cache_;
HostToGuestThunk host_to_guest_thunk_;
GuestToHostThunk guest_to_host_thunk_;
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_BACKEND_H_

View File

@@ -0,0 +1,48 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_CODE_CACHE_H_
#define XENIA_BACKEND_X64_X64_CODE_CACHE_H_
#include <mutex>
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64CodeChunk;
class X64CodeCache {
public:
X64CodeCache(size_t chunk_size = DEFAULT_CHUNK_SIZE);
virtual ~X64CodeCache();
int Initialize();
// TODO(benvanik): ELF serialization/etc
// TODO(benvanik): keep track of code blocks
// TODO(benvanik): padding/guards/etc
void* PlaceCode(void* machine_code, size_t code_size, size_t stack_size);
private:
const static size_t DEFAULT_CHUNK_SIZE = 4 * 1024 * 1024;
std::mutex lock_;
size_t chunk_size_;
X64CodeChunk* head_chunk_;
X64CodeChunk* active_chunk_;
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_CODE_CACHE_H_

View File

@@ -0,0 +1,98 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_code_cache.h"
#include <sys/mman.h>
#include "poly/assert.h"
#include "poly/math.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64CodeChunk {
public:
X64CodeChunk(size_t chunk_size);
~X64CodeChunk();
public:
X64CodeChunk* next;
size_t capacity;
uint8_t* buffer;
size_t offset;
};
X64CodeCache::X64CodeCache(size_t chunk_size)
: chunk_size_(chunk_size), head_chunk_(NULL), active_chunk_(NULL) {}
X64CodeCache::~X64CodeCache() {
std::lock_guard<std::mutex> guard(lock_);
auto chunk = head_chunk_;
while (chunk) {
auto next = chunk->next;
delete chunk;
chunk = next;
}
head_chunk_ = NULL;
}
int X64CodeCache::Initialize() { return 0; }
void* X64CodeCache::PlaceCode(void* machine_code, size_t code_size,
size_t stack_size) {
// Always move the code to land on 16b alignment. We do this by rounding up
// to 16b so that all offsets are aligned.
code_size = poly::round_up(code_size, 16);
lock_.lock();
if (active_chunk_) {
if (active_chunk_->capacity - active_chunk_->offset < code_size) {
auto next = active_chunk_->next;
if (!next) {
assert_true(code_size < chunk_size_, "need to support larger chunks");
next = new X64CodeChunk(chunk_size_);
active_chunk_->next = next;
}
active_chunk_ = next;
}
} else {
head_chunk_ = active_chunk_ = new X64CodeChunk(chunk_size_);
}
uint8_t* final_address = active_chunk_->buffer + active_chunk_->offset;
active_chunk_->offset += code_size;
lock_.unlock();
// Copy code.
memcpy(final_address, machine_code, code_size);
return final_address;
}
X64CodeChunk::X64CodeChunk(size_t chunk_size)
: next(NULL), capacity(chunk_size), buffer(0), offset(0) {
buffer = (uint8_t*)mmap(nullptr, chunk_size, PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
}
X64CodeChunk::~X64CodeChunk() {
if (buffer) {
munmap(buffer, capacity);
}
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,281 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_code_cache.h"
#include "poly/poly.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64CodeChunk {
public:
X64CodeChunk(size_t chunk_size);
~X64CodeChunk();
public:
X64CodeChunk* next;
size_t capacity;
uint8_t* buffer;
size_t offset;
// Estimate of function sized use to determine initial table capacity.
const static uint32_t ESTIMATED_FN_SIZE = 512;
// Size of unwind info per function.
// TODO(benvanik): move this to emitter.
const static uint32_t UNWIND_INFO_SIZE = 4 + (2 * 1 + 2 + 2);
void* fn_table_handle;
RUNTIME_FUNCTION* fn_table;
uint32_t fn_table_count;
uint32_t fn_table_capacity;
void AddTableEntry(uint8_t* code, size_t code_size, size_t stack_size);
};
X64CodeCache::X64CodeCache(size_t chunk_size)
: chunk_size_(chunk_size), head_chunk_(NULL), active_chunk_(NULL) {}
X64CodeCache::~X64CodeCache() {
std::lock_guard<std::mutex> guard(lock_);
auto chunk = head_chunk_;
while (chunk) {
auto next = chunk->next;
delete chunk;
chunk = next;
}
head_chunk_ = NULL;
}
int X64CodeCache::Initialize() { return 0; }
void* X64CodeCache::PlaceCode(void* machine_code, size_t code_size,
size_t stack_size) {
size_t alloc_size = code_size;
// Add unwind info into the allocation size. Keep things 16b aligned.
alloc_size += poly::round_up(X64CodeChunk::UNWIND_INFO_SIZE, 16);
// Always move the code to land on 16b alignment. We do this by rounding up
// to 16b so that all offsets are aligned.
alloc_size = poly::round_up(alloc_size, 16);
lock_.lock();
if (active_chunk_) {
if (active_chunk_->capacity - active_chunk_->offset < alloc_size) {
auto next = active_chunk_->next;
if (!next) {
assert_true(alloc_size < chunk_size_, "need to support larger chunks");
next = new X64CodeChunk(chunk_size_);
active_chunk_->next = next;
}
active_chunk_ = next;
}
} else {
head_chunk_ = active_chunk_ = new X64CodeChunk(chunk_size_);
}
uint8_t* final_address = active_chunk_->buffer + active_chunk_->offset;
active_chunk_->offset += alloc_size;
// Add entry to fn table.
active_chunk_->AddTableEntry(final_address, alloc_size, stack_size);
lock_.unlock();
// Copy code.
memcpy(final_address, machine_code, code_size);
// This isn't needed on x64 (probably), but is convention.
FlushInstructionCache(GetCurrentProcess(), final_address, alloc_size);
return final_address;
}
X64CodeChunk::X64CodeChunk(size_t chunk_size)
: next(NULL), capacity(chunk_size), buffer(0), offset(0) {
buffer = (uint8_t*)VirtualAlloc(NULL, capacity, MEM_RESERVE | MEM_COMMIT,
PAGE_EXECUTE_READWRITE);
fn_table_capacity =
static_cast<uint32_t>(poly::round_up(capacity / ESTIMATED_FN_SIZE, 16));
size_t table_size = fn_table_capacity * sizeof(RUNTIME_FUNCTION);
fn_table = (RUNTIME_FUNCTION*)malloc(table_size);
fn_table_count = 0;
fn_table_handle = 0;
RtlAddGrowableFunctionTable(&fn_table_handle, fn_table, fn_table_count,
fn_table_capacity, (ULONG_PTR)buffer,
(ULONG_PTR)buffer + capacity);
}
X64CodeChunk::~X64CodeChunk() {
if (fn_table_handle) {
RtlDeleteGrowableFunctionTable(fn_table_handle);
}
if (buffer) {
VirtualFree(buffer, 0, MEM_RELEASE);
}
}
// http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx
namespace {
typedef enum _UNWIND_OP_CODES {
UWOP_PUSH_NONVOL = 0, /* info == register number */
UWOP_ALLOC_LARGE, /* no info, alloc size in next 2 slots */
UWOP_ALLOC_SMALL, /* info == size of allocation / 8 - 1 */
UWOP_SET_FPREG, /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
UWOP_SAVE_NONVOL, /* info == register number, offset in next slot */
UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
UWOP_SAVE_XMM128, /* info == XMM reg number, offset in next slot */
UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
UWOP_PUSH_MACHFRAME /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;
class UNWIND_REGISTER {
public:
enum _ {
RAX = 0,
RCX = 1,
RDX = 2,
RBX = 3,
RSP = 4,
RBP = 5,
RSI = 6,
RDI = 7,
R8 = 8,
R9 = 9,
R10 = 10,
R11 = 11,
R12 = 12,
R13 = 13,
R14 = 14,
R15 = 15,
};
};
typedef union _UNWIND_CODE {
struct {
uint8_t CodeOffset;
uint8_t UnwindOp : 4;
uint8_t OpInfo : 4;
};
USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
typedef struct _UNWIND_INFO {
uint8_t Version : 3;
uint8_t Flags : 5;
uint8_t SizeOfProlog;
uint8_t CountOfCodes;
uint8_t FrameRegister : 4;
uint8_t FrameOffset : 4;
UNWIND_CODE UnwindCode[1];
/* UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
* union {
* OPTIONAL ULONG ExceptionHandler;
* OPTIONAL ULONG FunctionEntry;
* };
* OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;
} // namespace
void X64CodeChunk::AddTableEntry(uint8_t* code, size_t code_size,
size_t stack_size) {
// NOTE: we assume a chunk lock.
if (fn_table_count + 1 > fn_table_capacity) {
// Table exhausted, need to realloc. If this happens a lot we should tune
// the table size to prevent this.
PLOGW("X64CodeCache growing FunctionTable - adjust ESTIMATED_FN_SIZE");
RtlDeleteGrowableFunctionTable(fn_table_handle);
size_t old_size = fn_table_capacity * sizeof(RUNTIME_FUNCTION);
size_t new_size = old_size * 2;
auto new_table = (RUNTIME_FUNCTION*)realloc(fn_table, new_size);
assert_not_null(new_table);
if (!new_table) {
return;
}
fn_table = new_table;
fn_table_capacity *= 2;
RtlAddGrowableFunctionTable(&fn_table_handle, fn_table, fn_table_count,
fn_table_capacity, (ULONG_PTR)buffer,
(ULONG_PTR)buffer + capacity);
}
// Allocate unwind data. We know we have space because we overallocated.
// This should be the tailing 16b with 16b alignment.
size_t unwind_info_offset = offset - UNWIND_INFO_SIZE;
if (!stack_size) {
// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
UNWIND_INFO* unwind_info = (UNWIND_INFO*)(buffer + unwind_info_offset);
unwind_info->Version = 1;
unwind_info->Flags = 0;
unwind_info->SizeOfProlog = 0;
unwind_info->CountOfCodes = 0;
unwind_info->FrameRegister = 0;
unwind_info->FrameOffset = 0;
} else if (stack_size <= 128) {
uint8_t prolog_size = 4;
// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
UNWIND_INFO* unwind_info = (UNWIND_INFO*)(buffer + unwind_info_offset);
unwind_info->Version = 1;
unwind_info->Flags = 0;
unwind_info->SizeOfProlog = prolog_size;
unwind_info->CountOfCodes = 1;
unwind_info->FrameRegister = 0;
unwind_info->FrameOffset = 0;
// http://msdn.microsoft.com/en-us/library/ck9asaa9.aspx
size_t co = 0;
auto& unwind_code = unwind_info->UnwindCode[co++];
unwind_code.CodeOffset =
14; // end of instruction + 1 == offset of next instruction
unwind_code.UnwindOp = UWOP_ALLOC_SMALL;
unwind_code.OpInfo = stack_size / 8 - 1;
} else {
// TODO(benvanik): take as parameters?
uint8_t prolog_size = 7;
// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
UNWIND_INFO* unwind_info = (UNWIND_INFO*)(buffer + unwind_info_offset);
unwind_info->Version = 1;
unwind_info->Flags = 0;
unwind_info->SizeOfProlog = prolog_size;
unwind_info->CountOfCodes = 3;
unwind_info->FrameRegister = 0;
unwind_info->FrameOffset = 0;
// http://msdn.microsoft.com/en-us/library/ck9asaa9.aspx
size_t co = 0;
auto& unwind_code = unwind_info->UnwindCode[co++];
unwind_code.CodeOffset =
7; // end of instruction + 1 == offset of next instruction
unwind_code.UnwindOp = UWOP_ALLOC_LARGE;
unwind_code.OpInfo = 0;
unwind_code = unwind_info->UnwindCode[co++];
unwind_code.FrameOffset = (USHORT)(stack_size) / 8;
}
// Add entry.
auto& fn_entry = fn_table[fn_table_count++];
fn_entry.BeginAddress = (DWORD)(code - buffer);
fn_entry.EndAddress = (DWORD)(fn_entry.BeginAddress + code_size);
fn_entry.UnwindData = (DWORD)unwind_info_offset;
// Notify the function table that it has new entries.
RtlGrowFunctionTable(fn_table_handle, fn_table_count);
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,942 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_emitter.h"
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/backend/x64/x64_code_cache.h"
#include "xenia/cpu/backend/x64/x64_function.h"
#include "xenia/cpu/backend/x64/x64_sequences.h"
#include "xenia/cpu/backend/x64/x64_thunk_emitter.h"
#include "xenia/cpu/cpu-private.h"
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/runtime/debug_info.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/cpu/runtime/symbol_info.h"
#include "xenia/cpu/runtime/thread_state.h"
#include "poly/vec128.h"
#include "xdb/protocol.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using namespace xe::cpu::runtime;
using poly::vec128b;
using poly::vec128f;
using poly::vec128i;
using namespace Xbyak;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::runtime::Function;
using xe::cpu::runtime::FunctionInfo;
using xe::cpu::runtime::SourceMapEntry;
using xe::cpu::runtime::ThreadState;
static const size_t MAX_CODE_SIZE = 1 * 1024 * 1024;
static const size_t STASH_OFFSET = 32;
static const size_t STASH_OFFSET_HIGH = 32 + 32;
// If we are running with tracing on we have to store the EFLAGS in the stack,
// otherwise our calls out to C to print will clear it before DID_CARRY/etc
// can get the value.
#define STORE_EFLAGS 1
const uint32_t X64Emitter::gpr_reg_map_[X64Emitter::GPR_COUNT] = {
Operand::RBX, Operand::R12, Operand::R13, Operand::R14, Operand::R15,
};
const uint32_t X64Emitter::xmm_reg_map_[X64Emitter::XMM_COUNT] = {
6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
};
X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
: CodeGenerator(MAX_CODE_SIZE, AutoGrow, allocator),
runtime_(backend->runtime()),
backend_(backend),
code_cache_(backend->code_cache()),
allocator_(allocator),
current_instr_(0) {}
X64Emitter::~X64Emitter() {}
int X64Emitter::Initialize() { return 0; }
int X64Emitter::Emit(HIRBuilder* builder, uint32_t debug_info_flags,
runtime::DebugInfo* debug_info, uint32_t trace_flags,
void*& out_code_address, size_t& out_code_size) {
SCOPE_profile_cpu_f("cpu");
// Reset.
if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) {
source_map_count_ = 0;
source_map_arena_.Reset();
}
trace_flags_ = trace_flags;
// Fill the generator with code.
size_t stack_size = 0;
int result = Emit(builder, stack_size);
if (result) {
return result;
}
// Copy the final code to the cache and relocate it.
out_code_size = getSize();
out_code_address = Emplace(stack_size);
// Stash source map.
if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) {
debug_info->InitializeSourceMap(
source_map_count_, (SourceMapEntry*)source_map_arena_.CloneContents());
}
return 0;
}
void* X64Emitter::Emplace(size_t stack_size) {
// To avoid changing xbyak, we do a switcharoo here.
// top_ points to the Xbyak buffer, and since we are in AutoGrow mode
// it has pending relocations. We copy the top_ to our buffer, swap the
// pointer, relocate, then return the original scratch pointer for use.
uint8_t* old_address = top_;
void* new_address = code_cache_->PlaceCode(top_, size_, stack_size);
top_ = (uint8_t*)new_address;
ready();
top_ = old_address;
reset();
return new_address;
}
int X64Emitter::Emit(HIRBuilder* builder, size_t& out_stack_size) {
// Calculate stack size. We need to align things to their natural sizes.
// This could be much better (sort by type/etc).
auto locals = builder->locals();
size_t stack_offset = StackLayout::GUEST_STACK_SIZE;
for (auto it = locals.begin(); it != locals.end(); ++it) {
auto slot = *it;
size_t type_size = GetTypeSize(slot->type);
// Align to natural size.
stack_offset = poly::align(stack_offset, type_size);
slot->set_constant((uint32_t)stack_offset);
stack_offset += type_size;
}
// Ensure 16b alignment.
stack_offset -= StackLayout::GUEST_STACK_SIZE;
stack_offset = poly::align(stack_offset, static_cast<size_t>(16));
// Function prolog.
// Must be 16b aligned.
// Windows is very strict about the form of this and the epilog:
// http://msdn.microsoft.com/en-us/library/tawsa7cb.aspx
// TODO(benvanik): save off non-volatile registers so we can use them:
// RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
// Only want to do this if we actually use them, though, otherwise
// it just adds overhead.
// IMPORTANT: any changes to the prolog must be kept in sync with
// X64CodeCache, which dynamically generates exception information.
// Adding or changing anything here must be matched!
const bool emit_prolog = true;
const size_t stack_size = StackLayout::GUEST_STACK_SIZE + stack_offset;
assert_true((stack_size + 8) % 16 == 0);
out_stack_size = stack_size;
stack_size_ = stack_size;
if (emit_prolog) {
sub(rsp, (uint32_t)stack_size);
mov(qword[rsp + StackLayout::GUEST_RCX_HOME], rcx);
mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rdx);
mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], 0);
mov(rdx, qword[rcx + 8]); // membase
}
uint64_t trace_base = runtime_->memory()->trace_base();
if (trace_base && trace_flags_ & TRACE_USER_CALLS) {
mov(rax, trace_base);
mov(r8d, static_cast<uint32_t>(sizeof(xdb::protocol::UserCallEvent)));
lock();
xadd(qword[rax], r8);
mov(rax, static_cast<uint64_t>(xdb::protocol::EventType::USER_CALL) |
(static_cast<uint64_t>(0) << 8) | (0ull << 32));
mov(qword[r8], rax);
EmitGetCurrentThreadId();
mov(word[r8 + 2], ax);
}
// Body.
auto block = builder->first_block();
while (block) {
// Mark block labels.
auto label = block->label_head;
while (label) {
L(label->name);
label = label->next;
}
// Process instructions.
const Instr* instr = block->instr_head;
while (instr) {
const Instr* new_tail = instr;
// Special handling of TRACE_SOURCE.
if (instr->opcode == &OPCODE_TRACE_SOURCE_info) {
if (trace_flags_ & TRACE_SOURCE) {
EmitTraceSource(instr);
}
instr = instr->next;
continue;
}
if (!SelectSequence(*this, instr, &new_tail)) {
// No sequence found!
assert_always();
PLOGE("Unable to process HIR opcode %s", instr->opcode->name);
break;
}
instr = new_tail;
}
block = block->next;
}
// Function epilog.
L("epilog");
EmitTraceUserCallReturn();
if (emit_prolog) {
mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]);
add(rsp, (uint32_t)stack_size);
}
ret();
#if XE_DEBUG
nop();
nop();
nop();
nop();
nop();
#endif // XE_DEBUG
return 0;
}
void X64Emitter::MarkSourceOffset(const Instr* i) {
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
entry->source_offset = i->src1.offset;
entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
entry->code_offset = getSize();
source_map_count_++;
}
void X64Emitter::EmitTraceSource(const Instr* instr) {
uint64_t trace_base = runtime_->memory()->trace_base();
if (!trace_base || !(trace_flags_ & TRACE_SOURCE)) {
return;
}
// TODO(benvanik): make this a function call to some append fn.
uint8_t dest_reg_0 = instr->flags & 0xFF;
uint8_t dest_reg_1 = instr->flags >> 8;
xdb::protocol::EventType event_type;
size_t event_size = 0;
if (dest_reg_0 == 100) {
event_type = xdb::protocol::EventType::INSTR;
event_size = sizeof(xdb::protocol::InstrEvent);
} else if (dest_reg_1 == 100) {
if (dest_reg_0 & (1 << 7)) {
event_type = xdb::protocol::EventType::INSTR_R16;
event_size = sizeof(xdb::protocol::InstrEventR16);
} else {
event_type = xdb::protocol::EventType::INSTR_R8;
event_size = sizeof(xdb::protocol::InstrEventR8);
}
} else {
if (dest_reg_0 & (1 << 7) && dest_reg_1 & (1 << 7)) {
event_type = xdb::protocol::EventType::INSTR_R16_R16;
event_size = sizeof(xdb::protocol::InstrEventR16R16);
} else if (dest_reg_0 & (1 << 7) && !(dest_reg_1 & (1 << 7))) {
event_type = xdb::protocol::EventType::INSTR_R16_R8;
event_size = sizeof(xdb::protocol::InstrEventR16R8);
} else if (!(dest_reg_0 & (1 << 7)) && dest_reg_1 & (1 << 7)) {
event_type = xdb::protocol::EventType::INSTR_R8_R16;
event_size = sizeof(xdb::protocol::InstrEventR8R16);
} else if (!(dest_reg_0 & (1 << 7)) && !(dest_reg_1 & (1 << 7))) {
event_type = xdb::protocol::EventType::INSTR_R8_R8;
event_size = sizeof(xdb::protocol::InstrEventR8R8);
}
}
assert_not_zero(event_size);
mov(rax, trace_base);
mov(r8d, static_cast<uint32_t>(event_size));
lock();
xadd(qword[rax], r8);
// r8 is now the pointer where we can write our event.
// Write the header, which is the same for everything (pretty much).
// Some event types ignore the dest reg, and that's fine.
uint64_t qword_0 = static_cast<uint64_t>(event_type) |
(static_cast<uint64_t>(dest_reg_0) << 8) |
(instr->src1.offset << 32);
mov(rax, qword_0);
mov(qword[r8], rax);
// Write thread ID.
EmitGetCurrentThreadId();
mov(word[r8 + 2], ax);
switch (event_type) {
default:
case xdb::protocol::EventType::INSTR:
break;
case xdb::protocol::EventType::INSTR_R8:
case xdb::protocol::EventType::INSTR_R16:
if (dest_reg_0 & (1 << 7)) {
EmitTraceSourceAppendValue(instr->src2.value, 8);
} else {
EmitTraceSourceAppendValue(instr->src2.value, 8);
}
break;
case xdb::protocol::EventType::INSTR_R8_R8:
case xdb::protocol::EventType::INSTR_R8_R16:
case xdb::protocol::EventType::INSTR_R16_R8:
case xdb::protocol::EventType::INSTR_R16_R16:
mov(word[r8 + 8], dest_reg_0 | static_cast<uint16_t>(dest_reg_1 << 8));
size_t offset = 8;
if (dest_reg_0 & (1 << 7)) {
EmitTraceSourceAppendValue(instr->src2.value, offset);
offset += 16;
} else {
EmitTraceSourceAppendValue(instr->src2.value, offset);
offset += 8;
}
if (dest_reg_1 & (1 << 7)) {
EmitTraceSourceAppendValue(instr->src3.value, offset);
offset += 16;
} else {
EmitTraceSourceAppendValue(instr->src3.value, offset);
offset += 8;
}
break;
}
}
void X64Emitter::EmitTraceSourceAppendValue(const Value* value,
size_t r8_offset) {
//
}
void X64Emitter::EmitGetCurrentThreadId() {
// rcx must point to context. We could fetch from the stack if needed.
mov(ax, word[rcx + runtime_->frontend()->context_info()->thread_id_offset()]);
}
void X64Emitter::EmitTraceUserCallReturn() {
auto trace_base = runtime_->memory()->trace_base();
if (!trace_base || !(trace_flags_ & TRACE_USER_CALLS)) {
return;
}
mov(rdx, rax);
mov(rax, trace_base);
mov(r8d, static_cast<uint32_t>(sizeof(xdb::protocol::UserCallReturnEvent)));
lock();
xadd(qword[rax], r8);
mov(rax, static_cast<uint64_t>(xdb::protocol::EventType::USER_CALL_RETURN) |
(static_cast<uint64_t>(0) << 8) | (0ull << 32));
mov(qword[r8], rax);
EmitGetCurrentThreadId();
mov(word[r8 + 2], ax);
mov(rax, rdx);
ReloadEDX();
}
void X64Emitter::DebugBreak() {
// TODO(benvanik): notify debugger.
db(0xCC);
}
void X64Emitter::Trap(uint16_t trap_type) {
switch (trap_type) {
case 20:
// 0x0FE00014 is a 'debug print' where r3 = buffer r4 = length
// TODO(benvanik): debug print at runtime.
break;
case 0:
case 22:
// Always trap?
// TODO(benvanik): post software interrupt to debugger.
if (FLAGS_break_on_debugbreak) {
db(0xCC);
}
break;
default:
PLOGW("Unknown trap type %d", trap_type);
db(0xCC);
break;
}
}
void X64Emitter::UnimplementedInstr(const hir::Instr* i) {
// TODO(benvanik): notify debugger.
db(0xCC);
assert_always();
}
// Total size of ResolveFunctionSymbol call site in bytes.
// Used to overwrite it with nops as needed.
const size_t TOTAL_RESOLVE_SIZE = 27;
const size_t ASM_OFFSET = 2 + 2 + 8 + 2 + 8;
uint64_t ResolveFunctionSymbol(void* raw_context, uint64_t symbol_info_ptr) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
auto symbol_info = reinterpret_cast<FunctionInfo*>(symbol_info_ptr);
// Resolve function. This will demand compile as required.
Function* fn = NULL;
thread_state->runtime()->ResolveFunction(symbol_info->address(), &fn);
assert_not_null(fn);
auto x64_fn = static_cast<X64Function*>(fn);
uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code());
// Overwrite the call site.
// The return address points to ReloadRCX work after the call.
#if XE_LIKE_WIN32
uint64_t return_address = reinterpret_cast<uint64_t>(_ReturnAddress());
#else
uint64_t return_address =
reinterpret_cast<uint64_t>(__builtin_return_address(0));
#endif // XE_LIKE_WIN32
#pragma pack(push, 1)
struct Asm {
uint16_t mov_rax;
uint64_t rax_constant;
uint16_t mov_rdx;
uint64_t rdx_constant;
uint16_t call_rax;
uint8_t mov_rcx[5];
};
#pragma pack(pop)
static_assert_size(Asm, TOTAL_RESOLVE_SIZE);
Asm* code = reinterpret_cast<Asm*>(return_address - ASM_OFFSET);
code->rax_constant = addr;
code->call_rax = 0x9066;
// We need to return the target in rax so that it gets called.
return addr;
}
void X64Emitter::Call(const hir::Instr* instr,
runtime::FunctionInfo* symbol_info) {
auto fn = reinterpret_cast<X64Function*>(symbol_info->function());
// Resolve address to the function to call and store in rax.
if (fn) {
mov(rax, reinterpret_cast<uint64_t>(fn->machine_code()));
} else {
size_t start = getSize();
// 2b + 8b constant
mov(rax, reinterpret_cast<uint64_t>(ResolveFunctionSymbol));
// 2b + 8b constant
mov(rdx, reinterpret_cast<uint64_t>(symbol_info));
// 2b
call(rax);
// 5b
ReloadECX();
size_t total_size = getSize() - start;
assert_true(total_size == TOTAL_RESOLVE_SIZE);
// EDX overwritten, don't bother reloading.
}
// Actually jump/call to rax.
if (instr->flags & CALL_TAIL) {
// Since we skip the prolog we need to mark the return here.
EmitTraceUserCallReturn();
// Pass the callers return address over.
mov(rdx, qword[rsp + StackLayout::GUEST_RET_ADDR]);
add(rsp, static_cast<uint32_t>(stack_size()));
jmp(rax);
} else {
// Return address is from the previous SET_RETURN_ADDRESS.
mov(rdx, qword[rsp + StackLayout::GUEST_CALL_RET_ADDR]);
call(rax);
}
}
// NOTE: slot count limited by short jump size.
const int kICSlotCount = 4;
const int kICSlotSize = 23;
const uint64_t kICSlotInvalidTargetAddress = 0x0F0F0F0F0F0F0F0F;
uint64_t ResolveFunctionAddress(void* raw_context, uint64_t target_address) {
// TODO(benvanik): generate this thunk at runtime? or a shim?
auto thread_state = *reinterpret_cast<ThreadState**>(raw_context);
// TODO(benvanik): required?
target_address &= 0xFFFFFFFF;
assert_not_zero(target_address);
Function* fn = NULL;
thread_state->runtime()->ResolveFunction(target_address, &fn);
assert_not_null(fn);
auto x64_fn = static_cast<X64Function*>(fn);
uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code());
// Add an IC slot, if there is room.
#if XE_LIKE_WIN32
uint64_t return_address = reinterpret_cast<uint64_t>(_ReturnAddress());
#else
uint64_t return_address =
reinterpret_cast<uint64_t>(__builtin_return_address(0));
#endif // XE_LIKE_WIN32
#pragma pack(push, 1)
struct Asm {
uint16_t cmp_rdx;
uint32_t address_constant;
uint16_t jmp_next_slot;
uint16_t mov_rax;
uint64_t target_constant;
uint8_t jmp_skip_resolve[5];
};
#pragma pack(pop)
static_assert_size(Asm, kICSlotSize);
// TODO(benvanik): quick check table is full (so we don't have to enum slots)
// The return address points to ReloadRCX work after the call.
// To get the top of the table, look back a ways.
uint64_t table_start = return_address - 12 - kICSlotSize * kICSlotCount;
// NOTE: order matters here - we update the address BEFORE we switch the code
// over to passing the compare.
Asm* table_slot = reinterpret_cast<Asm*>(table_start);
bool wrote_ic = false;
for (int i = 0; i < kICSlotCount; ++i) {
if (poly::atomic_cas(kICSlotInvalidTargetAddress, addr,
&table_slot->target_constant)) {
// Got slot! Just write the compare and we're done.
table_slot->address_constant = static_cast<uint32_t>(target_address);
wrote_ic = true;
break;
}
++table_slot;
}
if (!wrote_ic) {
// TODO(benvanik): log that IC table is full.
}
// We need to return the target in rax so that it gets called.
return addr;
}
void X64Emitter::CallIndirect(const hir::Instr* instr, const Reg64& reg) {
// Check if return.
if (instr->flags & CALL_POSSIBLE_RETURN) {
cmp(reg.cvt32(), dword[rsp + StackLayout::GUEST_RET_ADDR]);
je("epilog", CodeGenerator::T_NEAR);
}
if (reg.getIdx() != rdx.getIdx()) {
mov(rdx, reg);
}
inLocalLabel();
Xbyak::Label skip_resolve;
// TODO(benvanik): make empty tables skippable (cmp, jump right to resolve).
// IC table, initially empty.
// This will get filled in as functions are resolved.
// Note that we only have a limited cache, and once it's full all calls
// will fall through.
// TODO(benvanik): check miss rate when full and add a 2nd-level table?
// 0000000264BD4DC3 81 FA 0F0F0F0F cmp edx,0F0F0F0Fh
// 0000000264BD4DC9 75 0C jne 0000000264BD4DD7
// 0000000264BD4DCB 48 B8 0F0F0F0F0F0F0F0F mov rax,0F0F0F0F0F0F0F0Fh
// 0000000264BD4DD5 EB XXXXXXXX jmp 0000000264BD4E00
size_t table_start = getSize();
for (int i = 0; i < kICSlotCount; ++i) {
// Compare target address with constant, if matches jump there.
// Otherwise, fall through.
// 6b
cmp(edx, 0x0F0F0F0F);
Xbyak::Label next_slot;
// 2b
jne(next_slot, T_SHORT);
// Match! Load up rax and skip down to the jmp code.
// 10b
mov(rax, kICSlotInvalidTargetAddress);
// 5b
jmp(skip_resolve, T_NEAR);
L(next_slot);
}
size_t table_size = getSize() - table_start;
assert_true(table_size == kICSlotSize * kICSlotCount);
// Resolve address to the function to call and store in rax.
// We fall through to this when there are no hits in the IC table.
CallNative(ResolveFunctionAddress);
// Actually jump/call to rax.
L(skip_resolve);
if (instr->flags & CALL_TAIL) {
// Since we skip the prolog we need to mark the return here.
EmitTraceUserCallReturn();
// Pass the callers return address over.
mov(rdx, qword[rsp + StackLayout::GUEST_RET_ADDR]);
add(rsp, static_cast<uint32_t>(stack_size()));
jmp(rax);
} else {
// Return address is from the previous SET_RETURN_ADDRESS.
mov(rdx, qword[rsp + StackLayout::GUEST_CALL_RET_ADDR]);
call(rax);
}
outLocalLabel();
}
uint64_t UndefinedCallExtern(void* raw_context, uint64_t symbol_info_ptr) {
auto symbol_info = reinterpret_cast<FunctionInfo*>(symbol_info_ptr);
PLOGW("undefined extern call to %.8llX %s", symbol_info->address(),
symbol_info->name().c_str());
return 0;
}
void X64Emitter::CallExtern(const hir::Instr* instr,
const FunctionInfo* symbol_info) {
assert_true(symbol_info->behavior() == FunctionInfo::BEHAVIOR_EXTERN);
uint64_t trace_base = runtime_->memory()->trace_base();
if (trace_base & trace_flags_ & TRACE_EXTERN_CALLS) {
mov(rax, trace_base);
mov(r8d, static_cast<uint32_t>(sizeof(xdb::protocol::KernelCallEvent)));
lock();
xadd(qword[rax], r8);
// TODO(benvanik): get module/ordinal.
uint32_t module_id = 0;
uint32_t ordinal = 0;
mov(rax, static_cast<uint64_t>(xdb::protocol::EventType::KERNEL_CALL) |
(static_cast<uint64_t>(0) << 8) | (module_id << 16) |
(ordinal));
mov(qword[r8], rax);
EmitGetCurrentThreadId();
mov(word[r8 + 2], ax);
}
if (!symbol_info->extern_handler()) {
CallNative(UndefinedCallExtern, reinterpret_cast<uint64_t>(symbol_info));
} else {
// rcx = context
// rdx = target host function
// r8 = arg0
// r9 = arg1
mov(rdx, reinterpret_cast<uint64_t>(symbol_info->extern_handler()));
mov(r8, reinterpret_cast<uint64_t>(symbol_info->extern_arg0()));
mov(r9, reinterpret_cast<uint64_t>(symbol_info->extern_arg1()));
auto thunk = backend()->guest_to_host_thunk();
mov(rax, reinterpret_cast<uint64_t>(thunk));
call(rax);
ReloadECX();
ReloadEDX();
// rax = host return
}
if (trace_base && trace_flags_ & TRACE_EXTERN_CALLS) {
mov(rax, trace_base);
mov(r8d,
static_cast<uint32_t>(sizeof(xdb::protocol::KernelCallReturnEvent)));
lock();
xadd(qword[rax], r8);
mov(rax,
static_cast<uint64_t>(xdb::protocol::EventType::KERNEL_CALL_RETURN) |
(static_cast<uint64_t>(0) << 8) | (0));
mov(qword[r8], rax);
EmitGetCurrentThreadId();
mov(word[r8 + 2], ax);
}
}
void X64Emitter::CallNative(void* fn) {
mov(rax, reinterpret_cast<uint64_t>(fn));
call(rax);
ReloadECX();
ReloadEDX();
}
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context)) {
mov(rax, reinterpret_cast<uint64_t>(fn));
call(rax);
ReloadECX();
ReloadEDX();
}
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0)) {
mov(rax, reinterpret_cast<uint64_t>(fn));
call(rax);
ReloadECX();
ReloadEDX();
}
void X64Emitter::CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0),
uint64_t arg0) {
mov(rdx, arg0);
mov(rax, reinterpret_cast<uint64_t>(fn));
call(rax);
ReloadECX();
ReloadEDX();
}
void X64Emitter::CallNativeSafe(void* fn) {
// rcx = context
// rdx = target host function
// r8 = arg0
// r9 = arg1
mov(rdx, reinterpret_cast<uint64_t>(fn));
auto thunk = backend()->guest_to_host_thunk();
mov(rax, reinterpret_cast<uint64_t>(thunk));
call(rax);
ReloadECX();
ReloadEDX();
// rax = host return
}
void X64Emitter::SetReturnAddress(uint64_t value) {
mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], value);
}
void X64Emitter::ReloadECX() {
mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]);
}
void X64Emitter::ReloadEDX() {
mov(rdx, qword[rcx + 8]); // membase
}
// Len Assembly Byte Sequence
// ============================================================================
// 2b 66 NOP 66 90H
// 3b NOP DWORD ptr [EAX] 0F 1F 00H
// 4b NOP DWORD ptr [EAX + 00H] 0F 1F 40 00H
// 5b NOP DWORD ptr [EAX + EAX*1 + 00H] 0F 1F 44 00 00H
// 6b 66 NOP DWORD ptr [EAX + EAX*1 + 00H] 66 0F 1F 44 00 00H
// 7b NOP DWORD ptr [EAX + 00000000H] 0F 1F 80 00 00 00 00H
// 8b NOP DWORD ptr [EAX + EAX*1 + 00000000H] 0F 1F 84 00 00 00 00 00H
// 9b 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H] 66 0F 1F 84 00 00 00 00 00H
void X64Emitter::nop(size_t length) {
// TODO(benvanik): fat nop
for (size_t i = 0; i < length; ++i) {
db(0x90);
}
}
void X64Emitter::LoadEflags() {
#if STORE_EFLAGS
mov(eax, dword[rsp + STASH_OFFSET]);
btr(eax, 0);
#else
// EFLAGS already present.
#endif // STORE_EFLAGS
}
void X64Emitter::StoreEflags() {
#if STORE_EFLAGS
pushf();
pop(dword[rsp + STASH_OFFSET]);
#else
// EFLAGS should have CA set?
// (so long as we don't fuck with it)
#endif // STORE_EFLAGS
}
bool X64Emitter::ConstantFitsIn32Reg(uint64_t v) {
if ((v & ~0x7FFFFFFF) == 0) {
// Fits under 31 bits, so just load using normal mov.
return true;
} else if ((v & ~0x7FFFFFFF) == ~0x7FFFFFFF) {
// Negative number that fits in 32bits.
return true;
}
return false;
}
void X64Emitter::MovMem64(const RegExp& addr, uint64_t v) {
if ((v & ~0x7FFFFFFF) == 0) {
// Fits under 31 bits, so just load using normal mov.
mov(qword[addr], v);
} else if ((v & ~0x7FFFFFFF) == ~0x7FFFFFFF) {
// Negative number that fits in 32bits.
mov(qword[addr], v);
} else if (!(v >> 32)) {
// All high bits are zero. It'd be nice if we had a way to load a 32bit
// immediate without sign extending!
// TODO(benvanik): this is super common, find a better way.
mov(dword[addr], static_cast<uint32_t>(v));
mov(dword[addr + 4], 0);
} else {
// 64bit number that needs double movs.
mov(dword[addr], static_cast<uint32_t>(v));
mov(dword[addr + 4], static_cast<uint32_t>(v >> 32));
}
}
Address X64Emitter::GetXmmConstPtr(XmmConst id) {
static const vec128_t xmm_consts[] = {
/* XMMZero */ vec128f(0.0f),
/* XMMOne */ vec128f(1.0f),
/* XMMNegativeOne */ vec128f(-1.0f, -1.0f, -1.0f, -1.0f),
/* XMMFFFF */ vec128i(0xFFFFFFFFu, 0xFFFFFFFFu,
0xFFFFFFFFu, 0xFFFFFFFFu),
/* XMMMaskX16Y16 */ vec128i(0x0000FFFFu, 0xFFFF0000u,
0x00000000u, 0x00000000u),
/* XMMFlipX16Y16 */ vec128i(0x00008000u, 0x00000000u,
0x00000000u, 0x00000000u),
/* XMMFixX16Y16 */ vec128f(-32768.0f, 0.0f, 0.0f, 0.0f),
/* XMMNormalizeX16Y16 */ vec128f(
1.0f / 32767.0f, 1.0f / (32767.0f * 65536.0f), 0.0f, 0.0f),
/* XMM0001 */ vec128f(0.0f, 0.0f, 0.0f, 1.0f),
/* XMM3301 */ vec128f(3.0f, 3.0f, 0.0f, 1.0f),
/* XMM3333 */ vec128f(3.0f, 3.0f, 3.0f, 3.0f),
/* XMMSignMaskPS */ vec128i(0x80000000u, 0x80000000u,
0x80000000u, 0x80000000u),
/* XMMSignMaskPD */ vec128i(0x00000000u, 0x80000000u,
0x00000000u, 0x80000000u),
/* XMMAbsMaskPS */ vec128i(0x7FFFFFFFu, 0x7FFFFFFFu,
0x7FFFFFFFu, 0x7FFFFFFFu),
/* XMMAbsMaskPD */ vec128i(0xFFFFFFFFu, 0x7FFFFFFFu,
0xFFFFFFFFu, 0x7FFFFFFFu),
/* XMMByteSwapMask */ vec128i(0x00010203u, 0x04050607u,
0x08090A0Bu, 0x0C0D0E0Fu),
/* XMMByteOrderMask */ vec128i(0x01000302u, 0x05040706u,
0x09080B0Au, 0x0D0C0F0Eu),
/* XMMPermuteControl15 */ vec128b(15),
/* XMMPermuteByteMask */ vec128b(0x1F),
/* XMMPackD3DCOLORSat */ vec128i(0x404000FFu),
/* XMMPackD3DCOLOR */ vec128i(0xFFFFFFFFu, 0xFFFFFFFFu,
0xFFFFFFFFu, 0x0C000408u),
/* XMMUnpackD3DCOLOR */ vec128i(0xFFFFFF0Eu, 0xFFFFFF0Du,
0xFFFFFF0Cu, 0xFFFFFF0Fu),
/* XMMPackFLOAT16_2 */ vec128i(0xFFFFFFFFu, 0xFFFFFFFFu,
0xFFFFFFFFu, 0x01000302u),
/* XMMUnpackFLOAT16_2 */ vec128i(0x0D0C0F0Eu, 0xFFFFFFFFu,
0xFFFFFFFFu, 0xFFFFFFFFu),
/* XMMPackFLOAT16_4 */ vec128i(0xFFFFFFFFu, 0xFFFFFFFFu,
0x05040706u, 0x01000302u),
/* XMMUnpackFLOAT16_4 */ vec128i(0x09080B0Au, 0x0D0C0F0Eu,
0xFFFFFFFFu, 0xFFFFFFFFu),
/* XMMPackSHORT_2Min */ vec128i(0x403F8001u),
/* XMMPackSHORT_2Max */ vec128i(0x40407FFFu),
/* XMMPackSHORT_2 */ vec128i(0xFFFFFFFFu, 0xFFFFFFFFu,
0xFFFFFFFFu, 0x01000504u),
/* XMMUnpackSHORT_2 */ vec128i(0xFFFF0F0Eu, 0xFFFF0D0Cu,
0xFFFFFFFFu, 0xFFFFFFFFu),
/* XMMOneOver255 */ vec128f(1.0f / 255.0f),
/* XMMMaskEvenPI16 */ vec128i(0x0000FFFFu, 0x0000FFFFu,
0x0000FFFFu, 0x0000FFFFu),
/* XMMShiftMaskEvenPI16 */ vec128i(0x0000000Fu, 0x0000000Fu,
0x0000000Fu, 0x0000000Fu),
/* XMMShiftMaskPS */ vec128i(0x0000001Fu, 0x0000001Fu,
0x0000001Fu, 0x0000001Fu),
/* XMMShiftByteMask */ vec128i(0x000000FFu, 0x000000FFu,
0x000000FFu, 0x000000FFu),
/* XMMSwapWordMask */ vec128i(0x03030303u, 0x03030303u,
0x03030303u, 0x03030303u),
/* XMMUnsignedDwordMax */ vec128i(0xFFFFFFFFu, 0x00000000u,
0xFFFFFFFFu, 0x00000000u),
/* XMM255 */ vec128f(255.0f),
/* XMMPI32 */ vec128i(32),
/* XMMSignMaskI8 */ vec128i(0x80808080u, 0x80808080u,
0x80808080u, 0x80808080u),
/* XMMSignMaskI16 */ vec128i(0x80008000u, 0x80008000u,
0x80008000u, 0x80008000u),
/* XMMSignMaskI32 */ vec128i(0x80000000u, 0x80000000u,
0x80000000u, 0x80000000u),
/* XMMSignMaskF32 */ vec128i(0x80000000u, 0x80000000u,
0x80000000u, 0x80000000u),
/* XMMShortMinPS */ vec128f(SHRT_MIN),
/* XMMShortMaxPS */ vec128f(SHRT_MAX),
};
// TODO(benvanik): cache base pointer somewhere? stack? It'd be nice to
// prevent this move.
// TODO(benvanik): move to predictable location in PPCContext? could then
// just do rcx relative addression with no rax overwriting.
mov(rax, (uint64_t)&xmm_consts[id]);
return ptr[rax];
}
void X64Emitter::LoadConstantXmm(Xbyak::Xmm dest, const vec128_t& v) {
// http://www.agner.org/optimize/optimizing_assembly.pdf
// 13.4 Generating constants
if (!v.low && !v.high) {
// 0000...
vpxor(dest, dest);
} else if (v.low == ~0ull && v.high == ~0ull) {
// 1111...
vpcmpeqb(dest, dest);
} else {
// TODO(benvanik): see what other common values are.
// TODO(benvanik): build constant table - 99% are reused.
MovMem64(rsp + STASH_OFFSET, v.low);
MovMem64(rsp + STASH_OFFSET + 8, v.high);
vmovdqa(dest, ptr[rsp + STASH_OFFSET]);
}
}
void X64Emitter::LoadConstantXmm(Xbyak::Xmm dest, float v) {
union {
float f;
uint32_t i;
} x = {v};
if (!v) {
// 0
vpxor(dest, dest);
} else if (x.i == ~0U) {
// 1111...
vpcmpeqb(dest, dest);
} else {
// TODO(benvanik): see what other common values are.
// TODO(benvanik): build constant table - 99% are reused.
mov(eax, x.i);
vmovd(dest, eax);
}
}
void X64Emitter::LoadConstantXmm(Xbyak::Xmm dest, double v) {
union {
double d;
uint64_t i;
} x = {v};
if (!v) {
// 0
vpxor(dest, dest);
} else if (x.i == ~0ULL) {
// 1111...
vpcmpeqb(dest, dest);
} else {
// TODO(benvanik): see what other common values are.
// TODO(benvanik): build constant table - 99% are reused.
mov(rax, x.i);
vmovq(dest, rax);
}
}
Address X64Emitter::StashXmm(int index, const Xmm& r) {
auto addr = ptr[rsp + STASH_OFFSET + (index * 16)];
vmovups(addr, r);
return addr;
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,217 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_EMITTER_H_
#define XENIA_BACKEND_X64_X64_EMITTER_H_
#include "xenia/cpu/hir/value.h"
#include "poly/arena.h"
#include "third_party/xbyak/xbyak/xbyak.h"
namespace xe {
namespace cpu {
namespace hir {
class HIRBuilder;
class Instr;
} // namespace hir
namespace runtime {
class DebugInfo;
class FunctionInfo;
class Runtime;
class SymbolInfo;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
using vec128_t = poly::vec128_t;
class X64Backend;
class X64CodeCache;
enum RegisterFlags {
REG_DEST = (1 << 0),
REG_ABCD = (1 << 1),
};
enum XmmConst {
XMMZero = 0,
XMMOne,
XMMNegativeOne,
XMMFFFF,
XMMMaskX16Y16,
XMMFlipX16Y16,
XMMFixX16Y16,
XMMNormalizeX16Y16,
XMM0001,
XMM3301,
XMM3333,
XMMSignMaskPS,
XMMSignMaskPD,
XMMAbsMaskPS,
XMMAbsMaskPD,
XMMByteSwapMask,
XMMByteOrderMask,
XMMPermuteControl15,
XMMPermuteByteMask,
XMMPackD3DCOLORSat,
XMMPackD3DCOLOR,
XMMUnpackD3DCOLOR,
XMMPackFLOAT16_2,
XMMUnpackFLOAT16_2,
XMMPackFLOAT16_4,
XMMUnpackFLOAT16_4,
XMMPackSHORT_2Min,
XMMPackSHORT_2Max,
XMMPackSHORT_2,
XMMUnpackSHORT_2,
XMMOneOver255,
XMMMaskEvenPI16,
XMMShiftMaskEvenPI16,
XMMShiftMaskPS,
XMMShiftByteMask,
XMMSwapWordMask,
XMMUnsignedDwordMax,
XMM255,
XMMPI32,
XMMSignMaskI8,
XMMSignMaskI16,
XMMSignMaskI32,
XMMSignMaskF32,
XMMShortMinPS,
XMMShortMaxPS,
};
// Unfortunately due to the design of xbyak we have to pass this to the ctor.
class XbyakAllocator : public Xbyak::Allocator {
public:
virtual bool useProtect() const { return false; }
};
class X64Emitter : public Xbyak::CodeGenerator {
public:
X64Emitter(X64Backend* backend, XbyakAllocator* allocator);
virtual ~X64Emitter();
runtime::Runtime* runtime() const { return runtime_; }
X64Backend* backend() const { return backend_; }
int Initialize();
int Emit(hir::HIRBuilder* builder, uint32_t debug_info_flags,
runtime::DebugInfo* debug_info, uint32_t trace_flags,
void*& out_code_address, size_t& out_code_size);
public:
// Reserved: rsp
// Scratch: rax/rcx/rdx
// xmm0-2 (could be only xmm0 with some trickery)
// Available: rbx, r12-r15 (save to get r8-r11, rbp, rsi, rdi?)
// xmm6-xmm15 (save to get xmm3-xmm5)
static const int GPR_COUNT = 5;
static const int XMM_COUNT = 10;
static void SetupReg(const hir::Value* v, Xbyak::Reg8& r) {
auto idx = gpr_reg_map_[v->reg.index];
r = Xbyak::Reg8(idx);
}
static void SetupReg(const hir::Value* v, Xbyak::Reg16& r) {
auto idx = gpr_reg_map_[v->reg.index];
r = Xbyak::Reg16(idx);
}
static void SetupReg(const hir::Value* v, Xbyak::Reg32& r) {
auto idx = gpr_reg_map_[v->reg.index];
r = Xbyak::Reg32(idx);
}
static void SetupReg(const hir::Value* v, Xbyak::Reg64& r) {
auto idx = gpr_reg_map_[v->reg.index];
r = Xbyak::Reg64(idx);
}
static void SetupReg(const hir::Value* v, Xbyak::Xmm& r) {
auto idx = xmm_reg_map_[v->reg.index];
r = Xbyak::Xmm(idx);
}
void MarkSourceOffset(const hir::Instr* i);
void DebugBreak();
void Trap(uint16_t trap_type = 0);
void UnimplementedInstr(const hir::Instr* i);
void UnimplementedExtern(const hir::Instr* i);
void Call(const hir::Instr* instr, runtime::FunctionInfo* symbol_info);
void CallIndirect(const hir::Instr* instr, const Xbyak::Reg64& reg);
void CallExtern(const hir::Instr* instr,
const runtime::FunctionInfo* symbol_info);
void CallNative(void* fn);
void CallNative(uint64_t (*fn)(void* raw_context));
void CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0));
void CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0),
uint64_t arg0);
void CallNativeSafe(void* fn);
void SetReturnAddress(uint64_t value);
void ReloadECX();
void ReloadEDX();
void nop(size_t length = 1);
// TODO(benvanik): Label for epilog (don't use strings).
void LoadEflags();
void StoreEflags();
// Moves a 64bit immediate into memory.
bool ConstantFitsIn32Reg(uint64_t v);
void MovMem64(const Xbyak::RegExp& addr, uint64_t v);
Xbyak::Address GetXmmConstPtr(XmmConst id);
void LoadConstantXmm(Xbyak::Xmm dest, float v);
void LoadConstantXmm(Xbyak::Xmm dest, double v);
void LoadConstantXmm(Xbyak::Xmm dest, const vec128_t& v);
Xbyak::Address StashXmm(int index, const Xbyak::Xmm& r);
size_t stack_size() const { return stack_size_; }
protected:
void* Emplace(size_t stack_size);
int Emit(hir::HIRBuilder* builder, size_t& out_stack_size);
void EmitTraceSource(const hir::Instr* instr);
void EmitTraceSourceAppendValue(const hir::Value* value, size_t r8_offset);
void EmitGetCurrentThreadId();
void EmitTraceUserCallReturn();
protected:
runtime::Runtime* runtime_;
X64Backend* backend_;
X64CodeCache* code_cache_;
XbyakAllocator* allocator_;
hir::Instr* current_instr_;
size_t source_map_count_;
poly::Arena source_map_arena_;
size_t stack_size_;
uint32_t trace_flags_;
static const uint32_t gpr_reg_map_[GPR_COUNT];
static const uint32_t xmm_reg_map_[XMM_COUNT];
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_EMITTER_H_

View File

@@ -0,0 +1,52 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_function.h"
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/cpu/runtime/thread_state.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
using xe::cpu::runtime::Breakpoint;
using xe::cpu::runtime::Function;
using xe::cpu::runtime::FunctionInfo;
using xe::cpu::runtime::ThreadState;
X64Function::X64Function(FunctionInfo* symbol_info)
: Function(symbol_info), machine_code_(nullptr), code_size_(0) {}
X64Function::~X64Function() {
// machine_code_ is freed by code cache.
}
void X64Function::Setup(void* machine_code, size_t code_size) {
machine_code_ = machine_code;
code_size_ = code_size;
}
int X64Function::AddBreakpointImpl(Breakpoint* breakpoint) { return 0; }
int X64Function::RemoveBreakpointImpl(Breakpoint* breakpoint) { return 0; }
int X64Function::CallImpl(ThreadState* thread_state, uint64_t return_address) {
auto backend = (X64Backend*)thread_state->runtime()->backend();
auto thunk = backend->host_to_guest_thunk();
thunk(machine_code_, thread_state->raw_context(), (void*)return_address);
return 0;
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,47 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_FUNCTION_H_
#define XENIA_BACKEND_X64_X64_FUNCTION_H_
#include "xenia/cpu/runtime/function.h"
#include "xenia/cpu/runtime/symbol_info.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64Function : public runtime::Function {
public:
X64Function(runtime::FunctionInfo* symbol_info);
virtual ~X64Function();
void* machine_code() const { return machine_code_; }
size_t code_size() const { return code_size_; }
void Setup(void* machine_code, size_t code_size);
protected:
virtual int AddBreakpointImpl(runtime::Breakpoint* breakpoint);
virtual int RemoveBreakpointImpl(runtime::Breakpoint* breakpoint);
virtual int CallImpl(runtime::ThreadState* thread_state,
uint64_t return_address);
private:
void* machine_code_;
size_t code_size_;
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_FUNCTION_H_

View File

@@ -0,0 +1,758 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
namespace {
enum KeyType {
KEY_TYPE_X = OPCODE_SIG_TYPE_X,
KEY_TYPE_L = OPCODE_SIG_TYPE_L,
KEY_TYPE_O = OPCODE_SIG_TYPE_O,
KEY_TYPE_S = OPCODE_SIG_TYPE_S,
KEY_TYPE_V_I8 = OPCODE_SIG_TYPE_V + INT8_TYPE,
KEY_TYPE_V_I16 = OPCODE_SIG_TYPE_V + INT16_TYPE,
KEY_TYPE_V_I32 = OPCODE_SIG_TYPE_V + INT32_TYPE,
KEY_TYPE_V_I64 = OPCODE_SIG_TYPE_V + INT64_TYPE,
KEY_TYPE_V_F32 = OPCODE_SIG_TYPE_V + FLOAT32_TYPE,
KEY_TYPE_V_F64 = OPCODE_SIG_TYPE_V + FLOAT64_TYPE,
KEY_TYPE_V_V128 = OPCODE_SIG_TYPE_V + VEC128_TYPE,
};
#pragma pack(push, 1)
union InstrKey {
struct {
uint32_t opcode : 8;
uint32_t dest : 5;
uint32_t src1 : 5;
uint32_t src2 : 5;
uint32_t src3 : 5;
uint32_t reserved : 4;
};
uint32_t value;
operator uint32_t() const {
return value;
}
InstrKey() : value(0) {}
InstrKey(uint32_t v) : value(v) {}
InstrKey(const Instr* i) : value(0) {
opcode = i->opcode->num;
uint32_t sig = i->opcode->signature;
dest = GET_OPCODE_SIG_TYPE_DEST(sig) ? OPCODE_SIG_TYPE_V + i->dest->type : 0;
src1 = GET_OPCODE_SIG_TYPE_SRC1(sig);
if (src1 == OPCODE_SIG_TYPE_V) {
src1 += i->src1.value->type;
}
src2 = GET_OPCODE_SIG_TYPE_SRC2(sig);
if (src2 == OPCODE_SIG_TYPE_V) {
src2 += i->src2.value->type;
}
src3 = GET_OPCODE_SIG_TYPE_SRC3(sig);
if (src3 == OPCODE_SIG_TYPE_V) {
src3 += i->src3.value->type;
}
}
template <Opcode OPCODE,
KeyType DEST = KEY_TYPE_X,
KeyType SRC1 = KEY_TYPE_X,
KeyType SRC2 = KEY_TYPE_X,
KeyType SRC3 = KEY_TYPE_X>
struct Construct {
static const uint32_t value =
(OPCODE) | (DEST << 8) | (SRC1 << 13) | (SRC2 << 18) | (SRC3 << 23);
};
};
#pragma pack(pop)
static_assert(sizeof(InstrKey) <= 4, "Key must be 4 bytes");
template <typename... Ts>
struct CombinedStruct;
template <>
struct CombinedStruct<> {};
template <typename T, typename... Ts>
struct CombinedStruct<T, Ts...> : T, CombinedStruct<Ts...> {};
struct OpBase {};
template <typename T, KeyType KEY_TYPE>
struct Op : OpBase {
static const KeyType key_type = KEY_TYPE;
};
struct VoidOp : Op<VoidOp, KEY_TYPE_X> {
protected:
template <typename T, KeyType KEY_TYPE> friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> friend struct I;
void Load(const Instr::Op& op) {}
};
struct OffsetOp : Op<OffsetOp, KEY_TYPE_O> {
uint64_t value;
protected:
template <typename T, KeyType KEY_TYPE> friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> friend struct I;
void Load(const Instr::Op& op) {
this->value = op.offset;
}
};
struct SymbolOp : Op<SymbolOp, KEY_TYPE_S> {
FunctionInfo* value;
protected:
template <typename T, KeyType KEY_TYPE> friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> friend struct I;
bool Load(const Instr::Op& op) {
this->value = op.symbol_info;
return true;
}
};
struct LabelOp : Op<LabelOp, KEY_TYPE_L> {
hir::Label* value;
protected:
template <typename T, KeyType KEY_TYPE> friend struct Op;
template <hir::Opcode OPCODE, typename... Ts> friend struct I;
void Load(const Instr::Op& op) {
this->value = op.label;
}
};
template <typename T, KeyType KEY_TYPE, typename REG_TYPE, typename CONST_TYPE, int TAG = -1>
struct ValueOp : Op<ValueOp<T, KEY_TYPE, REG_TYPE, CONST_TYPE, TAG>, KEY_TYPE> {
typedef REG_TYPE reg_type;
static const int tag = TAG;
const Value* value;
bool is_constant;
virtual bool ConstantFitsIn32Reg() const { return true; }
const REG_TYPE& reg() const {
assert_true(!is_constant);
return reg_;
}
operator const REG_TYPE&() const {
return reg();
}
bool IsEqual(const T& b) const {
if (is_constant && b.is_constant) {
return reinterpret_cast<const T*>(this)->constant() == b.constant();
} else if (!is_constant && !b.is_constant) {
return reg_.getIdx() == b.reg_.getIdx();
} else {
return false;
}
}
bool IsEqual(const Xbyak::Reg& b) const {
if (is_constant) {
return false;
} else if (!is_constant) {
return reg_.getIdx() == b.getIdx();
} else {
return false;
}
}
bool operator== (const T& b) const {
return IsEqual(b);
}
bool operator!= (const T& b) const {
return !IsEqual(b);
}
bool operator== (const Xbyak::Reg& b) const {
return IsEqual(b);
}
bool operator!= (const Xbyak::Reg& b) const {
return !IsEqual(b);
}
void Load(const Instr::Op& op) {
const Value* value = op.value;
this->value = value;
is_constant = value->IsConstant();
if (!is_constant) {
X64Emitter::SetupReg(value, reg_);
}
}
protected:
REG_TYPE reg_;
};
template <int TAG = -1>
struct I8 : ValueOp<I8<TAG>, KEY_TYPE_V_I8, Reg8, int8_t, TAG> {
typedef ValueOp<I8<TAG>, KEY_TYPE_V_I8, Reg8, int8_t, TAG> BASE;
const int8_t constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.i8;
}
};
template <int TAG = -1>
struct I16 : ValueOp<I16<TAG>, KEY_TYPE_V_I16, Reg16, int16_t, TAG> {
typedef ValueOp<I16<TAG>, KEY_TYPE_V_I16, Reg16, int16_t, TAG> BASE;
const int16_t constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.i16;
}
};
template <int TAG = -1>
struct I32 : ValueOp<I32<TAG>, KEY_TYPE_V_I32, Reg32, int32_t, TAG> {
typedef ValueOp<I32<TAG>, KEY_TYPE_V_I32, Reg32, int32_t, TAG> BASE;
const int32_t constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.i32;
}
};
template <int TAG = -1>
struct I64 : ValueOp<I64<TAG>, KEY_TYPE_V_I64, Reg64, int64_t, TAG> {
typedef ValueOp<I64<TAG>, KEY_TYPE_V_I64, Reg64, int64_t, TAG> BASE;
const int64_t constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.i64;
}
bool ConstantFitsIn32Reg() const override {
int64_t v = BASE::value->constant.i64;
if ((v & ~0x7FFFFFFF) == 0) {
// Fits under 31 bits, so just load using normal mov.
return true;
} else if ((v & ~0x7FFFFFFF) == ~0x7FFFFFFF) {
// Negative number that fits in 32bits.
return true;
}
return false;
}
};
template <int TAG = -1>
struct F32 : ValueOp<F32<TAG>, KEY_TYPE_V_F32, Xmm, float, TAG> {
typedef ValueOp<F32<TAG>, KEY_TYPE_V_F32, Xmm, float, TAG> BASE;
const float constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.f32;
}
};
template <int TAG = -1>
struct F64 : ValueOp<F64<TAG>, KEY_TYPE_V_F64, Xmm, double, TAG> {
typedef ValueOp<F64<TAG>, KEY_TYPE_V_F64, Xmm, double, TAG> BASE;
const double constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.f64;
}
};
template <int TAG = -1>
struct V128 : ValueOp<V128<TAG>, KEY_TYPE_V_V128, Xmm, vec128_t, TAG> {
typedef ValueOp<V128<TAG>, KEY_TYPE_V_V128, Xmm, vec128_t, TAG> BASE;
const vec128_t& constant() const {
assert_true(BASE::is_constant);
return BASE::value->constant.v128;
}
};
struct TagTable {
struct {
bool valid;
Instr::Op op;
} table[16];
template <typename T, typename std::enable_if<T::key_type == KEY_TYPE_X>::type* = nullptr>
bool CheckTag(const Instr::Op& op) {
return true;
}
template <typename T, typename std::enable_if<T::key_type == KEY_TYPE_L>::type* = nullptr>
bool CheckTag(const Instr::Op& op) {
return true;
}
template <typename T, typename std::enable_if<T::key_type == KEY_TYPE_O>::type* = nullptr>
bool CheckTag(const Instr::Op& op) {
return true;
}
template <typename T, typename std::enable_if<T::key_type == KEY_TYPE_S>::type* = nullptr>
bool CheckTag(const Instr::Op& op) {
return true;
}
template <typename T, typename std::enable_if<T::key_type >= KEY_TYPE_V_I8>::type* = nullptr>
bool CheckTag(const Instr::Op& op) {
const Value* value = op.value;
if (T::tag == -1) {
return true;
}
if (table[T::tag].valid &&
table[T::tag].op.value != value) {
return false;
}
table[T::tag].valid = true;
table[T::tag].op.value = (Value*)value;
return true;
}
};
template <typename DEST, typename... Tf>
struct DestField;
template <typename DEST>
struct DestField<DEST> {
DEST dest;
protected:
bool LoadDest(const Instr* i, TagTable& tag_table) {
Instr::Op op;
op.value = i->dest;
if (tag_table.CheckTag<DEST>(op)) {
dest.Load(op);
return true;
}
return false;
}
};
template <>
struct DestField<VoidOp> {
protected:
bool LoadDest(const Instr* i, TagTable& tag_table) {
return true;
}
};
template <hir::Opcode OPCODE, typename... Ts>
struct I;
template <hir::Opcode OPCODE, typename DEST>
struct I<OPCODE, DEST> : DestField<DEST> {
typedef DestField<DEST> BASE;
static const hir::Opcode opcode = OPCODE;
static const uint32_t key = InstrKey::Construct<OPCODE, DEST::key_type>::value;
static const KeyType dest_type = DEST::key_type;
const Instr* instr;
protected:
template <typename... Ti> friend struct SequenceFields;
bool Load(const Instr* i, TagTable& tag_table) {
if (InstrKey(i).value == key &&
BASE::LoadDest(i, tag_table)) {
instr = i;
return true;
}
return false;
}
};
template <hir::Opcode OPCODE, typename DEST, typename SRC1>
struct I<OPCODE, DEST, SRC1> : DestField<DEST> {
typedef DestField<DEST> BASE;
static const hir::Opcode opcode = OPCODE;
static const uint32_t key = InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type>::value;
static const KeyType dest_type = DEST::key_type;
static const KeyType src1_type = SRC1::key_type;
const Instr* instr;
SRC1 src1;
protected:
template <typename... Ti> friend struct SequenceFields;
bool Load(const Instr* i, TagTable& tag_table) {
if (InstrKey(i).value == key &&
BASE::LoadDest(i, tag_table) &&
tag_table.CheckTag<SRC1>(i->src1)) {
instr = i;
src1.Load(i->src1);
return true;
}
return false;
}
};
template <hir::Opcode OPCODE, typename DEST, typename SRC1, typename SRC2>
struct I<OPCODE, DEST, SRC1, SRC2> : DestField<DEST> {
typedef DestField<DEST> BASE;
static const hir::Opcode opcode = OPCODE;
static const uint32_t key = InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type, SRC2::key_type>::value;
static const KeyType dest_type = DEST::key_type;
static const KeyType src1_type = SRC1::key_type;
static const KeyType src2_type = SRC2::key_type;
const Instr* instr;
SRC1 src1;
SRC2 src2;
protected:
template <typename... Ti> friend struct SequenceFields;
bool Load(const Instr* i, TagTable& tag_table) {
if (InstrKey(i).value == key &&
BASE::LoadDest(i, tag_table) &&
tag_table.CheckTag<SRC1>(i->src1) &&
tag_table.CheckTag<SRC2>(i->src2)) {
instr = i;
src1.Load(i->src1);
src2.Load(i->src2);
return true;
}
return false;
}
};
template <hir::Opcode OPCODE, typename DEST, typename SRC1, typename SRC2, typename SRC3>
struct I<OPCODE, DEST, SRC1, SRC2, SRC3> : DestField<DEST> {
typedef DestField<DEST> BASE;
static const hir::Opcode opcode = OPCODE;
static const uint32_t key = InstrKey::Construct<OPCODE, DEST::key_type, SRC1::key_type, SRC2::key_type, SRC3::key_type>::value;
static const KeyType dest_type = DEST::key_type;
static const KeyType src1_type = SRC1::key_type;
static const KeyType src2_type = SRC2::key_type;
static const KeyType src3_type = SRC3::key_type;
const Instr* instr;
SRC1 src1;
SRC2 src2;
SRC3 src3;
protected:
template <typename... Ti> friend struct SequenceFields;
bool Load(const Instr* i, TagTable& tag_table) {
if (InstrKey(i).value == key &&
BASE::LoadDest(i, tag_table) &&
tag_table.CheckTag<SRC1>(i->src1) &&
tag_table.CheckTag<SRC2>(i->src2) &&
tag_table.CheckTag<SRC3>(i->src3)) {
instr = i;
src1.Load(i->src1);
src2.Load(i->src2);
src3.Load(i->src3);
return true;
}
return false;
}
};
template <typename... Ti>
struct SequenceFields;
template <typename I1>
struct SequenceFields<I1> {
I1 i1;
protected:
template <typename SEQ, typename... Ti> friend struct Sequence;
bool Check(const Instr* i, TagTable& tag_table, const Instr** new_tail) {
if (i1.Load(i, tag_table)) {
*new_tail = i->next;
return true;
}
return false;
}
};
template <typename I1, typename I2>
struct SequenceFields<I1, I2> : SequenceFields<I1> {
I2 i2;
protected:
template <typename SEQ, typename... Ti> friend struct Sequence;
bool Check(const Instr* i, TagTable& tag_table, const Instr** new_tail) {
if (SequenceFields<I1>::Check(i, tag_table, new_tail)) {
auto ni = i->next;
if (ni && i2.Load(ni, tag_table)) {
*new_tail = ni;
return i;
}
}
return false;
}
};
template <typename I1, typename I2, typename I3>
struct SequenceFields<I1, I2, I3> : SequenceFields<I1, I2> {
I3 i3;
protected:
template <typename SEQ, typename... Ti> friend struct Sequence;
bool Check(const Instr* i, TagTable& tag_table, const Instr** new_tail) {
if (SequenceFields<I1, I2>::Check(i, tag_table, new_tail)) {
auto ni = i->next;
if (ni && i3.Load(ni, tag_table)) {
*new_tail = ni;
return i;
}
}
return false;
}
};
template <typename I1, typename I2, typename I3, typename I4>
struct SequenceFields<I1, I2, I3, I4> : SequenceFields<I1, I2, I3> {
I4 i4;
protected:
template <typename SEQ, typename... Ti> friend struct Sequence;
bool Check(const Instr* i, TagTable& tag_table, const Instr** new_tail) {
if (SequenceFields<I1, I2, I3>::Check(i, tag_table, new_tail)) {
auto ni = i->next;
if (ni && i4.Load(ni, tag_table)) {
*new_tail = ni;
return i;
}
}
return false;
}
};
template <typename I1, typename I2, typename I3, typename I4, typename I5>
struct SequenceFields<I1, I2, I3, I4, I5> : SequenceFields<I1, I2, I3, I4> {
I5 i5;
protected:
template <typename SEQ, typename... Ti> friend struct Sequence;
bool Check(const Instr* i, TagTable& tag_table, const Instr** new_tail) {
if (SequenceFields<I1, I2, I3, I4>::Check(i, tag_table, new_tail)) {
auto ni = i->next;
if (ni && i5.Load(ni, tag_table)) {
*new_tail = ni;
return i;
}
}
return false;
}
};
template <typename SEQ, typename... Ti>
struct Sequence {
struct EmitArgs : SequenceFields<Ti...> {};
static bool Select(X64Emitter& e, const Instr* i, const Instr** new_tail) {
EmitArgs args;
TagTable tag_table;
if (!args.Check(i, tag_table, new_tail)) {
return false;
}
SEQ::Emit(e, args);
return true;
}
};
template <typename T>
const T GetTempReg(X64Emitter& e);
template <>
const Reg8 GetTempReg<Reg8>(X64Emitter& e) {
return e.al;
}
template <>
const Reg16 GetTempReg<Reg16>(X64Emitter& e) {
return e.ax;
}
template <>
const Reg32 GetTempReg<Reg32>(X64Emitter& e) {
return e.eax;
}
template <>
const Reg64 GetTempReg<Reg64>(X64Emitter& e) {
return e.rax;
}
template <typename SEQ, typename T>
struct SingleSequence : public Sequence<SingleSequence<SEQ, T>, T> {
typedef Sequence<SingleSequence<SEQ, T>, T> BASE;
typedef T EmitArgType;
// TODO(benvanik): find a way to do this cross-compiler.
#if XE_COMPILER_MSVC
static uint32_t head_key() { return T::key; }
#else
static constexpr uint32_t head_key() { return T::key; }
#endif // XE_COMPILER_MSVC
static void Emit(X64Emitter& e, const typename BASE::EmitArgs& _) {
SEQ::Emit(e, _.i1);
}
template <typename REG_FN>
static void EmitUnaryOp(
X64Emitter& e, const EmitArgType& i,
const REG_FN& reg_fn) {
if (i.src1.is_constant) {
e.mov(i.dest, i.src1.constant());
reg_fn(e, i.dest);
} else {
if (i.dest != i.src1) {
e.mov(i.dest, i.src1);
}
reg_fn(e, i.dest);
}
}
template <typename REG_REG_FN, typename REG_CONST_FN>
static void EmitCommutativeBinaryOp(
X64Emitter& e, const EmitArgType& i,
const REG_REG_FN& reg_reg_fn, const REG_CONST_FN& reg_const_fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
if (i.dest == i.src2) {
if (i.src1.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src1.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
e.mov(temp, i.src1.constant());
reg_reg_fn(e, i.dest, temp);
}
} else {
e.mov(i.dest, i.src1.constant());
reg_reg_fn(e, i.dest, i.src2);
}
} else if (i.src2.is_constant) {
if (i.dest == i.src1) {
if (i.src2.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2.constant());
reg_reg_fn(e, i.dest, temp);
}
} else {
e.mov(i.dest, i.src2.constant());
reg_reg_fn(e, i.dest, i.src1);
}
} else {
if (i.dest == i.src1) {
reg_reg_fn(e, i.dest, i.src2);
} else if (i.dest == i.src2) {
reg_reg_fn(e, i.dest, i.src1);
} else {
e.mov(i.dest, i.src1);
reg_reg_fn(e, i.dest, i.src2);
}
}
}
template <typename REG_REG_FN, typename REG_CONST_FN>
static void EmitAssociativeBinaryOp(
X64Emitter& e, const EmitArgType& i,
const REG_REG_FN& reg_reg_fn, const REG_CONST_FN& reg_const_fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
if (i.dest == i.src2) {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2);
e.mov(i.dest, i.src1.constant());
reg_reg_fn(e, i.dest, temp);
} else {
e.mov(i.dest, i.src1.constant());
reg_reg_fn(e, i.dest, i.src2);
}
} else if (i.src2.is_constant) {
if (i.dest == i.src1) {
if (i.src2.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2.constant());
reg_reg_fn(e, i.dest, temp);
}
} else {
e.mov(i.dest, i.src1);
if (i.src2.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, static_cast<int32_t>(i.src2.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2.constant());
reg_reg_fn(e, i.dest, temp);
}
}
} else {
if (i.dest == i.src1) {
reg_reg_fn(e, i.dest, i.src2);
} else if (i.dest == i.src2) {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2);
e.mov(i.dest, i.src1);
reg_reg_fn(e, i.dest, temp);
} else {
e.mov(i.dest, i.src1);
reg_reg_fn(e, i.dest, i.src2);
}
}
}
template <typename FN>
static void EmitCommutativeBinaryXmmOp(
X64Emitter& e, const EmitArgType& i, const FN& fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
e.LoadConstantXmm(e.xmm0, i.src1.constant());
fn(e, i.dest, e.xmm0, i.src2);
} else if (i.src2.is_constant) {
e.LoadConstantXmm(e.xmm0, i.src2.constant());
fn(e, i.dest, i.src1, e.xmm0);
} else {
fn(e, i.dest, i.src1, i.src2);
}
}
template <typename FN>
static void EmitAssociativeBinaryXmmOp(
X64Emitter& e, const EmitArgType& i, const FN& fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
e.LoadConstantXmm(e.xmm0, i.src1.constant());
fn(e, i.dest, e.xmm0, i.src2);
} else if (i.src2.is_constant) {
e.LoadConstantXmm(e.xmm0, i.src2.constant());
fn(e, i.dest, i.src1, e.xmm0);
} else {
fn(e, i.dest, i.src1, i.src2);
}
}
template <typename REG_REG_FN, typename REG_CONST_FN>
static void EmitCommutativeCompareOp(
X64Emitter& e, const EmitArgType& i,
const REG_REG_FN& reg_reg_fn, const REG_CONST_FN& reg_const_fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
if (i.src1.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.src2, static_cast<int32_t>(i.src1.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
e.mov(temp, i.src1.constant());
reg_reg_fn(e, i.src2, temp);
}
} else if (i.src2.is_constant) {
if (i.src2.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.src1, static_cast<int32_t>(i.src2.constant()));
} else {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2.constant());
reg_reg_fn(e, i.src1, temp);
}
} else {
reg_reg_fn(e, i.src1, i.src2);
}
}
template <typename REG_REG_FN, typename REG_CONST_FN>
static void EmitAssociativeCompareOp(
X64Emitter& e, const EmitArgType& i,
const REG_REG_FN& reg_reg_fn, const REG_CONST_FN& reg_const_fn) {
if (i.src1.is_constant) {
assert_true(!i.src2.is_constant);
if (i.src1.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, i.src2, static_cast<int32_t>(i.src1.constant()), true);
} else {
auto temp = GetTempReg<typename decltype(i.src1)::reg_type>(e);
e.mov(temp, i.src1.constant());
reg_reg_fn(e, i.dest, i.src2, temp, true);
}
} else if (i.src2.is_constant) {
if (i.src2.ConstantFitsIn32Reg()) {
reg_const_fn(e, i.dest, i.src1, static_cast<int32_t>(i.src2.constant()), false);
} else {
auto temp = GetTempReg<typename decltype(i.src2)::reg_type>(e);
e.mov(temp, i.src2.constant());
reg_reg_fn(e, i.dest, i.src1, temp, false);
}
} else {
reg_reg_fn(e, i.dest, i.src1, i.src2, false);
}
}
};
static const int ANY = -1;
typedef int tag_t;
static const tag_t TAG0 = 0;
static const tag_t TAG1 = 1;
static const tag_t TAG2 = 2;
static const tag_t TAG3 = 3;
static const tag_t TAG4 = 4;
static const tag_t TAG5 = 5;
static const tag_t TAG6 = 6;
static const tag_t TAG7 = 7;
template <typename T>
void Register() {
sequence_table.insert({ T::head_key(), T::Select });
}
template <typename T, typename Tn, typename... Ts>
void Register() {
Register<T>();
Register<Tn, Ts...>();
};
#define EMITTER_OPCODE_TABLE(name, ...) \
void Register_##name() { \
Register<__VA_ARGS__>(); \
}
#define MATCH(...) __VA_ARGS__
#define EMITTER(name, match) struct name : SingleSequence<name, match>
#define SEQUENCE(name, match) struct name : Sequence<name, match>
} // namespace

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_SEQUENCES_H_
#define XENIA_BACKEND_X64_X64_SEQUENCES_H_
namespace xe {
namespace cpu {
namespace hir {
class Instr;
} // namespace hir
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64Emitter;
void RegisterSequences();
bool SelectSequence(X64Emitter& e, const hir::Instr* i,
const hir::Instr** new_tail);
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_SEQUENCES_H_

View File

@@ -0,0 +1,147 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_thunk_emitter.h"
#include "third_party/xbyak/xbyak/xbyak.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
using namespace Xbyak;
X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator)
: X64Emitter(backend, allocator) {}
X64ThunkEmitter::~X64ThunkEmitter() {}
HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
// rcx = target
// rdx = arg0
// r8 = arg1
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
// rsp + 0 = return address
mov(qword[rsp + 8 * 3], r8);
mov(qword[rsp + 8 * 2], rdx);
mov(qword[rsp + 8 * 1], rcx);
sub(rsp, stack_size);
mov(qword[rsp + 48], rbx);
mov(qword[rsp + 56], rcx);
mov(qword[rsp + 64], rbp);
mov(qword[rsp + 72], rsi);
mov(qword[rsp + 80], rdi);
mov(qword[rsp + 88], r12);
mov(qword[rsp + 96], r13);
mov(qword[rsp + 104], r14);
mov(qword[rsp + 112], r15);
/*movaps(ptr[rsp + 128], xmm6);
movaps(ptr[rsp + 144], xmm7);
movaps(ptr[rsp + 160], xmm8);
movaps(ptr[rsp + 176], xmm9);
movaps(ptr[rsp + 192], xmm10);
movaps(ptr[rsp + 208], xmm11);
movaps(ptr[rsp + 224], xmm12);
movaps(ptr[rsp + 240], xmm13);
movaps(ptr[rsp + 256], xmm14);
movaps(ptr[rsp + 272], xmm15);*/
mov(rax, rcx);
mov(rcx, rdx);
mov(rdx, r8);
call(rax);
/*movaps(xmm6, ptr[rsp + 128]);
movaps(xmm7, ptr[rsp + 144]);
movaps(xmm8, ptr[rsp + 160]);
movaps(xmm9, ptr[rsp + 176]);
movaps(xmm10, ptr[rsp + 192]);
movaps(xmm11, ptr[rsp + 208]);
movaps(xmm12, ptr[rsp + 224]);
movaps(xmm13, ptr[rsp + 240]);
movaps(xmm14, ptr[rsp + 256]);
movaps(xmm15, ptr[rsp + 272]);*/
mov(rbx, qword[rsp + 48]);
mov(rcx, qword[rsp + 56]);
mov(rbp, qword[rsp + 64]);
mov(rsi, qword[rsp + 72]);
mov(rdi, qword[rsp + 80]);
mov(r12, qword[rsp + 88]);
mov(r13, qword[rsp + 96]);
mov(r14, qword[rsp + 104]);
mov(r15, qword[rsp + 112]);
add(rsp, stack_size);
mov(rcx, qword[rsp + 8 * 1]);
mov(rdx, qword[rsp + 8 * 2]);
mov(r8, qword[rsp + 8 * 3]);
ret();
void* fn = Emplace(stack_size);
return (HostToGuestThunk)fn;
}
GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
// rcx = context
// rdx = target function
// r8 = arg0
// r9 = arg1
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
// rsp + 0 = return address
mov(qword[rsp + 8 * 2], rdx);
mov(qword[rsp + 8 * 1], rcx);
sub(rsp, stack_size);
mov(qword[rsp + 48], rbx);
mov(qword[rsp + 56], rcx);
mov(qword[rsp + 64], rbp);
mov(qword[rsp + 72], rsi);
mov(qword[rsp + 80], rdi);
mov(qword[rsp + 88], r12);
mov(qword[rsp + 96], r13);
mov(qword[rsp + 104], r14);
mov(qword[rsp + 112], r15);
// TODO(benvanik): save things? XMM0-5?
mov(rax, rdx);
mov(rdx, r8);
mov(r8, r9);
mov(r9, r10);
call(rax);
mov(rbx, qword[rsp + 48]);
mov(rcx, qword[rsp + 56]);
mov(rbp, qword[rsp + 64]);
mov(rsi, qword[rsp + 72]);
mov(rdi, qword[rsp + 80]);
mov(r12, qword[rsp + 88]);
mov(r13, qword[rsp + 96]);
mov(r14, qword[rsp + 104]);
mov(r15, qword[rsp + 112]);
add(rsp, stack_size);
mov(rcx, qword[rsp + 8 * 1]);
mov(rdx, qword[rsp + 8 * 2]);
ret();
void* fn = Emplace(stack_size);
return (HostToGuestThunk)fn;
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,143 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_THUNK_EMITTER_H_
#define XENIA_BACKEND_X64_X64_THUNK_EMITTER_H_
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/backend/x64/x64_emitter.h"
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
/**
* Stack Layout
* ----------------------------
* NOTE: stack must always be 16b aligned.
*
* Thunk stack:
* +------------------+
* | arg temp, 3 * 8 | rsp + 0
* | |
* | |
* +------------------+
* | scratch, 16b | rsp + 32
* | |
* +------------------+
* | rbx | rsp + 48
* +------------------+
* | rcx / context | rsp + 56
* +------------------+
* | rbp | rsp + 64
* +------------------+
* | rsi | rsp + 72
* +------------------+
* | rdi | rsp + 80
* +------------------+
* | r12 | rsp + 88
* +------------------+
* | r13 | rsp + 96
* +------------------+
* | r14 | rsp + 104
* +------------------+
* | r15 | rsp + 112
* +------------------+
* | (return address) | rsp + 120
* +------------------+
* | (rcx home) | rsp + 128
* +------------------+
* | (rdx home) | rsp + 136
* +------------------+
*
*
* TODO:
* +------------------+
* | xmm6 | rsp + 128
* | |
* +------------------+
* | xmm7 | rsp + 144
* | |
* +------------------+
* | xmm8 | rsp + 160
* | |
* +------------------+
* | xmm9 | rsp + 176
* | |
* +------------------+
* | xmm10 | rsp + 192
* | |
* +------------------+
* | xmm11 | rsp + 208
* | |
* +------------------+
* | xmm12 | rsp + 224
* | |
* +------------------+
* | xmm13 | rsp + 240
* | |
* +------------------+
* | xmm14 | rsp + 256
* | |
* +------------------+
* | xmm15 | rsp + 272
* | |
* +------------------+
*
* Guest stack:
* +------------------+
* | arg temp, 3 * 8 | rsp + 0
* | |
* | |
* +------------------+
* | scratch, 48b | rsp + 32
* | |
* +------------------+
* | rcx / context | rsp + 80
* +------------------+
* | guest ret addr | rsp + 88
* +------------------+
* | call ret addr | rsp + 96
* +------------------+
* ... locals ...
* +------------------+
* | (return address) |
* +------------------+
*
*/
class StackLayout {
public:
const static size_t THUNK_STACK_SIZE = 120;
const static size_t GUEST_STACK_SIZE = 104;
const static size_t GUEST_RCX_HOME = 80;
const static size_t GUEST_RET_ADDR = 88;
const static size_t GUEST_CALL_RET_ADDR = 96;
};
class X64ThunkEmitter : public X64Emitter {
public:
X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator);
virtual ~X64ThunkEmitter();
// Call a generated function, saving all stack parameters.
HostToGuestThunk EmitHostToGuestThunk();
// Function that guest code can call to transition into host code.
GuestToHostThunk EmitGuestToHostThunk();
};
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_THUNK_EMITTER_H_

View File

@@ -0,0 +1,202 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/backend/x64/x64_tracers.h"
#include "xenia/cpu/backend/x64/x64_emitter.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/cpu/runtime/thread_state.h"
using namespace xe;
using namespace xe::cpu::backend::x64;
using namespace xe::cpu::runtime;
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
#define ITRACE 0
#define DTRACE 0
#define TARGET_THREAD 1
#define IFLUSH() fflush(stdout)
#define IPRINT \
if (thread_state->thread_id() == TARGET_THREAD) printf
#define DFLUSH() fflush(stdout)
#define DPRINT \
DFLUSH(); \
if (thread_state->thread_id() == TARGET_THREAD) printf
uint32_t GetTracingMode() {
uint32_t mode = 0;
#if ITRACE
mode |= TRACING_INSTR;
#endif // ITRACE
#if DTRACE
mode |= TRACING_DATA;
#endif // DTRACE
return mode;
}
void TraceString(void* raw_context, const char* str) {
auto thread_state = *((ThreadState**)raw_context);
IPRINT("XE[t] :%d: %s\n", thread_state->thread_id(), str);
IFLUSH();
}
void TraceContextLoadI8(void* raw_context, uint64_t offset, uint8_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = ctx i8 +%llu\n", (int8_t)value, value, offset);
}
void TraceContextLoadI16(void* raw_context, uint64_t offset, uint16_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = ctx i16 +%llu\n", (int16_t)value, value, offset);
}
void TraceContextLoadI32(void* raw_context, uint64_t offset, uint32_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = ctx i32 +%llu\n", (int32_t)value, value, offset);
}
void TraceContextLoadI64(void* raw_context, uint64_t offset, uint64_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%lld (%llX) = ctx i64 +%llu\n", (int64_t)value, value, offset);
}
void TraceContextLoadF32(void* raw_context, uint64_t offset, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%e (%X) = ctx f32 +%llu\n", poly::m128_f32<0>(value),
poly::m128_i32<0>(value), offset);
}
void TraceContextLoadF64(void* raw_context, uint64_t offset,
const double* value) {
auto thread_state = *((ThreadState**)raw_context);
auto v = _mm_loadu_pd(value);
DPRINT("%le (%llX) = ctx f64 +%llu\n", poly::m128_f64<0>(v),
poly::m128_i64<0>(v), offset);
}
void TraceContextLoadV128(void* raw_context, uint64_t offset, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("[%e, %e, %e, %e] [%.8X, %.8X, %.8X, %.8X] = ctx v128 +%llu\n",
poly::m128_f32<0>(value), poly::m128_f32<1>(value),
poly::m128_f32<2>(value), poly::m128_f32<3>(value),
poly::m128_i32<0>(value), poly::m128_i32<1>(value),
poly::m128_i32<2>(value), poly::m128_i32<3>(value), offset);
}
void TraceContextStoreI8(void* raw_context, uint64_t offset, uint8_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx i8 +%llu = %d (%X)\n", offset, (int8_t)value, value);
}
void TraceContextStoreI16(void* raw_context, uint64_t offset, uint16_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx i16 +%llu = %d (%X)\n", offset, (int16_t)value, value);
}
void TraceContextStoreI32(void* raw_context, uint64_t offset, uint32_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx i32 +%llu = %d (%X)\n", offset, (int32_t)value, value);
}
void TraceContextStoreI64(void* raw_context, uint64_t offset, uint64_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx i64 +%llu = %lld (%llX)\n", offset, (int64_t)value, value);
}
void TraceContextStoreF32(void* raw_context, uint64_t offset, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx f32 +%llu = %e (%X)\n", offset, poly::m128_f32<0>(value),
poly::m128_i32<0>(value));
}
void TraceContextStoreF64(void* raw_context, uint64_t offset,
const double* value) {
auto thread_state = *((ThreadState**)raw_context);
auto v = _mm_loadu_pd(value);
DPRINT("ctx f64 +%llu = %le (%llX)\n", offset, poly::m128_f64<0>(v),
poly::m128_i64<0>(v));
}
void TraceContextStoreV128(void* raw_context, uint64_t offset, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("ctx v128 +%llu = [%e, %e, %e, %e] [%.8X, %.8X, %.8X, %.8X]\n", offset,
poly::m128_f32<0>(value), poly::m128_f32<1>(value),
poly::m128_f32<2>(value), poly::m128_f32<3>(value),
poly::m128_i32<0>(value), poly::m128_i32<1>(value),
poly::m128_i32<2>(value), poly::m128_i32<3>(value));
}
void TraceMemoryLoadI8(void* raw_context, uint32_t address, uint8_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = load.i8 %.8X\n", (int8_t)value, value, address);
}
void TraceMemoryLoadI16(void* raw_context, uint32_t address, uint16_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = load.i16 %.8X\n", (int16_t)value, value, address);
}
void TraceMemoryLoadI32(void* raw_context, uint32_t address, uint32_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%d (%X) = load.i32 %.8X\n", (int32_t)value, value, address);
}
void TraceMemoryLoadI64(void* raw_context, uint32_t address, uint64_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%lld (%llX) = load.i64 %.8X\n", (int64_t)value, value, address);
}
void TraceMemoryLoadF32(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%e (%X) = load.f32 %.8X\n", poly::m128_f32<0>(value),
poly::m128_i32<0>(value), address);
}
void TraceMemoryLoadF64(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("%le (%llX) = load.f64 %.8X\n", poly::m128_f64<0>(value),
poly::m128_i64<0>(value), address);
}
void TraceMemoryLoadV128(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("[%e, %e, %e, %e] [%.8X, %.8X, %.8X, %.8X] = load.v128 %.8X\n",
poly::m128_f32<0>(value), poly::m128_f32<1>(value),
poly::m128_f32<2>(value), poly::m128_f32<3>(value),
poly::m128_i32<0>(value), poly::m128_i32<1>(value),
poly::m128_i32<2>(value), poly::m128_i32<3>(value), address);
}
void TraceMemoryStoreI8(void* raw_context, uint32_t address, uint8_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.i8 %.8X = %d (%X)\n", address, (int8_t)value, value);
}
void TraceMemoryStoreI16(void* raw_context, uint32_t address, uint16_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.i16 %.8X = %d (%X)\n", address, (int16_t)value, value);
}
void TraceMemoryStoreI32(void* raw_context, uint32_t address, uint32_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.i32 %.8X = %d (%X)\n", address, (int32_t)value, value);
}
void TraceMemoryStoreI64(void* raw_context, uint32_t address, uint64_t value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.i64 %.8X = %lld (%llX)\n", address, (int64_t)value, value);
}
void TraceMemoryStoreF32(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.f32 %.8X = %e (%X)\n", address, poly::m128_f32<0>(value),
poly::m128_i32<0>(value));
}
void TraceMemoryStoreF64(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.f64 %.8X = %le (%llX)\n", address, poly::m128_f64<0>(value),
poly::m128_i64<0>(value));
}
void TraceMemoryStoreV128(void* raw_context, uint32_t address, __m128 value) {
auto thread_state = *((ThreadState**)raw_context);
DPRINT("store.v128 %.8X = [%e, %e, %e, %e] [%.8X, %.8X, %.8X, %.8X]\n",
address, poly::m128_f32<0>(value), poly::m128_f32<1>(value),
poly::m128_f32<2>(value), poly::m128_f32<3>(value),
poly::m128_i32<0>(value), poly::m128_i32<1>(value),
poly::m128_i32<2>(value), poly::m128_i32<3>(value));
}
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,72 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BACKEND_X64_X64_TRACERS_H_
#define XENIA_BACKEND_X64_X64_TRACERS_H_
#include <xmmintrin.h>
#include <cstdint>
namespace xe {
namespace cpu {
namespace backend {
namespace x64 {
class X64Emitter;
enum TracingMode {
TRACING_INSTR = (1 << 1),
TRACING_DATA = (1 << 2),
};
uint32_t GetTracingMode();
inline bool IsTracingInstr() { return (GetTracingMode() & TRACING_INSTR) != 0; }
inline bool IsTracingData() { return (GetTracingMode() & TRACING_DATA) != 0; }
void TraceString(void* raw_context, const char* str);
void TraceContextLoadI8(void* raw_context, uint64_t offset, uint8_t value);
void TraceContextLoadI16(void* raw_context, uint64_t offset, uint16_t value);
void TraceContextLoadI32(void* raw_context, uint64_t offset, uint32_t value);
void TraceContextLoadI64(void* raw_context, uint64_t offset, uint64_t value);
void TraceContextLoadF32(void* raw_context, uint64_t offset, __m128 value);
void TraceContextLoadF64(void* raw_context, uint64_t offset,
const double* value);
void TraceContextLoadV128(void* raw_context, uint64_t offset, __m128 value);
void TraceContextStoreI8(void* raw_context, uint64_t offset, uint8_t value);
void TraceContextStoreI16(void* raw_context, uint64_t offset, uint16_t value);
void TraceContextStoreI32(void* raw_context, uint64_t offset, uint32_t value);
void TraceContextStoreI64(void* raw_context, uint64_t offset, uint64_t value);
void TraceContextStoreF32(void* raw_context, uint64_t offset, __m128 value);
void TraceContextStoreF64(void* raw_context, uint64_t offset,
const double* value);
void TraceContextStoreV128(void* raw_context, uint64_t offset, __m128 value);
void TraceMemoryLoadI8(void* raw_context, uint32_t address, uint8_t value);
void TraceMemoryLoadI16(void* raw_context, uint32_t address, uint16_t value);
void TraceMemoryLoadI32(void* raw_context, uint32_t address, uint32_t value);
void TraceMemoryLoadI64(void* raw_context, uint32_t address, uint64_t value);
void TraceMemoryLoadF32(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryLoadF64(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryLoadV128(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryStoreI8(void* raw_context, uint32_t address, uint8_t value);
void TraceMemoryStoreI16(void* raw_context, uint32_t address, uint16_t value);
void TraceMemoryStoreI32(void* raw_context, uint32_t address, uint32_t value);
void TraceMemoryStoreI64(void* raw_context, uint32_t address, uint64_t value);
void TraceMemoryStoreF32(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryStoreF64(void* raw_context, uint32_t address, __m128 value);
void TraceMemoryStoreV128(void* raw_context, uint32_t address, __m128 value);
} // namespace x64
} // namespace backend
} // namespace cpu
} // namespace xe
#endif // XENIA_BACKEND_X64_X64_TRACERS_H_

View File

@@ -0,0 +1,49 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/compiler/compiler_pass.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
using xe::cpu::hir::HIRBuilder;
using xe::cpu::runtime::Runtime;
Compiler::Compiler(Runtime* runtime) : runtime_(runtime) {}
Compiler::~Compiler() { Reset(); }
void Compiler::AddPass(std::unique_ptr<CompilerPass> pass) {
pass->Initialize(this);
passes_.push_back(std::move(pass));
}
void Compiler::Reset() {}
int Compiler::Compile(HIRBuilder* builder) {
// TODO(benvanik): sophisticated stuff. Run passes in parallel, run until they
// stop changing things, etc.
for (size_t i = 0; i < passes_.size(); ++i) {
auto& pass = passes_[i];
scratch_arena_.Reset();
if (pass->Run(builder)) {
return 1;
}
}
return 0;
}
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,58 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_COMPILER_H_
#define XENIA_COMPILER_COMPILER_H_
#include <memory>
#include <vector>
#include "xenia/cpu/hir/hir_builder.h"
#include "poly/arena.h"
namespace xe {
namespace cpu {
namespace runtime {
class Runtime;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace compiler {
class CompilerPass;
class Compiler {
public:
Compiler(runtime::Runtime* runtime);
~Compiler();
runtime::Runtime* runtime() const { return runtime_; }
poly::Arena* scratch_arena() { return &scratch_arena_; }
void AddPass(std::unique_ptr<CompilerPass> pass);
void Reset();
int Compile(hir::HIRBuilder* builder);
private:
runtime::Runtime* runtime_;
poly::Arena scratch_arena_;
std::vector<std::unique_ptr<CompilerPass>> passes_;
};
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_COMPILER_H_

View File

@@ -0,0 +1,34 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/compiler_pass.h"
#include "xenia/cpu/compiler/compiler.h"
namespace xe {
namespace cpu {
namespace compiler {
CompilerPass::CompilerPass() : runtime_(0), compiler_(0) {}
CompilerPass::~CompilerPass() = default;
int CompilerPass::Initialize(Compiler* compiler) {
runtime_ = compiler->runtime();
compiler_ = compiler;
return 0;
}
poly::Arena* CompilerPass::scratch_arena() const {
return compiler_->scratch_arena();
}
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,51 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_COMPILER_PASS_H_
#define XENIA_COMPILER_COMPILER_PASS_H_
#include "xenia/cpu/hir/hir_builder.h"
#include "poly/arena.h"
namespace xe {
namespace cpu {
namespace runtime {
class Runtime;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace compiler {
class Compiler;
class CompilerPass {
public:
CompilerPass();
virtual ~CompilerPass();
virtual int Initialize(Compiler* compiler);
virtual int Run(hir::HIRBuilder* builder) = 0;
protected:
poly::Arena* scratch_arena() const;
protected:
runtime::Runtime* runtime_;
Compiler* compiler_;
};
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_COMPILER_PASS_H_

View File

@@ -0,0 +1,179 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_COMPILER_PASSES_H_
#define XENIA_COMPILER_COMPILER_PASSES_H_
#include "xenia/cpu/compiler/passes/constant_propagation_pass.h"
#include "xenia/cpu/compiler/passes/context_promotion_pass.h"
#include "xenia/cpu/compiler/passes/control_flow_analysis_pass.h"
#include "xenia/cpu/compiler/passes/control_flow_simplification_pass.h"
#include "xenia/cpu/compiler/passes/data_flow_analysis_pass.h"
#include "xenia/cpu/compiler/passes/dead_code_elimination_pass.h"
//#include "xenia/cpu/compiler/passes/dead_store_elimination_pass.h"
#include "xenia/cpu/compiler/passes/finalization_pass.h"
#include "xenia/cpu/compiler/passes/register_allocation_pass.h"
#include "xenia/cpu/compiler/passes/simplification_pass.h"
#include "xenia/cpu/compiler/passes/validation_pass.h"
#include "xenia/cpu/compiler/passes/value_reduction_pass.h"
// TODO:
// - mark_use/mark_set
// For now: mark_all_changed on all calls
// For external functions:
// - load_context/mark_use on all arguments
// - mark_set on return argument?
// For internal functions:
// - if liveness analysis already done, use that
// - otherwise, assume everything dirty (ACK!)
// - could use scanner to insert mark_use
//
// Maybe:
// - v0.xx = load_constant <c>
// - v0.xx = load_zero
// Would prevent NULL defs on values, and make constant de-duping possible.
// Not sure if it's worth it, though, as the extra register allocation
// pressure due to de-duped constants seems like it would slow things down
// a lot.
//
// - CFG:
// Blocks need predecessors()/successor()
// phi Instr reference
//
// - block liveness tracking (in/out)
// Block gets:
// AddIncomingValue(Value* value, Block* src_block) ??
// Potentially interesting passes:
//
// Run order:
// ContextPromotion
// Simplification
// ConstantPropagation
// TypePropagation
// ByteSwapElimination
// Simplification
// DeadStoreElimination
// DeadCodeElimination
//
// - TypePropagation
// There are many extensions/truncations in generated code right now due to
// various load/stores of varying widths. Being able to find and short-
// circuit the conversions early on would make following passes cleaner
// and faster as they'd have to trace through fewer value definitions.
// Example (after ContextPromotion):
// v81.i64 = load_context +88
// v82.i32 = truncate v81.i64
// v84.i32 = and v82.i32, 3F
// v85.i64 = zero_extend v84.i32
// v87.i64 = load_context +248
// v88.i64 = v85.i64
// v89.i32 = truncate v88.i64 <-- zero_extend/truncate => v84.i32
// v90.i32 = byte_swap v89.i32
// store v87.i64, v90.i32
// after type propagation / simplification / DCE:
// v81.i64 = load_context +88
// v82.i32 = truncate v81.i64
// v84.i32 = and v82.i32, 3F
// v87.i64 = load_context +248
// v90.i32 = byte_swap v84.i32
// store v87.i64, v90.i32
//
// - ByteSwapElimination
// Find chained byte swaps and replace with assignments. This is often found
// in memcpy paths.
// Example:
// v0 = load ...
// v1 = byte_swap v0
// v2 = byte_swap v1
// store ..., v2 <-- this could be v0
//
// It may be tricky to detect, though, as often times there are intervening
// instructions:
// v21.i32 = load v20.i64
// v22.i32 = byte_swap v21.i32
// v23.i64 = zero_extend v22.i32
// v88.i64 = v23.i64 (from ContextPromotion)
// v89.i32 = truncate v88.i64
// v90.i32 = byte_swap v89.i32
// store v87.i64, v90.i32
// After type propagation:
// v21.i32 = load v20.i64
// v22.i32 = byte_swap v21.i32
// v89.i32 = v22.i32
// v90.i32 = byte_swap v89.i32
// store v87.i64, v90.i32
// This could ideally become:
// v21.i32 = load v20.i64
// ... (DCE takes care of this) ...
// store v87.i64, v21.i32
//
// - DeadStoreElimination
// Generic DSE pass, removing all redundant stores. ContextPromotion may be
// able to take care of most of these, as the input assembly is generally
// pretty optimized already. This pass would mainly be looking for introduced
// stores, such as those from comparisons.
//
// Example:
// <block0>:
// v0 = compare_ult ... (later removed by DCE)
// v1 = compare_ugt ... (later removed by DCE)
// v2 = compare_eq ...
// store_context +300, v0 <-- removed
// store_context +301, v1 <-- removed
// store_context +302, v2 <-- removed
// branch_true v1, ...
// <block1>:
// v3 = compare_ult ...
// v4 = compare_ugt ...
// v5 = compare_eq ...
// store_context +300, v3 <-- these may be required if at end of function
// store_context +301, v4 or before a call
// store_context +302, v5
// branch_true v5, ...
//
// - X86Canonicalization
// For various opcodes add copies/commute the arguments to match x86
// operand semantics. This makes code generation easier and if done
// before register allocation can prevent a lot of extra shuffling in
// the emitted code.
//
// Example:
// <block0>:
// v0 = ...
// v1 = ...
// v2 = add v0, v1 <-- v1 now unused
// Becomes:
// v0 = ...
// v1 = ...
// v1 = add v1, v0 <-- src1 = dest/src, so reuse for both
// by commuting and setting dest = src1
//
// - RegisterAllocation
// Given a machine description (register classes, counts) run over values
// and assign them to registers, adding spills as needed. It should be
// possible to directly emit code from this form.
//
// Example:
// <block0>:
// v0 = load_context +0
// v1 = load_context +1
// v0 = add v0, v1
// ...
// v2 = mul v0, v1
// Becomes:
// reg0 = load_context +0
// reg1 = load_context +1
// reg2 = add reg0, reg1
// store_local +123, reg2 <-- spill inserted
// ...
// reg0 = load_local +123 <-- load inserted
// reg0 = mul reg0, reg1
#endif // XENIA_COMPILER_COMPILER_PASSES_H_

View File

@@ -0,0 +1,489 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/constant_propagation_pass.h"
#include "xenia/cpu/runtime/function.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::TypeName;
using xe::cpu::hir::Value;
ConstantPropagationPass::ConstantPropagationPass() : CompilerPass() {}
ConstantPropagationPass::~ConstantPropagationPass() {}
int ConstantPropagationPass::Run(HIRBuilder* builder) {
// Once ContextPromotion has run there will likely be a whole slew of
// constants that can be pushed through the function.
// Example:
// store_context +100, 1000
// v0 = load_context +100
// v1 = add v0, v0
// store_context +200, v1
// after PromoteContext:
// store_context +100, 1000
// v0 = 1000
// v1 = add v0, v0
// store_context +200, v1
// after PropagateConstants:
// store_context +100, 1000
// v0 = 1000
// v1 = add 1000, 1000
// store_context +200, 2000
// A DCE run after this should clean up any of the values no longer needed.
//
// Special care needs to be taken with paired instructions. For example,
// DID_CARRY needs to be set as a constant:
// v1 = sub.2 20, 1
// v2 = did_carry v1
// should become:
// v1 = 19
// v2 = 0
auto block = builder->first_block();
while (block) {
auto i = block->instr_head;
while (i) {
auto v = i->dest;
switch (i->opcode->num) {
case OPCODE_DEBUG_BREAK_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
i->Replace(&OPCODE_DEBUG_BREAK_info, i->flags);
} else {
i->Remove();
}
}
break;
case OPCODE_TRAP_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
i->Replace(&OPCODE_TRAP_info, i->flags);
} else {
i->Remove();
}
}
break;
case OPCODE_CALL_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
auto symbol_info = i->src2.symbol_info;
i->Replace(&OPCODE_CALL_info, i->flags);
i->src1.symbol_info = symbol_info;
} else {
i->Remove();
}
}
break;
case OPCODE_CALL_INDIRECT:
if (i->src1.value->IsConstant()) {
runtime::FunctionInfo* symbol_info;
if (runtime_->LookupFunctionInfo(
(uint32_t)i->src1.value->constant.i32, &symbol_info)) {
break;
}
i->Replace(&OPCODE_CALL_info, i->flags);
i->src1.symbol_info = symbol_info;
}
break;
case OPCODE_CALL_INDIRECT_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
auto value = i->src2.value;
i->Replace(&OPCODE_CALL_INDIRECT_info, i->flags);
i->set_src1(value);
} else {
i->Remove();
}
}
break;
case OPCODE_BRANCH_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
auto label = i->src2.label;
i->Replace(&OPCODE_BRANCH_info, i->flags);
i->src1.label = label;
} else {
i->Remove();
}
}
break;
case OPCODE_BRANCH_FALSE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantFalse()) {
auto label = i->src2.label;
i->Replace(&OPCODE_BRANCH_info, i->flags);
i->src1.label = label;
} else {
i->Remove();
}
}
break;
case OPCODE_CAST:
if (i->src1.value->IsConstant()) {
TypeName target_type = v->type;
v->set_from(i->src1.value);
v->Cast(target_type);
i->Remove();
}
break;
case OPCODE_ZERO_EXTEND:
if (i->src1.value->IsConstant()) {
TypeName target_type = v->type;
v->set_from(i->src1.value);
v->ZeroExtend(target_type);
i->Remove();
}
break;
case OPCODE_SIGN_EXTEND:
if (i->src1.value->IsConstant()) {
TypeName target_type = v->type;
v->set_from(i->src1.value);
v->SignExtend(target_type);
i->Remove();
}
break;
case OPCODE_TRUNCATE:
if (i->src1.value->IsConstant()) {
TypeName target_type = v->type;
v->set_from(i->src1.value);
v->Truncate(target_type);
i->Remove();
}
break;
case OPCODE_SELECT:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
v->set_from(i->src2.value);
} else {
v->set_from(i->src3.value);
}
i->Remove();
}
break;
case OPCODE_IS_TRUE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantTrue()) {
v->set_constant((int8_t)1);
} else {
v->set_constant((int8_t)0);
}
i->Remove();
}
break;
case OPCODE_IS_FALSE:
if (i->src1.value->IsConstant()) {
if (i->src1.value->IsConstantFalse()) {
v->set_constant((int8_t)1);
} else {
v->set_constant((int8_t)0);
}
i->Remove();
}
break;
// TODO(benvanik): compares
case OPCODE_COMPARE_EQ:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantEQ(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_NE:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantNE(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_SLT:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantSLT(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_SLE:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantSLE(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_SGT:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantSGT(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_SGE:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantSGE(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_ULT:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantULT(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_ULE:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantULE(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_UGT:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantUGT(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_COMPARE_UGE:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
bool value = i->src1.value->IsConstantUGE(i->src2.value);
i->dest->set_constant(value);
i->Remove();
}
break;
case OPCODE_DID_CARRY:
assert_true(!i->src1.value->IsConstant());
break;
case OPCODE_DID_OVERFLOW:
assert_true(!i->src1.value->IsConstant());
break;
case OPCODE_DID_SATURATE:
assert_true(!i->src1.value->IsConstant());
break;
case OPCODE_ADD:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
bool did_carry = v->Add(i->src2.value);
bool propagate_carry = !!(i->flags & ARITHMETIC_SET_CARRY);
i->Remove();
// If carry is set find the DID_CARRY and fix it.
if (propagate_carry) {
PropagateCarry(v, did_carry);
}
}
break;
// TODO(benvanik): ADD_CARRY (w/ ARITHMETIC_SET_CARRY)
case OPCODE_ADD_CARRY:
if (i->src1.value->IsConstantZero() &&
i->src2.value->IsConstantZero()) {
Value* ca = i->src3.value;
// If carry is set find the DID_CARRY and fix it.
if (!!(i->flags & ARITHMETIC_SET_CARRY)) {
auto next = i->dest->use_head;
while (next) {
auto use = next;
next = use->next;
if (use->instr->opcode == &OPCODE_DID_CARRY_info) {
// Replace carry value.
use->instr->Replace(&OPCODE_ASSIGN_info, 0);
use->instr->set_src1(ca);
}
}
}
if (i->dest->type == ca->type) {
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(ca);
} else {
i->Replace(&OPCODE_ZERO_EXTEND_info, 0);
i->set_src1(ca);
}
}
break;
case OPCODE_SUB:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
bool did_carry = v->Sub(i->src2.value);
bool propagate_carry = !!(i->flags & ARITHMETIC_SET_CARRY);
i->Remove();
// If carry is set find the DID_CARRY and fix it.
if (propagate_carry) {
PropagateCarry(v, did_carry);
}
}
break;
case OPCODE_MUL:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Mul(i->src2.value);
i->Remove();
}
break;
case OPCODE_DIV:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Div(i->src2.value);
i->Remove();
}
break;
// case OPCODE_MUL_ADD:
// case OPCODE_MUL_SUB
case OPCODE_NEG:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->Neg();
i->Remove();
}
break;
case OPCODE_ABS:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->Abs();
i->Remove();
}
break;
case OPCODE_SQRT:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->Sqrt();
i->Remove();
}
break;
case OPCODE_RSQRT:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->RSqrt();
i->Remove();
}
break;
case OPCODE_AND:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->And(i->src2.value);
i->Remove();
}
break;
case OPCODE_OR:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Or(i->src2.value);
i->Remove();
}
break;
case OPCODE_XOR:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Xor(i->src2.value);
i->Remove();
}
break;
case OPCODE_NOT:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->Not();
i->Remove();
}
break;
case OPCODE_SHL:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Shl(i->src2.value);
i->Remove();
}
break;
// TODO(benvanik): VECTOR_SHL
case OPCODE_SHR:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Shr(i->src2.value);
i->Remove();
}
break;
case OPCODE_SHA:
if (i->src1.value->IsConstant() && i->src2.value->IsConstant()) {
v->set_from(i->src1.value);
v->Sha(i->src2.value);
i->Remove();
}
break;
// TODO(benvanik): ROTATE_LEFT
case OPCODE_BYTE_SWAP:
if (i->src1.value->IsConstant()) {
v->set_from(i->src1.value);
v->ByteSwap();
i->Remove();
}
break;
case OPCODE_CNTLZ:
if (i->src1.value->IsConstant()) {
v->set_zero(v->type);
v->CountLeadingZeros(i->src1.value);
i->Remove();
}
break;
// TODO(benvanik): INSERT/EXTRACT
// TODO(benvanik): SPLAT/PERMUTE/SWIZZLE
case OPCODE_SPLAT:
if (i->src1.value->IsConstant()) {
// Quite a few of these, from building vec128s.
}
break;
default:
// Ignored.
break;
}
i = i->next;
}
block = block->next;
}
return 0;
}
void ConstantPropagationPass::PropagateCarry(Value* v, bool did_carry) {
auto next = v->use_head;
while (next) {
auto use = next;
next = use->next;
if (use->instr->opcode == &OPCODE_DID_CARRY_info) {
// Replace carry value.
use->instr->dest->set_constant(did_carry ? 1 : 0);
use->instr->Remove();
}
}
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,36 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_CONSTANT_PROPAGATION_PASS_H_
#define XENIA_COMPILER_PASSES_CONSTANT_PROPAGATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ConstantPropagationPass : public CompilerPass {
public:
ConstantPropagationPass();
~ConstantPropagationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
void PropagateCarry(hir::Value* v, bool did_carry);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_CONSTANT_PROPAGATION_PASS_H_

View File

@@ -0,0 +1,150 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/context_promotion_pass.h"
#include <gflags/gflags.h>
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
DEFINE_bool(store_all_context_values, false,
"Don't strip dead context stores to aid in debugging.");
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::frontend::ContextInfo;
using xe::cpu::hir::Block;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::Value;
ContextPromotionPass::ContextPromotionPass() : CompilerPass() {}
ContextPromotionPass::~ContextPromotionPass() {}
int ContextPromotionPass::Initialize(Compiler* compiler) {
if (CompilerPass::Initialize(compiler)) {
return 1;
}
// This is a terrible implementation.
ContextInfo* context_info = runtime_->frontend()->context_info();
context_values_.resize(context_info->size());
context_validity_.resize(static_cast<uint32_t>(context_info->size()));
return 0;
}
int ContextPromotionPass::Run(HIRBuilder* builder) {
// Like mem2reg, but because context memory is unaliasable it's easier to
// check and convert LoadContext/StoreContext into value operations.
// Example of load->value promotion:
// v0 = load_context +100
// store_context +200, v0
// v1 = load_context +100 <-- replace with v1 = v0
// store_context +200, v1
//
// It'd be possible in this stage to also remove redundant context stores:
// Example of dead store elimination:
// store_context +100, v0 <-- removed due to following store
// store_context +100, v1
// This is more generally done by DSE, however if it could be done here
// instead as it may be faster (at least on the block-level).
// Promote loads to values.
// Process each block independently, for now.
auto block = builder->first_block();
while (block) {
PromoteBlock(block);
block = block->next;
}
// Remove all dead stores.
if (!FLAGS_store_all_context_values) {
block = builder->first_block();
while (block) {
RemoveDeadStoresBlock(block);
block = block->next;
}
}
return 0;
}
void ContextPromotionPass::PromoteBlock(Block* block) {
auto& validity = context_validity_;
validity.reset();
Instr* i = block->instr_head;
while (i) {
auto next = i->next;
if (i->opcode->flags & OPCODE_FLAG_VOLATILE) {
// Volatile instruction - requires all context values be flushed.
validity.reset();
} else if (i->opcode == &OPCODE_LOAD_CONTEXT_info) {
size_t offset = i->src1.offset;
if (validity.test(static_cast<uint32_t>(offset))) {
// Legit previous value, reuse.
Value* previous_value = context_values_[offset];
i->opcode = &hir::OPCODE_ASSIGN_info;
i->set_src1(previous_value);
} else {
// Store the loaded value into the table.
context_values_[offset] = i->dest;
validity.set(static_cast<uint32_t>(offset));
}
} else if (i->opcode == &OPCODE_STORE_CONTEXT_info) {
size_t offset = i->src1.offset;
Value* value = i->src2.value;
// Store value into the table for later.
context_values_[offset] = value;
validity.set(static_cast<uint32_t>(offset));
}
i = next;
}
}
void ContextPromotionPass::RemoveDeadStoresBlock(Block* block) {
auto& validity = context_validity_;
validity.reset();
// Walk backwards and mark offsets that are written to.
// If the offset was written to earlier, ignore the store.
Instr* i = block->instr_tail;
while (i) {
Instr* prev = i->prev;
if (i->opcode->flags & (OPCODE_FLAG_VOLATILE | OPCODE_FLAG_BRANCH)) {
// Volatile instruction - requires all context values be flushed.
validity.reset();
} else if (i->opcode == &OPCODE_STORE_CONTEXT_info) {
size_t offset = i->src1.offset;
if (!validity.test(static_cast<uint32_t>(offset))) {
// Offset not yet written, mark and continue.
validity.set(static_cast<uint32_t>(offset));
} else {
// Already written to. Remove this store.
i->Remove();
}
}
i = prev;
}
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,54 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_CONTEXT_PROMOTION_PASS_H_
#define XENIA_COMPILER_PASSES_CONTEXT_PROMOTION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
#if XE_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#include <llvm/ADT/BitVector.h>
#pragma warning(pop)
#else
#include <cmath>
#include <llvm/ADT/BitVector.h>
#endif // XE_COMPILER_MSVC
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ContextPromotionPass : public CompilerPass {
public:
ContextPromotionPass();
virtual ~ContextPromotionPass() override;
int Initialize(Compiler* compiler) override;
int Run(hir::HIRBuilder* builder) override;
private:
void PromoteBlock(hir::Block* block);
void RemoveDeadStoresBlock(hir::Block* block);
private:
std::vector<hir::Value*> context_values_;
llvm::BitVector context_validity_;
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_CONTEXT_PROMOTION_PASS_H_

View File

@@ -0,0 +1,80 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/control_flow_analysis_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::Edge;
using xe::cpu::hir::HIRBuilder;
ControlFlowAnalysisPass::ControlFlowAnalysisPass() : CompilerPass() {}
ControlFlowAnalysisPass::~ControlFlowAnalysisPass() {}
int ControlFlowAnalysisPass::Run(HIRBuilder* builder) {
// Reset edges for all blocks. Needed to be re-runnable.
// Note that this wastes a bunch of arena memory, so we shouldn't
// re-run too often.
auto block = builder->first_block();
while (block) {
block->incoming_edge_head = nullptr;
block->outgoing_edge_head = nullptr;
block = block->next;
}
// Add edges.
block = builder->first_block();
while (block) {
auto instr = block->instr_tail;
while (instr) {
if ((instr->opcode->flags & OPCODE_FLAG_BRANCH) == 0) {
break;
}
if (instr->opcode == &OPCODE_BRANCH_info) {
auto label = instr->src1.label;
builder->AddEdge(block, label->block, Edge::UNCONDITIONAL);
} else if (instr->opcode == &OPCODE_BRANCH_TRUE_info ||
instr->opcode == &OPCODE_BRANCH_FALSE_info) {
auto label = instr->src2.label;
builder->AddEdge(block, label->block, 0);
}
instr = instr->prev;
}
block = block->next;
}
// Mark dominators.
block = builder->first_block();
while (block) {
if (block->incoming_edge_head &&
!block->incoming_edge_head->incoming_next) {
block->incoming_edge_head->flags |= Edge::DOMINATES;
}
block = block->next;
}
return 0;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,35 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_
#define XENIA_COMPILER_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ControlFlowAnalysisPass : public CompilerPass {
public:
ControlFlowAnalysisPass();
~ControlFlowAnalysisPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_CONTROL_FLOW_ANALYSIS_PASS_H_

View File

@@ -0,0 +1,61 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/control_flow_simplification_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::Edge;
using xe::cpu::hir::HIRBuilder;
ControlFlowSimplificationPass::ControlFlowSimplificationPass()
: CompilerPass() {}
ControlFlowSimplificationPass::~ControlFlowSimplificationPass() {}
int ControlFlowSimplificationPass::Run(HIRBuilder* builder) {
// Walk backwards and merge blocks if possible.
bool merged_any = false;
auto block = builder->last_block();
while (block) {
auto prev_block = block->prev;
const uint32_t expected = Edge::DOMINATES | Edge::UNCONDITIONAL;
if (block->incoming_edge_head &&
(block->incoming_edge_head->flags & expected) == expected) {
// Dominated by the incoming block.
// If that block comes immediately before us then we can merge the
// two blocks (assuming it's not a volatile instruction like Trap).
if (block->prev == block->incoming_edge_head->src &&
block->prev->instr_tail &&
!(block->prev->instr_tail->opcode->flags & OPCODE_FLAG_VOLATILE)) {
builder->MergeAdjacentBlocks(block->prev, block);
merged_any = true;
}
}
block = prev_block;
}
return 0;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,35 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_
#define XENIA_COMPILER_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ControlFlowSimplificationPass : public CompilerPass {
public:
ControlFlowSimplificationPass();
~ControlFlowSimplificationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_CONTROL_FLOW_SIMPLIFICATION_PASS_H_

View File

@@ -0,0 +1,211 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/data_flow_analysis_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
#if XE_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#include <llvm/ADT/BitVector.h>
#pragma warning(pop)
#else
#include <llvm/ADT/BitVector.h>
#endif // XE_COMPILER_MSVC
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::OpcodeSignatureType;
using xe::cpu::hir::Value;
DataFlowAnalysisPass::DataFlowAnalysisPass() : CompilerPass() {}
DataFlowAnalysisPass::~DataFlowAnalysisPass() {}
int DataFlowAnalysisPass::Run(HIRBuilder* builder) {
// Linearize blocks so that we can detect cycles and propagate dependencies.
uint32_t block_count = LinearizeBlocks(builder);
// Analyze value flow and add locals as needed.
AnalyzeFlow(builder, block_count);
return 0;
}
uint32_t DataFlowAnalysisPass::LinearizeBlocks(HIRBuilder* builder) {
// TODO(benvanik): actually do this - we cheat now knowing that they are in
// sequential order.
uint32_t block_ordinal = 0;
auto block = builder->first_block();
while (block) {
block->ordinal = block_ordinal++;
block = block->next;
}
return block_ordinal;
}
void DataFlowAnalysisPass::AnalyzeFlow(HIRBuilder* builder,
uint32_t block_count) {
uint32_t max_value_estimate =
builder->max_value_ordinal() + 1 + block_count * 4;
// Stash for value map. We may want to maintain this during building.
auto arena = builder->arena();
Value** value_map =
(Value**)arena->Alloc(sizeof(Value*) * max_value_estimate);
// Allocate incoming bitvectors for use by blocks. We don't need outgoing
// because they are only used during the block iteration.
// Mapped by block ordinal.
// TODO(benvanik): cache this list, grow as needed, etc.
auto incoming_bitvectors =
(llvm::BitVector**)arena->Alloc(sizeof(llvm::BitVector*) * block_count);
for (auto n = 0u; n < block_count; n++) {
incoming_bitvectors[n] = new llvm::BitVector(max_value_estimate);
}
// Walk blocks in reverse and calculate incoming/outgoing values.
auto block = builder->last_block();
while (block) {
// Allocate bitsets based on max value number.
block->incoming_values = incoming_bitvectors[block->ordinal];
auto& incoming_values = *block->incoming_values;
// Walk instructions and gather up incoming values.
auto instr = block->instr_head;
while (instr) {
uint32_t signature = instr->opcode->signature;
#define SET_INCOMING_VALUE(v) \
if (v->def && v->def->block != block) { \
incoming_values.set(v->ordinal); \
} \
assert_true(v->ordinal < max_value_estimate); \
value_map[v->ordinal] = v;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
SET_INCOMING_VALUE(instr->src1.value);
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
SET_INCOMING_VALUE(instr->src2.value);
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
SET_INCOMING_VALUE(instr->src3.value);
}
#undef SET_INCOMING_VALUE
instr = instr->next;
}
// Add all successor incoming values to our outgoing, as we need to
// pass them through.
llvm::BitVector outgoing_values(max_value_estimate);
auto outgoing_edge = block->outgoing_edge_head;
while (outgoing_edge) {
if (outgoing_edge->dest->ordinal > block->ordinal) {
outgoing_values |= *outgoing_edge->dest->incoming_values;
}
outgoing_edge = outgoing_edge->outgoing_next;
}
incoming_values |= outgoing_values;
// Add stores for all outgoing values.
auto outgoing_ordinal = outgoing_values.find_first();
while (outgoing_ordinal != -1) {
Value* src_value = value_map[outgoing_ordinal];
assert_not_null(src_value);
if (!src_value->local_slot) {
src_value->local_slot = builder->AllocLocal(src_value->type);
}
builder->StoreLocal(src_value->local_slot, src_value);
// If we are in the block the value was defined in:
if (src_value->def->block == block) {
// Move the store to right after the def, or as soon after
// as we can (respecting PAIRED flags).
auto def_next = src_value->def->next;
while (def_next && def_next->opcode->flags & OPCODE_FLAG_PAIRED_PREV) {
def_next = def_next->next;
}
assert_not_null(def_next);
builder->last_instr()->MoveBefore(def_next);
// We don't need it in the incoming list.
incoming_values.reset(outgoing_ordinal);
} else {
// Eh, just throw at the end, before the first branch.
auto tail = block->instr_tail;
while (tail && tail->opcode->flags & OPCODE_FLAG_BRANCH) {
tail = tail->prev;
}
assert_not_zero(tail);
builder->last_instr()->MoveBefore(tail->next);
}
outgoing_ordinal = outgoing_values.find_next(outgoing_ordinal);
}
// Add loads for all incoming values and rename them in the block.
auto incoming_ordinal = incoming_values.find_first();
while (incoming_ordinal != -1) {
Value* src_value = value_map[incoming_ordinal];
assert_not_null(src_value);
if (!src_value->local_slot) {
src_value->local_slot = builder->AllocLocal(src_value->type);
}
Value* local_value = builder->LoadLocal(src_value->local_slot);
builder->last_instr()->MoveBefore(block->instr_head);
// Swap uses of original value with the local value.
instr = block->instr_head;
while (instr) {
uint32_t signature = instr->opcode->signature;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src1.value == src_value) {
instr->set_src1(local_value);
}
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src2.value == src_value) {
instr->set_src2(local_value);
}
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src3.value == src_value) {
instr->set_src3(local_value);
}
}
instr = instr->next;
}
incoming_ordinal = incoming_values.find_next(incoming_ordinal);
}
block = block->prev;
}
// Cleanup bitvectors.
for (auto n = 0u; n < block_count; n++) {
delete incoming_bitvectors[n];
}
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,37 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_DATA_FLOW_ANALYSIS_PASS_H_
#define XENIA_COMPILER_PASSES_DATA_FLOW_ANALYSIS_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class DataFlowAnalysisPass : public CompilerPass {
public:
DataFlowAnalysisPass();
~DataFlowAnalysisPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
uint32_t LinearizeBlocks(hir::HIRBuilder* builder);
void AnalyzeFlow(hir::HIRBuilder* builder, uint32_t block_count);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_DATA_FLOW_ANALYSIS_PASS_H_

View File

@@ -0,0 +1,218 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/dead_code_elimination_pass.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::Value;
DeadCodeEliminationPass::DeadCodeEliminationPass() : CompilerPass() {}
DeadCodeEliminationPass::~DeadCodeEliminationPass() {}
int DeadCodeEliminationPass::Run(HIRBuilder* builder) {
// ContextPromotion/DSE will likely leave around a lot of dead statements.
// Code generated for comparison/testing produces many unused statements and
// with proper use analysis it should be possible to remove most of them:
// After context promotion/simplification:
// v33.i8 = compare_ult v31.i32, 0
// v34.i8 = compare_ugt v31.i32, 0
// v35.i8 = compare_eq v31.i32, 0
// store_context +300, v33.i8
// store_context +301, v34.i8
// store_context +302, v35.i8
// branch_true v35.i8, loc_8201A484
// After DSE:
// v33.i8 = compare_ult v31.i32, 0
// v34.i8 = compare_ugt v31.i32, 0
// v35.i8 = compare_eq v31.i32, 0
// branch_true v35.i8, loc_8201A484
// After DCE:
// v35.i8 = compare_eq v31.i32, 0
// branch_true v35.i8, loc_8201A484
// This also removes useless ASSIGNs:
// v1 = v0
// v2 = add v1, v1
// becomes:
// v2 = add v0, v0
// We process DCE by reverse iterating over instructions and looking at the
// use count of the dest value. If it's zero, we can safely remove the
// instruction. Once we do that, the use counts of any of the src ops may
// go to zero and we recursively kill up the graph. This is kind of
// nasty in that we walk randomly and scribble all over memory, but (I think)
// it's better than doing passes over all instructions until we quiesce.
// To prevent our iteration from getting all messed up we just replace
// all removed ops with NOP and then do a single pass that removes them
// all.
bool any_instr_removed = false;
bool any_locals_removed = false;
auto block = builder->first_block();
while (block) {
// Walk instructions in reverse.
Instr* i = block->instr_tail;
while (i) {
auto prev = i->prev;
auto opcode = i->opcode;
if (!(opcode->flags & OPCODE_FLAG_VOLATILE) && i->dest &&
!i->dest->use_head) {
// Has no uses and is not volatile. This instruction can die!
MakeNopRecursive(i);
any_instr_removed = true;
} else if (opcode == &OPCODE_ASSIGN_info) {
// Assignment. These are useless, so just try to remove by completely
// replacing the value.
ReplaceAssignment(i);
}
i = prev;
}
// Walk instructions forward.
i = block->instr_head;
while (i) {
auto next = i->next;
auto opcode = i->opcode;
if (opcode == &OPCODE_STORE_LOCAL_info) {
// Check to see if the store has any interceeding uses after the load.
// If not, it can be removed (as the local is just passing through the
// function).
// We do this after the previous pass so that removed code doesn't keep
// the local alive.
if (!CheckLocalUse(i)) {
any_locals_removed = true;
}
}
i = next;
}
block = block->next;
}
// Remove all nops.
if (any_instr_removed) {
block = builder->first_block();
while (block) {
Instr* i = block->instr_head;
while (i) {
Instr* next = i->next;
if (i->opcode == &OPCODE_NOP_info) {
// Nop - remove!
i->Remove();
}
i = next;
}
block = block->next;
}
}
// Remove any locals that no longer have uses.
if (any_locals_removed) {
// TODO(benvanik): local removal/dealloc.
auto locals = builder->locals();
for (auto it = locals.begin(); it != locals.end();) {
auto next = ++it;
auto value = *it;
if (!value->use_head) {
// Unused, can be removed.
locals.erase(it);
}
it = next;
}
}
return 0;
}
void DeadCodeEliminationPass::MakeNopRecursive(Instr* i) {
i->opcode = &hir::OPCODE_NOP_info;
i->dest->def = NULL;
i->dest = NULL;
#define MAKE_NOP_SRC(n) \
if (i->src##n##_use) { \
Value::Use* use = i->src##n##_use; \
Value* value = i->src##n.value; \
i->src##n##_use = NULL; \
i->src##n.value = NULL; \
value->RemoveUse(use); \
if (!value->use_head) { \
/* Value is now unused, so recursively kill it. */ \
if (value->def && value->def != i) { \
MakeNopRecursive(value->def); \
} \
} \
}
MAKE_NOP_SRC(1);
MAKE_NOP_SRC(2);
MAKE_NOP_SRC(3);
}
void DeadCodeEliminationPass::ReplaceAssignment(Instr* i) {
auto src = i->src1.value;
auto dest = i->dest;
auto use = dest->use_head;
while (use) {
auto use_instr = use->instr;
if (use_instr->src1.value == dest) {
use_instr->set_src1(src);
}
if (use_instr->src2.value == dest) {
use_instr->set_src2(src);
}
if (use_instr->src3.value == dest) {
use_instr->set_src3(src);
}
use = use->next;
}
i->Remove();
}
bool DeadCodeEliminationPass::CheckLocalUse(Instr* i) {
auto src = i->src2.value;
auto use = src->use_head;
if (use) {
auto use_instr = use->instr;
if (use_instr->opcode != &OPCODE_LOAD_LOCAL_info) {
// A valid use (probably). Keep it.
return true;
}
// Load/store are paired. They can both be removed.
use_instr->Remove();
}
i->Remove();
return false;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,38 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_
#define XENIA_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class DeadCodeEliminationPass : public CompilerPass {
public:
DeadCodeEliminationPass();
~DeadCodeEliminationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
void MakeNopRecursive(hir::Instr* i);
void ReplaceAssignment(hir::Instr* i);
bool CheckLocalUse(hir::Instr* i);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_DEAD_CODE_ELIMINATION_PASS_H_

View File

@@ -0,0 +1,74 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/finalization_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
FinalizationPass::FinalizationPass() : CompilerPass() {}
FinalizationPass::~FinalizationPass() {}
int FinalizationPass::Run(HIRBuilder* builder) {
// Process the HIR and prepare it for lowering.
// After this is done the HIR should be ready for emitting.
auto arena = builder->arena();
uint32_t block_ordinal = 0;
auto block = builder->first_block();
while (block) {
block->ordinal = block_ordinal++;
// Ensure all labels have names.
auto label = block->label_head;
while (label) {
if (!label->name) {
const size_t label_len = 6 + 4 + 1;
char* name = (char*)arena->Alloc(label_len);
snprintf(name, label_len, "_label%d", label->id);
label->name = name;
}
label = label->next;
}
// Remove unneeded jumps.
auto tail = block->instr_tail;
if (tail && tail->opcode == &OPCODE_BRANCH_info) {
// Jump. Check target.
auto target = tail->src1.label;
if (target->block == block->next) {
// Jumping to subsequent block. Remove.
tail->Remove();
}
}
block = block->next;
}
return 0;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,35 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_FINALIZATION_PASS_H_
#define XENIA_COMPILER_PASSES_FINALIZATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class FinalizationPass : public CompilerPass {
public:
FinalizationPass();
~FinalizationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_FINALIZATION_PASS_H_

View File

@@ -0,0 +1,564 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/register_allocation_pass.h"
#include <algorithm>
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::backend::MachineInfo;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::OpcodeSignatureType;
using xe::cpu::hir::RegAssignment;
using xe::cpu::hir::TypeName;
using xe::cpu::hir::Value;
#define ASSERT_NO_CYCLES 0
RegisterAllocationPass::RegisterAllocationPass(const MachineInfo* machine_info)
: CompilerPass() {
// Initialize register sets.
// TODO(benvanik): rewrite in a way that makes sense - this is terrible.
auto mi_sets = machine_info->register_sets;
memset(&usage_sets_, 0, sizeof(usage_sets_));
uint32_t n = 0;
while (mi_sets[n].count) {
auto& mi_set = mi_sets[n];
auto usage_set = new RegisterSetUsage();
usage_sets_.all_sets[n] = usage_set;
usage_set->count = mi_set.count;
usage_set->set = &mi_set;
if (mi_set.types & MachineInfo::RegisterSet::INT_TYPES) {
usage_sets_.int_set = usage_set;
}
if (mi_set.types & MachineInfo::RegisterSet::FLOAT_TYPES) {
usage_sets_.float_set = usage_set;
}
if (mi_set.types & MachineInfo::RegisterSet::VEC_TYPES) {
usage_sets_.vec_set = usage_set;
}
n++;
}
}
RegisterAllocationPass::~RegisterAllocationPass() {
for (size_t n = 0; n < poly::countof(usage_sets_.all_sets); n++) {
if (!usage_sets_.all_sets[n]) {
break;
}
delete usage_sets_.all_sets[n];
}
}
int RegisterAllocationPass::Run(HIRBuilder* builder) {
// Simple per-block allocator that operates on SSA form.
// Registers do not move across blocks, though this could be
// optimized with some intra-block analysis (dominators/etc).
// Really, it'd just be nice to have someone who knew what they
// were doing lower SSA and do this right.
uint32_t block_ordinal = 0;
uint32_t instr_ordinal = 0;
auto block = builder->first_block();
while (block) {
// Sequential block ordinals.
block->ordinal = block_ordinal++;
// Reset all state.
PrepareBlockState();
// Renumber all instructions in the block. This is required so that
// we can sort the usage pointers below.
auto instr = block->instr_head;
while (instr) {
// Sequential global instruction ordinals.
instr->ordinal = instr_ordinal++;
instr = instr->next;
}
instr = block->instr_head;
while (instr) {
const auto info = instr->opcode;
uint32_t signature = info->signature;
// Update the register use heaps.
AdvanceUses(instr);
// Check sources for retirement. If any are unused after this instruction
// we can eagerly evict them to speed up register allocation.
// Since X64 (and other platforms) can often take advantage of dest==src1
// register mappings we track retired src1 so that we can attempt to
// reuse it.
// NOTE: these checks require that the usage list be sorted!
bool has_preferred_reg = false;
RegAssignment preferred_reg = {0};
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V &&
!instr->src1.value->IsConstant()) {
if (!instr->src1_use->next) {
// Pull off preferred register. We will try to reuse this for the
// dest.
// NOTE: set may be null if this is a store local.
if (preferred_reg.set) {
has_preferred_reg = true;
preferred_reg = instr->src1.value->reg;
}
}
}
if (GET_OPCODE_SIG_TYPE_DEST(signature) == OPCODE_SIG_TYPE_V) {
// Must not have been set already.
assert_null(instr->dest->reg.set);
// Sort the usage list. We depend on this in future uses of this
// variable.
SortUsageList(instr->dest);
// If we have a preferred register, use that.
// This way we can help along the stupid X86 two opcode instructions.
bool allocated;
if (has_preferred_reg) {
// Allocate with the given preferred register. If the register is in
// the wrong set it will not be reused.
allocated = TryAllocateRegister(instr->dest, preferred_reg);
} else {
// Allocate a register. This will either reserve a free one or
// spill and reuse an active one.
allocated = TryAllocateRegister(instr->dest);
}
if (!allocated) {
// Failed to allocate register -- need to spill and try again.
// We spill only those registers we aren't using.
if (!SpillOneRegister(builder, block, instr->dest->type)) {
// Unable to spill anything - this shouldn't happen.
PLOGE("Unable to spill any registers");
assert_always();
return 1;
}
// Demand allocation.
if (!TryAllocateRegister(instr->dest)) {
// Boned.
PLOGE("Register allocation failed");
assert_always();
return 1;
}
}
}
instr = instr->next;
}
block = block->next;
}
return 0;
}
void RegisterAllocationPass::DumpUsage(const char* name) {
#if 0
fprintf(stdout, "\n%s:\n", name);
for (size_t i = 0; i < poly::countof(usage_sets_.all_sets); ++i) {
auto usage_set = usage_sets_.all_sets[i];
if (usage_set) {
fprintf(stdout, "set %s:\n", usage_set->set->name);
fprintf(stdout, " avail: %s\n", usage_set->availability.to_string().c_str());
fprintf(stdout, " upcoming uses:\n");
for (auto it = usage_set->upcoming_uses.begin();
it != usage_set->upcoming_uses.end(); ++it) {
fprintf(stdout, " v%d, used at %d\n",
it->value->ordinal,
it->use->instr->ordinal);
}
}
}
fflush(stdout);
#endif
}
void RegisterAllocationPass::PrepareBlockState() {
for (size_t i = 0; i < poly::countof(usage_sets_.all_sets); ++i) {
auto usage_set = usage_sets_.all_sets[i];
if (usage_set) {
usage_set->availability.set();
usage_set->upcoming_uses.clear();
}
}
DumpUsage("PrepareBlockState");
}
void RegisterAllocationPass::AdvanceUses(Instr* instr) {
for (size_t i = 0; i < poly::countof(usage_sets_.all_sets); ++i) {
auto usage_set = usage_sets_.all_sets[i];
if (!usage_set) {
break;
}
auto& upcoming_uses = usage_set->upcoming_uses;
for (auto it = upcoming_uses.begin(); it != upcoming_uses.end();) {
if (!it->use) {
// No uses at all - we can remove right away.
// This comes up from instructions where the dest is never used,
// like the ATOMIC ops.
MarkRegAvailable(it->value->reg);
it = upcoming_uses.erase(it);
continue;
}
if (it->use->instr != instr) {
// Not yet at this instruction.
++it;
continue;
}
// The use is from this instruction.
if (!it->use->next) {
// Last use of the value. We can retire it now.
MarkRegAvailable(it->value->reg);
it = upcoming_uses.erase(it);
} else {
// Used again. Push back the next use.
// Note that we may be used multiple times this instruction, so
// eat those.
auto next_use = it->use->next;
while (next_use->next && next_use->instr == instr) {
next_use = next_use->next;
}
// Remove the iterator.
auto value = it->value;
it = upcoming_uses.erase(it);
assert_true(next_use->instr->block == instr->block);
assert_true(value->def->block == instr->block);
upcoming_uses.emplace_back(value, next_use);
}
}
}
DumpUsage("AdvanceUses");
}
bool RegisterAllocationPass::IsRegInUse(const RegAssignment& reg) {
RegisterSetUsage* usage_set;
if (reg.set == usage_sets_.int_set->set) {
usage_set = usage_sets_.int_set;
} else if (reg.set == usage_sets_.float_set->set) {
usage_set = usage_sets_.float_set;
} else {
usage_set = usage_sets_.vec_set;
}
return !usage_set->availability.test(reg.index);
}
RegisterAllocationPass::RegisterSetUsage* RegisterAllocationPass::MarkRegUsed(
const RegAssignment& reg, Value* value, Value::Use* use) {
auto usage_set = RegisterSetForValue(value);
usage_set->availability.set(reg.index, false);
usage_set->upcoming_uses.emplace_back(value, use);
DumpUsage("MarkRegUsed");
return usage_set;
}
RegisterAllocationPass::RegisterSetUsage*
RegisterAllocationPass::MarkRegAvailable(const hir::RegAssignment& reg) {
RegisterSetUsage* usage_set;
if (reg.set == usage_sets_.int_set->set) {
usage_set = usage_sets_.int_set;
} else if (reg.set == usage_sets_.float_set->set) {
usage_set = usage_sets_.float_set;
} else {
usage_set = usage_sets_.vec_set;
}
usage_set->availability.set(reg.index, true);
return usage_set;
}
bool RegisterAllocationPass::TryAllocateRegister(
Value* value, const RegAssignment& preferred_reg) {
// If the preferred register matches type and is available, use it.
auto usage_set = RegisterSetForValue(value);
if (usage_set->set == preferred_reg.set) {
// Check if available.
if (!IsRegInUse(preferred_reg)) {
// Mark as in-use and return. Best case.
MarkRegUsed(preferred_reg, value, value->use_head);
value->reg = preferred_reg;
return true;
}
}
// Otherwise, fallback to allocating like normal.
return TryAllocateRegister(value);
}
bool RegisterAllocationPass::TryAllocateRegister(Value* value) {
// Get the set this register is in.
RegisterSetUsage* usage_set = RegisterSetForValue(value);
// Find the first free register, if any.
// We have to ensure it's a valid one (in our count).
uint32_t first_unused = 0;
bool none_used = poly::bit_scan_forward(
static_cast<uint32_t>(usage_set->availability.to_ulong()), &first_unused);
if (none_used && first_unused < usage_set->count) {
// Available! Use it!
value->reg.set = usage_set->set;
value->reg.index = first_unused;
MarkRegUsed(value->reg, value, value->use_head);
return true;
}
// None available! Spill required.
return false;
}
bool RegisterAllocationPass::SpillOneRegister(HIRBuilder* builder, Block* block,
TypeName required_type) {
// Get the set that we will be picking from.
RegisterSetUsage* usage_set;
if (required_type <= INT64_TYPE) {
usage_set = usage_sets_.int_set;
} else if (required_type <= FLOAT64_TYPE) {
usage_set = usage_sets_.float_set;
} else {
usage_set = usage_sets_.vec_set;
}
DumpUsage("SpillOneRegister (pre)");
// Pick the one with the furthest next use.
assert_true(!usage_set->upcoming_uses.empty());
auto furthest_usage = std::max_element(usage_set->upcoming_uses.begin(),
usage_set->upcoming_uses.end(),
RegisterUsage::Comparer());
assert_true(furthest_usage->value->def->block == block);
assert_true(furthest_usage->use->instr->block == block);
auto spill_value = furthest_usage->value;
Value::Use* prev_use = furthest_usage->use->prev;
Value::Use* next_use = furthest_usage->use;
assert_not_null(next_use);
usage_set->upcoming_uses.erase(furthest_usage);
DumpUsage("SpillOneRegister (post)");
const auto reg = spill_value->reg;
// We know the spill_value use list is sorted, so we can cut it right now.
// This makes it easier down below.
auto new_head_use = next_use;
// Allocate local.
if (spill_value->local_slot) {
// Value is already assigned a slot. Since we allocate in order and this is
// all SSA we know the stored value will be exactly what we want. Yay,
// we can prevent the redundant store!
// In fact, we may even want to pin this spilled value so that we always
// use the spilled value and prevent the need for more locals.
} else {
// Allocate a local slot.
spill_value->local_slot = builder->AllocLocal(spill_value->type);
// Add store.
builder->StoreLocal(spill_value->local_slot, spill_value);
auto spill_store = builder->last_instr();
auto spill_store_use = spill_store->src2_use;
assert_null(spill_store_use->prev);
if (prev_use && prev_use->instr->opcode->flags & OPCODE_FLAG_PAIRED_PREV) {
// Instruction is paired. This is bad. We will insert the spill after the
// paired instruction.
assert_not_null(prev_use->instr->next);
spill_store->MoveBefore(prev_use->instr->next);
// Update last use.
spill_value->last_use = spill_store;
} else if (prev_use) {
// We insert the store immediately before the previous use.
// If we were smarter we could then re-run allocation and reuse the
// register
// once dropped.
spill_store->MoveBefore(prev_use->instr);
// Update last use.
spill_value->last_use = prev_use->instr;
} else {
// This is the first use, so the only thing we have is the define.
// Move the store to right after that.
spill_store->MoveBefore(spill_value->def->next);
// Update last use.
spill_value->last_use = spill_store;
}
}
#if ASSERT_NO_CYCLES
builder->AssertNoCycles();
spill_value->def->block->AssertNoCycles();
#endif // ASSERT_NO_CYCLES
// Add load.
// Inserted immediately before the next use. Since by definition the next
// use is after the instruction requesting the spill we know we haven't
// done allocation for that code yet and can let that be handled
// automatically when we get to it.
auto new_value = builder->LoadLocal(spill_value->local_slot);
auto spill_load = builder->last_instr();
spill_load->MoveBefore(next_use->instr);
// Note: implicit first use added.
#if ASSERT_NO_CYCLES
builder->AssertNoCycles();
spill_value->def->block->AssertNoCycles();
#endif // ASSERT_NO_CYCLES
// Set the local slot of the new value to our existing one. This way we will
// reuse that same memory if needed.
new_value->local_slot = spill_value->local_slot;
// Rename all future uses of the SSA value to the new value as loaded
// from the local.
// We can quickly do this by walking the use list. Because the list is
// already sorted we know we are going to end up with a sorted list.
auto walk_use = new_head_use;
auto new_use_tail = walk_use;
while (walk_use) {
auto next_walk_use = walk_use->next;
auto instr = walk_use->instr;
uint32_t signature = instr->opcode->signature;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src1.value == spill_value) {
instr->set_src1(new_value);
}
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src2.value == spill_value) {
instr->set_src2(new_value);
}
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
if (instr->src3.value == spill_value) {
instr->set_src3(new_value);
}
}
walk_use = next_walk_use;
if (walk_use) {
new_use_tail = walk_use;
}
}
new_value->last_use = new_use_tail->instr;
// Update tracking.
MarkRegAvailable(reg);
return true;
}
RegisterAllocationPass::RegisterSetUsage*
RegisterAllocationPass::RegisterSetForValue(const Value* value) {
if (value->type <= INT64_TYPE) {
return usage_sets_.int_set;
} else if (value->type <= FLOAT64_TYPE) {
return usage_sets_.float_set;
} else {
return usage_sets_.vec_set;
}
}
namespace {
int CompareValueUse(const Value::Use* a, const Value::Use* b) {
return a->instr->ordinal - b->instr->ordinal;
}
} // namespace
void RegisterAllocationPass::SortUsageList(Value* value) {
// Modified in-place linked list sort from:
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c
if (!value->use_head) {
return;
}
Value::Use* head = value->use_head;
Value::Use* tail = nullptr;
int insize = 1;
while (true) {
auto p = head;
head = nullptr;
tail = nullptr;
// count number of merges we do in this pass
int nmerges = 0;
while (p) {
// there exists a merge to be done
nmerges++;
// step 'insize' places along from p
auto q = p;
int psize = 0;
for (int i = 0; i < insize; i++) {
psize++;
q = q->next;
if (!q) break;
}
// if q hasn't fallen off end, we have two lists to merge
int qsize = insize;
// now we have two lists; merge them
while (psize > 0 || (qsize > 0 && q)) {
// decide whether next element of merge comes from p or q
Value::Use* e = nullptr;
if (psize == 0) {
// p is empty; e must come from q
e = q;
q = q->next;
qsize--;
} else if (qsize == 0 || !q) {
// q is empty; e must come from p
e = p;
p = p->next;
psize--;
} else if (CompareValueUse(p, q) <= 0) {
// First element of p is lower (or same); e must come from p
e = p;
p = p->next;
psize--;
} else {
// First element of q is lower; e must come from q
e = q;
q = q->next;
qsize--;
}
// add the next element to the merged list
if (tail) {
tail->next = e;
} else {
head = e;
}
// Maintain reverse pointers in a doubly linked list.
e->prev = tail;
tail = e;
}
// now p has stepped 'insize' places along, and q has too
p = q;
}
if (tail) {
tail->next = nullptr;
}
// If we have done only one merge, we're finished
if (nmerges <= 1) {
// allow for nmerges==0, the empty list case
break;
}
// Otherwise repeat, merging lists twice the size
insize *= 2;
}
value->use_head = head;
value->last_use = tail->instr;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,87 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_REGISTER_ALLOCATION_PASS_H_
#define XENIA_COMPILER_PASSES_REGISTER_ALLOCATION_PASS_H_
#include <algorithm>
#include <bitset>
#include <vector>
#include "xenia/cpu/backend/machine_info.h"
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class RegisterAllocationPass : public CompilerPass {
public:
RegisterAllocationPass(const backend::MachineInfo* machine_info);
~RegisterAllocationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
// TODO(benvanik): rewrite all this set shit -- too much indirection, the
// complexity is not needed.
struct RegisterUsage {
hir::Value* value;
hir::Value::Use* use;
RegisterUsage() : value(nullptr), use(nullptr) {}
RegisterUsage(hir::Value* value_, hir::Value::Use* use_)
: value(value_), use(use_) {}
struct Comparer : std::binary_function<RegisterUsage, RegisterUsage, bool> {
bool operator()(const RegisterUsage& a, const RegisterUsage& b) const {
return a.use->instr->ordinal < b.use->instr->ordinal;
}
};
};
struct RegisterSetUsage {
const backend::MachineInfo::RegisterSet* set = nullptr;
uint32_t count = 0;
std::bitset<32> availability = 0;
// TODO(benvanik): another data type.
std::vector<RegisterUsage> upcoming_uses;
};
void DumpUsage(const char* name);
void PrepareBlockState();
void AdvanceUses(hir::Instr* instr);
bool IsRegInUse(const hir::RegAssignment& reg);
RegisterSetUsage* MarkRegUsed(const hir::RegAssignment& reg,
hir::Value* value, hir::Value::Use* use);
RegisterSetUsage* MarkRegAvailable(const hir::RegAssignment& reg);
bool TryAllocateRegister(hir::Value* value,
const hir::RegAssignment& preferred_reg);
bool TryAllocateRegister(hir::Value* value);
bool SpillOneRegister(hir::HIRBuilder* builder, hir::Block* block,
hir::TypeName required_type);
RegisterSetUsage* RegisterSetForValue(const hir::Value* value);
void SortUsageList(hir::Value* value);
private:
struct {
RegisterSetUsage* int_set = nullptr;
RegisterSetUsage* float_set = nullptr;
RegisterSetUsage* vec_set = nullptr;
RegisterSetUsage* all_sets[3];
} usage_sets_;
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_REGISTER_ALLOCATION_PASS_H_

View File

@@ -0,0 +1,173 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/simplification_pass.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::Value;
SimplificationPass::SimplificationPass() : CompilerPass() {}
SimplificationPass::~SimplificationPass() {}
int SimplificationPass::Run(HIRBuilder* builder) {
EliminateConversions(builder);
SimplifyAssignments(builder);
return 0;
}
void SimplificationPass::EliminateConversions(HIRBuilder* builder) {
// First, we check for truncates/extensions that can be skipped.
// This generates some assignments which then the second step will clean up.
// Both zero/sign extends can be skipped:
// v1.i64 = zero/sign_extend v0.i32
// v2.i32 = truncate v1.i64
// becomes:
// v1.i64 = zero/sign_extend v0.i32 (may be dead code removed later)
// v2.i32 = v0.i32
auto block = builder->first_block();
while (block) {
auto i = block->instr_head;
while (i) {
// To make things easier we check in reverse (source of truncate/extend
// back to definition).
if (i->opcode == &OPCODE_TRUNCATE_info) {
// Matches zero/sign_extend + truncate.
CheckTruncate(i);
} else if (i->opcode == &OPCODE_BYTE_SWAP_info) {
// Matches byte swap + byte swap.
// This is pretty rare within the same basic block, but is in the
// memcpy hot path and (probably) worth it. Maybe.
CheckByteSwap(i);
}
i = i->next;
}
block = block->next;
}
}
void SimplificationPass::CheckTruncate(Instr* i) {
// Walk backward up src's chain looking for an extend. We may have
// assigns, so skip those.
auto src = i->src1.value;
auto def = src->def;
while (def && def->opcode == &OPCODE_ASSIGN_info) {
// Skip asignments.
def = def->src1.value->def;
}
if (def) {
if (def->opcode == &OPCODE_SIGN_EXTEND_info) {
// Value comes from a sign extend.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
}
} else if (def->opcode == &OPCODE_ZERO_EXTEND_info) {
// Value comes from a zero extend.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
}
}
}
}
void SimplificationPass::CheckByteSwap(Instr* i) {
// Walk backward up src's chain looking for a byte swap. We may have
// assigns, so skip those.
auto src = i->src1.value;
auto def = src->def;
while (def && def->opcode == &OPCODE_ASSIGN_info) {
// Skip asignments.
def = def->src1.value->def;
}
if (def && def->opcode == &OPCODE_BYTE_SWAP_info) {
// Value comes from a byte swap.
if (def->src1.value->type == i->dest->type) {
// Types match, use original by turning this into an assign.
i->Replace(&OPCODE_ASSIGN_info, 0);
i->set_src1(def->src1.value);
}
}
}
void SimplificationPass::SimplifyAssignments(HIRBuilder* builder) {
// Run over the instructions and rename assigned variables:
// v1 = v0
// v2 = v1
// v3 = add v0, v2
// becomes:
// v1 = v0
// v2 = v0
// v3 = add v0, v0
// This could be run several times, as it could make other passes faster
// to compute (for example, ConstantPropagation). DCE will take care of
// the useless assigns.
//
// We do this by walking each instruction. For each value op we
// look at its def instr to see if it's an assign - if so, we use the src
// of that instr. Because we may have chains, we do this recursively until
// we find a non-assign def.
auto block = builder->first_block();
while (block) {
auto i = block->instr_head;
while (i) {
uint32_t signature = i->opcode->signature;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
i->set_src1(CheckValue(i->src1.value));
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
i->set_src2(CheckValue(i->src2.value));
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
i->set_src3(CheckValue(i->src3.value));
}
i = i->next;
}
block = block->next;
}
}
Value* SimplificationPass::CheckValue(Value* value) {
auto def = value->def;
if (def && def->opcode == &OPCODE_ASSIGN_info) {
// Value comes from an assignment - recursively find if it comes from
// another assignment. It probably doesn't, if we already replaced it.
auto replacement = def->src1.value;
while (true) {
def = replacement->def;
if (!def || def->opcode != &OPCODE_ASSIGN_info) {
break;
}
replacement = def->src1.value;
}
return replacement;
}
return value;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,41 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_SIMPLIFICATION_PASS_H_
#define XENIA_COMPILER_PASSES_SIMPLIFICATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class SimplificationPass : public CompilerPass {
public:
SimplificationPass();
~SimplificationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
void EliminateConversions(hir::HIRBuilder* builder);
void CheckTruncate(hir::Instr* i);
void CheckByteSwap(hir::Instr* i);
void SimplifyAssignments(hir::HIRBuilder* builder);
hir::Value* CheckValue(hir::Value* value);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_SIMPLIFICATION_PASS_H_

View File

@@ -0,0 +1,29 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'constant_propagation_pass.cc',
'constant_propagation_pass.h',
'context_promotion_pass.cc',
'context_promotion_pass.h',
'control_flow_analysis_pass.cc',
'control_flow_analysis_pass.h',
'control_flow_simplification_pass.cc',
'control_flow_simplification_pass.h',
'data_flow_analysis_pass.cc',
'data_flow_analysis_pass.h',
'dead_code_elimination_pass.cc',
'dead_code_elimination_pass.h',
'finalization_pass.cc',
'finalization_pass.h',
#'dead_store_elimination_pass.cc',
#'dead_store_elimination_pass.h',
'register_allocation_pass.cc',
'register_allocation_pass.h',
'simplification_pass.cc',
'simplification_pass.h',
'validation_pass.cc',
'validation_pass.h',
'value_reduction_pass.cc',
'value_reduction_pass.h',
],
}

View File

@@ -0,0 +1,118 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/validation_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::Block;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::Instr;
using xe::cpu::hir::OpcodeSignatureType;
using xe::cpu::hir::Value;
ValidationPass::ValidationPass() : CompilerPass() {}
ValidationPass::~ValidationPass() {}
int ValidationPass::Run(HIRBuilder* builder) {
#if 0
StringBuffer str;
builder->Dump(&str);
printf(str.GetString());
fflush(stdout);
str.Reset();
#endif // 0
auto block = builder->first_block();
while (block) {
auto label = block->label_head;
while (label) {
assert_true(label->block == block);
if (label->block != block) {
return 1;
}
label = label->next;
}
auto instr = block->instr_head;
while (instr) {
if (ValidateInstruction(block, instr)) {
return 1;
}
instr = instr->next;
}
block = block->next;
}
return 0;
}
int ValidationPass::ValidateInstruction(Block* block, Instr* instr) {
assert_true(instr->block == block);
if (instr->block != block) {
return 1;
}
if (instr->dest) {
assert_true(instr->dest->def == instr);
auto use = instr->dest->use_head;
while (use) {
assert_true(use->instr->block == block);
use = use->next;
}
}
uint32_t signature = instr->opcode->signature;
if (GET_OPCODE_SIG_TYPE_SRC1(signature) == OPCODE_SIG_TYPE_V) {
if (ValidateValue(block, instr, instr->src1.value)) {
return 1;
}
}
if (GET_OPCODE_SIG_TYPE_SRC2(signature) == OPCODE_SIG_TYPE_V) {
if (ValidateValue(block, instr, instr->src2.value)) {
return 1;
}
}
if (GET_OPCODE_SIG_TYPE_SRC3(signature) == OPCODE_SIG_TYPE_V) {
if (ValidateValue(block, instr, instr->src3.value)) {
return 1;
}
}
return 0;
}
int ValidationPass::ValidateValue(Block* block, Instr* instr, Value* value) {
// if (value->def) {
// auto def = value->def;
// assert_true(def->block == block);
// if (def->block != block) {
// return 1;
// }
//}
return 0;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,37 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_VALIDATION_PASS_H_
#define XENIA_COMPILER_PASSES_VALIDATION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ValidationPass : public CompilerPass {
public:
ValidationPass();
~ValidationPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
int ValidateInstruction(hir::Block* block, hir::Instr* instr);
int ValidateValue(hir::Block* block, hir::Instr* instr, hir::Value* value);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_VALIDATION_PASS_H_

View File

@@ -0,0 +1,147 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/compiler/passes/value_reduction_pass.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
#if XE_COMPILER_MSVC
#pragma warning(push)
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#include <llvm/ADT/BitVector.h>
#pragma warning(pop)
#else
#include <llvm/ADT/BitVector.h>
#endif // XE_COMPILER_MSVC
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::HIRBuilder;
using xe::cpu::hir::OpcodeInfo;
using xe::cpu::hir::Value;
ValueReductionPass::ValueReductionPass() : CompilerPass() {}
ValueReductionPass::~ValueReductionPass() {}
void ValueReductionPass::ComputeLastUse(Value* value) {
// TODO(benvanik): compute during construction?
// Note that this list isn't sorted (unfortunately), so we have to scan
// them all.
uint32_t max_ordinal = 0;
Value::Use* last_use = nullptr;
auto use = value->use_head;
while (use) {
if (!last_use || use->instr->ordinal >= max_ordinal) {
last_use = use;
max_ordinal = use->instr->ordinal;
}
use = use->next;
}
value->last_use = last_use ? last_use->instr : nullptr;
}
int ValueReductionPass::Run(HIRBuilder* builder) {
// Walk each block and reuse variable ordinals as much as possible.
llvm::BitVector ordinals(builder->max_value_ordinal());
auto block = builder->first_block();
while (block) {
// Reset used ordinals.
ordinals.reset();
// Renumber all instructions to make liveness tracking easier.
uint32_t instr_ordinal = 0;
auto instr = block->instr_head;
while (instr) {
instr->ordinal = instr_ordinal++;
instr = instr->next;
}
instr = block->instr_head;
while (instr) {
const OpcodeInfo* info = instr->opcode;
OpcodeSignatureType dest_type = GET_OPCODE_SIG_TYPE_DEST(info->signature);
OpcodeSignatureType src1_type = GET_OPCODE_SIG_TYPE_SRC1(info->signature);
OpcodeSignatureType src2_type = GET_OPCODE_SIG_TYPE_SRC2(info->signature);
OpcodeSignatureType src3_type = GET_OPCODE_SIG_TYPE_SRC3(info->signature);
if (src1_type == OPCODE_SIG_TYPE_V) {
auto v = instr->src1.value;
if (!v->last_use) {
ComputeLastUse(v);
}
if (v->last_use == instr) {
// Available.
if (!instr->src1.value->IsConstant()) {
ordinals.reset(v->ordinal);
}
}
}
if (src2_type == OPCODE_SIG_TYPE_V) {
auto v = instr->src2.value;
if (!v->last_use) {
ComputeLastUse(v);
}
if (v->last_use == instr) {
// Available.
if (!instr->src2.value->IsConstant()) {
ordinals.reset(v->ordinal);
}
}
}
if (src3_type == OPCODE_SIG_TYPE_V) {
auto v = instr->src3.value;
if (!v->last_use) {
ComputeLastUse(v);
}
if (v->last_use == instr) {
// Available.
if (!instr->src3.value->IsConstant()) {
ordinals.reset(v->ordinal);
}
}
}
if (dest_type == OPCODE_SIG_TYPE_V) {
// Dest values are processed last, as they may be able to reuse a
// source value ordinal.
auto v = instr->dest;
// Find a lower ordinal.
for (auto n = 0u; n < ordinals.size(); n++) {
if (!ordinals.test(n)) {
ordinals.set(n);
v->ordinal = n;
break;
}
}
}
instr = instr->next;
}
block = block->next;
}
return 0;
}
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,36 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_COMPILER_PASSES_VALUE_REDUCTION_PASS_H_
#define XENIA_COMPILER_PASSES_VALUE_REDUCTION_PASS_H_
#include "xenia/cpu/compiler/compiler_pass.h"
namespace xe {
namespace cpu {
namespace compiler {
namespace passes {
class ValueReductionPass : public CompilerPass {
public:
ValueReductionPass();
~ValueReductionPass() override;
int Run(hir::HIRBuilder* builder) override;
private:
void ComputeLastUse(hir::Value* value);
};
} // namespace passes
} // namespace compiler
} // namespace cpu
} // namespace xe
#endif // XENIA_COMPILER_PASSES_VALUE_REDUCTION_PASS_H_

View File

@@ -0,0 +1,14 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'compiler.cc',
'compiler.h',
'compiler_pass.cc',
'compiler_pass.h',
'compiler_passes.h',
],
'includes': [
'passes/sources.gypi',
],
}

View File

@@ -22,4 +22,13 @@ DECLARE_string(load_module_map);
DECLARE_string(dump_path);
DECLARE_bool(dump_module_map);
DECLARE_bool(debug);
DECLARE_bool(always_disasm);
DECLARE_bool(validate_hir);
DECLARE_uint64(break_on_instruction);
DECLARE_uint64(break_on_memory);
DECLARE_bool(break_on_debugbreak);
#endif // XENIA_CPU_PRIVATE_H_

View File

@@ -28,3 +28,25 @@ DEFINE_string(dump_path, "build/",
DEFINE_bool(dump_module_bitcode, true,
"Writes the module bitcode both before and after optimizations.");
DEFINE_bool(dump_module_map, true, "Dumps the module symbol database.");
#if 0 && DEBUG
#define DEFAULT_DEBUG_FLAG true
#else
#define DEFAULT_DEBUG_FLAG false
#endif
DEFINE_bool(debug, DEFAULT_DEBUG_FLAG,
"Allow debugging and retain debug information.");
DEFINE_bool(
always_disasm, false,
"Always add debug info to functions, even when no debugger is attached.");
DEFINE_bool(validate_hir, false,
"Perform validation checks on the HIR during compilation.");
// Breakpoints:
DEFINE_uint64(break_on_instruction, 0,
"int3 before the given guest address is executed.");
DEFINE_uint64(break_on_memory, 0,
"int3 on read/write to the given memory address.");
DEFINE_bool(break_on_debugbreak, true, "int3 on JITed __debugbreak requests.");

View File

@@ -11,6 +11,10 @@
#define XENIA_CPU_CPU_H_
#include "xenia/cpu/processor.h"
#include "xenia/cpu/runtime/function.h"
#include "xenia/cpu/runtime/module.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/cpu/runtime/thread_state.h"
#include "xenia/cpu/xenon_runtime.h"
#include "xenia/cpu/xenon_thread_state.h"
#include "xenia/cpu/xex_module.h"

View File

@@ -0,0 +1,26 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/context_info.h"
namespace xe {
namespace cpu {
namespace frontend {
ContextInfo::ContextInfo(size_t size, uintptr_t thread_state_offset,
uintptr_t thread_id_offset)
: size_(size),
thread_state_offset_(thread_state_offset),
thread_id_offset_(thread_id_offset) {}
ContextInfo::~ContextInfo() {}
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,41 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_CONTEXT_INFO_H_
#define XENIA_FRONTEND_CONTEXT_INFO_H_
#include <cstddef>
#include <cstdint>
namespace xe {
namespace cpu {
namespace frontend {
class ContextInfo {
public:
ContextInfo(size_t size, uintptr_t thread_state_offset,
uintptr_t thread_id_offset);
~ContextInfo();
size_t size() const { return size_; }
uintptr_t thread_state_offset() const { return thread_state_offset_; }
uintptr_t thread_id_offset() const { return thread_id_offset_; }
private:
size_t size_;
uintptr_t thread_state_offset_;
uintptr_t thread_id_offset_;
};
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_CONTEXT_INFO_H_

View File

@@ -0,0 +1,28 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/frontend.h"
#include "xenia/cpu/runtime/runtime.h"
namespace xe {
namespace cpu {
namespace frontend {
Frontend::Frontend(runtime::Runtime* runtime) : runtime_(runtime) {}
Frontend::~Frontend() = default;
Memory* Frontend::memory() const { return runtime_->memory(); }
int Frontend::Initialize() { return 0; }
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,57 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_FRONTEND_H_
#define XENIA_FRONTEND_FRONTEND_H_
#include <memory>
#include "xenia/cpu/frontend/context_info.h"
#include "xenia/memory.h"
#include "xenia/cpu/runtime/function.h"
#include "xenia/cpu/runtime/symbol_info.h"
namespace xe {
namespace cpu {
namespace runtime {
class Runtime;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace frontend {
class Frontend {
public:
Frontend(runtime::Runtime* runtime);
virtual ~Frontend();
runtime::Runtime* runtime() const { return runtime_; }
Memory* memory() const;
ContextInfo* context_info() const { return context_info_.get(); }
virtual int Initialize();
virtual int DeclareFunction(runtime::FunctionInfo* symbol_info) = 0;
virtual int DefineFunction(runtime::FunctionInfo* symbol_info,
uint32_t debug_info_flags, uint32_t trace_flags,
runtime::Function** out_function) = 0;
protected:
runtime::Runtime* runtime_;
std::unique_ptr<ContextInfo> context_info_;
};
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_FRONTEND_H_

View File

@@ -0,0 +1,88 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_context.h"
#include <cstdlib>
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
uint64_t ParseInt64(const char* value) {
return std::strtoull(value, nullptr, 0);
}
double ParseFloat64(const char* value) { return std::strtod(value, nullptr); }
vec128_t ParseVec128(const char* value) {
vec128_t v;
char* p = const_cast<char*>(value);
if (*p == '[') ++p;
v.i32[0] = std::strtoul(p, &p, 16);
while (*p == ' ' || *p == ',') ++p;
v.i32[1] = std::strtoul(p, &p, 16);
while (*p == ' ' || *p == ',') ++p;
v.i32[2] = std::strtoul(p, &p, 16);
while (*p == ' ' || *p == ',') ++p;
v.i32[3] = std::strtoul(p, &p, 16);
return v;
}
void PPCContext::SetRegFromString(const char* name, const char* value) {
int n;
if (sscanf(name, "r%d", &n) == 1) {
this->r[n] = ParseInt64(value);
} else if (sscanf(name, "f%d", &n) == 1) {
this->f[n] = ParseFloat64(value);
} else if (sscanf(name, "v%d", &n) == 1) {
this->v[n] = ParseVec128(value);
} else {
printf("Unrecognized register name: %s\n", name);
}
}
bool PPCContext::CompareRegWithString(const char* name, const char* value,
char* out_value, size_t out_value_size) {
int n;
if (sscanf(name, "r%d", &n) == 1) {
uint64_t expected = ParseInt64(value);
if (this->r[n] != expected) {
snprintf(out_value, out_value_size, "%016llX", this->r[n]);
return false;
}
return true;
} else if (sscanf(name, "f%d", &n) == 1) {
double expected = ParseFloat64(value);
// TODO(benvanik): epsilon
if (this->f[n] != expected) {
snprintf(out_value, out_value_size, "%f", this->f[n]);
return false;
}
return true;
} else if (sscanf(name, "v%d", &n) == 1) {
vec128_t expected = ParseVec128(value);
if (this->v[n] != expected) {
snprintf(out_value, out_value_size, "[%.8X, %.8X, %.8X, %.8X]",
this->v[n].i32[0], this->v[n].i32[1], this->v[n].i32[2],
this->v[n].i32[3]);
return false;
}
return true;
} else {
printf("Unrecognized register name: %s\n", name);
return false;
}
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,227 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_CONTEXT_H_
#define XENIA_FRONTEND_PPC_PPC_CONTEXT_H_
#include "poly/poly.h"
#include "poly/vec128.h"
namespace xe {
namespace cpu {
namespace runtime {
class Runtime;
class ThreadState;
} // namespace runtime
} // namespace cpu
} // namespace xe
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
using vec128_t = poly::vec128_t;
// Map:
// 0-31: GPR
// 32-63: FPR
// 64: LR
// 65: CTR
// 66: XER
// 67: FPSCR
// 68: VSCR
// 69-76: CR0-7
// 100: invalid
// 128-256: VR
#pragma pack(push, 4)
typedef struct alignas(64) PPCContext_s {
// Must be stored at 0x0 for now.
// TODO(benvanik): find a nice way to describe this to the JIT.
runtime::ThreadState* thread_state;
// TODO(benvanik): this is getting nasty. Must be here.
uint8_t* membase;
// Most frequently used registers first.
uint64_t r[32]; // General purpose registers
uint64_t lr; // Link register
uint64_t ctr; // Count register
// XER register
// Split to make it easier to do individual updates.
uint8_t xer_ca;
uint8_t xer_ov;
uint8_t xer_so;
// Condition registers
// These are split to make it easier to do DCE on unused stores.
union {
uint32_t value;
struct {
uint8_t cr0_lt; // Negative (LT) - result is negative
uint8_t cr0_gt; // Positive (GT) - result is positive (and not zero)
uint8_t cr0_eq; // Zero (EQ) - result is zero or a stwcx/stdcx completed
// successfully
uint8_t cr0_so; // Summary Overflow (SO) - copy of XER[SO]
};
} cr0;
union {
uint32_t value;
struct {
uint8_t cr1_fx; // FP exception summary - copy of FPSCR[FX]
uint8_t cr1_fex; // FP enabled exception summary - copy of FPSCR[FEX]
uint8_t
cr1_vx; // FP invalid operation exception summary - copy of FPSCR[VX]
uint8_t cr1_ox; // FP overflow exception - copy of FPSCR[OX]
};
} cr1;
union {
uint32_t value;
struct {
uint8_t cr2_0;
uint8_t cr2_1;
uint8_t cr2_2;
uint8_t cr2_3;
};
} cr2;
union {
uint32_t value;
struct {
uint8_t cr3_0;
uint8_t cr3_1;
uint8_t cr3_2;
uint8_t cr3_3;
};
} cr3;
union {
uint32_t value;
struct {
uint8_t cr4_0;
uint8_t cr4_1;
uint8_t cr4_2;
uint8_t cr4_3;
};
} cr4;
union {
uint32_t value;
struct {
uint8_t cr5_0;
uint8_t cr5_1;
uint8_t cr5_2;
uint8_t cr5_3;
};
} cr5;
union {
uint32_t value;
struct {
uint8_t cr6_all_equal;
uint8_t cr6_1;
uint8_t cr6_none_equal;
uint8_t cr6_3;
};
} cr6;
union {
uint32_t value;
struct {
uint8_t cr7_0;
uint8_t cr7_1;
uint8_t cr7_2;
uint8_t cr7_3;
};
} cr7;
union {
uint32_t value;
struct {
uint32_t rn : 2; // FP rounding control: 00 = nearest
// 01 = toward zero
// 10 = toward +infinity
// 11 = toward -infinity
uint32_t ni : 1; // Floating-point non-IEEE mode
uint32_t xe : 1; // IEEE floating-point inexact exception enable
uint32_t ze : 1; // IEEE floating-point zero divide exception enable
uint32_t ue : 1; // IEEE floating-point underflow exception enable
uint32_t oe : 1; // IEEE floating-point overflow exception enable
uint32_t ve : 1; // FP invalid op exception enable
uint32_t vxcvi : 1; // FP invalid op exception: invalid integer convert
// -- sticky
uint32_t vxsqrt : 1; // FP invalid op exception: invalid sqrt -- sticky
uint32_t vxsoft : 1; // FP invalid op exception: software request
// -- sticky
uint32_t reserved : 1;
uint32_t fprf_un : 1; // FP result unordered or NaN (FU or ?)
uint32_t fprf_eq : 1; // FP result equal or zero (FE or =)
uint32_t fprf_gt : 1; // FP result greater than or positive (FG or >)
uint32_t fprf_lt : 1; // FP result less than or negative (FL or <)
uint32_t fprf_c : 1; // FP result class
uint32_t fi : 1; // FP fraction inexact
uint32_t fr : 1; // FP fraction rounded
uint32_t vxvc : 1; // FP invalid op exception: invalid compare --
// sticky
uint32_t vximz : 1; // FP invalid op exception: infinity * 0 -- sticky
uint32_t vxzdz : 1; // FP invalid op exception: 0 / 0 -- sticky
uint32_t vxidi : 1; // FP invalid op exception: infinity / infinity
// -- sticky
uint32_t vxisi : 1; // FP invalid op exception: infinity - infinity
// -- sticky
uint32_t vxsnan : 1; // FP invalid op exception: SNaN -- sticky
uint32_t
xx : 1; // FP inexact exception -- sticky
uint32_t
zx : 1; // FP zero divide exception -- sticky
uint32_t
ux : 1; // FP underflow exception -- sticky
uint32_t
ox : 1; // FP overflow exception -- sticky
uint32_t vx : 1; // FP invalid operation exception summary
uint32_t fex : 1; // FP enabled exception summary
uint32_t
fx : 1; // FP exception summary -- sticky
} bits;
} fpscr; // Floating-point status and control register
uint8_t vscr_sat;
double f[32]; // Floating-point registers
vec128_t v[128]; // VMX128 vector registers
// uint32_t get_fprf() {
// return fpscr.value & 0x000F8000;
// }
// void set_fprf(const uint32_t v) {
// fpscr.value = (fpscr.value & ~0x000F8000) | v;
// }
// Thread ID assigned to this context.
uint32_t thread_id;
// Reserve address for load acquire/store release. Shared.
uint64_t* reserve_address;
uint64_t* reserve_value;
// Used to shuttle data into externs. Contents volatile.
uint64_t scratch;
// Runtime-specific data pointer. Used on callbacks to get access to the
// current runtime and its data.
runtime::Runtime* runtime;
void SetRegFromString(const char* name, const char* value);
bool CompareRegWithString(const char* name, const char* value,
char* out_value, size_t out_value_size);
} PPCContext;
#pragma pack(pop)
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_CONTEXT_H_

View File

@@ -0,0 +1,505 @@
/*
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_disasm.h"
#include "poly/poly.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
void Disasm_0(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s ???", i.type->name);
}
void Disasm__(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s", i.type->name);
}
void Disasm_X_FRT_FRB(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s f%d, f%d", i.X.Rc ? -7 : -8, i.type->name,
i.X.Rc ? "." : "", i.X.RT, i.X.RB);
}
void Disasm_A_FRT_FRB(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s f%d, f%d", i.A.Rc ? -7 : -8, i.type->name,
i.A.Rc ? "." : "", i.A.FRT, i.A.FRB);
}
void Disasm_A_FRT_FRA_FRB(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s f%d, f%d, f%d", i.A.Rc ? -7 : -8, i.type->name,
i.A.Rc ? "." : "", i.A.FRT, i.A.FRA, i.A.FRB);
}
void Disasm_A_FRT_FRA_FRB_FRC(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s f%d, f%d, f%d, f%d", i.A.Rc ? -7 : -8, i.type->name,
i.A.Rc ? "." : "", i.A.FRT, i.A.FRA, i.A.FRB, i.A.FRC);
}
void Disasm_X_RT_RA_RB(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, r%d, r%d", i.type->name, i.X.RT, i.X.RA, i.X.RB);
}
void Disasm_X_RT_RA0_RB(InstrData& i, poly::StringBuffer* str) {
if (i.X.RA) {
str->Append("%-8s r%d, r%d, r%d", i.type->name, i.X.RT, i.X.RA, i.X.RB);
} else {
str->Append("%-8s r%d, 0, r%d", i.type->name, i.X.RT, i.X.RB);
}
}
void Disasm_X_FRT_RA_RB(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s f%d, r%d, r%d", i.type->name, i.X.RT, i.X.RA, i.X.RB);
}
void Disasm_X_FRT_RA0_RB(InstrData& i, poly::StringBuffer* str) {
if (i.X.RA) {
str->Append("%-8s f%d, r%d, r%d", i.type->name, i.X.RT, i.X.RA, i.X.RB);
} else {
str->Append("%-8s f%d, 0, r%d", i.type->name, i.X.RT, i.X.RB);
}
}
void Disasm_D_RT_RA_I(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, r%d, %d", i.type->name, i.D.RT, i.D.RA,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
}
void Disasm_D_RT_RA0_I(InstrData& i, poly::StringBuffer* str) {
if (i.D.RA) {
str->Append("%-8s r%d, r%d, %d", i.type->name, i.D.RT, i.D.RA,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
} else {
str->Append("%-8s r%d, 0, %d", i.type->name, i.D.RT,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
}
}
void Disasm_D_FRT_RA_I(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s f%d, r%d, %d", i.type->name, i.D.RT, i.D.RA,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
}
void Disasm_D_FRT_RA0_I(InstrData& i, poly::StringBuffer* str) {
if (i.D.RA) {
str->Append("%-8s f%d, r%d, %d", i.type->name, i.D.RT, i.D.RA,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
} else {
str->Append("%-8s f%d, 0, %d", i.type->name, i.D.RT,
(int32_t)(int16_t) XEEXTS16(i.D.DS));
}
}
void Disasm_DS_RT_RA_I(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, r%d, %d", i.type->name, i.DS.RT, i.DS.RA,
(int32_t)(int16_t) XEEXTS16(i.DS.DS << 2));
}
void Disasm_DS_RT_RA0_I(InstrData& i, poly::StringBuffer* str) {
if (i.DS.RA) {
str->Append("%-8s r%d, r%d, %d", i.type->name, i.DS.RT, i.DS.RA,
(int32_t)(int16_t) XEEXTS16(i.DS.DS << 2));
} else {
str->Append("%-8s r%d, 0, %d", i.type->name, i.DS.RT,
(int32_t)(int16_t) XEEXTS16(i.DS.DS << 2));
}
}
void Disasm_D_RA(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d", i.type->name, i.D.RA);
}
void Disasm_X_RA_RB(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, r%d", i.type->name, i.X.RA, i.X.RB);
}
void Disasm_XO_RT_RA_RB(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s%s r%d, r%d, r%d", i.XO.Rc ? -7 : -8, i.type->name,
i.XO.OE ? "o" : "", i.XO.Rc ? "." : "", i.XO.RT, i.XO.RA,
i.XO.RB);
}
void Disasm_XO_RT_RA(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s%s r%d, r%d", i.XO.Rc ? -7 : -8, i.type->name,
i.XO.OE ? "o" : "", i.XO.Rc ? "." : "", i.XO.RT, i.XO.RA);
}
void Disasm_X_RA_RT_RB(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d, r%d", i.X.Rc ? -7 : -8, i.type->name,
i.X.Rc ? "." : "", i.X.RA, i.X.RT, i.X.RB);
}
void Disasm_D_RA_RT_I(InstrData& i, poly::StringBuffer* str) {
str->Append("%-7s. r%d, r%d, %.4Xh", i.type->name, i.D.RA, i.D.RT, i.D.DS);
}
void Disasm_X_RA_RT(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d", i.X.Rc ? -7 : -8, i.type->name,
i.X.Rc ? "." : "", i.X.RA, i.X.RT);
}
#define OP(x) ((((uint32_t)(x)) & 0x3f) << 26)
#define VX128(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x3d0))
#define VX128_1(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x7f3))
#define VX128_2(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x210))
#define VX128_3(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x7f0))
#define VX128_4(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x730))
#define VX128_5(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x10))
#define VX128_P(op, xop) (OP(op) | (((uint32_t)(xop)) & 0x630))
#define VX128_VD128 (i.VX128.VD128l | (i.VX128.VD128h << 5))
#define VX128_VA128 \
(i.VX128.VA128l | (i.VX128.VA128h << 5) | (i.VX128.VA128H << 6))
#define VX128_VB128 (i.VX128.VB128l | (i.VX128.VB128h << 5))
#define VX128_1_VD128 (i.VX128_1.VD128l | (i.VX128_1.VD128h << 5))
#define VX128_2_VD128 (i.VX128_2.VD128l | (i.VX128_2.VD128h << 5))
#define VX128_2_VA128 \
(i.VX128_2.VA128l | (i.VX128_2.VA128h << 5) | (i.VX128_2.VA128H << 6))
#define VX128_2_VB128 (i.VX128_2.VB128l | (i.VX128_2.VB128h << 5))
#define VX128_2_VC (i.VX128_2.VC)
#define VX128_3_VD128 (i.VX128_3.VD128l | (i.VX128_3.VD128h << 5))
#define VX128_3_VB128 (i.VX128_3.VB128l | (i.VX128_3.VB128h << 5))
#define VX128_3_IMM (i.VX128_3.IMM)
#define VX128_4_VD128 (i.VX128_4.VD128l | (i.VX128_4.VD128h << 5))
#define VX128_4_VB128 (i.VX128_4.VB128l | (i.VX128_4.VB128h << 5))
#define VX128_5_VD128 (i.VX128_5.VD128l | (i.VX128_5.VD128h << 5))
#define VX128_5_VA128 \
(i.VX128_5.VA128l | (i.VX128_5.VA128h << 5)) | (i.VX128_5.VA128H << 6)
#define VX128_5_VB128 (i.VX128_5.VB128l | (i.VX128_5.VB128h << 5))
#define VX128_5_SH (i.VX128_5.SH)
#define VX128_R_VD128 (i.VX128_R.VD128l | (i.VX128_R.VD128h << 5))
#define VX128_R_VA128 \
(i.VX128_R.VA128l | (i.VX128_R.VA128h << 5) | (i.VX128_R.VA128H << 6))
#define VX128_R_VB128 (i.VX128_R.VB128l | (i.VX128_R.VB128h << 5))
void Disasm_X_VX_RA0_RB(InstrData& i, poly::StringBuffer* str) {
if (i.X.RA) {
str->Append("%-8s v%d, r%d, r%d", i.type->name, i.X.RT, i.X.RA, i.X.RB);
} else {
str->Append("%-8s v%d, 0, r%d", i.type->name, i.X.RT, i.X.RB);
}
}
void Disasm_VX1281_VD_RA0_RB(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_1_VD128;
if (i.VX128_1.RA) {
str->Append("%-8s v%d, r%d, r%d", i.type->name, vd, i.VX128_1.RA,
i.VX128_1.RB);
} else {
str->Append("%-8s v%d, 0, r%d", i.type->name, vd, i.VX128_1.RB);
}
}
void Disasm_VX1283_VD_VB(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_3_VD128;
const uint32_t vb = VX128_3_VB128;
str->Append("%-8s v%d, v%d", i.type->name, vd, vb);
}
void Disasm_VX1283_VD_VB_I(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_VD128;
const uint32_t va = VX128_VA128;
const uint32_t uimm = i.VX128_3.IMM;
str->Append("%-8s v%d, v%d, %.2Xh", i.type->name, vd, va, uimm);
}
void Disasm_VX_VD_VA_VB(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s v%d, v%d, v%d", i.type->name, i.VX.VD, i.VX.VA, i.VX.VB);
}
void Disasm_VX128_VD_VA_VB(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_VD128;
const uint32_t va = VX128_VA128;
const uint32_t vb = VX128_VB128;
str->Append("%-8s v%d, v%d, v%d", i.type->name, vd, va, vb);
}
void Disasm_VX128_VD_VA_VD_VB(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_VD128;
const uint32_t va = VX128_VA128;
const uint32_t vb = VX128_VB128;
str->Append("%-8s v%d, v%d, v%d, v%d", i.type->name, vd, va, vd, vb);
}
void Disasm_VX1282_VD_VA_VB_VC(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_2_VD128;
const uint32_t va = VX128_2_VA128;
const uint32_t vb = VX128_2_VB128;
const uint32_t vc = i.VX128_2.VC;
str->Append("%-8s v%d, v%d, v%d, v%d", i.type->name, vd, va, vb, vc);
}
void Disasm_VXA_VD_VA_VB_VC(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s v%d, v%d, v%d, v%d", i.type->name, i.VXA.VD, i.VXA.VA,
i.VXA.VB, i.VXA.VC);
}
void Disasm_sync(InstrData& i, poly::StringBuffer* str) {
const char* name;
int L = i.X.RT & 3;
switch (L) {
case 0:
name = "hwsync";
break;
case 1:
name = "lwsync";
break;
default:
case 2:
case 3:
name = "sync";
break;
}
str->Append("%-8s %.2X", name, L);
}
void Disasm_dcbf(InstrData& i, poly::StringBuffer* str) {
const char* name;
switch (i.X.RT & 3) {
case 0:
name = "dcbf";
break;
case 1:
name = "dcbfl";
break;
case 2:
name = "dcbf.RESERVED";
break;
case 3:
name = "dcbflp";
break;
default:
name = "dcbf.??";
break;
}
str->Append("%-8s r%d, r%d", name, i.X.RA, i.X.RB);
}
void Disasm_dcbz(InstrData& i, poly::StringBuffer* str) {
// or dcbz128 0x7C2007EC
if (i.X.RA) {
str->Append("%-8s r%d, r%d", i.type->name, i.X.RA, i.X.RB);
} else {
str->Append("%-8s 0, r%d", i.type->name, i.X.RB);
}
}
void Disasm_fcmp(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s cr%d, f%d, f%d", i.type->name, i.X.RT >> 2, i.X.RA, i.X.RB);
}
void Disasm_mffsx(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s f%d, FPSCR", i.X.Rc ? -7 : -8, i.type->name,
i.X.Rc ? "." : "", i.X.RT);
}
void Disasm_bx(InstrData& i, poly::StringBuffer* str) {
const char* name = i.I.LK ? "bl" : "b";
uint32_t nia;
if (i.I.AA) {
nia = (uint32_t)XEEXTS26(i.I.LI << 2);
} else {
nia = (uint32_t)(i.address + XEEXTS26(i.I.LI << 2));
}
str->Append("%-8s %.8X", name, nia);
// TODO(benvanik): resolve target name?
}
void Disasm_bcx(InstrData& i, poly::StringBuffer* str) {
const char* s0 = i.B.LK ? "lr, " : "";
const char* s1;
if (!select_bits(i.B.BO, 2, 2)) {
s1 = "ctr, ";
} else {
s1 = "";
}
char s2[8] = {0};
if (!select_bits(i.B.BO, 4, 4)) {
snprintf(s2, poly::countof(s2), "cr%d, ", i.B.BI >> 2);
}
uint32_t nia;
if (i.B.AA) {
nia = (uint32_t)XEEXTS16(i.B.BD << 2);
} else {
nia = (uint32_t)(i.address + XEEXTS16(i.B.BD << 2));
}
str->Append("%-8s %s%s%s%.8X", i.type->name, s0, s1, s2, nia);
// TODO(benvanik): resolve target name?
}
void Disasm_bcctrx(InstrData& i, poly::StringBuffer* str) {
// TODO(benvanik): mnemonics
const char* s0 = i.XL.LK ? "lr, " : "";
char s2[8] = {0};
if (!select_bits(i.XL.BO, 4, 4)) {
snprintf(s2, poly::countof(s2), "cr%d, ", i.XL.BI >> 2);
}
str->Append("%-8s %s%sctr", i.type->name, s0, s2);
// TODO(benvanik): resolve target name?
}
void Disasm_bclrx(InstrData& i, poly::StringBuffer* str) {
const char* name = "bclr";
if (i.code == 0x4E800020) {
name = "blr";
}
const char* s1;
if (!select_bits(i.XL.BO, 2, 2)) {
s1 = "ctr, ";
} else {
s1 = "";
}
char s2[8] = {0};
if (!select_bits(i.XL.BO, 4, 4)) {
snprintf(s2, poly::countof(s2), "cr%d, ", i.XL.BI >> 2);
}
str->Append("%-8s %s%s", name, s1, s2);
}
void Disasm_mfcr(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, cr", i.type->name, i.X.RT);
}
const char* Disasm_spr_name(uint32_t n) {
const char* reg = "???";
switch (n) {
case 1:
reg = "xer";
break;
case 8:
reg = "lr";
break;
case 9:
reg = "ctr";
break;
}
return reg;
}
void Disasm_mfspr(InstrData& i, poly::StringBuffer* str) {
const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F);
const char* reg = Disasm_spr_name(n);
str->Append("%-8s r%d, %s", i.type->name, i.XFX.RT, reg);
}
void Disasm_mtspr(InstrData& i, poly::StringBuffer* str) {
const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F);
const char* reg = Disasm_spr_name(n);
str->Append("%-8s %s, r%d", i.type->name, reg, i.XFX.RT);
}
void Disasm_mftb(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, tb", i.type->name, i.XFX.RT);
}
void Disasm_mfmsr(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d", i.type->name, i.X.RT);
}
void Disasm_mtmsr(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s r%d, %d", i.type->name, i.X.RT, (i.X.RA & 16) ? 1 : 0);
}
void Disasm_cmp(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s cr%d, %.2X, r%d, r%d", i.type->name, i.X.RT >> 2,
i.X.RT & 1, i.X.RA, i.X.RB);
}
void Disasm_cmpi(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s cr%d, %.2X, r%d, %d", i.type->name, i.D.RT >> 2, i.D.RT & 1,
i.D.RA, XEEXTS16(i.D.DS));
}
void Disasm_cmpli(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s cr%d, %.2X, r%d, %.2X", i.type->name, i.D.RT >> 2,
i.D.RT & 1, i.D.RA, XEEXTS16(i.D.DS));
}
void Disasm_rld(InstrData& i, poly::StringBuffer* str) {
if (i.MD.idx == 0) {
// XEDISASMR(rldiclx, 0x78000000, MD )
str->Append("%*s%s r%d, r%d, %d, %d", i.MD.Rc ? -7 : -8, "rldicl",
i.MD.Rc ? "." : "", i.MD.RA, i.MD.RT, (i.MD.SH5 << 5) | i.MD.SH,
(i.MD.MB5 << 5) | i.MD.MB);
} else if (i.MD.idx == 1) {
// XEDISASMR(rldicrx, 0x78000004, MD )
str->Append("%*s%s r%d, r%d, %d, %d", i.MD.Rc ? -7 : -8, "rldicr",
i.MD.Rc ? "." : "", i.MD.RA, i.MD.RT, (i.MD.SH5 << 5) | i.MD.SH,
(i.MD.MB5 << 5) | i.MD.MB);
} else if (i.MD.idx == 2) {
// XEDISASMR(rldicx, 0x78000008, MD )
uint32_t sh = (i.MD.SH5 << 5) | i.MD.SH;
uint32_t mb = (i.MD.MB5 << 5) | i.MD.MB;
const char* name = (mb == 0x3E) ? "sldi" : "rldic";
str->Append("%*s%s r%d, r%d, %d, %d", i.MD.Rc ? -7 : -8, name,
i.MD.Rc ? "." : "", i.MD.RA, i.MD.RT, sh, mb);
} else if (i.MDS.idx == 8) {
// XEDISASMR(rldclx, 0x78000010, MDS)
str->Append("%*s%s r%d, r%d, %d, %d", i.MDS.Rc ? -7 : -8, "rldcl",
i.MDS.Rc ? "." : "", i.MDS.RA, i.MDS.RT, i.MDS.RB,
(i.MDS.MB5 << 5) | i.MDS.MB);
} else if (i.MDS.idx == 9) {
// XEDISASMR(rldcrx, 0x78000012, MDS)
str->Append("%*s%s r%d, r%d, %d, %d", i.MDS.Rc ? -7 : -8, "rldcr",
i.MDS.Rc ? "." : "", i.MDS.RA, i.MDS.RT, i.MDS.RB,
(i.MDS.MB5 << 5) | i.MDS.MB);
} else if (i.MD.idx == 3) {
// XEDISASMR(rldimix, 0x7800000C, MD )
str->Append("%*s%s r%d, r%d, %d, %d", i.MD.Rc ? -7 : -8, "rldimi",
i.MD.Rc ? "." : "", i.MD.RA, i.MD.RT, (i.MD.SH5 << 5) | i.MD.SH,
(i.MD.MB5 << 5) | i.MD.MB);
} else {
assert_always();
}
}
void Disasm_rlwim(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d, %d, %d, %d", i.M.Rc ? -7 : -8, i.type->name,
i.M.Rc ? "." : "", i.M.RA, i.M.RT, i.M.SH, i.M.MB, i.M.ME);
}
void Disasm_rlwnmx(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d, r%d, %d, %d", i.M.Rc ? -7 : -8, i.type->name,
i.M.Rc ? "." : "", i.M.RA, i.M.RT, i.M.SH, i.M.MB, i.M.ME);
}
void Disasm_srawix(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d, %d", i.X.Rc ? -7 : -8, i.type->name,
i.X.Rc ? "." : "", i.X.RA, i.X.RT, i.X.RB);
}
void Disasm_sradix(InstrData& i, poly::StringBuffer* str) {
str->Append("%*s%s r%d, r%d, %d", i.XS.Rc ? -7 : -8, i.type->name,
i.XS.Rc ? "." : "", i.XS.RA, i.XS.RT, (i.XS.SH5 << 5) | i.XS.SH);
}
void Disasm_vpermwi128(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = i.VX128_P.VD128l | (i.VX128_P.VD128h << 5);
const uint32_t vb = i.VX128_P.VB128l | (i.VX128_P.VB128h << 5);
str->Append("%-8s v%d, v%d, %.2X", i.type->name, vd, vb,
i.VX128_P.PERMl | (i.VX128_P.PERMh << 5));
}
void Disasm_vrfin128(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_3_VD128;
const uint32_t vb = VX128_3_VB128;
str->Append("%-8s v%d, v%d", i.type->name, vd, vb);
}
void Disasm_vrlimi128(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_4_VD128;
const uint32_t vb = VX128_4_VB128;
str->Append("%-8s v%d, v%d, %.2X, %.2X", i.type->name, vd, vb, i.VX128_4.IMM,
i.VX128_4.z);
}
void Disasm_vsldoi128(InstrData& i, poly::StringBuffer* str) {
const uint32_t vd = VX128_5_VD128;
const uint32_t va = VX128_5_VA128;
const uint32_t vb = VX128_5_VB128;
const uint32_t sh = i.VX128_5.SH;
str->Append("%-8s v%d, v%d, v%d, %.2X", i.type->name, vd, va, vb, sh);
}
void Disasm_vspltb(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s v%d, v%d, %.2X", i.type->name, i.VX.VD, i.VX.VB,
i.VX.VA & 0xF);
}
void Disasm_vsplth(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s v%d, v%d, %.2X", i.type->name, i.VX.VD, i.VX.VB,
i.VX.VA & 0x7);
}
void Disasm_vspltw(InstrData& i, poly::StringBuffer* str) {
str->Append("%-8s v%d, v%d, %.2X", i.type->name, i.VX.VD, i.VX.VB, i.VX.VA);
}
void Disasm_vspltisb(InstrData& i, poly::StringBuffer* str) {
// 5bit -> 8bit sign extend
int8_t simm = (i.VX.VA & 0x10) ? (i.VX.VA | 0xF0) : i.VX.VA;
str->Append("%-8s v%d, %.2X", i.type->name, i.VX.VD, simm);
}
void Disasm_vspltish(InstrData& i, poly::StringBuffer* str) {
// 5bit -> 16bit sign extend
int16_t simm = (i.VX.VA & 0x10) ? (i.VX.VA | 0xFFF0) : i.VX.VA;
str->Append("%-8s v%d, %.4X", i.type->name, i.VX.VD, simm);
}
void Disasm_vspltisw(InstrData& i, poly::StringBuffer* str) {
// 5bit -> 32bit sign extend
int32_t simm = (i.VX.VA & 0x10) ? (i.VX.VA | 0xFFFFFFF0) : i.VX.VA;
str->Append("%-8s v%d, %.8X", i.type->name, i.VX.VD, simm);
}
int DisasmPPC(InstrData& i, poly::StringBuffer* str) {
if (!i.type) {
str->Append("???");
} else {
i.type->disasm(i, str);
}
return 0;
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,28 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_DISASM_H_
#define XENIA_FRONTEND_PPC_PPC_DISASM_H_
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
int DisasmPPC(InstrData& i, poly::StringBuffer* str);
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_DISASM_H_

View File

@@ -0,0 +1,35 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_EMIT_PRIVATE_H_
#define XENIA_FRONTEND_PPC_PPC_EMIT_PRIVATE_H_
#include "xenia/cpu/frontend/ppc/ppc_emit.h"
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
#define XEEMITTER(name, opcode, format) int InstrEmit_##name
#define XEREGISTERINSTR(name, opcode) \
RegisterInstrEmit(opcode, (InstrEmitFn)InstrEmit_##name);
//#define XEINSTRNOTIMPLEMENTED()
#define XEINSTRNOTIMPLEMENTED() assert_always("Instruction not implemented");
//#define XEINSTRNOTIMPLEMENTED() __debugbreak()
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_EMIT_PRIVATE_H_

View File

@@ -0,0 +1,31 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_EMIT_H_
#define XENIA_FRONTEND_PPC_PPC_EMIT_H_
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
void RegisterEmitCategoryAltivec();
void RegisterEmitCategoryALU();
void RegisterEmitCategoryControl();
void RegisterEmitCategoryFPU();
void RegisterEmitCategoryMemory();
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_EMIT_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,754 @@
/*
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_emit-private.h"
#include "xenia/cpu/frontend/ppc/ppc_context.h"
#include "xenia/cpu/frontend/ppc/ppc_hir_builder.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::Label;
using xe::cpu::hir::Value;
int InstrEmit_branch(PPCHIRBuilder& f, const char* src, uint64_t cia,
Value* nia, bool lk, Value* cond = NULL,
bool expect_true = true, bool nia_is_lr = false) {
uint32_t call_flags = 0;
// TODO(benvanik): this may be wrong and overwrite LRs when not desired!
// The docs say always, though...
// Note that we do the update before we branch/call as we need it to
// be correct for returns.
if (lk) {
Value* return_address = f.LoadConstant(cia + 4);
f.SetReturnAddress(return_address);
f.StoreLR(return_address);
}
if (!lk) {
// If LR is not set this call will never return here.
call_flags |= CALL_TAIL;
}
// TODO(benvanik): set CALL_TAIL if !lk and the last block in the fn.
// This is almost always a jump to restore gpr.
if (nia->IsConstant()) {
// Direct branch to address.
// If it's a block inside of ourself, setup a fast jump.
// Unless it's to ourselves directly, in which case it's
// recursion.
uint64_t nia_value = nia->AsUint64() & 0xFFFFFFFF;
bool is_recursion = false;
if (nia_value == f.symbol_info()->address() && lk) {
is_recursion = true;
}
Label* label = is_recursion ? NULL : f.LookupLabel(nia_value);
if (label) {
// Branch to label.
uint32_t branch_flags = 0;
if (cond) {
if (expect_true) {
f.BranchTrue(cond, label, branch_flags);
} else {
f.BranchFalse(cond, label, branch_flags);
}
} else {
f.Branch(label, branch_flags);
}
} else {
// Call function.
auto symbol_info = f.LookupFunction(nia_value);
if (cond) {
if (!expect_true) {
cond = f.IsFalse(cond);
}
f.CallTrue(cond, symbol_info, call_flags);
} else {
f.Call(symbol_info, call_flags);
}
}
} else {
// Indirect branch to pointer.
// TODO(benvanik): runtime recursion detection?
// TODO(benvanik): run a DFA pass to see if we can detect whether this is
// a normal function return that is pulling the LR from the stack that
// it set in the prolog. If so, we can omit the dynamic check!
//// Dynamic test when branching to LR, which is usually used for the return.
//// We only do this if LK=0 as returns wouldn't set LR.
//// Ideally it's a return and we can just do a simple ret and be done.
//// If it's not, we fall through to the full indirection logic.
// if (!lk && reg == kXEPPCRegLR) {
// // The return block will spill registers for us.
// // TODO(benvanik): 'lr_mismatch' debug info.
// // Note: we need to test on *only* the 32-bit target, as the target ptr may
// // have garbage in the upper 32 bits.
// c.cmp(target.r32(), c.getGpArg(1).r32());
// // TODO(benvanik): evaluate hint here.
// c.je(e.GetReturnLabel(), kCondHintLikely);
//}
#if 0
// This breaks longjump, as that uses blr with a non-return lr.
// It'd be nice to move SET_RETURN_ADDRESS semantics up into context
// so that we can just use this.
if (!lk && nia_is_lr) {
// Return (most likely).
// TODO(benvanik): test? ReturnCheck()?
if (cond) {
if (!expect_true) {
cond = f.IsFalse(cond);
}
f.ReturnTrue(cond);
} else {
f.Return();
}
} else {
#else
{
#endif
// Jump to pointer.
bool likely_return = !lk && nia_is_lr;
if (likely_return) {
call_flags |= CALL_POSSIBLE_RETURN;
}
if (cond) {
if (!expect_true) {
cond = f.IsFalse(cond);
}
f.CallIndirectTrue(cond, nia, call_flags);
} else {
f.CallIndirect(nia, call_flags);
}
}
}
return 0;
}
XEEMITTER(bx, 0x48000000, I)(PPCHIRBuilder& f, InstrData& i) {
// if AA then
// NIA <- EXTS(LI || 0b00)
// else
// NIA <- CIA + EXTS(LI || 0b00)
// if LK then
// LR <- CIA + 4
uint32_t nia;
if (i.I.AA) {
nia = (uint32_t)XEEXTS26(i.I.LI << 2);
} else {
nia = (uint32_t)(i.address + XEEXTS26(i.I.LI << 2));
}
return InstrEmit_branch(f, "bx", i.address, f.LoadConstant(nia), i.I.LK);
}
XEEMITTER(bcx, 0x40000000, B)(PPCHIRBuilder& f, InstrData& i) {
// if ¬BO[2] then
// CTR <- CTR - 1
// ctr_ok <- BO[2] | ((CTR[0:63] != 0) XOR BO[3])
// cond_ok <- BO[0] | (CR[BI+32] ≡ BO[1])
// if ctr_ok & cond_ok then
// if AA then
// NIA <- EXTS(BD || 0b00)
// else
// NIA <- CIA + EXTS(BD || 0b00)
// if LK then
// LR <- CIA + 4
// NOTE: the condition bits are reversed!
// 01234 (docs)
// 43210 (real)
Value* ctr_ok = NULL;
if (select_bits(i.B.BO, 2, 2)) {
// Ignore ctr.
} else {
// Decrement counter.
Value* ctr = f.LoadCTR();
ctr = f.Sub(ctr, f.LoadConstant((int64_t)1));
f.StoreCTR(ctr);
// Ctr check.
ctr = f.Truncate(ctr, INT32_TYPE);
// TODO(benvanik): could do something similar to cond and avoid the
// is_true/branch_true pairing.
if (select_bits(i.B.BO, 1, 1)) {
ctr_ok = f.IsFalse(ctr);
} else {
ctr_ok = f.IsTrue(ctr);
}
}
Value* cond_ok = NULL;
bool not_cond_ok = false;
if (select_bits(i.B.BO, 4, 4)) {
// Ignore cond.
} else {
Value* cr = f.LoadCRField(i.B.BI >> 2, i.B.BI & 3);
cond_ok = cr;
if (select_bits(i.B.BO, 3, 3)) {
// Expect true.
not_cond_ok = false;
} else {
// Expect false.
not_cond_ok = true;
}
}
// We do a bit of optimization here to make the llvm assembly easier to read.
Value* ok = NULL;
bool expect_true = true;
if (ctr_ok && cond_ok) {
if (not_cond_ok) {
cond_ok = f.IsFalse(cond_ok);
}
ok = f.And(ctr_ok, cond_ok);
} else if (ctr_ok) {
ok = ctr_ok;
} else if (cond_ok) {
ok = cond_ok;
expect_true = !not_cond_ok;
}
uint32_t nia;
if (i.B.AA) {
nia = (uint32_t)XEEXTS16(i.B.BD << 2);
} else {
nia = (uint32_t)(i.address + XEEXTS16(i.B.BD << 2));
}
return InstrEmit_branch(f, "bcx", i.address, f.LoadConstant(nia), i.B.LK, ok,
expect_true);
}
XEEMITTER(bcctrx, 0x4C000420, XL)(PPCHIRBuilder& f, InstrData& i) {
// cond_ok <- BO[0] | (CR[BI+32] ≡ BO[1])
// if cond_ok then
// NIA <- CTR[0:61] || 0b00
// if LK then
// LR <- CIA + 4
// NOTE: the condition bits are reversed!
// 01234 (docs)
// 43210 (real)
Value* cond_ok = NULL;
bool not_cond_ok = false;
if (select_bits(i.XL.BO, 4, 4)) {
// Ignore cond.
} else {
Value* cr = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
cond_ok = cr;
if (select_bits(i.XL.BO, 3, 3)) {
// Expect true.
not_cond_ok = false;
} else {
// Expect false.
not_cond_ok = true;
}
}
bool expect_true = !not_cond_ok;
return InstrEmit_branch(f, "bcctrx", i.address, f.LoadCTR(), i.XL.LK, cond_ok,
expect_true);
}
XEEMITTER(bclrx, 0x4C000020, XL)(PPCHIRBuilder& f, InstrData& i) {
// if ¬BO[2] then
// CTR <- CTR - 1
// ctr_ok <- BO[2] | ((CTR[0:63] != 0) XOR BO[3]
// cond_ok <- BO[0] | (CR[BI+32] ≡ BO[1])
// if ctr_ok & cond_ok then
// NIA <- LR[0:61] || 0b00
// if LK then
// LR <- CIA + 4
// NOTE: the condition bits are reversed!
// 01234 (docs)
// 43210 (real)
Value* ctr_ok = NULL;
if (select_bits(i.XL.BO, 2, 2)) {
// Ignore ctr.
} else {
// Decrement counter.
Value* ctr = f.LoadCTR();
ctr = f.Sub(ctr, f.LoadConstant((int64_t)1));
f.StoreCTR(ctr);
// Ctr check.
ctr = f.Truncate(ctr, INT32_TYPE);
// TODO(benvanik): could do something similar to cond and avoid the
// is_true/branch_true pairing.
if (select_bits(i.XL.BO, 1, 1)) {
ctr_ok = f.IsFalse(ctr);
} else {
ctr_ok = f.IsTrue(ctr);
}
}
Value* cond_ok = NULL;
bool not_cond_ok = false;
if (select_bits(i.XL.BO, 4, 4)) {
// Ignore cond.
} else {
Value* cr = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
cond_ok = cr;
if (select_bits(i.XL.BO, 3, 3)) {
// Expect true.
not_cond_ok = false;
} else {
// Expect false.
not_cond_ok = true;
}
}
// We do a bit of optimization here to make the llvm assembly easier to read.
Value* ok = NULL;
bool expect_true = true;
if (ctr_ok && cond_ok) {
if (not_cond_ok) {
cond_ok = f.IsFalse(cond_ok);
}
ok = f.And(ctr_ok, cond_ok);
} else if (ctr_ok) {
ok = ctr_ok;
} else if (cond_ok) {
ok = cond_ok;
expect_true = !not_cond_ok;
}
return InstrEmit_branch(f, "bclrx", i.address, f.LoadLR(), i.XL.LK, ok,
expect_true, true);
}
// Condition register logical (A-23)
XEEMITTER(crand, 0x4C000202, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] & CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.And(ba, bb);
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(crandc, 0x4C000102, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] & ¬CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.And(ba, f.Not(bb));
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(creqv, 0x4C000242, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] == CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.CompareEQ(ba, bb);
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(crnand, 0x4C0001C2, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- ¬(CR[ba] & CR[bb]) bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.Not(f.And(ba, bb));
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(crnor, 0x4C000042, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- ¬(CR[ba] | CR[bb]) bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.Not(f.Or(ba, bb));
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(cror, 0x4C000382, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] | CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.Or(ba, bb);
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(crorc, 0x4C000342, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] | ¬CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.Or(ba, f.Not(bb));
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(crxor, 0x4C000182, XL)(PPCHIRBuilder& f, InstrData& i) {
// CR[bt] <- CR[ba] xor CR[bb] bt=bo, ba=bi, bb=bb
Value* ba = f.LoadCRField(i.XL.BI >> 2, i.XL.BI & 3);
Value* bb = f.LoadCRField(i.XL.BB >> 2, i.XL.BB & 3);
Value* bt = f.Xor(ba, bb);
f.StoreCRField(i.XL.BO >> 2, i.XL.BO & 3, bt);
return 0;
}
XEEMITTER(mcrf, 0x4C000000, XL)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
// System linkage (A-24)
XEEMITTER(sc, 0x44000002, SC)(PPCHIRBuilder& f, InstrData& i) {
f.CallExtern(f.symbol_info());
return 0;
}
// Trap (A-25)
int InstrEmit_trap(PPCHIRBuilder& f, InstrData& i, Value* va, Value* vb,
uint32_t TO) {
// if (a < b) & TO[0] then TRAP
// if (a > b) & TO[1] then TRAP
// if (a = b) & TO[2] then TRAP
// if (a <u b) & TO[3] then TRAP
// if (a >u b) & TO[4] then TRAP
// Bits swapped:
// 01234
// 43210
if (!TO) {
return 0;
}
Value* v = nullptr;
if (TO & (1 << 4)) {
// a < b
auto cmp = f.CompareSLT(va, vb);
v = v ? f.Or(v, cmp) : cmp;
}
if (TO & (1 << 3)) {
// a > b
auto cmp = f.CompareSGT(va, vb);
v = v ? f.Or(v, cmp) : cmp;
}
if (TO & (1 << 2)) {
// a = b
auto cmp = f.CompareEQ(va, vb);
v = v ? f.Or(v, cmp) : cmp;
}
if (TO & (1 << 1)) {
// a <u b
auto cmp = f.CompareULT(va, vb);
v = v ? f.Or(v, cmp) : cmp;
}
if (TO & (1 << 0)) {
// a >u b
auto cmp = f.CompareUGT(va, vb);
v = v ? f.Or(v, cmp) : cmp;
}
if (v) {
f.TrapTrue(v);
}
return 0;
}
XEEMITTER(td, 0x7C000088, X)(PPCHIRBuilder& f, InstrData& i) {
// a <- (RA)
// b <- (RB)
// if (a < b) & TO[0] then TRAP
// if (a > b) & TO[1] then TRAP
// if (a = b) & TO[2] then TRAP
// if (a <u b) & TO[3] then TRAP
// if (a >u b) & TO[4] then TRAP
Value* ra = f.LoadGPR(i.X.RA);
Value* rb = f.LoadGPR(i.X.RB);
return InstrEmit_trap(f, i, ra, rb, i.X.RT);
}
XEEMITTER(tdi, 0x08000000, D)(PPCHIRBuilder& f, InstrData& i) {
// a <- (RA)
// if (a < EXTS(SI)) & TO[0] then TRAP
// if (a > EXTS(SI)) & TO[1] then TRAP
// if (a = EXTS(SI)) & TO[2] then TRAP
// if (a <u EXTS(SI)) & TO[3] then TRAP
// if (a >u EXTS(SI)) & TO[4] then TRAP
Value* ra = f.LoadGPR(i.D.RA);
Value* rb = f.LoadConstant(XEEXTS16(i.D.DS));
return InstrEmit_trap(f, i, ra, rb, i.D.RT);
}
XEEMITTER(tw, 0x7C000008, X)(PPCHIRBuilder& f, InstrData& i) {
// a <- EXTS((RA)[32:63])
// b <- EXTS((RB)[32:63])
// if (a < b) & TO[0] then TRAP
// if (a > b) & TO[1] then TRAP
// if (a = b) & TO[2] then TRAP
// if (a <u b) & TO[3] then TRAP
// if (a >u b) & TO[4] then TRAP
Value* ra =
f.SignExtend(f.Truncate(f.LoadGPR(i.X.RA), INT32_TYPE), INT64_TYPE);
Value* rb =
f.SignExtend(f.Truncate(f.LoadGPR(i.X.RB), INT32_TYPE), INT64_TYPE);
return InstrEmit_trap(f, i, ra, rb, i.X.RT);
}
XEEMITTER(twi, 0x0C000000, D)(PPCHIRBuilder& f, InstrData& i) {
// a <- EXTS((RA)[32:63])
// if (a < EXTS(SI)) & TO[0] then TRAP
// if (a > EXTS(SI)) & TO[1] then TRAP
// if (a = EXTS(SI)) & TO[2] then TRAP
// if (a <u EXTS(SI)) & TO[3] then TRAP
// if (a >u EXTS(SI)) & TO[4] then TRAP
if (i.D.RA == 0 && i.D.RT == 0x1F) {
// This is a special trap. Probably.
uint16_t type = (uint16_t)XEEXTS16(i.D.DS);
f.Trap(type);
return 0;
}
Value* ra =
f.SignExtend(f.Truncate(f.LoadGPR(i.D.RA), INT32_TYPE), INT64_TYPE);
Value* rb = f.LoadConstant(XEEXTS16(i.D.DS));
return InstrEmit_trap(f, i, ra, rb, i.D.RT);
}
// Processor control (A-26)
XEEMITTER(mfcr, 0x7C000026, XFX)(PPCHIRBuilder& f, InstrData& i) {
// mfocrf RT,FXM
// RT <- undefined
// count <- 0
// do i = 0 to 7
// if FXMi = 1 then
// n <- i
// count <- count + 1
// if count = 1 then
// RT4un + 32:4un + 35 <- CR4un + 32 : 4un + 35
// TODO(benvanik): optimize mfcr sequences.
// Often look something like this:
// mfocrf r11, cr6
// not r10, r11
// extrwi r3, r10, 1, 26
// Could recognize this and only load the appropriate CR bit.
Value* v;
if (i.XFX.spr & (1 << 9)) {
uint32_t bits = (i.XFX.spr & 0x1FF) >> 1;
int count = 0;
int cri = 0;
for (int b = 0; b <= 7; ++b) {
if (bits & (1 << b)) {
cri = 7 - b;
++count;
}
}
if (count == 1) {
v = f.LoadCR(cri);
} else {
v = f.LoadZero(INT64_TYPE);
}
} else {
v = f.LoadCR();
}
f.StoreGPR(i.XFX.RT, v);
return 0;
}
XEEMITTER(mfspr, 0x7C0002A6, XFX)(PPCHIRBuilder& f, InstrData& i) {
// n <- spr[5:9] || spr[0:4]
// if length(SPR(n)) = 64 then
// RT <- SPR(n)
// else
// RT <- i32.0 || SPR(n)
Value* v;
const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F);
switch (n) {
case 1:
// XER
v = f.LoadXER();
break;
case 8:
// LR
v = f.LoadLR();
break;
case 9:
// CTR
v = f.LoadCTR();
break;
case 268:
// TB
v = f.LoadClock();
break;
case 269:
// TBU
v = f.Shr(f.LoadClock(), 32);
break;
default:
XEINSTRNOTIMPLEMENTED();
return 1;
}
f.StoreGPR(i.XFX.RT, v);
return 0;
}
XEEMITTER(mftb, 0x7C0002E6, XFX)(PPCHIRBuilder& f, InstrData& i) {
Value* time = f.LoadClock();
const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F);
if (n == 268) {
// TB - full bits.
} else {
// TBU - upper bits only.
time = f.Shr(time, 32);
}
f.StoreGPR(i.XFX.RT, time);
return 0;
}
XEEMITTER(mtcrf, 0x7C000120, XFX)(PPCHIRBuilder& f, InstrData& i) {
// mtocrf FXM,RS
// count <- 0
// do i = 0 to 7
// if FXMi = 1 then
// n <- i
// count <- count + 1
// if count = 1 then
// CR4un + 32 : 4un + 35 <- RS4un + 32:4un + 35
Value* v = f.LoadGPR(i.XFX.RT);
if (i.XFX.spr & (1 << 9)) {
uint32_t bits = (i.XFX.spr & 0x1FF) >> 1;
int count = 0;
int cri = 0;
for (int b = 0; b <= 7; ++b) {
if (bits & (1 << b)) {
cri = 7 - b;
++count;
}
}
if (count == 1) {
f.StoreCR(cri, v);
} else {
// Invalid; store zero to CR.
f.StoreCR(f.LoadZero(INT64_TYPE));
}
} else {
f.StoreCR(v);
}
return 0;
}
XEEMITTER(mtspr, 0x7C0003A6, XFX)(PPCHIRBuilder& f, InstrData& i) {
// n <- spr[5:9] || spr[0:4]
// if length(SPR(n)) = 64 then
// SPR(n) <- (RS)
// else
// SPR(n) <- (RS)[32:63]
Value* rt = f.LoadGPR(i.XFX.RT);
const uint32_t n = ((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F);
switch (n) {
case 1:
// XER
f.StoreXER(rt);
break;
case 8:
// LR
f.StoreLR(rt);
break;
case 9:
// CTR
f.StoreCTR(rt);
break;
default:
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
// MSR is used for toggling interrupts (among other things).
// We track it here for taking a global processor lock, as lots of lockfree
// code requires it. Sequences of mtmsr/lwar/stcw/mtmsr come up a lot, and
// without the lock here threads can livelock.
XEEMITTER(mfmsr, 0x7C0000A6, X)(PPCHIRBuilder& f, InstrData& i) {
f.StoreGPR(i.X.RT, f.LoadMSR());
return 0;
}
XEEMITTER(mtmsr, 0x7C000124, X)(PPCHIRBuilder& f, InstrData& i) {
if (i.X.RA & 0x01) {
// L = 1
f.StoreMSR(f.ZeroExtend(f.LoadGPR(i.X.RT), INT64_TYPE));
return 0;
} else {
// L = 0
XEINSTRNOTIMPLEMENTED();
return 1;
}
}
XEEMITTER(mtmsrd, 0x7C000164, X)(PPCHIRBuilder& f, InstrData& i) {
if (i.X.RA & 0x01) {
// L = 1
f.StoreMSR(f.LoadGPR(i.X.RT));
return 0;
} else {
// L = 0
XEINSTRNOTIMPLEMENTED();
return 1;
}
}
void RegisterEmitCategoryControl() {
XEREGISTERINSTR(bx, 0x48000000);
XEREGISTERINSTR(bcx, 0x40000000);
XEREGISTERINSTR(bcctrx, 0x4C000420);
XEREGISTERINSTR(bclrx, 0x4C000020);
XEREGISTERINSTR(crand, 0x4C000202);
XEREGISTERINSTR(crandc, 0x4C000102);
XEREGISTERINSTR(creqv, 0x4C000242);
XEREGISTERINSTR(crnand, 0x4C0001C2);
XEREGISTERINSTR(crnor, 0x4C000042);
XEREGISTERINSTR(cror, 0x4C000382);
XEREGISTERINSTR(crorc, 0x4C000342);
XEREGISTERINSTR(crxor, 0x4C000182);
XEREGISTERINSTR(mcrf, 0x4C000000);
XEREGISTERINSTR(sc, 0x44000002);
XEREGISTERINSTR(td, 0x7C000088);
XEREGISTERINSTR(tdi, 0x08000000);
XEREGISTERINSTR(tw, 0x7C000008);
XEREGISTERINSTR(twi, 0x0C000000);
XEREGISTERINSTR(mfcr, 0x7C000026);
XEREGISTERINSTR(mfspr, 0x7C0002A6);
XEREGISTERINSTR(mftb, 0x7C0002E6);
XEREGISTERINSTR(mtcrf, 0x7C000120);
XEREGISTERINSTR(mtspr, 0x7C0003A6);
XEREGISTERINSTR(mfmsr, 0x7C0000A6);
XEREGISTERINSTR(mtmsr, 0x7C000124);
XEREGISTERINSTR(mtmsrd, 0x7C000164);
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,559 @@
/*
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_emit-private.h"
#include "xenia/cpu/frontend/ppc/ppc_context.h"
#include "xenia/cpu/frontend/ppc/ppc_hir_builder.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::RoundMode;
using xe::cpu::hir::Value;
// Good source of information:
// http://mamedev.org/source/src/emu/cpu/powerpc/ppc_ops.c
// The correctness of that code is not reflected here yet -_-
// Enable rounding numbers to single precision as required.
// This adds a bunch of work per operation and I'm not sure it's required.
#define ROUND_TO_SINGLE
// Floating-point arithmetic (A-8)
XEEMITTER(faddx, 0xFC00002A, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) + (frB)
Value* v = f.Add(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(faddsx, 0xEC00002A, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) + (frB)
Value* v = f.Add(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fdivx, 0xFC000024, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- frA / frB
Value* v = f.Div(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fdivsx, 0xEC000024, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- frA / frB
Value* v = f.Div(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmulx, 0xFC000032, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) x (frC)
Value* v = f.Mul(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmulsx, 0xEC000032, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) x (frC)
Value* v = f.Mul(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fresx, 0xEC000030, A)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(frsqrtex, 0xFC000034, A)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(fsubx, 0xFC000028, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) - (frB)
Value* v = f.Sub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fsubsx, 0xEC000028, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA) - (frB)
Value* v = f.Sub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRB));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fselx, 0xFC00002E, A)(PPCHIRBuilder& f, InstrData& i) {
// if (frA) >= 0.0
// then frD <- (frC)
// else frD <- (frB)
Value* ge = f.CompareSGE(f.LoadFPR(i.A.FRA), f.LoadConstant(0.0));
Value* v = f.Select(ge, f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fsqrtx, 0xFC00002C, A)(PPCHIRBuilder& f, InstrData& i) {
// Double precision:
// frD <- sqrt(frB)
Value* v = f.Sqrt(f.LoadFPR(i.A.FRA));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fsqrtsx, 0xEC00002C, A)(PPCHIRBuilder& f, InstrData& i) {
// Single precision:
// frD <- sqrt(frB)
Value* v = f.Sqrt(f.LoadFPR(i.A.FRA));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
// Floating-point multiply-add (A-9)
XEEMITTER(fmaddx, 0xFC00003A, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA x frC) + frB
Value* v =
f.MulAdd(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmaddsx, 0xEC00003A, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA x frC) + frB
Value* v =
f.MulAdd(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmsubx, 0xFC000038, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA x frC) - frB
Value* v =
f.MulSub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmsubsx, 0xEC000038, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frA x frC) - frB
Value* v =
f.MulSub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fnmaddx, 0xFC00003E, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- -([frA x frC] + frB)
Value* v = f.Neg(
f.MulAdd(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB)));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fnmaddsx, 0xEC00003E, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- -([frA x frC] + frB)
Value* v = f.Neg(
f.MulAdd(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB)));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fnmsubx, 0xFC00003C, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- -([frA x frC] - frB)
Value* v = f.Neg(
f.MulSub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB)));
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fnmsubsx, 0xEC00003C, A)(PPCHIRBuilder& f, InstrData& i) {
// frD <- -([frA x frC] - frB)
Value* v = f.Neg(
f.MulSub(f.LoadFPR(i.A.FRA), f.LoadFPR(i.A.FRC), f.LoadFPR(i.A.FRB)));
v = f.Convert(f.Convert(v, FLOAT32_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.A.FRT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
// Floating-point rounding and conversion (A-10)
XEEMITTER(fcfidx, 0xFC00069C, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- signed_int64_to_double( frB )
Value* v = f.Convert(f.Cast(f.LoadFPR(i.X.RB), INT64_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.X.RT, v);
// f.UpdateFPRF(v);
if (i.A.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fctidx, 0xFC00065C, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- double_to_signed_int64( frB )
// TODO(benvanik): pull from FPSCR[RN]
RoundMode round_mode = ROUND_TO_ZERO;
Value* v = f.Convert(f.LoadFPR(i.X.RB), INT64_TYPE, round_mode);
v = f.Cast(v, FLOAT64_TYPE);
f.StoreFPR(i.X.RT, v);
// f.UpdateFPRF(v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fctidzx, 0xFC00065E, X)(PPCHIRBuilder& f, InstrData& i) {
// TODO(benvanik): assuming round to zero is always set, is that ok?
return InstrEmit_fctidx(f, i);
}
XEEMITTER(fctiwx, 0xFC00001C, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- double_to_signed_int32( frB )
// TODO(benvanik): pull from FPSCR[RN]
RoundMode round_mode = ROUND_TO_ZERO;
Value* v = f.Convert(f.LoadFPR(i.X.RB), INT32_TYPE, round_mode);
v = f.Cast(f.ZeroExtend(v, INT64_TYPE), FLOAT64_TYPE);
f.StoreFPR(i.X.RT, v);
// f.UpdateFPRF(v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fctiwzx, 0xFC00001E, X)(PPCHIRBuilder& f, InstrData& i) {
// TODO(benvanik): assuming round to zero is always set, is that ok?
return InstrEmit_fctiwx(f, i);
}
XEEMITTER(frspx, 0xFC000018, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- Round_single(frB)
// TODO(benvanik): pull from FPSCR[RN]
RoundMode round_mode = ROUND_TO_ZERO;
Value* v = f.Convert(f.LoadFPR(i.X.RB), FLOAT32_TYPE, round_mode);
v = f.Convert(v, FLOAT64_TYPE);
f.StoreFPR(i.X.RT, v);
// f.UpdateFPRF(v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
// Floating-point compare (A-11)
int InstrEmit_fcmpx_(PPCHIRBuilder& f, InstrData& i, bool ordered) {
// if (FRA) is a NaN or (FRB) is a NaN then
// c <- 0b0001
// else if (FRA) < (FRB) then
// c <- 0b1000
// else if (FRA) > (FRB) then
// c <- 0b0100
// else {
// c <- 0b0010
// }
// FPCC <- c
// CR[4*BF:4*BF+3] <- c
// if (FRA) is an SNaN or (FRB) is an SNaN then
// VXSNAN <- 1
// TODO(benvanik): update FPCC for mffsx/etc
// TODO(benvanik): update VXSNAN
const uint32_t crf = i.X.RT >> 2;
// f.UpdateFPRF(v);
f.UpdateCR(crf, f.LoadFPR(i.X.RA), f.LoadFPR(i.X.RB), false);
return 0;
}
XEEMITTER(fcmpo, 0xFC000040, X)(PPCHIRBuilder& f, InstrData& i) {
return InstrEmit_fcmpx_(f, i, true);
}
XEEMITTER(fcmpu, 0xFC000000, X)(PPCHIRBuilder& f, InstrData& i) {
return InstrEmit_fcmpx_(f, i, false);
}
// Floating-point status and control register (A
XEEMITTER(mcrfs, 0xFC000080, X)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(mffsx, 0xFC00048E, X)(PPCHIRBuilder& f, InstrData& i) {
if (i.X.Rc) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
Value* v = f.Cast(f.LoadFPSCR(), FLOAT64_TYPE);
f.StoreFPR(i.X.RT, v);
return 0;
}
XEEMITTER(mtfsb0x, 0xFC00008C, X)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(mtfsb1x, 0xFC00004C, X)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(mtfsfx, 0xFC00058E, XFL)(PPCHIRBuilder& f, InstrData& i) {
if (i.XFL.Rc) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
if (i.XFL.L) {
// Move/shift.
XEINSTRNOTIMPLEMENTED();
return 1;
} else {
// Directly store.
// TODO(benvanik): use w/field mask to select bits.
i.XFL.W;
i.XFL.FM;
f.StoreFPSCR(f.Cast(f.LoadFPR(i.XFL.RB), INT64_TYPE));
}
return 0;
}
XEEMITTER(mtfsfix, 0xFC00010C, X)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
// Floating-point move (A-21)
XEEMITTER(fabsx, 0xFC000210, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- abs(frB)
Value* v = f.Abs(f.LoadFPR(i.X.RB));
f.StoreFPR(i.X.RT, v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fmrx, 0xFC000090, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- (frB)
Value* v = f.LoadFPR(i.X.RB);
f.StoreFPR(i.X.RT, v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
XEEMITTER(fnabsx, 0xFC000110, X)(PPCHIRBuilder& f, InstrData& i) {
XEINSTRNOTIMPLEMENTED();
return 1;
}
XEEMITTER(fnegx, 0xFC000050, X)(PPCHIRBuilder& f, InstrData& i) {
// frD <- ¬ frB[0] || frB[1-63]
Value* v = f.Neg(f.LoadFPR(i.X.RB));
f.StoreFPR(i.X.RT, v);
if (i.X.Rc) {
// e.update_cr_with_cond(1, v);
XEINSTRNOTIMPLEMENTED();
return 1;
}
return 0;
}
void RegisterEmitCategoryFPU() {
XEREGISTERINSTR(faddx, 0xFC00002A);
XEREGISTERINSTR(faddsx, 0xEC00002A);
XEREGISTERINSTR(fdivx, 0xFC000024);
XEREGISTERINSTR(fdivsx, 0xEC000024);
XEREGISTERINSTR(fmulx, 0xFC000032);
XEREGISTERINSTR(fmulsx, 0xEC000032);
XEREGISTERINSTR(fresx, 0xEC000030);
XEREGISTERINSTR(frsqrtex, 0xFC000034);
XEREGISTERINSTR(fsubx, 0xFC000028);
XEREGISTERINSTR(fsubsx, 0xEC000028);
XEREGISTERINSTR(fselx, 0xFC00002E);
XEREGISTERINSTR(fsqrtx, 0xFC00002C);
XEREGISTERINSTR(fsqrtsx, 0xEC00002C);
XEREGISTERINSTR(fmaddx, 0xFC00003A);
XEREGISTERINSTR(fmaddsx, 0xEC00003A);
XEREGISTERINSTR(fmsubx, 0xFC000038);
XEREGISTERINSTR(fmsubsx, 0xEC000038);
XEREGISTERINSTR(fnmaddx, 0xFC00003E);
XEREGISTERINSTR(fnmaddsx, 0xEC00003E);
XEREGISTERINSTR(fnmsubx, 0xFC00003C);
XEREGISTERINSTR(fnmsubsx, 0xEC00003C);
XEREGISTERINSTR(fcfidx, 0xFC00069C);
XEREGISTERINSTR(fctidx, 0xFC00065C);
XEREGISTERINSTR(fctidzx, 0xFC00065E);
XEREGISTERINSTR(fctiwx, 0xFC00001C);
XEREGISTERINSTR(fctiwzx, 0xFC00001E);
XEREGISTERINSTR(frspx, 0xFC000018);
XEREGISTERINSTR(fcmpo, 0xFC000040);
XEREGISTERINSTR(fcmpu, 0xFC000000);
XEREGISTERINSTR(mcrfs, 0xFC000080);
XEREGISTERINSTR(mffsx, 0xFC00048E);
XEREGISTERINSTR(mtfsb0x, 0xFC00008C);
XEREGISTERINSTR(mtfsb1x, 0xFC00004C);
XEREGISTERINSTR(mtfsfx, 0xFC00058E);
XEREGISTERINSTR(mtfsfix, 0xFC00010C);
XEREGISTERINSTR(fabsx, 0xFC000210);
XEREGISTERINSTR(fmrx, 0xFC000090);
XEREGISTERINSTR(fnabsx, 0xFC000110);
XEREGISTERINSTR(fnegx, 0xFC000050);
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_frontend.h"
#include "xenia/cpu/frontend/ppc/ppc_context.h"
#include "xenia/cpu/frontend/ppc/ppc_disasm.h"
#include "xenia/cpu/frontend/ppc/ppc_emit.h"
#include "xenia/cpu/frontend/ppc/ppc_translator.h"
#include "xenia/cpu/runtime/runtime.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
using xe::cpu::runtime::Function;
using xe::cpu::runtime::FunctionInfo;
using xe::cpu::runtime::Runtime;
void InitializeIfNeeded();
void CleanupOnShutdown();
void InitializeIfNeeded() {
static bool has_initialized = false;
if (has_initialized) {
return;
}
has_initialized = true;
RegisterEmitCategoryAltivec();
RegisterEmitCategoryALU();
RegisterEmitCategoryControl();
RegisterEmitCategoryFPU();
RegisterEmitCategoryMemory();
atexit(CleanupOnShutdown);
}
void CleanupOnShutdown() {}
PPCFrontend::PPCFrontend(Runtime* runtime) : Frontend(runtime) {
InitializeIfNeeded();
std::unique_ptr<ContextInfo> context_info(
new ContextInfo(sizeof(PPCContext), offsetof(PPCContext, thread_state),
offsetof(PPCContext, thread_id)));
// Add fields/etc.
context_info_ = std::move(context_info);
}
PPCFrontend::~PPCFrontend() {
// Force cleanup now before we deinit.
translator_pool_.Reset();
}
void CheckGlobalLock(PPCContext* ppc_state, void* arg0, void* arg1) {
ppc_state->scratch = 0x8000;
}
void HandleGlobalLock(PPCContext* ppc_state, void* arg0, void* arg1) {
std::mutex* global_lock = reinterpret_cast<std::mutex*>(arg0);
volatile bool* global_lock_taken = reinterpret_cast<bool*>(arg1);
uint64_t value = ppc_state->scratch;
if (value == 0x8000) {
global_lock->unlock();
*global_lock_taken = false;
} else if (value == ppc_state->r[13]) {
global_lock->lock();
*global_lock_taken = true;
}
}
int PPCFrontend::Initialize() {
int result = Frontend::Initialize();
if (result) {
return result;
}
void* arg0 = reinterpret_cast<void*>(&builtins_.global_lock);
void* arg1 = reinterpret_cast<void*>(&builtins_.global_lock_taken);
builtins_.check_global_lock = runtime_->DefineBuiltin(
"CheckGlobalLock", (FunctionInfo::ExternHandler)CheckGlobalLock, arg0,
arg1);
builtins_.handle_global_lock = runtime_->DefineBuiltin(
"HandleGlobalLock", (FunctionInfo::ExternHandler)HandleGlobalLock, arg0,
arg1);
return result;
}
int PPCFrontend::DeclareFunction(FunctionInfo* symbol_info) {
// Could scan or something here.
// Could also check to see if it's a well-known function type and classify
// for later.
// Could also kick off a precompiler, since we know it's likely the function
// will be demanded soon.
return 0;
}
int PPCFrontend::DefineFunction(FunctionInfo* symbol_info,
uint32_t debug_info_flags, uint32_t trace_flags,
Function** out_function) {
PPCTranslator* translator = translator_pool_.Allocate(this);
int result = translator->Translate(symbol_info, debug_info_flags, trace_flags,
out_function);
translator_pool_.Release(translator);
return result;
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,56 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_FRONTEND_H_
#define XENIA_FRONTEND_PPC_PPC_FRONTEND_H_
#include <mutex>
#include "xenia/cpu/frontend/frontend.h"
#include "poly/type_pool.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
class PPCTranslator;
struct PPCBuiltins {
std::mutex global_lock;
bool global_lock_taken;
runtime::FunctionInfo* check_global_lock;
runtime::FunctionInfo* handle_global_lock;
};
class PPCFrontend : public Frontend {
public:
PPCFrontend(runtime::Runtime* runtime);
~PPCFrontend() override;
int Initialize() override;
PPCBuiltins* builtins() { return &builtins_; }
int DeclareFunction(runtime::FunctionInfo* symbol_info) override;
int DefineFunction(runtime::FunctionInfo* symbol_info,
uint32_t debug_info_flags, uint32_t trace_flags,
runtime::Function** out_function) override;
private:
poly::TypePool<PPCTranslator, PPCFrontend*> translator_pool_;
PPCBuiltins builtins_;
};
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_FRONTEND_H_

View File

@@ -0,0 +1,498 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_hir_builder.h"
#include "xenia/cpu/cpu-private.h"
#include "xenia/cpu/frontend/ppc/ppc_context.h"
#include "xenia/cpu/frontend/ppc/ppc_disasm.h"
#include "xenia/cpu/frontend/ppc/ppc_frontend.h"
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
#include "xenia/cpu/hir/label.h"
#include "xenia/cpu/runtime/runtime.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::hir;
using xe::cpu::hir::Label;
using xe::cpu::hir::TypeName;
using xe::cpu::hir::Value;
using xe::cpu::runtime::Runtime;
using xe::cpu::runtime::FunctionInfo;
PPCHIRBuilder::PPCHIRBuilder(PPCFrontend* frontend)
: HIRBuilder(), frontend_(frontend), comment_buffer_(4096) {}
PPCHIRBuilder::~PPCHIRBuilder() = default;
void PPCHIRBuilder::Reset() {
start_address_ = 0;
instr_offset_list_ = NULL;
label_list_ = NULL;
with_debug_info_ = false;
HIRBuilder::Reset();
}
int PPCHIRBuilder::Emit(FunctionInfo* symbol_info, uint32_t flags) {
SCOPE_profile_cpu_f("cpu");
Memory* memory = frontend_->memory();
const uint8_t* p = memory->membase();
symbol_info_ = symbol_info;
start_address_ = symbol_info->address();
instr_count_ = (symbol_info->end_address() - symbol_info->address()) / 4 + 1;
with_debug_info_ = (flags & EMIT_DEBUG_COMMENTS) == EMIT_DEBUG_COMMENTS;
if (with_debug_info_) {
Comment("%s fn %.8X-%.8X %s", symbol_info->module()->name().c_str(),
symbol_info->address(), symbol_info->end_address(),
symbol_info->name().c_str());
}
// Allocate offset list.
// This is used to quickly map labels to instructions.
// The list is built as the instructions are traversed, with the values
// being the previous HIR Instr before the given instruction. An
// instruction may have a label assigned to it if it hasn't been hit
// yet.
size_t list_size = instr_count_ * sizeof(void*);
instr_offset_list_ = (Instr**)arena_->Alloc(list_size);
label_list_ = (Label**)arena_->Alloc(list_size);
memset(instr_offset_list_, 0, list_size);
memset(label_list_, 0, list_size);
// Always mark entry with label.
label_list_[0] = NewLabel();
uint64_t start_address = symbol_info->address();
uint64_t end_address = symbol_info->end_address();
InstrData i;
for (uint64_t address = start_address, offset = 0; address <= end_address;
address += 4, offset++) {
i.address = address;
i.code = poly::load_and_swap<uint32_t>(p + address);
// TODO(benvanik): find a way to avoid using the opcode tables.
i.type = GetInstrType(i.code);
trace_info_.dest_count = 0;
// Mark label, if we were assigned one earlier on in the walk.
// We may still get a label, but it'll be inserted by LookupLabel
// as needed.
Label* label = label_list_[offset];
if (label) {
MarkLabel(label);
}
Instr* first_instr = 0;
if (with_debug_info_) {
if (label) {
AnnotateLabel(address, label);
}
comment_buffer_.Reset();
DisasmPPC(i, &comment_buffer_);
Comment("%.8X %.8X %s", address, i.code, comment_buffer_.GetString());
first_instr = last_instr();
}
// Mark source offset for debugging.
// We could omit this if we never wanted to debug.
SourceOffset(i.address);
if (!first_instr) {
first_instr = last_instr();
}
// Stash instruction offset. It's either the SOURCE_OFFSET or the COMMENT.
instr_offset_list_[offset] = first_instr;
if (!i.type) {
PLOGE("Invalid instruction %.8llX %.8X", i.address, i.code);
Comment("INVALID!");
// TraceInvalidInstruction(i);
continue;
}
++i.type->translation_count;
typedef int (*InstrEmitter)(PPCHIRBuilder& f, InstrData& i);
InstrEmitter emit = (InstrEmitter)i.type->emit;
if (i.address == FLAGS_break_on_instruction) {
Comment("--break-on-instruction target");
DebugBreak();
}
if (!i.type->emit || emit(*this, i)) {
PLOGE("Unimplemented instr %.8llX %.8X %s", i.address, i.code,
i.type->name);
Comment("UNIMPLEMENTED!");
// DebugBreak();
// TraceInvalidInstruction(i);
}
if (flags & EMIT_TRACE_SOURCE) {
if (flags & EMIT_TRACE_SOURCE_VALUES) {
switch (trace_info_.dest_count) {
case 0:
TraceSource(i.address);
break;
case 1:
TraceSource(i.address, trace_info_.dests[0].reg,
trace_info_.dests[0].value);
break;
case 2:
TraceSource(i.address, trace_info_.dests[0].reg,
trace_info_.dests[0].value, trace_info_.dests[1].reg,
trace_info_.dests[1].value);
break;
default:
assert_unhandled_case(trace_info_.dest_count);
break;
}
} else {
TraceSource(i.address);
}
}
}
return Finalize();
}
void PPCHIRBuilder::AnnotateLabel(uint64_t address, Label* label) {
char name_buffer[13];
snprintf(name_buffer, poly::countof(name_buffer), "loc_%.8X",
(uint32_t)address);
label->name = (char*)arena_->Alloc(sizeof(name_buffer));
memcpy(label->name, name_buffer, sizeof(name_buffer));
}
FunctionInfo* PPCHIRBuilder::LookupFunction(uint64_t address) {
Runtime* runtime = frontend_->runtime();
FunctionInfo* symbol_info;
if (runtime->LookupFunctionInfo(address, &symbol_info)) {
return NULL;
}
return symbol_info;
}
Label* PPCHIRBuilder::LookupLabel(uint64_t address) {
if (address < start_address_) {
return NULL;
}
size_t offset = (address - start_address_) / 4;
if (offset >= instr_count_) {
return NULL;
}
Label* label = label_list_[offset];
if (label) {
return label;
}
// No label. If we haven't yet hit the instruction in the walk
// then create a label. Otherwise, we must go back and insert
// the label.
label = NewLabel();
label_list_[offset] = label;
Instr* instr = instr_offset_list_[offset];
if (instr) {
if (instr->prev) {
// Insert label, breaking up existing instructions.
InsertLabel(label, instr->prev);
} else {
// Instruction is at the head of a block, so just add the label.
MarkLabel(label, instr->block);
}
// Annotate the label, as we won't do it later.
if (with_debug_info_) {
AnnotateLabel(address, label);
}
}
return label;
}
// Value* PPCHIRBuilder::LoadXER() {
//}
//
// void PPCHIRBuilder::StoreXER(Value* value) {
//}
Value* PPCHIRBuilder::LoadLR() {
return LoadContext(offsetof(PPCContext, lr), INT64_TYPE);
}
void PPCHIRBuilder::StoreLR(Value* value) {
assert_true(value->type == INT64_TYPE);
StoreContext(offsetof(PPCContext, lr), value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 64;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadCTR() {
return LoadContext(offsetof(PPCContext, ctr), INT64_TYPE);
}
void PPCHIRBuilder::StoreCTR(Value* value) {
assert_true(value->type == INT64_TYPE);
StoreContext(offsetof(PPCContext, ctr), value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 65;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadCR() {
// All bits. This is expensive, but seems to be less used than the
// field-specific LoadCR.
Value* v = LoadCR(0);
for (int i = 1; i <= 7; ++i) {
v = Or(v, LoadCR(i));
}
return v;
}
Value* PPCHIRBuilder::LoadCR(uint32_t n) {
// Construct the entire word of just the bits we care about.
// This makes it easier for the optimizer to exclude things, though
// we could be even more clever and watch sequences.
Value* v = Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 0,
INT8_TYPE),
INT64_TYPE),
4 * (7 - n) + 3);
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 1,
INT8_TYPE),
INT64_TYPE),
4 * (7 - n) + 2));
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 2,
INT8_TYPE),
INT64_TYPE),
4 * (7 - n) + 1));
v = Or(v, Shl(ZeroExtend(LoadContext(offsetof(PPCContext, cr0) + (4 * n) + 3,
INT8_TYPE),
INT64_TYPE),
4 * (7 - n) + 0));
return v;
}
Value* PPCHIRBuilder::LoadCRField(uint32_t n, uint32_t bit) {
return LoadContext(offsetof(PPCContext, cr0) + (4 * n) + bit, INT8_TYPE);
}
void PPCHIRBuilder::StoreCR(Value* value) {
// All bits. This is expensive, but seems to be less used than the
// field-specific StoreCR.
for (int i = 0; i <= 7; ++i) {
StoreCR(i, value);
}
}
void PPCHIRBuilder::StoreCR(uint32_t n, Value* value) {
// Pull out the bits we are interested in.
// Optimization passes will kill any unneeded stores (mostly).
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0,
And(Truncate(Shr(value, 4 * (7 - n) + 3), INT8_TYPE),
LoadConstant(uint8_t(1))));
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1,
And(Truncate(Shr(value, 4 * (7 - n) + 2), INT8_TYPE),
LoadConstant(uint8_t(1))));
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 2,
And(Truncate(Shr(value, 4 * (7 - n) + 1), INT8_TYPE),
LoadConstant(uint8_t(1))));
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 3,
And(Truncate(Shr(value, 4 * (7 - n) + 0), INT8_TYPE),
LoadConstant(uint8_t(1))));
}
void PPCHIRBuilder::StoreCRField(uint32_t n, uint32_t bit, Value* value) {
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + bit, value);
// TODO(benvanik): trace CR.
}
void PPCHIRBuilder::UpdateCR(uint32_t n, Value* lhs, bool is_signed) {
UpdateCR(n, Truncate(lhs, INT32_TYPE), LoadZero(INT32_TYPE), is_signed);
}
void PPCHIRBuilder::UpdateCR(uint32_t n, Value* lhs, Value* rhs,
bool is_signed) {
if (is_signed) {
Value* lt = CompareSLT(lhs, rhs);
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0, lt);
Value* gt = CompareSGT(lhs, rhs);
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1, gt);
} else {
Value* lt = CompareULT(lhs, rhs);
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 0, lt);
Value* gt = CompareUGT(lhs, rhs);
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 1, gt);
}
Value* eq = CompareEQ(lhs, rhs);
StoreContext(offsetof(PPCContext, cr0) + (4 * n) + 2, eq);
// Value* so = AllocValue(UINT8_TYPE);
// StoreContext(offsetof(PPCContext, cr) + (4 * n) + 3, so);
// TOOD(benvanik): trace CR.
}
void PPCHIRBuilder::UpdateCR6(Value* src_value) {
// Testing for all 1's and all 0's.
// if (Rc) CR6 = all_equal | 0 | none_equal | 0
// TODO(benvanik): efficient instruction?
StoreContext(offsetof(PPCContext, cr6.cr6_1), LoadZero(INT8_TYPE));
StoreContext(offsetof(PPCContext, cr6.cr6_3), LoadZero(INT8_TYPE));
StoreContext(offsetof(PPCContext, cr6.cr6_all_equal),
IsFalse(Not(src_value)));
StoreContext(offsetof(PPCContext, cr6.cr6_none_equal), IsFalse(src_value));
// TOOD(benvanik): trace CR.
}
Value* PPCHIRBuilder::LoadMSR() {
// bit 48 = EE; interrupt enabled
// bit 62 = RI; recoverable interrupt
// return 8000h if unlocked, else 0
CallExtern(frontend_->builtins()->check_global_lock);
return LoadContext(offsetof(PPCContext, scratch), INT64_TYPE);
}
void PPCHIRBuilder::StoreMSR(Value* value) {
// if & 0x8000 == 0, lock, else unlock
StoreContext(offsetof(PPCContext, scratch), ZeroExtend(value, INT64_TYPE));
CallExtern(frontend_->builtins()->handle_global_lock);
}
Value* PPCHIRBuilder::LoadFPSCR() {
return LoadContext(offsetof(PPCContext, fpscr), INT64_TYPE);
}
void PPCHIRBuilder::StoreFPSCR(Value* value) {
assert_true(value->type == INT64_TYPE);
StoreContext(offsetof(PPCContext, fpscr), value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 67;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadXER() {
assert_always();
return NULL;
}
void PPCHIRBuilder::StoreXER(Value* value) { assert_always(); }
Value* PPCHIRBuilder::LoadCA() {
return LoadContext(offsetof(PPCContext, xer_ca), INT8_TYPE);
}
void PPCHIRBuilder::StoreCA(Value* value) {
assert_true(value->type == INT8_TYPE);
StoreContext(offsetof(PPCContext, xer_ca), value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 66;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadSAT() {
return LoadContext(offsetof(PPCContext, vscr_sat), INT8_TYPE);
}
void PPCHIRBuilder::StoreSAT(Value* value) {
value = Truncate(value, INT8_TYPE);
StoreContext(offsetof(PPCContext, vscr_sat), value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 44;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadGPR(uint32_t reg) {
return LoadContext(offsetof(PPCContext, r) + reg * 8, INT64_TYPE);
}
void PPCHIRBuilder::StoreGPR(uint32_t reg, Value* value) {
assert_true(value->type == INT64_TYPE);
StoreContext(offsetof(PPCContext, r) + reg * 8, value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = reg;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadFPR(uint32_t reg) {
return LoadContext(offsetof(PPCContext, f) + reg * 8, FLOAT64_TYPE);
}
void PPCHIRBuilder::StoreFPR(uint32_t reg, Value* value) {
assert_true(value->type == FLOAT64_TYPE);
StoreContext(offsetof(PPCContext, f) + reg * 8, value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = reg + 32;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadVR(uint32_t reg) {
return LoadContext(offsetof(PPCContext, v) + reg * 16, VEC128_TYPE);
}
void PPCHIRBuilder::StoreVR(uint32_t reg, Value* value) {
assert_true(value->type == VEC128_TYPE);
StoreContext(offsetof(PPCContext, v) + reg * 16, value);
auto& trace_reg = trace_info_.dests[trace_info_.dest_count++];
trace_reg.reg = 128 + reg;
trace_reg.value = value;
}
Value* PPCHIRBuilder::LoadAcquire(Value* address, TypeName type,
uint32_t load_flags) {
AtomicExchange(LoadContext(offsetof(PPCContext, reserve_address), INT64_TYPE),
Truncate(address, INT32_TYPE));
Value* value = Load(address, type, load_flags);
// Save the value so that we can compare it later in StoreRelease.
AtomicExchange(LoadContext(offsetof(PPCContext, reserve_value), INT64_TYPE),
value);
return value;
}
Value* PPCHIRBuilder::StoreRelease(Value* address, Value* value,
uint32_t store_flags) {
Value* old_address = AtomicExchange(
LoadContext(offsetof(PPCContext, reserve_address), INT64_TYPE),
LoadZero(INT32_TYPE));
// HACK: ensure the reservation addresses match AND the value hasn't changed.
Value* old_value = AtomicExchange(
LoadContext(offsetof(PPCContext, reserve_value), INT64_TYPE),
LoadZero(value->type));
Value* current_value = Load(address, value->type);
Value* eq = And(CompareEQ(Truncate(address, INT32_TYPE), old_address),
CompareEQ(current_value, old_value));
StoreContext(offsetof(PPCContext, cr0.cr0_eq), eq);
StoreContext(offsetof(PPCContext, cr0.cr0_lt), LoadZero(INT8_TYPE));
StoreContext(offsetof(PPCContext, cr0.cr0_gt), LoadZero(INT8_TYPE));
auto skip_label = NewLabel();
BranchFalse(eq, skip_label, BRANCH_UNLIKELY);
Store(address, value, store_flags);
MarkLabel(skip_label);
return eq;
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,120 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_HIR_BUILDER_H_
#define XENIA_FRONTEND_PPC_PPC_HIR_BUILDER_H_
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/runtime/function.h"
#include "xenia/cpu/runtime/symbol_info.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
class PPCFrontend;
class PPCHIRBuilder : public hir::HIRBuilder {
using Instr = xe::cpu::hir::Instr;
using Label = xe::cpu::hir::Label;
using Value = xe::cpu::hir::Value;
public:
PPCHIRBuilder(PPCFrontend* frontend);
virtual ~PPCHIRBuilder();
virtual void Reset();
enum EmitFlags {
// Emit comment nodes.
EMIT_DEBUG_COMMENTS = 1 << 0,
// Emit TraceSource nodes.
EMIT_TRACE_SOURCE = 1 << 1,
// Emit TraceSource nodes with the resulting values of the operations.
EMIT_TRACE_SOURCE_VALUES = EMIT_TRACE_SOURCE | (1 << 2),
};
int Emit(runtime::FunctionInfo* symbol_info, uint32_t flags);
runtime::FunctionInfo* symbol_info() const { return symbol_info_; }
runtime::FunctionInfo* LookupFunction(uint64_t address);
Label* LookupLabel(uint64_t address);
Value* LoadLR();
void StoreLR(Value* value);
Value* LoadCTR();
void StoreCTR(Value* value);
Value* LoadCR();
Value* LoadCR(uint32_t n);
Value* LoadCRField(uint32_t n, uint32_t bit);
void StoreCR(Value* value);
void StoreCR(uint32_t n, Value* value);
void StoreCRField(uint32_t n, uint32_t bit, Value* value);
void UpdateCR(uint32_t n, Value* lhs, bool is_signed = true);
void UpdateCR(uint32_t n, Value* lhs, Value* rhs, bool is_signed = true);
void UpdateCR6(Value* src_value);
Value* LoadMSR();
void StoreMSR(Value* value);
Value* LoadFPSCR();
void StoreFPSCR(Value* value);
Value* LoadXER();
void StoreXER(Value* value);
// void UpdateXERWithOverflow();
// void UpdateXERWithOverflowAndCarry();
// void StoreOV(Value* value);
Value* LoadCA();
void StoreCA(Value* value);
Value* LoadSAT();
void StoreSAT(Value* value);
Value* LoadGPR(uint32_t reg);
void StoreGPR(uint32_t reg, Value* value);
Value* LoadFPR(uint32_t reg);
void StoreFPR(uint32_t reg, Value* value);
Value* LoadVR(uint32_t reg);
void StoreVR(uint32_t reg, Value* value);
Value* LoadAcquire(Value* address, hir::TypeName type,
uint32_t load_flags = 0);
Value* StoreRelease(Value* address, Value* value, uint32_t store_flags = 0);
private:
void AnnotateLabel(uint64_t address, Label* label);
private:
PPCFrontend* frontend_;
// Reset whenever needed:
poly::StringBuffer comment_buffer_;
// Reset each Emit:
bool with_debug_info_;
runtime::FunctionInfo* symbol_info_;
uint64_t start_address_;
uint64_t instr_count_;
Instr** instr_offset_list_;
Label** label_list_;
// Reset each instruction.
struct {
uint32_t dest_count;
struct {
uint8_t reg;
Value* value;
} dests[4];
} trace_info_;
};
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_HIR_BUILDER_H_

View File

@@ -0,0 +1,408 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
#include <sstream>
#include <vector>
#include "xenia/cpu/frontend/ppc/ppc_instr_tables.h"
#include "poly/poly.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
std::vector<InstrType*> all_instrs_;
void DumpAllInstrCounts() {
poly::StringBuffer sb;
sb.Append("Instruction translation counts:\n");
for (auto instr_type : all_instrs_) {
if (instr_type->translation_count) {
sb.Append("%8d : %s\n", instr_type->translation_count, instr_type->name);
}
}
fprintf(stdout, sb.GetString());
fflush(stdout);
}
void InstrOperand::Dump(std::string& out_str) {
if (display) {
out_str += display;
return;
}
char buffer[32];
const size_t max_count = poly::countof(buffer);
switch (type) {
case InstrOperand::kRegister:
switch (reg.set) {
case InstrRegister::kXER:
snprintf(buffer, max_count, "XER");
break;
case InstrRegister::kLR:
snprintf(buffer, max_count, "LR");
break;
case InstrRegister::kCTR:
snprintf(buffer, max_count, "CTR");
break;
case InstrRegister::kCR:
snprintf(buffer, max_count, "CR%d", reg.ordinal);
break;
case InstrRegister::kFPSCR:
snprintf(buffer, max_count, "FPSCR");
break;
case InstrRegister::kGPR:
snprintf(buffer, max_count, "r%d", reg.ordinal);
break;
case InstrRegister::kFPR:
snprintf(buffer, max_count, "f%d", reg.ordinal);
break;
case InstrRegister::kVMX:
snprintf(buffer, max_count, "vr%d", reg.ordinal);
break;
}
break;
case InstrOperand::kImmediate:
switch (imm.width) {
case 1:
if (imm.is_signed) {
snprintf(buffer, max_count, "%d", (int32_t)(int8_t) imm.value);
} else {
snprintf(buffer, max_count, "0x%.2X", (uint8_t)imm.value);
}
break;
case 2:
if (imm.is_signed) {
snprintf(buffer, max_count, "%d", (int32_t)(int16_t) imm.value);
} else {
snprintf(buffer, max_count, "0x%.4X", (uint16_t)imm.value);
}
break;
case 4:
if (imm.is_signed) {
snprintf(buffer, max_count, "%d", (int32_t)imm.value);
} else {
snprintf(buffer, max_count, "0x%.8X", (uint32_t)imm.value);
}
break;
case 8:
if (imm.is_signed) {
snprintf(buffer, max_count, "%lld", (int64_t)imm.value);
} else {
snprintf(buffer, max_count, "0x%.16llX", imm.value);
}
break;
}
break;
}
out_str += buffer;
}
void InstrAccessBits::Clear() { spr = cr = gpr = fpr = 0; }
void InstrAccessBits::Extend(InstrAccessBits& other) {
spr |= other.spr;
cr |= other.cr;
gpr |= other.gpr;
fpr |= other.fpr;
vr31_0 |= other.vr31_0;
vr63_32 |= other.vr63_32;
vr95_64 |= other.vr95_64;
vr127_96 |= other.vr127_96;
}
void InstrAccessBits::MarkAccess(InstrRegister& reg) {
uint64_t bits = 0;
if (reg.access & InstrRegister::kRead) {
bits |= 0x1;
}
if (reg.access & InstrRegister::kWrite) {
bits |= 0x2;
}
switch (reg.set) {
case InstrRegister::kXER:
spr |= bits << (2 * 0);
break;
case InstrRegister::kLR:
spr |= bits << (2 * 1);
break;
case InstrRegister::kCTR:
spr |= bits << (2 * 2);
break;
case InstrRegister::kCR:
cr |= bits << (2 * reg.ordinal);
break;
case InstrRegister::kFPSCR:
spr |= bits << (2 * 3);
break;
case InstrRegister::kGPR:
gpr |= bits << (2 * reg.ordinal);
break;
case InstrRegister::kFPR:
fpr |= bits << (2 * reg.ordinal);
break;
case InstrRegister::kVMX:
if (reg.ordinal < 32) {
vr31_0 |= bits << (2 * reg.ordinal);
} else if (reg.ordinal < 64) {
vr63_32 |= bits << (2 * (reg.ordinal - 32));
} else if (reg.ordinal < 96) {
vr95_64 |= bits << (2 * (reg.ordinal - 64));
} else {
vr127_96 |= bits << (2 * (reg.ordinal - 96));
}
break;
default:
assert_unhandled_case(reg.set);
break;
}
}
void InstrAccessBits::Dump(std::string& out_str) {
std::stringstream str;
if (spr) {
uint64_t spr_t = spr;
if (spr_t & 0x3) {
str << "XER [";
str << ((spr_t & 1) ? "R" : " ");
str << ((spr_t & 2) ? "W" : " ");
str << "] ";
}
spr_t >>= 2;
if (spr_t & 0x3) {
str << "LR [";
str << ((spr_t & 1) ? "R" : " ");
str << ((spr_t & 2) ? "W" : " ");
str << "] ";
}
spr_t >>= 2;
if (spr_t & 0x3) {
str << "CTR [";
str << ((spr_t & 1) ? "R" : " ");
str << ((spr_t & 2) ? "W" : " ");
str << "] ";
}
spr_t >>= 2;
if (spr_t & 0x3) {
str << "FPCSR [";
str << ((spr_t & 1) ? "R" : " ");
str << ((spr_t & 2) ? "W" : " ");
str << "] ";
}
spr_t >>= 2;
}
if (cr) {
uint64_t cr_t = cr;
for (size_t n = 0; n < 8; n++) {
if (cr_t & 0x3) {
str << "cr" << n << " [";
str << ((cr_t & 1) ? "R" : " ");
str << ((cr_t & 2) ? "W" : " ");
str << "] ";
}
cr_t >>= 2;
}
}
if (gpr) {
uint64_t gpr_t = gpr;
for (size_t n = 0; n < 32; n++) {
if (gpr_t & 0x3) {
str << "r" << n << " [";
str << ((gpr_t & 1) ? "R" : " ");
str << ((gpr_t & 2) ? "W" : " ");
str << "] ";
}
gpr_t >>= 2;
}
}
if (fpr) {
uint64_t fpr_t = fpr;
for (size_t n = 0; n < 32; n++) {
if (fpr_t & 0x3) {
str << "f" << n << " [";
str << ((fpr_t & 1) ? "R" : " ");
str << ((fpr_t & 2) ? "W" : " ");
str << "] ";
}
fpr_t >>= 2;
}
}
if (vr31_0) {
uint64_t vr31_0_t = vr31_0;
for (size_t n = 0; n < 32; n++) {
if (vr31_0_t & 0x3) {
str << "vr" << n << " [";
str << ((vr31_0_t & 1) ? "R" : " ");
str << ((vr31_0_t & 2) ? "W" : " ");
str << "] ";
}
vr31_0_t >>= 2;
}
}
if (vr63_32) {
uint64_t vr63_32_t = vr63_32;
for (size_t n = 0; n < 32; n++) {
if (vr63_32_t & 0x3) {
str << "vr" << (n + 32) << " [";
str << ((vr63_32_t & 1) ? "R" : " ");
str << ((vr63_32_t & 2) ? "W" : " ");
str << "] ";
}
vr63_32_t >>= 2;
}
}
if (vr95_64) {
uint64_t vr95_64_t = vr95_64;
for (size_t n = 0; n < 32; n++) {
if (vr95_64_t & 0x3) {
str << "vr" << (n + 64) << " [";
str << ((vr95_64_t & 1) ? "R" : " ");
str << ((vr95_64_t & 2) ? "W" : " ");
str << "] ";
}
vr95_64_t >>= 2;
}
}
if (vr127_96) {
uint64_t vr127_96_t = vr127_96;
for (size_t n = 0; n < 32; n++) {
if (vr127_96_t & 0x3) {
str << "vr" << (n + 96) << " [";
str << ((vr127_96_t & 1) ? "R" : " ");
str << ((vr127_96_t & 2) ? "W" : " ");
str << "] ";
}
vr127_96_t >>= 2;
}
}
out_str = str.str();
}
void InstrDisasm::Init(const char* name, const char* info, uint32_t flags) {
this->name = name;
this->info = info;
this->flags = flags;
}
void InstrDisasm::AddLR(InstrRegister::Access access) {}
void InstrDisasm::AddCTR(InstrRegister::Access access) {}
void InstrDisasm::AddCR(uint32_t bf, InstrRegister::Access access) {}
void InstrDisasm::AddFPSCR(InstrRegister::Access access) {}
void InstrDisasm::AddRegOperand(InstrRegister::RegisterSet set,
uint32_t ordinal, InstrRegister::Access access,
const char* display) {}
void InstrDisasm::AddSImmOperand(uint64_t value, size_t width,
const char* display) {}
void InstrDisasm::AddUImmOperand(uint64_t value, size_t width,
const char* display) {}
int InstrDisasm::Finish() { return 0; }
void InstrDisasm::Dump(std::string& out_str, size_t pad) {
out_str = name;
if (flags & InstrDisasm::kOE) {
out_str += "o";
}
if (flags & InstrDisasm::kRc) {
out_str += ".";
}
if (flags & InstrDisasm::kLR) {
out_str += "l";
}
}
InstrType* GetInstrType(uint32_t code) {
// Fast lookup via tables.
InstrType* slot = NULL;
switch (code >> 26) {
case 4:
// Opcode = 4, index = bits 10-0 (10)
slot = tables::instr_table_4[select_bits(code, 0, 10)];
break;
case 19:
// Opcode = 19, index = bits 10-1 (10)
slot = tables::instr_table_19[select_bits(code, 1, 10)];
break;
case 30:
// Opcode = 30, index = bits 4-1 (4)
// Special cased to an uber instruction.
slot = tables::instr_table_30[select_bits(code, 0, 0)];
break;
case 31:
// Opcode = 31, index = bits 10-1 (10)
slot = tables::instr_table_31[select_bits(code, 1, 10)];
break;
case 58:
// Opcode = 58, index = bits 1-0 (2)
slot = tables::instr_table_58[select_bits(code, 0, 1)];
break;
case 59:
// Opcode = 59, index = bits 5-1 (5)
slot = tables::instr_table_59[select_bits(code, 1, 5)];
break;
case 62:
// Opcode = 62, index = bits 1-0 (2)
slot = tables::instr_table_62[select_bits(code, 0, 1)];
break;
case 63:
// Opcode = 63, index = bits 10-1 (10)
slot = tables::instr_table_63[select_bits(code, 1, 10)];
break;
default:
slot = tables::instr_table[select_bits(code, 26, 31)];
break;
}
if (slot && slot->opcode) {
return slot;
}
// Slow lookup via linear scan.
// This is primarily due to laziness. It could be made fast like the others.
for (size_t n = 0; n < poly::countof(tables::instr_table_scan); n++) {
slot = &(tables::instr_table_scan[n]);
if (slot->opcode == (code & slot->opcode_mask)) {
return slot;
}
}
return NULL;
}
int RegisterInstrEmit(uint32_t code, InstrEmitFn emit) {
InstrType* instr_type = GetInstrType(code);
assert_not_null(instr_type);
if (!instr_type) {
return 1;
}
all_instrs_.push_back(instr_type);
assert_null(instr_type->emit);
instr_type->emit = emit;
return 0;
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,577 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_INSTR_H_
#define XENIA_FRONTEND_PPC_PPC_INSTR_H_
#include <cstdint>
#include <string>
#include <vector>
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
inline uint32_t make_bitmask(uint32_t a, uint32_t b) {
return (static_cast<uint32_t>(-1) >> (31 - b)) & ~((1u << a) - 1);
}
inline uint32_t select_bits(uint32_t value, uint32_t a, uint32_t b) {
return (value & make_bitmask(a, b)) >> a;
}
// TODO(benvanik): rename these
typedef enum {
kXEPPCInstrFormatI = 0,
kXEPPCInstrFormatB = 1,
kXEPPCInstrFormatSC = 2,
kXEPPCInstrFormatD = 3,
kXEPPCInstrFormatDS = 4,
kXEPPCInstrFormatX = 5,
kXEPPCInstrFormatXL = 6,
kXEPPCInstrFormatXFX = 7,
kXEPPCInstrFormatXFL = 8,
kXEPPCInstrFormatXS = 9,
kXEPPCInstrFormatXO = 10,
kXEPPCInstrFormatA = 11,
kXEPPCInstrFormatM = 12,
kXEPPCInstrFormatMD = 13,
kXEPPCInstrFormatMDS = 14,
kXEPPCInstrFormatVXA = 15,
kXEPPCInstrFormatVX = 16,
kXEPPCInstrFormatVXR = 17,
kXEPPCInstrFormatVX128 = 18,
kXEPPCInstrFormatVX128_1 = 19,
kXEPPCInstrFormatVX128_2 = 20,
kXEPPCInstrFormatVX128_3 = 21,
kXEPPCInstrFormatVX128_4 = 22,
kXEPPCInstrFormatVX128_5 = 23,
kXEPPCInstrFormatVX128_P = 24,
kXEPPCInstrFormatVX128_R = 25,
kXEPPCInstrFormatXDSS = 26,
} xe_ppc_instr_format_e;
enum xe_ppc_instr_mask_e : uint32_t {
kXEPPCInstrMaskVXR = 0xFC0003FF,
kXEPPCInstrMaskVXA = 0xFC00003F,
kXEPPCInstrMaskVX128 = 0xFC0003D0,
kXEPPCInstrMaskVX128_1 = 0xFC0007F3,
kXEPPCInstrMaskVX128_2 = 0xFC000210,
kXEPPCInstrMaskVX128_3 = 0xFC0007F0,
kXEPPCInstrMaskVX128_4 = 0xFC000730,
kXEPPCInstrMaskVX128_5 = 0xFC000010,
kXEPPCInstrMaskVX128_P = 0xFC000630,
kXEPPCInstrMaskVX128_R = 0xFC000390,
};
typedef enum {
kXEPPCInstrTypeGeneral = (1 << 0),
kXEPPCInstrTypeBranch = (1 << 1),
kXEPPCInstrTypeBranchCond = kXEPPCInstrTypeBranch | (1 << 2),
kXEPPCInstrTypeBranchAlways = kXEPPCInstrTypeBranch | (1 << 3),
kXEPPCInstrTypeSyscall = (1 << 4),
} xe_ppc_instr_type_e;
typedef enum {
kXEPPCInstrFlagReserved = 0,
} xe_ppc_instr_flag_e;
class InstrType;
static inline int64_t XEEXTS16(uint32_t v) { return (int64_t)((int16_t)v); }
static inline int64_t XEEXTS26(uint32_t v) {
return (int64_t)(v & 0x02000000 ? (int32_t)v | 0xFC000000 : (int32_t)(v));
}
static inline uint64_t XEEXTZ16(uint32_t v) { return (uint64_t)((uint16_t)v); }
static inline uint64_t XEMASK(uint32_t mstart, uint32_t mstop) {
// if mstart ≤ mstop then
// mask[mstart:mstop] = ones
// mask[all other bits] = zeros
// else
// mask[mstart:63] = ones
// mask[0:mstop] = ones
// mask[all other bits] = zeros
mstart &= 0x3F;
mstop &= 0x3F;
uint64_t value =
(UINT64_MAX >> mstart) ^ ((mstop >= 63) ? 0 : UINT64_MAX >> (mstop + 1));
return mstart <= mstop ? value : ~value;
}
typedef struct {
InstrType* type;
uint64_t address;
union {
uint32_t code;
// kXEPPCInstrFormatI
struct {
uint32_t LK : 1;
uint32_t AA : 1;
uint32_t LI : 24;
uint32_t:
6;
} I;
// kXEPPCInstrFormatB
struct {
uint32_t LK : 1;
uint32_t AA : 1;
uint32_t BD : 14;
uint32_t BI : 5;
uint32_t BO : 5;
uint32_t:
6;
} B;
// kXEPPCInstrFormatSC
// kXEPPCInstrFormatD
struct {
uint32_t DS : 16;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} D;
// kXEPPCInstrFormatDS
struct {
uint32_t:
2;
uint32_t DS : 14;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} DS;
// kXEPPCInstrFormatX
struct {
uint32_t Rc : 1;
uint32_t:
10;
uint32_t RB : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} X;
// kXEPPCInstrFormatXL
struct {
uint32_t LK : 1;
uint32_t:
10;
uint32_t BB : 5;
uint32_t BI : 5;
uint32_t BO : 5;
uint32_t:
6;
} XL;
// kXEPPCInstrFormatXFX
struct {
uint32_t:
1;
uint32_t:
10;
uint32_t spr : 10;
uint32_t RT : 5;
uint32_t:
6;
} XFX;
// kXEPPCInstrFormatXFL
struct {
uint32_t Rc : 1;
uint32_t:
10;
uint32_t RB : 5;
uint32_t W : 1;
uint32_t FM : 8;
uint32_t L : 1;
uint32_t:
6;
} XFL;
// kXEPPCInstrFormatXS
struct {
uint32_t Rc : 1;
uint32_t SH5 : 1;
uint32_t:
9;
uint32_t SH : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} XS;
// kXEPPCInstrFormatXO
struct {
uint32_t Rc : 1;
uint32_t:
9;
uint32_t OE : 1;
uint32_t RB : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} XO;
// kXEPPCInstrFormatA
struct {
uint32_t Rc : 1;
uint32_t XO : 5;
uint32_t FRC : 5;
uint32_t FRB : 5;
uint32_t FRA : 5;
uint32_t FRT : 5;
uint32_t:
6;
} A;
// kXEPPCInstrFormatM
struct {
uint32_t Rc : 1;
uint32_t ME : 5;
uint32_t MB : 5;
uint32_t SH : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} M;
// kXEPPCInstrFormatMD
struct {
uint32_t Rc : 1;
uint32_t SH5 : 1;
uint32_t idx : 3;
uint32_t MB5 : 1;
uint32_t MB : 5;
uint32_t SH : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} MD;
// kXEPPCInstrFormatMDS
struct {
uint32_t Rc : 1;
uint32_t idx : 4;
uint32_t MB5 : 1;
uint32_t MB : 5;
uint32_t RB : 5;
uint32_t RA : 5;
uint32_t RT : 5;
uint32_t:
6;
} MDS;
// kXEPPCInstrFormatVXA
struct {
uint32_t:
6;
uint32_t VC : 5;
uint32_t VB : 5;
uint32_t VA : 5;
uint32_t VD : 5;
uint32_t:
6;
} VXA;
// kXEPPCInstrFormatVX
struct {
uint32_t:
11;
uint32_t VB : 5;
uint32_t VA : 5;
uint32_t VD : 5;
uint32_t:
6;
} VX;
// kXEPPCInstrFormatVXR
struct {
uint32_t:
10;
uint32_t Rc : 1;
uint32_t VB : 5;
uint32_t VA : 5;
uint32_t VD : 5;
uint32_t:
6;
} VXR;
// kXEPPCInstrFormatVX128
struct {
// VD128 = VD128l | (VD128h << 5)
// VA128 = VA128l | (VA128h << 5) | (VA128H << 6)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
1;
uint32_t VA128h : 1;
uint32_t:
4;
uint32_t VA128H : 1;
uint32_t VB128l : 5;
uint32_t VA128l : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128;
// kXEPPCInstrFormatVX128_1
struct {
// VD128 = VD128l | (VD128h << 5)
uint32_t:
2;
uint32_t VD128h : 2;
uint32_t:
7;
uint32_t RB : 5;
uint32_t RA : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_1;
// kXEPPCInstrFormatVX128_2
struct {
// VD128 = VD128l | (VD128h << 5)
// VA128 = VA128l | (VA128h << 5) | (VA128H << 6)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
1;
uint32_t VA128h : 1;
uint32_t VC : 3;
uint32_t:
1;
uint32_t VA128H : 1;
uint32_t VB128l : 5;
uint32_t VA128l : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_2;
// kXEPPCInstrFormatVX128_3
struct {
// VD128 = VD128l | (VD128h << 5)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
7;
uint32_t VB128l : 5;
uint32_t IMM : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_3;
// kXEPPCInstrFormatVX128_4
struct {
// VD128 = VD128l | (VD128h << 5)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
2;
uint32_t z : 2;
uint32_t:
3;
uint32_t VB128l : 5;
uint32_t IMM : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_4;
// kXEPPCInstrFormatVX128_5
struct {
// VD128 = VD128l | (VD128h << 5)
// VA128 = VA128l | (VA128h << 5) | (VA128H << 6)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
1;
uint32_t VA128h : 1;
uint32_t SH : 4;
uint32_t VA128H : 1;
uint32_t VB128l : 5;
uint32_t VA128l : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_5;
// kXEPPCInstrFormatVX128_P
struct {
// VD128 = VD128l | (VD128h << 5)
// VB128 = VB128l | (VB128h << 5)
// PERM = PERMl | (PERMh << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
2;
uint32_t PERMh : 3;
uint32_t:
2;
uint32_t VB128l : 5;
uint32_t PERMl : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_P;
// kXEPPCInstrFormatVX128_R
struct {
// VD128 = VD128l | (VD128h << 5)
// VA128 = VA128l | (VA128h << 5) | (VA128H << 6)
// VB128 = VB128l | (VB128h << 5)
uint32_t VB128h : 2;
uint32_t VD128h : 2;
uint32_t:
1;
uint32_t VA128h : 1;
uint32_t Rc : 1;
uint32_t:
3;
uint32_t VA128H : 1;
uint32_t VB128l : 5;
uint32_t VA128l : 5;
uint32_t VD128l : 5;
uint32_t:
6;
} VX128_R;
// kXEPPCInstrFormatXDSS
struct {
} XDSS;
};
} InstrData;
typedef struct {
enum RegisterSet {
kXER,
kLR,
kCTR,
kCR, // 0-7
kFPSCR,
kGPR, // 0-31
kFPR, // 0-31
kVMX, // 0-127
};
enum Access {
kRead = 1 << 0,
kWrite = 1 << 1,
kReadWrite = kRead | kWrite,
};
RegisterSet set;
uint32_t ordinal;
Access access;
} InstrRegister;
typedef struct {
enum OperandType {
kRegister,
kImmediate,
};
OperandType type;
const char* display;
union {
InstrRegister reg;
struct {
bool is_signed;
uint64_t value;
size_t width;
} imm;
};
void Dump(std::string& out_str);
} InstrOperand;
class InstrAccessBits {
public:
InstrAccessBits()
: spr(0),
cr(0),
gpr(0),
fpr(0),
vr31_0(0),
vr63_32(0),
vr95_64(0),
vr127_96(0) {}
// Bitmasks derived from the accesses to registers.
// Format is 2 bits for each register, even bits indicating reads and odds
// indicating writes.
uint64_t spr; // fpcsr/ctr/lr/xer
uint64_t cr; // cr7/6/5/4/3/2/1/0
uint64_t gpr; // r31-0
uint64_t fpr; // f31-0
uint64_t vr31_0;
uint64_t vr63_32;
uint64_t vr95_64;
uint64_t vr127_96;
void Clear();
void Extend(InstrAccessBits& other);
void MarkAccess(InstrRegister& reg);
void Dump(std::string& out_str);
};
class InstrDisasm {
public:
enum Flags {
kOE = 1 << 0,
kRc = 1 << 1,
kCA = 1 << 2,
kLR = 1 << 4,
kFP = 1 << 5,
kVMX = 1 << 6,
};
const char* name;
const char* info;
uint32_t flags;
void Init(const char* name, const char* info, uint32_t flags);
void AddLR(InstrRegister::Access access);
void AddCTR(InstrRegister::Access access);
void AddCR(uint32_t bf, InstrRegister::Access access);
void AddFPSCR(InstrRegister::Access access);
void AddRegOperand(InstrRegister::RegisterSet set, uint32_t ordinal,
InstrRegister::Access access, const char* display = NULL);
void AddSImmOperand(uint64_t value, size_t width, const char* display = NULL);
void AddUImmOperand(uint64_t value, size_t width, const char* display = NULL);
int Finish();
void Dump(std::string& out_str, size_t pad = 13);
};
typedef void (*InstrDisasmFn)(InstrData& i, poly::StringBuffer* str);
typedef void* InstrEmitFn;
class InstrType {
public:
uint32_t opcode;
uint32_t opcode_mask; // Only used for certain opcodes (altivec, etc).
uint32_t format; // xe_ppc_instr_format_e
uint32_t type; // xe_ppc_instr_type_e
uint32_t flags; // xe_ppc_instr_flag_e
InstrDisasmFn disasm;
char name[16];
uint32_t translation_count;
InstrEmitFn emit;
};
void DumpAllInstrCounts();
InstrType* GetInstrType(uint32_t code);
int RegisterInstrEmit(uint32_t code, InstrEmitFn emit);
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_INSTR_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,365 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_scanner.h"
#include <algorithm>
#include <map>
#include "xenia/cpu/frontend/ppc/ppc_frontend.h"
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
#include "xenia/cpu/runtime/runtime.h"
#include "poly/logging.h"
#include "poly/memory.h"
#include "xenia/profiling.h"
#if 0
#define LOGPPC(fmt, ...) PLOGCORE('p', fmt, ##__VA_ARGS__)
#else
#define LOGPPC(fmt, ...) POLY_EMPTY_MACRO
#endif
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
using xe::cpu::runtime::FunctionInfo;
PPCScanner::PPCScanner(PPCFrontend* frontend) : frontend_(frontend) {}
PPCScanner::~PPCScanner() {}
bool PPCScanner::IsRestGprLr(uint64_t address) {
FunctionInfo* symbol_info;
if (frontend_->runtime()->LookupFunctionInfo(address, &symbol_info)) {
return false;
}
return symbol_info->behavior() == FunctionInfo::BEHAVIOR_EPILOG_RETURN;
}
int PPCScanner::FindExtents(FunctionInfo* symbol_info) {
// This is a simple basic block analyizer. It walks the start address to the
// end address looking for branches. Each span of instructions between
// branches is considered a basic block. When the last blr (that has no
// branches to after it) is found the function is considered ended. If this
// is before the expected end address then the function address range is
// split up and the second half is treated as another function.
Memory* memory = frontend_->memory();
const uint8_t* p = memory->membase();
LOGPPC("Analyzing function %.8X...", symbol_info->address());
uint32_t start_address = static_cast<uint32_t>(symbol_info->address());
uint32_t end_address = static_cast<uint32_t>(symbol_info->end_address());
uint32_t address = start_address;
uint32_t furthest_target = start_address;
size_t blocks_found = 0;
bool in_block = false;
bool starts_with_mfspr_lr = false;
InstrData i;
while (true) {
i.address = address;
i.code = poly::load_and_swap<uint32_t>(p + address);
// If we fetched 0 assume that we somehow hit one of the awesome
// 'no really we meant to end after that bl' functions.
if (!i.code) {
LOGPPC("function end %.8X (0x00000000 read)", address);
// Don't include the 0's.
address -= 4;
break;
}
// TODO(benvanik): find a way to avoid using the opcode tables.
// This lookup is *expensive* and should be avoided when scanning.
i.type = GetInstrType(i.code);
// Check if the function starts with a mfspr lr, as that's a good indication
// of whether or not this is a normal function with a prolog/epilog.
// Some valid leaf functions won't have this, but most will.
if (address == start_address && i.type && i.type->opcode == 0x7C0002A6 &&
(((i.XFX.spr & 0x1F) << 5) | ((i.XFX.spr >> 5) & 0x1F)) == 8) {
starts_with_mfspr_lr = true;
}
if (!in_block) {
in_block = true;
blocks_found++;
}
bool ends_fn = false;
bool ends_block = false;
if (!i.type) {
// Invalid instruction.
// We can just ignore it because there's (very little)/no chance it'll
// affect flow control.
LOGPPC("Invalid instruction at %.8X: %.8X", address, i.code);
} else if (i.code == 0x4E800020) {
// blr -- unconditional branch to LR.
// This is generally a return.
if (furthest_target > address) {
// Remaining targets within function, not end.
LOGPPC("ignoring blr %.8X (branch to %.8X)", address, furthest_target);
} else {
// Function end point.
LOGPPC("function end %.8X", address);
ends_fn = true;
}
ends_block = true;
} else if (i.code == 0x4E800420) {
// bctr -- unconditional branch to CTR.
// This is generally a jump to a function pointer (non-return).
// This is almost always a jump table.
// TODO(benvanik): decode jump tables.
if (furthest_target > address) {
// Remaining targets within function, not end.
LOGPPC("ignoring bctr %.8X (branch to %.8X)", address, furthest_target);
} else {
// Function end point.
LOGPPC("function end %.8X", address);
ends_fn = true;
}
ends_block = true;
} else if (i.type->opcode == 0x48000000) {
// b/ba/bl/bla
uint32_t target =
(uint32_t)XEEXTS26(i.I.LI << 2) + (i.I.AA ? 0 : (int32_t)address);
if (i.I.LK) {
LOGPPC("bl %.8X -> %.8X", address, target);
// Queue call target if needed.
// GetOrInsertFunction(target);
} else {
LOGPPC("b %.8X -> %.8X", address, target);
// If the target is back into the function and there's no further target
// we are at the end of a function.
// (Indirect branches may still go beyond, but no way of knowing).
if (target >= start_address && target < address &&
furthest_target <= address) {
LOGPPC("function end %.8X (back b)", address);
ends_fn = true;
}
// If the target is not a branch and it goes to before the current
// address it's definitely a tail call.
if (!ends_fn && target < start_address && furthest_target <= address) {
LOGPPC("function end %.8X (back b before addr)", address);
ends_fn = true;
}
// If the target is a __restgprlr_* method it's the end of a function.
// Note that sometimes functions stick this in a basic block *inside*
// of the function somewhere, so ensure we don't have any branches over
// it.
if (!ends_fn && furthest_target <= address && IsRestGprLr(target)) {
LOGPPC("function end %.8X (__restgprlr_*)", address);
ends_fn = true;
}
// Heuristic: if there's an unconditional branch in the first block of
// the function it's likely a thunk.
// Ex:
// li r3, 0
// b KeBugCheck
// This check may hit on functions that jump over data code, so only
// trigger this check in leaf functions (no mfspr lr/prolog).
if (!ends_fn && !starts_with_mfspr_lr && blocks_found == 1) {
LOGPPC("HEURISTIC: ending at simple leaf thunk %.8X", address);
ends_fn = true;
}
// Heuristic: if this is an unconditional branch at the end of the
// function (nothing jumps over us) and we are jumping forward there's
// a good chance it's a tail call.
// This may not be true if the code is jumping over data/etc.
// TODO(benvanik): figure out how to do this reliably. This check as is
// is too aggressive and turns a lot of valid branches into tail calls.
// It seems like a lot of functions end up with some prologue bit then
// jump deep inside only to jump back towards the top soon after. May
// need something more complex than just a simple 1-pass system to
// detect these, unless more signals can be found.
/*
if (!ends_fn &&
target > addr &&
furthest_target < addr) {
LOGPPC("HEURISTIC: ending at tail call branch %.8X", addr);
ends_fn = true;
}
*/
if (!ends_fn && !IsRestGprLr(target)) {
furthest_target = std::max(furthest_target, target);
// TODO(benvanik): perhaps queue up for a speculative check? I think
// we are running over tail-call functions here that branch to
// somewhere else.
// GetOrInsertFunction(target);
}
}
ends_block = true;
} else if (i.type->opcode == 0x40000000) {
// bc/bca/bcl/bcla
uint32_t target =
(uint32_t)XEEXTS16(i.B.BD << 2) + (i.B.AA ? 0 : (int32_t)address);
if (i.B.LK) {
LOGPPC("bcl %.8X -> %.8X", address, target);
// Queue call target if needed.
// TODO(benvanik): see if this is correct - not sure anyone makes
// function calls with bcl.
// GetOrInsertFunction(target);
} else {
LOGPPC("bc %.8X -> %.8X", address, target);
// TODO(benvanik): GetOrInsertFunction? it's likely a BB
if (!IsRestGprLr(target)) {
furthest_target = std::max(furthest_target, target);
}
}
ends_block = true;
} else if (i.type->opcode == 0x4C000020) {
// bclr/bclrl
if (i.XL.LK) {
LOGPPC("bclrl %.8X", address);
} else {
LOGPPC("bclr %.8X", address);
}
ends_block = true;
} else if (i.type->opcode == 0x4C000420) {
// bcctr/bcctrl
if (i.XL.LK) {
LOGPPC("bcctrl %.8X", address);
} else {
LOGPPC("bcctr %.8X", address);
}
ends_block = true;
}
if (ends_block) {
in_block = false;
}
if (ends_fn) {
break;
}
address += 4;
if (end_address && address > end_address) {
// Hmm....
LOGPPC("Ran over function bounds! %.8X-%.8X", start_address, end_address);
break;
}
}
if (end_address && address + 4 < end_address) {
// Ran under the expected value - since we probably got the initial bounds
// from someplace valid (like method hints) this may indicate an error.
// It's also possible that we guessed in hole-filling and there's another
// function below this one.
LOGPPC("Function ran under: %.8X-%.8X ended at %.8X", start_address,
end_address, address + 4);
}
symbol_info->set_end_address(address);
// If there's spare bits at the end, split the function.
// TODO(benvanik): splitting?
// TODO(benvanik): find and record stack information
// - look for __savegprlr_* and __restgprlr_*
// - if present, flag function as needing a stack
// - record prolog/epilog lengths/stack size/etc
LOGPPC("Finished analyzing %.8X", start_address);
return 0;
}
std::vector<BlockInfo> PPCScanner::FindBlocks(FunctionInfo* symbol_info) {
Memory* memory = frontend_->memory();
const uint8_t* p = memory->membase();
std::map<uint64_t, BlockInfo> block_map;
uint64_t start_address = symbol_info->address();
uint64_t end_address = symbol_info->end_address();
bool in_block = false;
uint64_t block_start = 0;
InstrData i;
for (uint64_t address = start_address; address <= end_address; address += 4) {
i.address = address;
i.code = poly::load_and_swap<uint32_t>(p + address);
if (!i.code) {
continue;
}
// TODO(benvanik): find a way to avoid using the opcode tables.
// This lookup is *expensive* and should be avoided when scanning.
i.type = GetInstrType(i.code);
if (!in_block) {
in_block = true;
block_start = address;
}
bool ends_block = false;
if (!i.type) {
// Invalid instruction.
} else if (i.code == 0x4E800020) {
// blr -- unconditional branch to LR.
ends_block = true;
} else if (i.code == 0x4E800420) {
// bctr -- unconditional branch to CTR.
// This is almost always a jump table.
// TODO(benvanik): decode jump tables.
ends_block = true;
} else if (i.type->opcode == 0x48000000) {
// b/ba/bl/bla
// uint32_t target =
// (uint32_t)XEEXTS26(i.I.LI << 2) + (i.I.AA ? 0 : (int32_t)address);
ends_block = true;
} else if (i.type->opcode == 0x40000000) {
// bc/bca/bcl/bcla
// uint32_t target =
// (uint32_t)XEEXTS16(i.B.BD << 2) + (i.B.AA ? 0 : (int32_t)address);
ends_block = true;
} else if (i.type->opcode == 0x4C000020) {
// bclr/bclrl
ends_block = true;
} else if (i.type->opcode == 0x4C000420) {
// bcctr/bcctrl
ends_block = true;
}
if (ends_block) {
in_block = false;
block_map[block_start] = {
block_start, address,
};
}
}
if (in_block) {
block_map[block_start] = {
block_start, end_address,
};
}
std::vector<BlockInfo> blocks;
for (auto it = block_map.begin(); it != block_map.end(); ++it) {
blocks.push_back(it->second);
}
return blocks;
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,50 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_SCANNER_H_
#define XENIA_FRONTEND_PPC_PPC_SCANNER_H_
#include <vector>
#include "xenia/cpu/runtime/symbol_info.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
class PPCFrontend;
typedef struct BlockInfo_t {
uint64_t start_address;
uint64_t end_address;
} BlockInfo;
class PPCScanner {
public:
PPCScanner(PPCFrontend* frontend);
~PPCScanner();
int FindExtents(runtime::FunctionInfo* symbol_info);
std::vector<BlockInfo> FindBlocks(runtime::FunctionInfo* symbol_info);
private:
bool IsRestGprLr(uint64_t address);
private:
PPCFrontend* frontend_;
};
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_SCANNER_H_

View File

@@ -0,0 +1,216 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/frontend/ppc/ppc_translator.h"
#include "xenia/cpu/compiler/compiler_passes.h"
#include "xenia/cpu/cpu-private.h"
#include "xenia/cpu/frontend/ppc/ppc_disasm.h"
#include "xenia/cpu/frontend/ppc/ppc_frontend.h"
#include "xenia/cpu/frontend/ppc/ppc_hir_builder.h"
#include "xenia/cpu/frontend/ppc/ppc_instr.h"
#include "xenia/cpu/frontend/ppc/ppc_scanner.h"
#include "xenia/cpu/runtime/runtime.h"
#include "poly/reset_scope.h"
#include "xenia/profiling.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
// TODO(benvanik): remove when enums redefined.
using namespace xe::cpu::runtime;
using xe::cpu::backend::Backend;
using xe::cpu::compiler::Compiler;
using xe::cpu::runtime::Function;
using xe::cpu::runtime::FunctionInfo;
namespace passes = xe::cpu::compiler::passes;
PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
Backend* backend = frontend->runtime()->backend();
scanner_.reset(new PPCScanner(frontend));
builder_.reset(new PPCHIRBuilder(frontend));
compiler_.reset(new Compiler(frontend->runtime()));
assembler_ = std::move(backend->CreateAssembler());
assembler_->Initialize();
bool validate = FLAGS_validate_hir;
// Merge blocks early. This will let us use more context in other passes.
// The CFG is required for simplification and dirtied by it.
compiler_->AddPass(std::make_unique<passes::ControlFlowAnalysisPass>());
compiler_->AddPass(std::make_unique<passes::ControlFlowSimplificationPass>());
compiler_->AddPass(std::make_unique<passes::ControlFlowAnalysisPass>());
// Passes are executed in the order they are added. Multiple of the same
// pass type may be used.
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::ContextPromotionPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::SimplificationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::ConstantPropagationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::SimplificationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
// compiler_->AddPass(std::make_unique<passes::DeadStoreEliminationPass>());
// if (validate)
// compiler_->AddPass(std::make_unique<passes::ValidationPass>());
compiler_->AddPass(std::make_unique<passes::DeadCodeEliminationPass>());
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
//// Removes all unneeded variables. Try not to add new ones after this.
// compiler_->AddPass(new passes::ValueReductionPass());
// if (validate) compiler_->AddPass(new passes::ValidationPass());
// Register allocation for the target backend.
// Will modify the HIR to add loads/stores.
// This should be the last pass before finalization, as after this all
// registers are assigned and ready to be emitted.
compiler_->AddPass(std::make_unique<passes::RegisterAllocationPass>(
backend->machine_info()));
if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
// Must come last. The HIR is not really HIR after this.
compiler_->AddPass(std::make_unique<passes::FinalizationPass>());
}
PPCTranslator::~PPCTranslator() = default;
int PPCTranslator::Translate(FunctionInfo* symbol_info,
uint32_t debug_info_flags, uint32_t trace_flags,
Function** out_function) {
SCOPE_profile_cpu_f("cpu");
// Reset() all caching when we leave.
poly::make_reset_scope(builder_);
poly::make_reset_scope(compiler_);
poly::make_reset_scope(assembler_);
poly::make_reset_scope(&string_buffer_);
// Scan the function to find its extents. We only need to do this if we
// haven't already been provided with them from some other source.
if (!symbol_info->has_end_address()) {
// TODO(benvanik): find a way to remove the need for the scan. A fixup
// scheme acting on branches could go back and modify calls to branches
// if they are within the extents.
int result = scanner_->FindExtents(symbol_info);
if (result) {
return result;
}
}
// NOTE: we only want to do this when required, as it's expensive to build.
if (FLAGS_always_disasm) {
debug_info_flags |= DEBUG_INFO_ALL_DISASM;
}
std::unique_ptr<DebugInfo> debug_info;
if (debug_info_flags) {
debug_info.reset(new DebugInfo());
}
// Stash source.
if (debug_info_flags & DEBUG_INFO_SOURCE_DISASM) {
DumpSource(symbol_info, &string_buffer_);
debug_info->set_source_disasm(string_buffer_.ToString());
string_buffer_.Reset();
}
if (false) {
xe::cpu::frontend::ppc::DumpAllInstrCounts();
}
// Emit function.
uint32_t emit_flags = 0;
if (debug_info) {
emit_flags |= PPCHIRBuilder::EMIT_DEBUG_COMMENTS;
}
if (trace_flags & TRACE_SOURCE_VALUES) {
emit_flags |= PPCHIRBuilder::EMIT_TRACE_SOURCE_VALUES;
} else if (trace_flags & TRACE_SOURCE) {
emit_flags |= PPCHIRBuilder::EMIT_TRACE_SOURCE;
}
int result = builder_->Emit(symbol_info, emit_flags);
if (result) {
return result;
}
// Stash raw HIR.
if (debug_info_flags & DEBUG_INFO_RAW_HIR_DISASM) {
builder_->Dump(&string_buffer_);
debug_info->set_raw_hir_disasm(string_buffer_.ToString());
string_buffer_.Reset();
}
// Compile/optimize/etc.
result = compiler_->Compile(builder_.get());
if (result) {
return result;
}
// Stash optimized HIR.
if (debug_info_flags & DEBUG_INFO_HIR_DISASM) {
builder_->Dump(&string_buffer_);
debug_info->set_hir_disasm(string_buffer_.ToString());
string_buffer_.Reset();
}
// Assemble to backend machine code.
result =
assembler_->Assemble(symbol_info, builder_.get(), debug_info_flags,
std::move(debug_info), trace_flags, out_function);
if (result) {
return result;
}
return 0;
};
void PPCTranslator::DumpSource(runtime::FunctionInfo* symbol_info,
poly::StringBuffer* string_buffer) {
Memory* memory = frontend_->memory();
const uint8_t* p = memory->membase();
string_buffer->Append("%s fn %.8X-%.8X %s\n",
symbol_info->module()->name().c_str(),
symbol_info->address(), symbol_info->end_address(),
symbol_info->name().c_str());
auto blocks = scanner_->FindBlocks(symbol_info);
uint64_t start_address = symbol_info->address();
uint64_t end_address = symbol_info->end_address();
InstrData i;
auto block_it = blocks.begin();
for (uint64_t address = start_address, offset = 0; address <= end_address;
address += 4, offset++) {
i.address = address;
i.code = poly::load_and_swap<uint32_t>(p + address);
// TODO(benvanik): find a way to avoid using the opcode tables.
i.type = GetInstrType(i.code);
// Check labels.
if (block_it != blocks.end() && block_it->start_address == address) {
string_buffer->Append("%.8X loc_%.8X:\n", address, address);
++block_it;
}
string_buffer->Append("%.8X %.8X ", address, i.code);
DisasmPPC(i, string_buffer);
string_buffer->Append("\n");
}
}
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe

View File

@@ -0,0 +1,56 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_FRONTEND_PPC_PPC_TRANSLATOR_H_
#define XENIA_FRONTEND_PPC_PPC_TRANSLATOR_H_
#include <memory>
#include "xenia/cpu/backend/assembler.h"
#include "xenia/cpu/compiler/compiler.h"
#include "xenia/cpu/runtime/symbol_info.h"
#include "poly/string_buffer.h"
namespace xe {
namespace cpu {
namespace frontend {
namespace ppc {
class PPCFrontend;
class PPCHIRBuilder;
class PPCScanner;
class PPCTranslator {
public:
PPCTranslator(PPCFrontend* frontend);
~PPCTranslator();
int Translate(runtime::FunctionInfo* symbol_info, uint32_t debug_info_flags,
uint32_t trace_flags, runtime::Function** out_function);
private:
void DumpSource(runtime::FunctionInfo* symbol_info,
poly::StringBuffer* string_buffer);
private:
PPCFrontend* frontend_;
std::unique_ptr<PPCScanner> scanner_;
std::unique_ptr<PPCHIRBuilder> builder_;
std::unique_ptr<compiler::Compiler> compiler_;
std::unique_ptr<backend::Assembler> assembler_;
poly::StringBuffer string_buffer_;
};
} // namespace ppc
} // namespace frontend
} // namespace cpu
} // namespace xe
#endif // XENIA_FRONTEND_PPC_PPC_TRANSLATOR_H_

View File

@@ -0,0 +1,30 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'ppc_context.cc',
'ppc_context.h',
'ppc_disasm.cc',
'ppc_disasm.h',
'ppc_emit-private.h',
'ppc_emit.h',
'ppc_emit_altivec.cc',
'ppc_emit_alu.cc',
'ppc_emit_control.cc',
'ppc_emit_fpu.cc',
'ppc_emit_memory.cc',
'ppc_frontend.cc',
'ppc_frontend.h',
'ppc_hir_builder.cc',
'ppc_hir_builder.h',
'ppc_instr.cc',
'ppc_instr.h',
'ppc_instr_tables.h',
'ppc_scanner.cc',
'ppc_scanner.h',
'ppc_translator.cc',
'ppc_translator.h',
],
'includes': [
],
}

View File

@@ -0,0 +1,61 @@
# Codegen Tests
This directory contains the test assets used by the automated codegen test
runner.
Each test is structured as a source `[name].s` PPC assembly file and the
generated outputs. The outputs are made using the custom build of binutils
setup when `xenia-build setup` is called and are checked in to make it easier
to run the tests on Windows.
Tests are run using the `xenia-test` app or via `xenia-build test`.
## Execution
The test binary is placed into memory at `0x82010000` and all other memory is
zeroed.
All registers are reset to zero. In order to provide useful inputs tests can
specify `# REGISTER_IN` values.
The code is jumped into at the starting address and executed until the last
instruction in the input file is reached.
After all instructions complete any `# REGISTER_OUT` values are checked and if
they do not match the test is failed.
## Annotations
Annotations can appear at any line in a file. If a number is required it can
be in either hex or decimal form, or IEEE if floating-point.
### REGISTER_IN
```
# REGISTER_IN [register name] [register value]
```
Sets the value of a register prior to executing the instructions.
Examples:
```
# REGISTER_IN r4 0x1234
# REGISTER_IN r4 5678
```
### REGISTER_OUT
```
# REGISTER_OUT [register name] [register value]
```
Defines the expected register value when the instructions have executed.
If after all instructions have completed the register value does not match
the value given here the test will fail.
Examples:
```
# REGISTER_OUT r3 123
```
TODO: memory setup/assertions

Binary file not shown.

View File

@@ -0,0 +1,13 @@
/vagrant/src/xenia/cpu/frontend/ppc/test/bin//instr_add.o: file format elf64-powerpc
Disassembly of section .text:
0000000000100000 <test_add_1>:
100000: 7d 65 ca 14 add r11,r5,r25
100004: 4e 80 00 20 blr
0000000000100008 <test_add_2>:
100008: 7d 60 ca 14 add r11,r0,r25
10000c: 4e 80 00 20 blr

View File

@@ -0,0 +1,2 @@
0000000000000000 t test_add_1
0000000000000008 t test_add_2

Binary file not shown.

View File

@@ -0,0 +1,30 @@
/vagrant/src/xenia/cpu/frontend/ppc/test/bin//instr_addc.o: file format elf64-powerpc
Disassembly of section .text:
0000000000100000 <test_addc_1>:
100000: 7c 64 28 14 addc r3,r4,r5
100004: 7c c0 01 14 adde r6,r0,r0
100008: 4e 80 00 20 blr
000000000010000c <test_addc_2>:
10000c: 7c 64 28 14 addc r3,r4,r5
100010: 7c c0 01 14 adde r6,r0,r0
100014: 4e 80 00 20 blr
0000000000100018 <test_addc_3>:
100018: 7c 64 28 14 addc r3,r4,r5
10001c: 7c c0 01 14 adde r6,r0,r0
100020: 4e 80 00 20 blr
0000000000100024 <test_addc_4>:
100024: 7c 64 28 14 addc r3,r4,r5
100028: 7c c0 01 14 adde r6,r0,r0
10002c: 4e 80 00 20 blr
0000000000100030 <test_addc_5>:
100030: 7c 64 28 14 addc r3,r4,r5
100034: 7c c0 01 14 adde r6,r0,r0
100038: 4e 80 00 20 blr

View File

@@ -0,0 +1,5 @@
0000000000000000 t test_addc_1
000000000000000c t test_addc_2
0000000000000018 t test_addc_3
0000000000000024 t test_addc_4
0000000000000030 t test_addc_5

Binary file not shown.

View File

@@ -0,0 +1,70 @@
/vagrant/src/xenia/cpu/frontend/ppc/test/bin//instr_adde.o: file format elf64-powerpc
Disassembly of section .text:
0000000000100000 <test_adde_1>:
100000: 7c 64 29 14 adde r3,r4,r5
100004: 7c c0 01 14 adde r6,r0,r0
100008: 4e 80 00 20 blr
000000000010000c <test_adde_2>:
10000c: 7c 63 1a 78 xor r3,r3,r3
100010: 7c 63 18 f8 not r3,r3
100014: 30 63 00 01 addic r3,r3,1
100018: 7c 64 29 14 adde r3,r4,r5
10001c: 7c c0 01 14 adde r6,r0,r0
100020: 4e 80 00 20 blr
0000000000100024 <test_adde_3>:
100024: 7c 64 29 14 adde r3,r4,r5
100028: 7c c0 01 14 adde r6,r0,r0
10002c: 4e 80 00 20 blr
0000000000100030 <test_adde_4>:
100030: 7c 63 1a 78 xor r3,r3,r3
100034: 7c 63 18 f8 not r3,r3
100038: 30 63 00 01 addic r3,r3,1
10003c: 7c 64 29 14 adde r3,r4,r5
100040: 7c c0 01 14 adde r6,r0,r0
100044: 4e 80 00 20 blr
0000000000100048 <test_adde_5>:
100048: 7c 64 29 14 adde r3,r4,r5
10004c: 7c c0 01 14 adde r6,r0,r0
100050: 4e 80 00 20 blr
0000000000100054 <test_adde_6>:
100054: 7c 63 1a 78 xor r3,r3,r3
100058: 7c 63 18 f8 not r3,r3
10005c: 30 63 00 01 addic r3,r3,1
100060: 7c 64 29 14 adde r3,r4,r5
100064: 7c c0 01 14 adde r6,r0,r0
100068: 4e 80 00 20 blr
000000000010006c <test_adde_7>:
10006c: 7c 64 29 14 adde r3,r4,r5
100070: 7c c0 01 14 adde r6,r0,r0
100074: 4e 80 00 20 blr
0000000000100078 <test_adde_8>:
100078: 7c 63 1a 78 xor r3,r3,r3
10007c: 7c 63 18 f8 not r3,r3
100080: 30 63 00 01 addic r3,r3,1
100084: 7c 64 29 14 adde r3,r4,r5
100088: 7c c0 01 14 adde r6,r0,r0
10008c: 4e 80 00 20 blr
0000000000100090 <test_adde_9>:
100090: 7c 64 29 14 adde r3,r4,r5
100094: 7c c0 01 14 adde r6,r0,r0
100098: 4e 80 00 20 blr
000000000010009c <test_adde_10>:
10009c: 7c 63 1a 78 xor r3,r3,r3
1000a0: 7c 63 18 f8 not r3,r3
1000a4: 30 63 00 01 addic r3,r3,1
1000a8: 7c 64 29 14 adde r3,r4,r5
1000ac: 7c c0 01 14 adde r6,r0,r0
1000b0: 4e 80 00 20 blr

View File

@@ -0,0 +1,10 @@
0000000000000000 t test_adde_1
000000000000000c t test_adde_2
0000000000000024 t test_adde_3
0000000000000030 t test_adde_4
0000000000000048 t test_adde_5
0000000000000054 t test_adde_6
000000000000006c t test_adde_7
0000000000000078 t test_adde_8
0000000000000090 t test_adde_9
000000000000009c t test_adde_10

Binary file not shown.

View File

@@ -0,0 +1,15 @@
/vagrant/src/xenia/cpu/frontend/ppc/test/bin//instr_addic.o: file format elf64-powerpc
Disassembly of section .text:
0000000000100000 <test_addic_1>:
100000: 30 84 00 01 addic r4,r4,1
100004: 7c c0 01 14 adde r6,r0,r0
100008: 4e 80 00 20 blr
000000000010000c <test_addic_2>:
10000c: 30 84 00 01 addic r4,r4,1
100010: 7c c0 01 14 adde r6,r0,r0
100014: 4e 80 00 20 blr

View File

@@ -0,0 +1,2 @@
0000000000000000 t test_addic_1
000000000000000c t test_addic_2

Binary file not shown.

View File

@@ -0,0 +1,57 @@
/vagrant/src/xenia/cpu/frontend/ppc/test/bin//instr_addme.o: file format elf64-powerpc
Disassembly of section .text:
0000000000100000 <test_addme_1>:
100000: 7c 64 01 d4 addme r3,r4
100004: 7c c0 01 14 adde r6,r0,r0
100008: 4e 80 00 20 blr
000000000010000c <test_addme_2>:
10000c: 7c 63 1a 78 xor r3,r3,r3
100010: 7c 63 18 f8 not r3,r3
100014: 30 63 00 01 addic r3,r3,1
100018: 7c 64 01 d4 addme r3,r4
10001c: 7c c0 01 14 adde r6,r0,r0
100020: 4e 80 00 20 blr
0000000000100024 <test_addme_3>:
100024: 7c 64 01 d4 addme r3,r4
100028: 7c c0 01 14 adde r6,r0,r0
10002c: 4e 80 00 20 blr
0000000000100030 <test_addme_4>:
100030: 7c 63 1a 78 xor r3,r3,r3
100034: 7c 63 18 f8 not r3,r3
100038: 30 63 00 01 addic r3,r3,1
10003c: 7c 64 01 d4 addme r3,r4
100040: 7c c0 01 14 adde r6,r0,r0
100044: 4e 80 00 20 blr
0000000000100048 <test_addme_5>:
100048: 7c 64 01 d4 addme r3,r4
10004c: 7c c0 01 14 adde r6,r0,r0
100050: 4e 80 00 20 blr
0000000000100054 <test_addme_6>:
100054: 7c 63 1a 78 xor r3,r3,r3
100058: 7c 63 18 f8 not r3,r3
10005c: 30 63 00 01 addic r3,r3,1
100060: 7c 64 01 d4 addme r3,r4
100064: 7c c0 01 14 adde r6,r0,r0
100068: 4e 80 00 20 blr
000000000010006c <test_addme_7>:
10006c: 7c 64 01 d4 addme r3,r4
100070: 7c c0 01 14 adde r6,r0,r0
100074: 4e 80 00 20 blr
0000000000100078 <test_addme_8>:
100078: 7c 63 1a 78 xor r3,r3,r3
10007c: 7c 63 18 f8 not r3,r3
100080: 30 63 00 01 addic r3,r3,1
100084: 7c 64 01 d4 addme r3,r4
100088: 7c c0 01 14 adde r6,r0,r0
10008c: 4e 80 00 20 blr

View File

@@ -0,0 +1,8 @@
0000000000000000 t test_addme_1
000000000000000c t test_addme_2
0000000000000024 t test_addme_3
0000000000000030 t test_addme_4
0000000000000048 t test_addme_5
0000000000000054 t test_addme_6
000000000000006c t test_addme_7
0000000000000078 t test_addme_8

Some files were not shown because too many files have changed in this diff Show More