Work on CPU, codegen skeleton, and thunk.

This commit is contained in:
Ben Vanik 2013-01-13 15:48:18 -08:00
parent 2f4bc598e5
commit 099e37490a
12 changed files with 557 additions and 46 deletions

View File

@ -13,6 +13,7 @@
#include <xenia/common.h>
#include <xenia/core.h>
#include <xenia/kernel/export.h>
#include <xenia/kernel/module.h>
@ -33,7 +34,8 @@ void xe_cpu_release(xe_cpu_ref cpu);
xe_pal_ref xe_cpu_get_pal(xe_cpu_ref cpu);
xe_memory_ref xe_cpu_get_memory(xe_cpu_ref cpu);
int xe_cpu_prepare_module(xe_cpu_ref cpu, xe_module_ref module);
int xe_cpu_prepare_module(xe_cpu_ref cpu, xe_module_ref module,
xe_kernel_export_resolver_ref export_resolver);
int xe_cpu_execute(xe_cpu_ref cpu, uint32_t address);

194
src/cpu/codegen.cc Normal file
View File

@ -0,0 +1,194 @@
/**
******************************************************************************
* 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 "cpu/codegen.h"
#include <llvm/Linker.h>
#include <llvm/PassManager.h>
#include <llvm/Analysis/Verifier.h>
#include <llvm/IR/Attributes.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
using namespace llvm;
void xe_cpu_codegen_add_imports(xe_memory_ref memory,
xe_kernel_export_resolver_ref export_resolver,
xe_module_ref module, Module *m);
void xe_cpu_codegen_add_missing_import(
Module *m, const xe_xex2_import_library_t *library,
const xe_xex2_import_info_t* info, xe_kernel_export_t *kernel_export);
void xe_cpu_codegen_add_import(
Module *m, const xe_xex2_import_library_t *library,
const xe_xex2_import_info_t* info, xe_kernel_export_t *kernel_export);
void xe_cpu_codegen_optimize(Module *m, Function *fn);
llvm::Module *xe_cpu_codegen(llvm::LLVMContext& context, xe_memory_ref memory,
xe_kernel_export_resolver_ref export_resolver,
xe_module_ref module, Module *shared_module,
xe_codegen_options_t options) {
std::string error_message;
// Initialize the module.
Module *m = new Module("generated.xex", context);
// TODO(benavnik): addModuleFlag?
// Link shared module into generated module.
// This gives us a single module that we can optimize and prevents the need
// for foreward declarations.
Linker::LinkModules(m, shared_module, 0, &error_message);
// Add import thunks/etc.
xe_cpu_codegen_add_imports(memory, export_resolver, module, m);
// Add export wrappers.
//
Constant* c = m->getOrInsertFunction("mul_add",
/*ret type*/ IntegerType::get(context, 32),
/*args*/ IntegerType::get(context, 32),
IntegerType::get(context, 32),
IntegerType::get(context, 32),
/*varargs terminated with null*/ NULL);
Function* mul_add = cast<Function>(c);
mul_add->setCallingConv(CallingConv::C);
Function::arg_iterator args = mul_add->arg_begin();
Value* x = args++;
x->setName("x");
Value* y = args++;
y->setName("y");
Value* z = args++;
z->setName("z");
BasicBlock* block = BasicBlock::Create(getGlobalContext(), "entry", mul_add);
IRBuilder<> builder(block);
Value* tmp = builder.CreateBinOp(Instruction::Mul,
x, y, "tmp");
Value* tmp2 = builder.CreateBinOp(Instruction::Add,
tmp, z, "tmp2");
builder.CreateRet(tmp2);
// Run the optimizer on the function.
// Doing this here keeps the size of the IR small and speeds up the later
// passes.
xe_cpu_codegen_optimize(m, mul_add);
return m;
}
void xe_cpu_codegen_add_imports(xe_memory_ref memory,
xe_kernel_export_resolver_ref export_resolver,
xe_module_ref module, Module *m) {
xe_xex2_ref xex = xe_module_get_xex(module);
const xe_xex2_header_t *header = xe_xex2_get_header(xex);
for (size_t n = 0; n < header->import_library_count; n++) {
const xe_xex2_import_library_t *library = &header->import_libraries[n];
xe_xex2_import_info_t* import_infos;
size_t import_info_count;
XEIGNORE(xe_xex2_get_import_infos(xex, library,
&import_infos, &import_info_count));
for (size_t i = 0; i < import_info_count; i++) {
const xe_xex2_import_info_t *info = &import_infos[i];
xe_kernel_export_t *kernel_export =
xe_kernel_export_resolver_get_by_ordinal(
export_resolver, library->name, info->ordinal);
if (!kernel_export || !xe_kernel_export_is_implemented(kernel_export)) {
// Not implemented or known.
xe_cpu_codegen_add_missing_import(m, library, info, kernel_export);
} else {
// Implemented.
xe_cpu_codegen_add_import(m, library, info, kernel_export);
}
}
xe_free(import_infos);
}
xe_xex2_release(xex);
}
void xe_cpu_codegen_add_missing_import(
Module *m, const xe_xex2_import_library_t *library,
const xe_xex2_import_info_t* info, xe_kernel_export_t *kernel_export) {
LLVMContext& context = m->getContext();
char name[128];
xesnprintfa(name, XECOUNT(name), "__%s_%.8X",
library->name, kernel_export->ordinal);
// TODO(benvanik): add name as comment/alias?
// name = kernel_export->name;
if (info->thunk_address) {
AttributeWithIndex awi[] = {
//AttributeWithIndex::get(context, 2, Attributes::NoCapture),
AttributeWithIndex::get(context,
AttributeSet::FunctionIndex, Attribute::NoUnwind),
};
AttributeSet attrs = AttributeSet::get(context, awi);
std::vector<Type*> args;
Type *return_type = Type::getInt32Ty(context);
FunctionType *ft = FunctionType::get(return_type,
ArrayRef<Type*>(args), false);
Function *f = cast<Function>(m->getOrInsertFunction(
StringRef(name), ft, attrs));
f->setCallingConv(CallingConv::C);
f->setVisibility(GlobalValue::DefaultVisibility);
BasicBlock* block = BasicBlock::Create(context, "entry", f);
IRBuilder<> builder(block);
Value *tmp = builder.getInt32(0);
builder.getInt32(123);
builder.CreateRet(tmp);
xe_cpu_codegen_optimize(m, f);
//GlobalAlias *alias = new GlobalAlias(f->getType(), GlobalValue::InternalLinkage, name, f, m);
// printf(" F %.8X %.8X %.3X (%3d) %s %s\n",
// info->value_address, info->thunk_address, info->ordinal,
// info->ordinal, implemented ? " " : "!!", name);
} else {
// printf(" V %.8X %.3X (%3d) %s %s\n",
// info->value_address, info->ordinal, info->ordinal,
// implemented ? " " : "!!", name);
}
}
void xe_cpu_codegen_add_import(
Module *m, const xe_xex2_import_library_t *library,
const xe_xex2_import_info_t* info, xe_kernel_export_t *kernel_export) {
//
}
void xe_cpu_codegen_optimize(Module *m, Function *fn) {
FunctionPassManager pm(m);
PassManagerBuilder pmb;
pmb.OptLevel = 3;
pmb.SizeLevel = 0;
pmb.Inliner = createFunctionInliningPass();
pmb.Vectorize = true;
pmb.LoopVectorize = true;
pmb.populateFunctionPassManager(pm);
pm.add(createVerifierPass());
pm.run(*fn);
}

31
src/cpu/codegen.h Normal file
View File

@ -0,0 +1,31 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2013 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_CPU_CODEGEN_H_
#define XENIA_CPU_CODEGEN_H_
#include <xenia/core/memory.h>
#include <xenia/kernel/module.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
typedef struct {
int reserved;
} xe_codegen_options_t;
llvm::Module *xe_cpu_codegen(llvm::LLVMContext& context, xe_memory_ref memory,
xe_kernel_export_resolver_ref export_resolver,
xe_module_ref module, llvm::Module *shared_module,
xe_codegen_options_t options);
#endif // XENIA_CPU_CODEGEN_H_

View File

@ -9,14 +9,39 @@
#include <xenia/cpu.h>
#include <llvm/PassManager.h>
#include <llvm/Analysis/Verifier.h>
#include <llvm/Bitcode/ReaderWriter.h>
#include <llvm/ExecutionEngine/GenericValue.h>
#include <llvm/ExecutionEngine/ExecutionEngine.h>
#include <llvm/ExecutionEngine/Interpreter.h>
#include <llvm/ExecutionEngine/JIT.h>
#include <llvm/IR/DataLayout.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/Support/Host.h>
#include <llvm/Support/ManagedStatic.h>
#include <llvm/Support/MemoryBuffer.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/Support/system_error.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/Threading.h>
#include <llvm/Transforms/IPO.h>
#include <llvm/Transforms/IPO/PassManagerBuilder.h>
#include "cpu/codegen.h"
#include "cpu/xethunk/xethunk.h"
using namespace llvm;
typedef struct {
xe_module_ref module;
LLVMContext *context;
Module *m;
} xe_cpu_module_entry_t;
typedef struct xe_cpu {
xe_ref_t ref;
@ -24,9 +49,18 @@ typedef struct xe_cpu {
xe_pal_ref pal;
xe_memory_ref memory;
std::vector<xe_cpu_module_entry_t> entries;
ExecutionEngine *engine;
} xe_cpu_t;
int xe_cpu_setup_engine(xe_cpu_ref cpu, Module *gen_module);
int xe_cpu_init_module(xe_cpu_ref, Module *gen_module);
void xe_cpu_uninit_module(xe_cpu_ref cpu, xe_cpu_module_entry_t *module_entry);
xe_cpu_ref xe_cpu_create(xe_pal_ref pal, xe_memory_ref memory,
xe_cpu_options_t options) {
xe_cpu_ref cpu = (xe_cpu_ref)xe_calloc(sizeof(xe_cpu));
@ -37,10 +71,32 @@ xe_cpu_ref xe_cpu_create(xe_pal_ref pal, xe_memory_ref memory,
cpu->pal = xe_pal_retain(pal);
cpu->memory = xe_memory_retain(memory);
LLVMLinkInInterpreter();
LLVMLinkInJIT();
InitializeNativeTarget();
XEEXPECTTRUE(llvm_start_multithreaded());
return cpu;
XECLEANUP:
xe_cpu_release(cpu);
return NULL;
}
void xe_cpu_dealloc(xe_cpu_ref cpu) {
// Cleanup all modules.
for (std::vector<xe_cpu_module_entry_t>::iterator it = cpu->entries.begin();
it != cpu->entries.end(); ++it) {
xe_cpu_uninit_module(cpu, &*it);
cpu->engine->removeModule(it->m);
delete it->m;
delete it->context;
xe_module_release(it->module);
}
delete cpu->engine;
llvm_shutdown();
xe_memory_release(cpu->memory);
xe_pal_release(cpu->pal);
}
@ -62,52 +118,163 @@ xe_memory_ref xe_cpu_get_memory(xe_cpu_ref cpu) {
return xe_memory_retain(cpu->memory);
}
int xe_cpu_prepare_module(xe_cpu_ref cpu, xe_module_ref module) {
// TODO(benvanik): lookup the module in the cache.
int xe_cpu_setup_engine(xe_cpu_ref cpu, Module *gen_module) {
if (cpu->engine) {
// Engine already initialized - just add the module.
cpu->engine->addModule(gen_module);
return 0;
}
// TODO(benvanik): implement prepare module.
std::string error_message;
cpu->engine = ExecutionEngine::create(gen_module, false, &error_message,
CodeGenOpt::Aggressive);
XELOGCPU(XT("cpu"));
LLVMContext &context = getGlobalContext();
//IRBuilder<> builder(context);
Module *m = new Module("my cool jit", context);
Constant* c = m->getOrInsertFunction("mul_add",
/*ret type*/ IntegerType::get(context, 32),
/*args*/ IntegerType::get(context, 32),
IntegerType::get(context, 32),
IntegerType::get(context, 32),
/*varargs terminated with null*/ NULL);
Function* mul_add = cast<Function>(c);
mul_add->setCallingConv(CallingConv::C);
Function::arg_iterator args = mul_add->arg_begin();
Value* x = args++;
x->setName("x");
Value* y = args++;
y->setName("y");
Value* z = args++;
z->setName("z");
BasicBlock* block = BasicBlock::Create(getGlobalContext(), "entry", mul_add);
IRBuilder<> builder(block);
Value* tmp = builder.CreateBinOp(Instruction::Mul,
x, y, "tmp");
Value* tmp2 = builder.CreateBinOp(Instruction::Add,
tmp, z, "tmp2");
builder.CreateRet(tmp2);
XELOGD(XT("woo %d"), 123);
m->dump();
delete m;
return 0;
}
int xe_cpu_prepare_module(xe_cpu_ref cpu, xe_module_ref module,
xe_kernel_export_resolver_ref export_resolver) {
int result_code = 1;
std::string error_message;
LLVMContext *context = NULL;
OwningPtr<MemoryBuffer> shared_module_buffer;
Module *gen_module = NULL;
Module *shared_module = NULL;
raw_ostream *outs = NULL;
PassManager pm;
PassManagerBuilder pmb;
// TODO(benvanik): embed the bc file into the emulator.
const char *thunk_path = "src/cpu/xethunk/xethunk.bc";
// Create a LLVM context for this prepare.
// This is required to ensure thread safety/etc.
context = new LLVMContext();
// Calculate a cache path based on the module, the CPU version, and other
// bits.
// TODO(benvanik): cache path calculation.
const char *cache_path = "build/generated.bc";
// Check the cache to see if the bitcode exists.
// If it does, load that module directly. In the future we could also cache
// on linked binaries but that requires more safety around versioning.
// TODO(benvanik): check cache for module bitcode and load.
// if (path_exists(cache_key)) {
// gen_module = load_bitcode(cache_key);
// }
// If not found in cache, generate a new module.
if (!gen_module) {
// Load shared bitcode files.
// These contain globals and common thunk code that are used by the
// generated code.
XEEXPECTZERO(MemoryBuffer::getFile(thunk_path, shared_module_buffer));
shared_module = ParseBitcodeFile(&*shared_module_buffer, *context,
&error_message);
XEEXPECTNOTNULL(shared_module);
// Build the module from the source code.
xe_codegen_options_t codegen_options;
xe_zero_struct(&codegen_options, sizeof(codegen_options));
gen_module = xe_cpu_codegen(*context, cpu->memory, export_resolver,
module, shared_module,
codegen_options);
// Write to cache.
outs = new raw_fd_ostream(cache_path, error_message,
raw_fd_ostream::F_Binary);
XEEXPECTTRUE(error_message.empty());
WriteBitcodeToFile(gen_module, *outs);
}
// Link optimizations.
XEEXPECTZERO(gen_module->MaterializeAllPermanently(&error_message));
// Reset target triple (ignore what's in xethunk).
gen_module->setTargetTriple(llvm::sys::getDefaultTargetTriple());
// Run full module optimizations.
pm.add(new DataLayout(gen_module));
pm.add(createVerifierPass());
pmb.OptLevel = 3;
pmb.SizeLevel = 0;
pmb.Inliner = createFunctionInliningPass();
pmb.Vectorize = true;
pmb.LoopVectorize = true;
pmb.populateModulePassManager(pm);
pmb.populateLTOPassManager(pm, false, true);
pm.add(createVerifierPass());
pm.run(*gen_module);
// TODO(benvanik): experiment with LLD to see if we can write out a dll.
// Setup the execution engine (if needed).
// The engine is required to get the data layout and other values.
XEEXPECTZERO(xe_cpu_setup_engine(cpu, gen_module));
// Initialize the module.
XEEXPECTZERO(xe_cpu_init_module(cpu, gen_module));
// Force JIT of all functions.
for (Module::iterator I = gen_module->begin(), E = gen_module->end();
I != E; I++) {
Function* fn = &*I;
if (!fn->isDeclaration()) {
cpu->engine->getPointerToFunction(fn);
}
}
gen_module->dump();
// Stash the module entry to allow cleanup later.
xe_cpu_module_entry_t module_entry;
module_entry.module = xe_module_retain(module);
module_entry.context = context;
module_entry.m = gen_module;
cpu->entries.push_back(module_entry);
result_code = 0;
XECLEANUP:
delete outs;
delete shared_module;
if (result_code) {
delete gen_module;
delete context;
}
return result_code;
}
int xe_cpu_init_module(xe_cpu_ref cpu, Module *gen_module) {
// Run static initializers. I'm not sure we'll have any, but who knows.
cpu->engine->runStaticConstructorsDestructors(gen_module, false);
// Prepare init options.
xe_module_init_options_t init_options;
xe_zero_struct(&init_options, sizeof(init_options));
init_options.memory_base = xe_memory_addr(cpu->memory, 0);
// Grab the init function and call it.
Function *xe_module_init = gen_module->getFunction("xe_module_init");
std::vector<GenericValue> args;
args.push_back(GenericValue(&init_options));
GenericValue ret = cpu->engine->runFunction(xe_module_init, args);
return ret.IntVal.getSExtValue();
}
void xe_cpu_uninit_module(xe_cpu_ref cpu, xe_cpu_module_entry_t *module_entry) {
// Grab function and call it.
Function *xe_module_uninit = module_entry->m->getFunction("xe_module_uninit");
std::vector<GenericValue> args;
cpu->engine->runFunction(xe_module_uninit, args);
// Run static destructors.
cpu->engine->runStaticConstructorsDestructors(module_entry->m, true);
}
int xe_cpu_execute(xe_cpu_ref cpu, uint32_t address) {
// TODO(benvanik): implement execute.
return 0;

View File

@ -1,6 +1,7 @@
# Copyright 2013 Ben Vanik. All Rights Reserved.
{
'sources': [
'codegen.cc',
'cpu.cc',
],
}

BIN
src/cpu/xethunk/xethunk.bc Normal file

Binary file not shown.

39
src/cpu/xethunk/xethunk.c Normal file
View File

@ -0,0 +1,39 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
/**
* This file is compiled with clang to produce LLVM bitcode.
* When the emulator goes to build a full module it then imports this code into
* the generated module to provide globals/other shared values.
*
* Changes to this file require building a new version and checking it into the
* repo on a machine that has clang.
*
* # rebuild the xethunk.bc/.ll files:
* xb xethunk
*/
// NOTE: only headers in this directory should be included.
#include "xethunk.h"
// The base pointer that all guest addresses should be relative to.
static void* xe_memory_base;
int xe_module_init(xe_module_init_options_t *options) {
xe_memory_base = options->memory_base;
// TODO(benvanik): setup call table, etc?
return 0;
}
void xe_module_uninit() {
}

27
src/cpu/xethunk/xethunk.h Normal file
View File

@ -0,0 +1,27 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
/**
* This file is shared between xethunk and the loader to pass structures
* between the two. Since this file is compiled with the LLVM clang it cannot
* include any other files.
*/
#ifndef XENIA_CPU_XELIB_H_
#define XENIA_CPU_XELIB_H_
typedef struct {
void* memory_base;
// TODO(benvanik): execute call thunk
} xe_module_init_options_t;
#endif // XENIA_CPU_XELIB_H_

View File

@ -0,0 +1,21 @@
; ModuleID = 'src/cpu/xethunk/xethunk.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx10.8.0"
%struct.xe_module_init_options_t = type { i8* }
@xe_memory_base = internal global i8* null, align 8
define i32 @xe_module_init(%struct.xe_module_init_options_t* %options) nounwind uwtable ssp {
%1 = alloca %struct.xe_module_init_options_t*, align 8
store %struct.xe_module_init_options_t* %options, %struct.xe_module_init_options_t** %1, align 8
%2 = load %struct.xe_module_init_options_t** %1, align 8
%3 = getelementptr inbounds %struct.xe_module_init_options_t* %2, i32 0, i32 0
%4 = load i8** %3, align 8
store i8* %4, i8** @xe_memory_base, align 8
ret i32 0
}
define void @xe_module_uninit() nounwind uwtable ssp {
ret void
}

View File

@ -122,7 +122,8 @@ xe_module_ref xe_kernel_load_module(xe_kernel_ref kernel,
}
// Prepare the module.
XEEXPECTZERO(xe_cpu_prepare_module(kernel->cpu, module));
XEEXPECTZERO(xe_cpu_prepare_module(kernel->cpu, module,
kernel->export_resolver));
// Stash in modules list (takes reference).
// TODO(benvanik): stash in list.

View File

@ -61,6 +61,7 @@ def discover_commands():
'pull': PullCommand(),
'gyp': GypCommand(),
'build': BuildCommand(),
'xethunk': XethunkCommand(),
'clean': CleanCommand(),
'nuke': NukeCommand(),
}
@ -379,6 +380,33 @@ class BuildCommand(Command):
return 0
class XethunkCommand(Command):
"""'xethunk' command."""
def __init__(self, *args, **kwargs):
super(XethunkCommand, self).__init__(
name='xethunk',
help_short='Updates the xethunk.bc file.',
*args, **kwargs)
def execute(self, args, cwd):
print 'Building xethunk...'
print ''
path = 'src/cpu/xethunk/xethunk'
result = shell_call('clang -emit-llvm -O0 -c %s.c -o %s.bc' % (path, path),
throw_on_error=False)
if result != 0:
return result
shell_call('build/llvm/release/bin/llvm-dis %s.bc -o %s.ll' % (path, path))
shell_call('cat %s.ll' % (path))
print 'Success!'
return 0
class CleanCommand(Command):
"""'clean' command."""

View File

@ -74,7 +74,7 @@
['_type=="executable"', {
'libraries': [
'<!@(<(llvm_config) --ldflags)',
'<!@(<(llvm_config) --libs core)',
'<!@(<(llvm_config) --libs all)',
],
'library_dirs': [
# NOTE: this doesn't actually do anything...
@ -84,7 +84,7 @@
'xcode_settings': {
'OTHER_LDFLAGS': [
'<!@(<(llvm_config) --ldflags)',
'<!@(<(llvm_config) --libs core)',
'<!@(<(llvm_config) --libs all)',
],
},
}],