diff --git a/.gitmodules b/.gitmodules index 31ad04ace0..edfd14ccb9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "third_party/wxWidgets"] path = third_party/wxWidgets url = https://github.com/wxWidgets/wxWidgets.git +[submodule "third_party/catch"] + path = third_party/catch + url = https://github.com/philsquared/Catch.git diff --git a/src/alloy/frontend/ppc/ppc_translator.cc b/src/alloy/frontend/ppc/ppc_translator.cc index b894978852..008ba8948b 100644 --- a/src/alloy/frontend/ppc/ppc_translator.cc +++ b/src/alloy/frontend/ppc/ppc_translator.cc @@ -10,13 +10,13 @@ #include #include -#include #include #include #include #include #include #include +#include #include #include diff --git a/src/alloy/runtime/module.h b/src/alloy/runtime/module.h index 88a1c4b94c..6bd50b6f0c 100644 --- a/src/alloy/runtime/module.h +++ b/src/alloy/runtime/module.h @@ -37,10 +37,10 @@ class Module { virtual bool ContainsAddress(uint64_t address); SymbolInfo* LookupSymbol(uint64_t address, bool wait = true); - SymbolInfo::Status DeclareFunction(uint64_t address, - FunctionInfo** out_symbol_info); - SymbolInfo::Status DeclareVariable(uint64_t address, - VariableInfo** out_symbol_info); + virtual SymbolInfo::Status DeclareFunction(uint64_t address, + FunctionInfo** out_symbol_info); + virtual SymbolInfo::Status DeclareVariable(uint64_t address, + VariableInfo** out_symbol_info); SymbolInfo::Status DefineFunction(FunctionInfo* symbol_info); SymbolInfo::Status DefineVariable(VariableInfo* symbol_info); diff --git a/src/alloy/runtime/sources.gypi b/src/alloy/runtime/sources.gypi index d1d7648b52..283b16c793 100644 --- a/src/alloy/runtime/sources.gypi +++ b/src/alloy/runtime/sources.gypi @@ -19,6 +19,8 @@ 'runtime.h', 'symbol_info.cc', 'symbol_info.h', + 'test_module.cc', + 'test_module.h', 'thread_state.cc', 'thread_state.h', ], diff --git a/src/alloy/runtime/test_module.cc b/src/alloy/runtime/test_module.cc new file mode 100644 index 0000000000..8ed7662173 --- /dev/null +++ b/src/alloy/runtime/test_module.cc @@ -0,0 +1,103 @@ +/** + ****************************************************************************** + * 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 + +#include +#include +#include +#include +#include + +namespace alloy { +namespace runtime { + +using alloy::backend::Backend; +using alloy::compiler::Compiler; +using alloy::hir::HIRBuilder; +using alloy::runtime::Function; +using alloy::runtime::FunctionInfo; +namespace passes = alloy::compiler::passes; + +TestModule::TestModule(Runtime* runtime, const std::string& name, + std::function contains_address, + std::function generate) + : Module(runtime), + name_(name), + contains_address_(contains_address), + generate_(generate) { + builder_.reset(new HIRBuilder()); + compiler_.reset(new Compiler(runtime)); + assembler_ = std::move(runtime->backend()->CreateAssembler()); + assembler_->Initialize(); + + // Merge blocks early. This will let us use more context in other passes. + // The CFG is required for simplification and dirtied by it. + compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + + // Passes are executed in the order they are added. Multiple of the same + // pass type may be used. + compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + // compiler_->AddPass(std::make_unique()); + compiler_->AddPass(std::make_unique()); + + //// Removes all unneeded variables. Try not to add new ones after this. + // compiler_->AddPass(new passes::ValueReductionPass()); + + // Register allocation for the target backend. + // Will modify the HIR to add loads/stores. + // This should be the last pass before finalization, as after this all + // registers are assigned and ready to be emitted. + compiler_->AddPass(std::make_unique( + runtime->backend()->machine_info())); + + // Must come last. The HIR is not really HIR after this. + compiler_->AddPass(std::make_unique()); +} + +TestModule::~TestModule() = default; + +bool TestModule::ContainsAddress(uint64_t address) { + return contains_address_(address); +} + +SymbolInfo::Status TestModule::DeclareFunction(uint64_t address, + FunctionInfo** out_symbol_info) { + SymbolInfo::Status status = Module::DeclareFunction(address, out_symbol_info); + if (status == SymbolInfo::STATUS_NEW) { + auto symbol_info = *out_symbol_info; + + // Reset() all caching when we leave. + make_reset_scope(compiler_); + make_reset_scope(assembler_); + + if (!generate_(*builder_.get())) { + symbol_info->set_status(SymbolInfo::STATUS_FAILED); + return SymbolInfo::STATUS_FAILED; + } + + compiler_->Compile(builder_.get()); + + Function* fn = nullptr; + assembler_->Assemble(symbol_info, builder_.get(), 0, nullptr, 0, &fn); + + symbol_info->set_function(fn); + status = SymbolInfo::STATUS_DEFINED; + symbol_info->set_status(status); + } + return status; +} + +} // namespace runtime +} // namespace alloy diff --git a/src/alloy/runtime/test_module.h b/src/alloy/runtime/test_module.h new file mode 100644 index 0000000000..822509e408 --- /dev/null +++ b/src/alloy/runtime/test_module.h @@ -0,0 +1,52 @@ +/** + ****************************************************************************** + * 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 ALLOY_RUNTIME_TEST_MODULE_H_ +#define ALLOY_RUNTIME_TEST_MODULE_H_ + +#include +#include +#include + +#include +#include +#include +#include + +namespace alloy { +namespace runtime { + +class TestModule : public Module { + public: + TestModule(Runtime* runtime, const std::string& name, + std::function contains_address, + std::function generate); + ~TestModule() override; + + const std::string& name() const override { return name_; } + + bool ContainsAddress(uint64_t address) override; + + SymbolInfo::Status DeclareFunction(uint64_t address, + FunctionInfo** out_symbol_info) override; + + private: + std::string name_; + std::function contains_address_; + std::function generate_; + + std::unique_ptr builder_; + std::unique_ptr compiler_; + std::unique_ptr assembler_; +}; + +} // namespace runtime +} // namespace alloy + +#endif // ALLOY_RUNTIME_TEST_MODULE_H_ diff --git a/third_party/catch b/third_party/catch new file mode 160000 index 0000000000..85d33e2cbd --- /dev/null +++ b/third_party/catch @@ -0,0 +1 @@ +Subproject commit 85d33e2cbd5354c36000424f823fc87707a24c3c diff --git a/tools/alloy-test/alloy-test.cc b/tools/alloy-test/alloy-test.cc new file mode 100644 index 0000000000..da558735a8 --- /dev/null +++ b/tools/alloy-test/alloy-test.cc @@ -0,0 +1,47 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#define CATCH_CONFIG_RUNNER +#include + +#include + +using namespace alloy; +using namespace alloy::hir; +using namespace alloy::runtime; +using alloy::frontend::ppc::PPCContext; + +TEST_CASE("Meta test", "[test]") { + alloy::test::TestFunction test([](hir::HIRBuilder& b) { + auto r = b.Add(b.LoadContext(offsetof(PPCContext, r) + 5 * 8, INT64_TYPE), + b.LoadContext(offsetof(PPCContext, r) + 25 * 8, INT64_TYPE)); + b.StoreContext(offsetof(PPCContext, r) + 11 * 8, r); + b.Return(); + return true; + }); + + test.Run([](PPCContext* ctx) { + ctx->r[5] = 10; + ctx->r[25] = 25; + }, + [](PPCContext* ctx) { + auto result = ctx->r[11]; + REQUIRE(result == 0x23); + }); + test.Run([](PPCContext* ctx) { + ctx->r[5] = 10; + ctx->r[25] = 25; + }, + [](PPCContext* ctx) { + auto result = ctx->r[11]; + REQUIRE(result == 0x24); + }); +} + +DEFINE_ENTRY_POINT(L"alloy-test", L"?", alloy::test::main); diff --git a/tools/alloy-test/alloy-test.gypi b/tools/alloy-test/alloy-test.gypi new file mode 100644 index 0000000000..36de70cd8d --- /dev/null +++ b/tools/alloy-test/alloy-test.gypi @@ -0,0 +1,29 @@ +# Copyright 2014 Ben Vanik. All Rights Reserved. +{ + 'targets': [ + { + 'target_name': 'alloy-test', + 'type': 'executable', + + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '1' + }, + }, + + 'dependencies': [ + 'alloy', + 'xenia', + ], + + 'include_dirs': [ + '.', + ], + + 'sources': [ + 'alloy-test.cc', + 'test_util.h', + ], + }, + ], +} diff --git a/tools/alloy-test/test_util.h b/tools/alloy-test/test_util.h new file mode 100644 index 0000000000..34f4964b4a --- /dev/null +++ b/tools/alloy-test/test_util.h @@ -0,0 +1,148 @@ +/** + ****************************************************************************** + * 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 ALLOY_TEST_TEST_UTIL_H_ +#define ALLOY_TEST_TEST_UTIL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace alloy { +namespace test { + +using alloy::frontend::ppc::PPCContext; +using alloy::runtime::Runtime; + +int main(std::vector& args) { + std::vector narrow_args; + auto narrow_argv = new char* [args.size()]; + for (size_t i = 0; i < args.size(); ++i) { + auto narrow_arg = poly::to_string(args[i]); + narrow_argv[i] = const_cast(narrow_arg.data()); + narrow_args.push_back(std::move(narrow_arg)); + } + return Catch::Session().run(int(args.size()), narrow_argv); +} + +class ThreadState : public alloy::runtime::ThreadState { + public: + ThreadState(Runtime* runtime, uint32_t thread_id, uint64_t stack_address, + size_t stack_size, uint64_t thread_state_address) + : alloy::runtime::ThreadState(runtime, thread_id), + stack_address_(stack_address), + stack_size_(stack_size), + thread_state_address_(thread_state_address) { + memset(memory_->Translate(stack_address_), 0, stack_size_); + + // Allocate with 64b alignment. + context_ = (PPCContext*)calloc(1, sizeof(PPCContext)); + assert_true((reinterpret_cast(context_) & 0xF) == 0); + + // Stash pointers to common structures that callbacks may need. + context_->reserve_address = memory_->reserve_address(); + context_->membase = memory_->membase(); + context_->runtime = runtime; + context_->thread_state = this; + + // Set initial registers. + context_->r[1] = stack_address_ + stack_size; + context_->r[13] = thread_state_address_; + + // Pad out stack a bit, as some games seem to overwrite the caller by about + // 16 to 32b. + context_->r[1] -= 64; + + raw_context_ = context_; + + runtime_->debugger()->OnThreadCreated(this); + } + ~ThreadState() override { + runtime_->debugger()->OnThreadDestroyed(this); + free(context_); + } + + PPCContext* context() const { return context_; } + + private: + uint64_t stack_address_; + size_t stack_size_; + uint64_t thread_state_address_; + + // NOTE: must be 64b aligned for SSE ops. + PPCContext* context_; +}; + +class TestFunction { + public: + TestFunction(std::function generator) { + memory_size = 16 * 1024 * 1024; + memory.reset(new SimpleMemory(memory_size)); + runtime.reset(new Runtime(memory.get())); + + auto frontend = + std::make_unique(runtime.get()); + std::unique_ptr backend; + // backend = + // std::make_unique(runtime.get()); + // backend = + // std::make_unique(runtime.get()); + runtime->Initialize(std::move(frontend), std::move(backend)); + + auto module = std::make_unique( + runtime.get(), "Test", + [](uint64_t address) { return address == 0x1000; }, + [generator](hir::HIRBuilder& b) { return generator(b); }); + runtime->AddModule(std::move(module)); + + runtime->ResolveFunction(0x1000, &fn); + } + + ~TestFunction() { + runtime.reset(); + memory.reset(); + } + + void Run(std::function pre_call, + std::function post_call) { + memory->Zero(0, memory_size); + + uint64_t stack_size = 64 * 1024; + uint64_t stack_address = memory_size - stack_size; + uint64_t thread_state_address = stack_address - 0x1000; + auto thread_state = std::make_unique( + runtime.get(), 100, stack_address, stack_size, thread_state_address); + auto ctx = thread_state->context(); + ctx->lr = 0xBEBEBEBE; + + pre_call(ctx); + + fn->Call(thread_state.get(), ctx->lr); + + post_call(ctx); + } + + size_t memory_size; + std::unique_ptr memory; + std::unique_ptr runtime; + alloy::runtime::Function* fn; +}; + +} // namespace test +} // namespace alloy + +#endif // ALLOY_TEST_TEST_UTIL_H_ diff --git a/tools/tools.gypi b/tools/tools.gypi index 3e6a56ffdb..26b5fba732 100644 --- a/tools/tools.gypi +++ b/tools/tools.gypi @@ -2,6 +2,7 @@ { 'includes': [ 'alloy-sandbox/alloy-sandbox.gypi', + 'alloy-test/alloy-test.gypi', 'xenia-compare/xenia-compare.gypi', 'xenia-debug/xenia-debug.gypi', 'xenia-run/xenia-run.gypi',