Moving alloy/ into xenia/cpu/ to start simplifying things.
This commit is contained in:
26
src/xenia/cpu/backend/assembler.cc
Normal file
26
src/xenia/cpu/backend/assembler.cc
Normal 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
|
||||
58
src/xenia/cpu/backend/assembler.h
Normal file
58
src/xenia/cpu/backend/assembler.h
Normal 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_
|
||||
32
src/xenia/cpu/backend/backend.cc
Normal file
32
src/xenia/cpu/backend/backend.cc
Normal 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
|
||||
55
src/xenia/cpu/backend/backend.h
Normal file
55
src/xenia/cpu/backend/backend.h
Normal 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_
|
||||
37
src/xenia/cpu/backend/machine_info.h
Normal file
37
src/xenia/cpu/backend/machine_info.h
Normal 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_
|
||||
14
src/xenia/cpu/backend/sources.gypi
Normal file
14
src/xenia/cpu/backend/sources.gypi
Normal 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',
|
||||
],
|
||||
}
|
||||
34
src/xenia/cpu/backend/x64/sources.gypi
Normal file
34
src/xenia/cpu/backend/x64/sources.gypi
Normal 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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
||||
135
src/xenia/cpu/backend/x64/x64_assembler.cc
Normal file
135
src/xenia/cpu/backend/x64/x64_assembler.cc
Normal 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
|
||||
58
src/xenia/cpu/backend/x64/x64_assembler.h
Normal file
58
src/xenia/cpu/backend/x64/x64_assembler.h
Normal 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_
|
||||
67
src/xenia/cpu/backend/x64/x64_backend.cc
Normal file
67
src/xenia/cpu/backend/x64/x64_backend.cc
Normal 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
|
||||
51
src/xenia/cpu/backend/x64/x64_backend.h
Normal file
51
src/xenia/cpu/backend/x64/x64_backend.h
Normal 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_
|
||||
48
src/xenia/cpu/backend/x64/x64_code_cache.h
Normal file
48
src/xenia/cpu/backend/x64/x64_code_cache.h
Normal 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_
|
||||
98
src/xenia/cpu/backend/x64/x64_code_cache_posix.cc
Normal file
98
src/xenia/cpu/backend/x64/x64_code_cache_posix.cc
Normal 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
|
||||
281
src/xenia/cpu/backend/x64/x64_code_cache_win.cc
Normal file
281
src/xenia/cpu/backend/x64/x64_code_cache_win.cc
Normal 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
|
||||
942
src/xenia/cpu/backend/x64/x64_emitter.cc
Normal file
942
src/xenia/cpu/backend/x64/x64_emitter.cc
Normal 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
|
||||
217
src/xenia/cpu/backend/x64/x64_emitter.h
Normal file
217
src/xenia/cpu/backend/x64/x64_emitter.h
Normal 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_
|
||||
52
src/xenia/cpu/backend/x64/x64_function.cc
Normal file
52
src/xenia/cpu/backend/x64/x64_function.cc
Normal 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
|
||||
47
src/xenia/cpu/backend/x64/x64_function.h
Normal file
47
src/xenia/cpu/backend/x64/x64_function.h
Normal 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_
|
||||
758
src/xenia/cpu/backend/x64/x64_sequence.inl
Normal file
758
src/xenia/cpu/backend/x64/x64_sequence.inl
Normal 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
|
||||
5887
src/xenia/cpu/backend/x64/x64_sequences.cc
Normal file
5887
src/xenia/cpu/backend/x64/x64_sequences.cc
Normal file
File diff suppressed because it is too large
Load Diff
37
src/xenia/cpu/backend/x64/x64_sequences.h
Normal file
37
src/xenia/cpu/backend/x64/x64_sequences.h
Normal 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_
|
||||
147
src/xenia/cpu/backend/x64/x64_thunk_emitter.cc
Normal file
147
src/xenia/cpu/backend/x64/x64_thunk_emitter.cc
Normal 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
|
||||
143
src/xenia/cpu/backend/x64/x64_thunk_emitter.h
Normal file
143
src/xenia/cpu/backend/x64/x64_thunk_emitter.h
Normal 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_
|
||||
202
src/xenia/cpu/backend/x64/x64_tracers.cc
Normal file
202
src/xenia/cpu/backend/x64/x64_tracers.cc
Normal 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
|
||||
72
src/xenia/cpu/backend/x64/x64_tracers.h
Normal file
72
src/xenia/cpu/backend/x64/x64_tracers.h
Normal 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_
|
||||
Reference in New Issue
Block a user