(WIP) Add a native PowerPC testing frontend
This commit is contained in:
parent
095f65c19e
commit
ccd6d4b199
15
premake5.lua
15
premake5.lua
|
@ -5,6 +5,14 @@ location(build_root)
|
|||
targetdir(build_bin)
|
||||
objdir(build_obj)
|
||||
|
||||
-- Define an ARCH variable
|
||||
-- Only use this to enable architecture-specific functionality.
|
||||
if os.is("linux") then
|
||||
ARCH = os.outputof("uname -p")
|
||||
else
|
||||
ARCH = "unknown"
|
||||
end
|
||||
|
||||
includedirs({
|
||||
".",
|
||||
"src",
|
||||
|
@ -84,6 +92,13 @@ filter("platforms:Linux")
|
|||
"pthread",
|
||||
})
|
||||
|
||||
filter({"platforms:Linux", "language:C++", "toolset:gcc"})
|
||||
buildoptions({
|
||||
"--std=c++11",
|
||||
})
|
||||
links({
|
||||
})
|
||||
|
||||
filter({"platforms:Linux", "language:C++", "toolset:clang"})
|
||||
buildoptions({
|
||||
"-std=c++14",
|
||||
|
|
|
@ -0,0 +1,523 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2017 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/cpu/ppc/ppc_context.h"
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif // XE_COMPILER_MSVC
|
||||
|
||||
DEFINE_string(test_path, "src/xenia/cpu/ppc/testing/",
|
||||
"Directory scanned for test files.");
|
||||
DEFINE_string(test_bin_path, "src/xenia/cpu/ppc/testing/bin/",
|
||||
"Directory with binary outputs of the test files.");
|
||||
|
||||
extern "C" void xe_call_native(void* context, void* fn);
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace test {
|
||||
|
||||
struct Context {
|
||||
uint64_t r[32];
|
||||
double f[32];
|
||||
vec128_t v[32]; // For now, only support 32 vector registers.
|
||||
uint32_t cr; // Condition register
|
||||
};
|
||||
|
||||
typedef std::vector<std::pair<std::string, std::string>> AnnotationList;
|
||||
|
||||
const uint32_t START_ADDRESS = 0x00000000;
|
||||
|
||||
struct TestCase {
|
||||
TestCase(uint32_t address, std::string& name)
|
||||
: address(address), name(name) {}
|
||||
uint32_t address;
|
||||
std::string name;
|
||||
AnnotationList annotations;
|
||||
};
|
||||
|
||||
class TestSuite {
|
||||
public:
|
||||
TestSuite(const std::wstring& src_file_path) : src_file_path(src_file_path) {
|
||||
name = src_file_path.substr(src_file_path.find_last_of(xe::kPathSeparator) +
|
||||
1);
|
||||
name = ReplaceExtension(name, L"");
|
||||
map_file_path = xe::to_wstring(FLAGS_test_bin_path) + name + L".map";
|
||||
bin_file_path = xe::to_wstring(FLAGS_test_bin_path) + name + L".bin";
|
||||
}
|
||||
|
||||
bool Load() {
|
||||
if (!ReadMap(map_file_path)) {
|
||||
XELOGE("Unable to read map for test %ls", src_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ReadAnnotations(src_file_path)) {
|
||||
XELOGE("Unable to read annotations for test %ls", src_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring name;
|
||||
std::wstring src_file_path;
|
||||
std::wstring map_file_path;
|
||||
std::wstring bin_file_path;
|
||||
std::vector<TestCase> test_cases;
|
||||
|
||||
private:
|
||||
std::wstring ReplaceExtension(const std::wstring& path,
|
||||
const std::wstring& new_extension) {
|
||||
std::wstring result = path;
|
||||
auto last_dot = result.find_last_of('.');
|
||||
result.replace(result.begin() + last_dot, result.end(), new_extension);
|
||||
return result;
|
||||
}
|
||||
|
||||
TestCase* FindTestCase(const std::string& name) {
|
||||
for (auto& test_case : test_cases) {
|
||||
if (test_case.name == name) {
|
||||
return &test_case;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ReadMap(const std::wstring& map_file_path) {
|
||||
FILE* f = fopen(xe::to_string(map_file_path).c_str(), "r");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
char line_buffer[BUFSIZ];
|
||||
while (fgets(line_buffer, sizeof(line_buffer), f)) {
|
||||
if (!strlen(line_buffer)) {
|
||||
continue;
|
||||
}
|
||||
// 0000000000000000 t test_add1\n
|
||||
char* newline = strrchr(line_buffer, '\n');
|
||||
if (newline) {
|
||||
*newline = 0;
|
||||
}
|
||||
char* t_test_ = strstr(line_buffer, " t test_");
|
||||
if (!t_test_) {
|
||||
continue;
|
||||
}
|
||||
std::string address(line_buffer, t_test_ - line_buffer);
|
||||
std::string name(t_test_ + strlen(" t test_"));
|
||||
test_cases.emplace_back(START_ADDRESS + std::stoul(address, 0, 16), name);
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadAnnotations(const std::wstring& src_file_path) {
|
||||
TestCase* current_test_case = nullptr;
|
||||
FILE* f = fopen(xe::to_string(src_file_path).c_str(), "r");
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
char line_buffer[BUFSIZ];
|
||||
while (fgets(line_buffer, sizeof(line_buffer), f)) {
|
||||
if (!strlen(line_buffer)) {
|
||||
continue;
|
||||
}
|
||||
// Eat leading whitespace.
|
||||
char* start = line_buffer;
|
||||
while (*start == ' ') {
|
||||
++start;
|
||||
}
|
||||
if (strncmp(start, "test_", strlen("test_")) == 0) {
|
||||
// Global test label.
|
||||
std::string label(start + strlen("test_"), strchr(start, ':'));
|
||||
current_test_case = FindTestCase(label);
|
||||
if (!current_test_case) {
|
||||
XELOGE("Test case %s not found in corresponding map for %ls",
|
||||
label.c_str(), src_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
} else if (strlen(start) > 3 && start[0] == '#' && start[1] == '_') {
|
||||
// Annotation.
|
||||
// We don't actually verify anything here.
|
||||
char* next_space = strchr(start + 3, ' ');
|
||||
if (next_space) {
|
||||
// Looks legit.
|
||||
std::string key(start + 3, next_space);
|
||||
std::string value(next_space + 1);
|
||||
while (value.find_last_of(" \t\n") == value.size() - 1) {
|
||||
value.erase(value.end() - 1);
|
||||
}
|
||||
if (!current_test_case) {
|
||||
XELOGE("Annotation outside of test case in %ls",
|
||||
src_file_path.c_str());
|
||||
return false;
|
||||
}
|
||||
current_test_case->annotations.emplace_back(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class TestRunner {
|
||||
public:
|
||||
TestRunner() {
|
||||
memory_size_ = 64 * 1024 * 1024;
|
||||
memory_ = memory::AllocFixed(nullptr, memory_size_,
|
||||
memory::AllocationType::kReserveCommit,
|
||||
memory::PageAccess::kExecuteReadWrite);
|
||||
|
||||
context_ = memory::AlignedAlloc<Context>(32);
|
||||
}
|
||||
|
||||
~TestRunner() {
|
||||
memory::DeallocFixed(memory_, memory_size_,
|
||||
memory::DeallocationType::kDecommitRelease);
|
||||
|
||||
memory::AlignedFree(context_);
|
||||
}
|
||||
|
||||
bool Setup(TestSuite& suite) {
|
||||
// TODO: Load the test suite into memory.
|
||||
FILE* file = filesystem::OpenFile(suite.bin_file_path, "rb");
|
||||
if (!file) {
|
||||
XELOGE("Failed to open file %s!", suite.bin_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
uint32_t file_length = static_cast<uint32_t>(ftell(file));
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (file_length > memory_size_) {
|
||||
XELOGE("Bin file %s is too big!", suite.bin_file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read entire file into our memory.
|
||||
fread(memory_, file_length, 1, file);
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Run(TestCase& test_case) {
|
||||
// Setup test state from annotations.
|
||||
if (!SetupTestState(test_case)) {
|
||||
XELOGE("Test setup failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute test.
|
||||
xe_call_native(reinterpret_cast<void*>(context_),
|
||||
reinterpret_cast<uint8_t*>(memory_) + test_case.address);
|
||||
return CheckTestResults(test_case);
|
||||
}
|
||||
|
||||
bool CompareRegWithString(const char* name, const char* value,
|
||||
char* out_value, size_t out_value_size,
|
||||
Context* ctx) const {
|
||||
int n;
|
||||
if (sscanf(name, "r%d", &n) == 1) {
|
||||
uint64_t expected = string_util::from_string<uint64_t>(value);
|
||||
if (ctx->r[n] != expected) {
|
||||
std::snprintf(out_value, out_value_size, "%016" PRIX64, ctx->r[n]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (sscanf(name, "f%d", &n) == 1) {
|
||||
if (std::strstr(value, "0x")) {
|
||||
// Special case: Treat float as integer.
|
||||
uint64_t expected = string_util::from_string<uint64_t>(value, true);
|
||||
|
||||
union {
|
||||
double f;
|
||||
uint64_t u;
|
||||
} f2u;
|
||||
f2u.f = ctx->f[n];
|
||||
|
||||
if (f2u.u != expected) {
|
||||
std::snprintf(out_value, out_value_size, "%016" PRIX64, f2u.u);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
double expected = string_util::from_string<double>(value);
|
||||
|
||||
// TODO(benvanik): epsilon
|
||||
if (ctx->f[n] != expected) {
|
||||
std::snprintf(out_value, out_value_size, "%f", ctx->f[n]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (sscanf(name, "v%d", &n) == 1) {
|
||||
vec128_t expected = string_util::from_string<vec128_t>(value);
|
||||
if (ctx->v[n] != expected) {
|
||||
std::snprintf(out_value, out_value_size, "[%.8X, %.8X, %.8X, %.8X]",
|
||||
ctx->v[n].i32[0], ctx->v[n].i32[1], ctx->v[n].i32[2],
|
||||
ctx->v[n].i32[3]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else if (std::strcmp(name, "cr") == 0) {
|
||||
// TODO(DrChat)
|
||||
/*
|
||||
uint64_t actual = ctx->cr();
|
||||
uint64_t expected = string_util::from_string<uint64_t>(value);
|
||||
if (actual != expected) {
|
||||
std::snprintf(out_value, out_value_size, "%016" PRIX64, actual);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return false; // true;
|
||||
} else {
|
||||
assert_always("Unrecognized register name: %s\n", name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SetRegFromString(const char* name, const char* value, Context* ctx) {
|
||||
int n;
|
||||
if (sscanf(name, "r%d", &n) == 1) {
|
||||
ctx->r[n] = string_util::from_string<uint64_t>(value);
|
||||
} else if (sscanf(name, "f%d", &n) == 1) {
|
||||
ctx->f[n] = string_util::from_string<double>(value);
|
||||
} else if (sscanf(name, "v%d", &n) == 1) {
|
||||
ctx->v[n] = string_util::from_string<vec128_t>(value);
|
||||
} else if (std::strcmp(name, "cr") == 0) {
|
||||
// this->set_cr(string_util::from_string<uint64_t>(value));
|
||||
} else {
|
||||
printf("Unrecognized register name: %s\n", name);
|
||||
}
|
||||
}
|
||||
|
||||
bool SetupTestState(TestCase& test_case) {
|
||||
for (auto& it : test_case.annotations) {
|
||||
if (it.first == "REGISTER_IN") {
|
||||
size_t space_pos = it.second.find(" ");
|
||||
auto reg_name = it.second.substr(0, space_pos);
|
||||
auto reg_value = it.second.substr(space_pos + 1);
|
||||
if (!SetRegFromString(reg_name.c_str(), reg_value.c_str(), context_)) {
|
||||
return false;
|
||||
}
|
||||
} else if (it.first == "MEMORY_IN") {
|
||||
XELOGW("Warning: MEMORY_IN unimplemented");
|
||||
return false;
|
||||
/*
|
||||
size_t space_pos = it.second.find(" ");
|
||||
auto address_str = it.second.substr(0, space_pos);
|
||||
auto bytes_str = it.second.substr(space_pos + 1);
|
||||
uint32_t address = std::strtoul(address_str.c_str(), nullptr, 16);
|
||||
auto p = memory->TranslateVirtual(address);
|
||||
const char* c = bytes_str.c_str();
|
||||
while (*c) {
|
||||
while (*c == ' ') ++c;
|
||||
if (!*c) {
|
||||
break;
|
||||
}
|
||||
char ccs[3] = {c[0], c[1], 0};
|
||||
c += 2;
|
||||
uint32_t b = std::strtoul(ccs, nullptr, 16);
|
||||
*p = static_cast<uint8_t>(b);
|
||||
++p;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckTestResults(TestCase& test_case) {
|
||||
char actual_value[2048];
|
||||
|
||||
bool any_failed = false;
|
||||
for (auto& it : test_case.annotations) {
|
||||
if (it.first == "REGISTER_OUT") {
|
||||
size_t space_pos = it.second.find(" ");
|
||||
auto reg_name = it.second.substr(0, space_pos);
|
||||
auto reg_value = it.second.substr(space_pos + 1);
|
||||
if (!CompareRegWithString(reg_name.c_str(), reg_value.c_str(),
|
||||
actual_value, xe::countof(actual_value),
|
||||
context_)) {
|
||||
any_failed = true;
|
||||
XELOGE("Register %s assert failed:\n", reg_name.c_str());
|
||||
XELOGE(" Expected: %s == %s\n", reg_name.c_str(), reg_value.c_str());
|
||||
XELOGE(" Actual: %s == %s\n", reg_name.c_str(), actual_value);
|
||||
}
|
||||
} else if (it.first == "MEMORY_OUT") {
|
||||
XELOGW("Warning: MEMORY_OUT unimplemented");
|
||||
any_failed = true;
|
||||
/*
|
||||
size_t space_pos = it.second.find(" ");
|
||||
auto address_str = it.second.substr(0, space_pos);
|
||||
auto bytes_str = it.second.substr(space_pos + 1);
|
||||
uint32_t address = std::strtoul(address_str.c_str(), nullptr, 16);
|
||||
auto base_address = memory->TranslateVirtual(address);
|
||||
auto p = base_address;
|
||||
const char* c = bytes_str.c_str();
|
||||
while (*c) {
|
||||
while (*c == ' ') ++c;
|
||||
if (!*c) {
|
||||
break;
|
||||
}
|
||||
char ccs[3] = {c[0], c[1], 0};
|
||||
c += 2;
|
||||
uint32_t current_address =
|
||||
address + static_cast<uint32_t>(p - base_address);
|
||||
uint32_t expected = std::strtoul(ccs, nullptr, 16);
|
||||
uint8_t actual = *p;
|
||||
if (expected != actual) {
|
||||
any_failed = true;
|
||||
XELOGE("Memory %s assert failed:\n", address_str.c_str());
|
||||
XELOGE(" Expected: %.8X %.2X\n", current_address, expected);
|
||||
XELOGE(" Actual: %.8X %.2X\n", current_address, actual);
|
||||
}
|
||||
++p;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
return !any_failed;
|
||||
}
|
||||
|
||||
void* memory_;
|
||||
size_t memory_size_;
|
||||
Context* context_;
|
||||
};
|
||||
|
||||
bool DiscoverTests(std::wstring& test_path,
|
||||
std::vector<std::wstring>& test_files) {
|
||||
auto file_infos = xe::filesystem::ListFiles(test_path);
|
||||
for (auto& file_info : file_infos) {
|
||||
if (file_info.name != L"." && file_info.name != L".." &&
|
||||
file_info.name.rfind(L".s") == file_info.name.size() - 2) {
|
||||
test_files.push_back(xe::join_paths(test_path, file_info.name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
int filter(unsigned int code) {
|
||||
if (code == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
return EXCEPTION_EXECUTE_HANDLER;
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
#endif // XE_COMPILER_MSVC
|
||||
|
||||
void ProtectedRunTest(TestSuite& test_suite, TestRunner& runner,
|
||||
TestCase& test_case, int& failed_count,
|
||||
int& passed_count) {
|
||||
#if XE_COMPILER_MSVC
|
||||
__try {
|
||||
#endif // XE_COMPILER_MSVC
|
||||
|
||||
if (!runner.Setup(test_suite)) {
|
||||
XELOGE(" TEST FAILED SETUP");
|
||||
++failed_count;
|
||||
}
|
||||
if (runner.Run(test_case)) {
|
||||
++passed_count;
|
||||
} else {
|
||||
XELOGE(" TEST FAILED");
|
||||
++failed_count;
|
||||
}
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
} __except (filter(GetExceptionCode())) {
|
||||
XELOGE(" TEST FAILED (UNSUPPORTED INSTRUCTION)");
|
||||
++failed_count;
|
||||
}
|
||||
#endif // XE_COMPILER_MSVC
|
||||
}
|
||||
|
||||
bool RunTests(const std::wstring& test_name) {
|
||||
int result_code = 1;
|
||||
int failed_count = 0;
|
||||
int passed_count = 0;
|
||||
|
||||
auto test_path_root =
|
||||
xe::fix_path_separators(xe::to_wstring(FLAGS_test_path));
|
||||
std::vector<std::wstring> test_files;
|
||||
if (!DiscoverTests(test_path_root, test_files)) {
|
||||
return false;
|
||||
}
|
||||
if (!test_files.size()) {
|
||||
XELOGE("No tests discovered - invalid path?");
|
||||
return false;
|
||||
}
|
||||
XELOGI("%d tests discovered.", (int)test_files.size());
|
||||
XELOGI("");
|
||||
|
||||
std::vector<TestSuite> test_suites;
|
||||
bool load_failed = false;
|
||||
for (auto& test_path : test_files) {
|
||||
TestSuite test_suite(test_path);
|
||||
if (!test_name.empty() && test_suite.name != test_name) {
|
||||
continue;
|
||||
}
|
||||
if (!test_suite.Load()) {
|
||||
XELOGE("TEST SUITE %ls FAILED TO LOAD", test_path.c_str());
|
||||
load_failed = true;
|
||||
continue;
|
||||
}
|
||||
test_suites.push_back(std::move(test_suite));
|
||||
}
|
||||
if (load_failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TestRunner runner;
|
||||
for (auto& test_suite : test_suites) {
|
||||
XELOGI("%ls.s:", test_suite.name.c_str());
|
||||
|
||||
for (auto& test_case : test_suite.test_cases) {
|
||||
XELOGI(" - %s", test_case.name.c_str());
|
||||
ProtectedRunTest(test_suite, runner, test_case, failed_count,
|
||||
passed_count);
|
||||
}
|
||||
|
||||
XELOGI("");
|
||||
}
|
||||
|
||||
XELOGI("");
|
||||
XELOGI("Total tests: %d", failed_count + passed_count);
|
||||
XELOGI("Passed: %d", passed_count);
|
||||
XELOGI("Failed: %d", failed_count);
|
||||
|
||||
return failed_count ? false : true;
|
||||
}
|
||||
|
||||
int main(const std::vector<std::wstring>& args) {
|
||||
// Grab test name, if present.
|
||||
std::wstring test_name;
|
||||
if (args.size() >= 2) {
|
||||
test_name = args[1];
|
||||
}
|
||||
|
||||
return RunTests(test_name) ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-cpu-ppc-test", L"xenia-cpu-ppc-test [test name]",
|
||||
xe::cpu::test::main);
|
|
@ -0,0 +1,195 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2017 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// r3 = context
|
||||
// this does not touch r1, r3, r4, r13
|
||||
.load_registers_ctx:
|
||||
ld r0, 0x00(r3)
|
||||
// r1 cannot be used
|
||||
ld r2, 0x10(r3)
|
||||
// r3 will be loaded before the call
|
||||
// r4 will be loaded before the call
|
||||
ld r5, 0x28(r3)
|
||||
ld r6, 0x30(r3)
|
||||
ld r7, 0x38(r3)
|
||||
ld r8, 0x40(r3)
|
||||
ld r9, 0x48(r3)
|
||||
ld r10, 0x50(r3)
|
||||
ld r11, 0x58(r3)
|
||||
ld r12, 0x60(r3)
|
||||
// r13 cannot be used (OS use only)
|
||||
ld r14, 0x70(r3)
|
||||
ld r15, 0x78(r3)
|
||||
ld r16, 0x80(r3)
|
||||
ld r17, 0x88(r3)
|
||||
ld r18, 0x90(r3)
|
||||
ld r19, 0x98(r3)
|
||||
ld r20, 0xA0(r3)
|
||||
ld r21, 0xA8(r3)
|
||||
ld r22, 0xB0(r3)
|
||||
ld r23, 0xB8(r3)
|
||||
ld r24, 0xC0(r3)
|
||||
ld r25, 0xC8(r3)
|
||||
ld r26, 0xD0(r3)
|
||||
ld r27, 0xD8(r3)
|
||||
ld r28, 0xE0(r3)
|
||||
ld r29, 0xE8(r3)
|
||||
ld r30, 0xF0(r3)
|
||||
ld r31, 0xF8(r3)
|
||||
blr
|
||||
|
||||
// r3 = context
|
||||
// this does not save r1, r3, r13
|
||||
.save_registers_ctx:
|
||||
std r0, 0x00(r3)
|
||||
// r1 cannot be used
|
||||
std r2, 0x10(r3)
|
||||
// r3 will be saved later
|
||||
std r4, 0x20(r3)
|
||||
std r5, 0x28(r3)
|
||||
std r6, 0x30(r3)
|
||||
std r7, 0x38(r3)
|
||||
std r8, 0x40(r3)
|
||||
std r9, 0x48(r3)
|
||||
std r10, 0x50(r3)
|
||||
std r11, 0x58(r3)
|
||||
std r12, 0x60(r3)
|
||||
// r13 cannot be used (OS use only)
|
||||
std r14, 0x70(r3)
|
||||
std r15, 0x78(r3)
|
||||
std r16, 0x80(r3)
|
||||
std r17, 0x88(r3)
|
||||
std r18, 0x90(r3)
|
||||
std r19, 0x98(r3)
|
||||
std r20, 0xA0(r3)
|
||||
std r21, 0xA8(r3)
|
||||
std r22, 0xB0(r3)
|
||||
std r23, 0xB8(r3)
|
||||
std r24, 0xC0(r3)
|
||||
std r25, 0xC8(r3)
|
||||
std r26, 0xD0(r3)
|
||||
std r27, 0xD8(r3)
|
||||
std r28, 0xE0(r3)
|
||||
std r29, 0xE8(r3)
|
||||
std r30, 0xF0(r3)
|
||||
std r31, 0xF8(r3)
|
||||
blr
|
||||
|
||||
// void xe_call_native(Context* ctx, void* func)
|
||||
.globl xe_call_native
|
||||
xe_call_native:
|
||||
mflr r12
|
||||
stw r12, -0x8(r1)
|
||||
stwu r1, -0x380(r1) // 0x200(gpr + fp) + 0x200(vr)
|
||||
|
||||
// Save nonvolatile registers on the stack.
|
||||
std r2, 0x110(r1)
|
||||
std r3, 0x118(r1) // Store the context, this will be needed later.
|
||||
std r14, 0x170(r1)
|
||||
std r15, 0x178(r1)
|
||||
std r16, 0x180(r1)
|
||||
std r17, 0x188(r1)
|
||||
std r18, 0x190(r1)
|
||||
std r19, 0x198(r1)
|
||||
std r20, 0x1A0(r1)
|
||||
std r21, 0x1A8(r1)
|
||||
std r22, 0x1B0(r1)
|
||||
std r23, 0x1B8(r1)
|
||||
std r24, 0x1C0(r1)
|
||||
std r25, 0x1C8(r1)
|
||||
std r26, 0x1D0(r1)
|
||||
std r27, 0x1D8(r1)
|
||||
std r28, 0x1E0(r1)
|
||||
std r29, 0x1E8(r1)
|
||||
std r30, 0x1F0(r1)
|
||||
std r31, 0x1F8(r1)
|
||||
|
||||
stfd f14, 0x270(r1)
|
||||
stfd f15, 0x278(r1)
|
||||
stfd f16, 0x280(r1)
|
||||
stfd f17, 0x288(r1)
|
||||
stfd f18, 0x290(r1)
|
||||
stfd f19, 0x298(r1)
|
||||
stfd f20, 0x2A0(r1)
|
||||
stfd f21, 0x2A8(r1)
|
||||
stfd f22, 0x2B0(r1)
|
||||
stfd f23, 0x2B8(r1)
|
||||
stfd f24, 0x2C0(r1)
|
||||
stfd f25, 0x2C8(r1)
|
||||
stfd f26, 0x2D0(r1)
|
||||
stfd f27, 0x2D8(r1)
|
||||
stfd f28, 0x2E0(r1)
|
||||
stfd f29, 0x2E8(r1)
|
||||
stfd f30, 0x2F0(r1)
|
||||
stfd f31, 0x2F8(r1)
|
||||
|
||||
// Load registers from context
|
||||
bl load_registers_ctx
|
||||
|
||||
// Call the test routine
|
||||
mtctr r4
|
||||
ld r4, 0x28(r3)
|
||||
ld r3, 0x20(r3)
|
||||
bctrl
|
||||
|
||||
// Temporarily store r3 into the stack (in the place of r0)
|
||||
std r3, 0x100(r1)
|
||||
|
||||
// Store registers into context
|
||||
ld r3, 0x118(r1)
|
||||
bl save_registers_ctx
|
||||
|
||||
// Now store r3
|
||||
ld r4, 0x100(r1)
|
||||
std r4, 0x20(r3)
|
||||
|
||||
// Restore nonvolatile registers from the stack
|
||||
ld r2, 0x110(r1)
|
||||
ld r14, 0x170(r1)
|
||||
ld r15, 0x178(r1)
|
||||
ld r16, 0x180(r1)
|
||||
ld r17, 0x188(r1)
|
||||
ld r18, 0x190(r1)
|
||||
ld r19, 0x198(r1)
|
||||
ld r20, 0x1A0(r1)
|
||||
ld r21, 0x1A8(r1)
|
||||
ld r22, 0x1B0(r1)
|
||||
ld r23, 0x1B8(r1)
|
||||
ld r24, 0x1C0(r1)
|
||||
ld r25, 0x1C8(r1)
|
||||
ld r26, 0x1D0(r1)
|
||||
ld r27, 0x1D8(r1)
|
||||
ld r28, 0x1E0(r1)
|
||||
ld r29, 0x1E8(r1)
|
||||
ld r30, 0x1F0(r1)
|
||||
ld r31, 0x1F8(r1)
|
||||
|
||||
lfd f14, 0x270(r1)
|
||||
lfd f15, 0x278(r1)
|
||||
lfd f16, 0x280(r1)
|
||||
lfd f17, 0x288(r1)
|
||||
lfd f18, 0x290(r1)
|
||||
lfd f19, 0x298(r1)
|
||||
lfd f20, 0x2A0(r1)
|
||||
lfd f21, 0x2A8(r1)
|
||||
lfd f22, 0x2B0(r1)
|
||||
lfd f23, 0x2B8(r1)
|
||||
lfd f24, 0x2C0(r1)
|
||||
lfd f25, 0x2C8(r1)
|
||||
lfd f26, 0x2D0(r1)
|
||||
lfd f27, 0x2D8(r1)
|
||||
lfd f28, 0x2E0(r1)
|
||||
lfd f29, 0x2E8(r1)
|
||||
lfd f30, 0x2F0(r1)
|
||||
lfd f31, 0x2F8(r1)
|
||||
|
||||
addi r1, r1, 0x380
|
||||
lwz r12, -0x8(r1)
|
||||
mtlr r12
|
||||
blr
|
|
@ -36,3 +36,33 @@ project("xenia-cpu-ppc-tests")
|
|||
|
||||
-- xenia-base needs this
|
||||
links({"xenia-ui"})
|
||||
|
||||
if ARCH == "ppc64" or ARCH == "powerpc64" then
|
||||
|
||||
project("xenia-cpu-ppc-nativetests")
|
||||
uuid("E381E8EE-65CD-4D5E-9223-D9C03B2CE78C")
|
||||
kind("ConsoleApp")
|
||||
language("C++")
|
||||
links({
|
||||
"xenia-base",
|
||||
"xenia-core",
|
||||
})
|
||||
files({
|
||||
"ppc_testing_native_main.cc",
|
||||
"../../../base/main_"..platform_suffix..".cc",
|
||||
})
|
||||
files({
|
||||
"*.s",
|
||||
})
|
||||
includedirs({
|
||||
project_root.."/third_party/gflags/src",
|
||||
})
|
||||
filter("files:*.s")
|
||||
flags({"ExcludeFromBuild"})
|
||||
filter({})
|
||||
|
||||
files({
|
||||
"ppc_testing_native_thunks.s",
|
||||
})
|
||||
|
||||
end
|
Loading…
Reference in New Issue