Repository cleanup & README.md. (#5)

This commit is contained in:
Skyth (Asilkan) 2025-02-27 00:11:11 +03:00 committed by GitHub
parent cd6fcb33bd
commit 04e716178b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 340 additions and 268 deletions

View File

@ -24,8 +24,7 @@ add_subdirectory(${XENONANALYSE_ROOT})
add_subdirectory(${XENONRECOMP_ROOT})
add_subdirectory(${XENONUTILS_ROOT})
# Only build sample and tests if this is the top level project
# Only tests if this is the top level project
if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
add_subdirectory(XenonSample)
add_subdirectory(XenonTests)
endif()

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2025 hedge-dev and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

258
README.md Normal file
View File

@ -0,0 +1,258 @@
# XenonRecomp
XenonRecomp is a tool that converts Xbox 360 executables into C++ code, which can then be recompiled for any platform. Currently, it only supports x86 platforms due to the use of x86 intrinsics.
This project was heavily inspired by [N64: Recompiled](https://github.com/N64Recomp/N64Recomp), a similar tool for N64 executables.
## Implementation Details
### Instructions
The instructions are directly converted without any effort to make them resemble decompiled code, meaning the output is not very human-readable. The CPU state is passed as an argument to every PPC function, which includes definitions for every PPC register and their current values at the time of execution. The second argument is the base address pointer, as the Xbox 360 CPU uses 32-bit pointers.
A good amount of PPC instructions are implemented, with missing ones primarily being variants of already implemented instructions. Some instructions, like the D3D unpack/pack instructions, do not support all operand types. When a missing case is encountered, a warning is generated, or a debug break is inserted into the converted C++ code.
The instruction implementations operate on little-endian values. However, since the Xbox 360 is a big-endian machine, the memory load instructions swap endianness when reading values, and memory store instructions reverse it to big-endian before writing. All the memory loads and stores are marked volatile to prevent Clang from doing unsafe code reordering.
Vector registers' endianness handling is more complicated. Instead of swapping individual 32-bit elements, the recompiler chooses to reverse the entire 16-byte vector. Instructions must account for this reversed order, such as using the WZY components instead of XYZ in dot products or requiring reversed arguments for vector pack instructions.
The FPU expects denormalized numbers to remain unmodified, while VMX instructions always flush them. This is managed by storing the current floating-point state in the CPU state struct and enabling or disabling denormal flushing as necessary before executing each instruction.
Most VMX instructions are implemented using x86 intrinsics. Luckily, the number of AVX intrinsics used is relatively low, so adding support for other architectures using libraries like [SIMD Everywhere](https://github.com/simd-everywhere/simde) might be possible.
### MMIO
MMIO, which is typically used for hardware operations such as XMA decoding, is currently unimplemented. There is an unfinished attempt to implement MMIO, but supporting it may be non-trivial and could require advanced analysis of instructions.
### Indirect Functions
Virtual function calls are resolved by creating a "perfect hash table" at runtime, where dereferencing a 64-bit pointer (using the original instruction address multiplied by 2) gives the address of the recompiled function. This was previously implemented by creating an 8 GB virtual allocation, but it had too much memory pressure. Now it relies on function addresses being placed after the valid XEX memory region in the base memory pointer. These regions are exported as macros in the output `ppc_config.h` file.
### Jump Tables
Jump tables, at least in older Xbox 360 binaries, often have predictable assembly patterns, making them easy to detect statically without needing a virtual machine. XenonAnalyse has logic for detecting jump tables in Sonic Unleashed, though variations in other games (likely due to updates in the Xbox 360 compiler) may require modifications to the detection logic. Currently, there is no fully generic solution for handling jump tables, so updates to the detection logic may be needed for other games.
The typical way to find jump tables is by searching for the `mtctr r0` instruction. It will almost always be followed with a `bctr`, with the previous instructions computing the jump address.
XenonAnalyse generates a TOML file containing detected jump tables, which can be referenced in the main TOML config file. This allows the recompiler to generate real switch cases for these jump tables.
### Function Boundary Analysis
XenonAnalyse includes a function boundary analyzer that works well in most cases. Functions with stack space have their boundaries defined in the `.pdata` segment of the XEX. For functions not found in this segment, the analyzer detects the start of functions by searching for branch link instructions, and determines their length via static analysis.
However, the analyzer struggles with functions containing jump tables, since they look like tail calls without enough information. While there is currently no solution for this, it might be relatively simple to extend the function analyzer to account for jump tables defined in the TOML file. As a workaround, the recompiler TOML file allows users to manually define function boundaries.
### Exceptions
The recompiler currently does not support exceptions. This is challenging due to the use of the link register and the fact that exception handlers can jump to arbitrary code locations.
### setjmp
`setjmp` and `longjmp` are implemented by redirecting them to native implementations. Thanks to the Xbox 360's large number of vector registers, the guest CPU state struct is large enough to hold the x86 CPU state and potentially states from other architectures.
### Optimizations
Since Xbox 360 binaries typically follow a stable ABI, we can make certain assumptions about code structure, allowing the Clang compiler to generate better code. Several optimization options are available in the recompiler, but it's recommended to test them only after having a successfully functioning recompilation.
The link register can be skipped assuming the game does not utilize exceptions, as the whole process of recompilation already takes care of function return behavior.
The following registers, assuming the game doesn't violate the ABI, can be safely converted into local variables, as they never leave the function scope:
* Count register
* XER
* Reserved register
* Condition registers
* Non argument registers
* Non volatile registers
The local variable optimization particularly introduces the most improvements, as the calls to the register restore/save functions can be completely removed, and the redundant stores to the PPC context struct can be eliminated. In [Unleashed Recompiled](https://github.com/hedge-dev/UnleashedRecomp), the executable size decreases by around 20 MB with these optimizations, and frame times are reduced by several milliseconds.
### Patch Mechanisms
XenonRecomp defines PPC functions in a way that makes them easy to hook, using techniques in the Clang compiler. By aliasing a PPC function to an "implementation function" and marking the original function as weakly linked, users can override it with a custom implementation while retaining access to the original function:
```cpp
PPC_FUNC_IMPL(__imp__sub_XXXXXXXX);
PPC_FUNC(sub_XXXXXXXX)
{
__imp__sub_XXXXXXXX(ctx, base);
}
```
Additionally, mid-asm hooks can be inserted directly into the translated C++ code at specific instruction addresses. The recompiler inserts these function calls, and users are responsible for implementing them in their recompilation project. The linker resolves them during compilation.
## Usage
### XenonAnalyse
XenonAnalyse, when used as a command-line application, allows an XEX file to be passed as an input argument to output a TOML file containing all the detected jump tables in the executable:
```
XenonAnalyse [input XEX file path] [output jump table TOML file path]
```
However, as explained in the earlier sections, due to variations between games, additional support may be needed to handle different patterns.
[An example jump table TOML file can be viewed in the Unleashed Recompiled repository.](https://github.com/hedge-dev/UnleashedRecomp/blob/main/UnleashedRecompLib/config/SWA_switch_tables.toml)
### XenonRecomp
XenonRecomp accepts a TOML file with recompiler configurations and the path to the `ppc_context.h` file located in the XenonUtils directory:
```
XenonRecomp [input TOML file path] [input PPC context header file path]
```
[An example recompiler TOML file can be viewed in the Unleashed Recompiled repository.](https://github.com/hedge-dev/UnleashedRecomp/blob/main/UnleashedRecompLib/config/SWA.toml)
#### Main
```toml
[main]
file_path = "../private/default.xex"
patch_file_path = "../private/default.xexp"
patched_file_path = "../private/default_patched.xex"
out_directory_path = "../ppc"
switch_table_file_path = "SWA_switch_tables.toml"
```
All the paths are relative to the directory where the TOML file is stored.
Property|Description
-|-
file_path|Path to the XEX file.
patch_file_path|Path to the XEXP file. This is not required if the game has no title updates.
patched_file_path|Path to the patched XEX file. XenonRecomp will create this file automatically if it is missing and reuse it in subsequent recompilations. It does nothing if no XEXP file is specified. You can pass this output file to XenonAnalyse.
out_directory_path|Path to the directory that will contain the output C++ code. This directory must exist before running the recompiler.
switch_table_file_path|Path to the TOML file containing the jump table definitions. The recompiler uses this file to convert jump tables to real switch cases.
#### Optimizations
```toml
skip_lr = false
skip_msr = false
ctr_as_local = false
xer_as_local = false
reserved_as_local = false
cr_as_local = false
non_argument_as_local = false
non_volatile_as_local = false
```
Enables or disables various optimizations explained earlier in the documentation. It is recommended not to enable these optimizations until you have a successfully running recompilation.
#### Register Restore & Save Functions
```toml
restgprlr_14_address = 0x831B0B40
savegprlr_14_address = 0x831B0AF0
restfpr_14_address = 0x831B144C
savefpr_14_address = 0x831B1400
restvmx_14_address = 0x831B36E8
savevmx_14_address = 0x831B3450
restvmx_64_address = 0x831B377C
savevmx_64_address = 0x831B34E4
```
Xbox 360 binaries feature specialized register restore & save functions that act similarly to switch case fallthroughs. Every function that utilizes non-volatile registers either has an inlined version of these functions or explicitly calls them. The recompiler requires the starting address of each restore/save function in the TOML file to recompile them correctly. These functions could likely be auto-detected, but there is currently no mechanism for it.
Property|Description
-|-
restgprlr_14_address|Start address of the `__restgprlr_14` function. It starts with `ld r14, -0x98(r1)`, repeating the same operation for the rest of the non-volatile registers and restoring the link register at the end.
savegprlr_14_address|Start address of the `__savegprlr_14` function. It starts with `std r14, -0x98(r1)`, repeating the same operation for the rest of the non-volatile registers and saving the link register at the end.
restfpr_14_address|Start address of the `__restfpr_14` function. It starts with `lfd f14, -0x90(r12)`, repeating the same operation for the rest of the non-volatile FPU registers.
savefpr_14_address|Start address of the `__savefpr_14` function. It starts with `stfd r14, -0x90(r12)`, repeating the same operation for the rest of the non-volatile FPU registers.
restvmx_14_address|Start address of the `__restvmx_14` function. It starts with `li r11, -0x120` and `lvx v14, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers until `v31`.
savevmx_14_address|Start address of the `__savevmx_14` function. It starts with `li r11, -0x120` and `stvx v14, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers until `v31`.
restvmx_64_address|Start address of the `__restvmx_64` function. It starts with `li r11, -0x400` and `lvx128 v64, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers.
savevmx_64_address|Start address of the `__savevmx_64` function. It starts with `li r11, -0x400` and `stvx128 v64, r11, r12`, repeating the same operation for the rest of the non-volatile VMX registers.
#### longjmp & setjmp
```toml
longjmp_address = 0x831B6790
setjmp_address = 0x831B6AB0
```
These are addresses for the `longjmp` and `setjmp` functions in the executable. The recompiler directly redirects these functions to native versions. The implementation of these functions might vary between games. In some cases, you might find `longjmp` by looking for calls to `RtlUnwind`, and `setjmp` typically appears just after it.
If the game does not use these functions, you can remove the properties from the TOML file.
#### Explicit Function Boundaries
```toml
functions = [
{ address = 0x824E7EF0, size = 0x98 },
{ address = 0x824E7F28, size = 0x60 },
]
```
You can define function boundaries explicitly using the `functions` property if XenonAnalyse fails to analyze them correctly, for example, with functions containing jump tables.
#### Invalid Instruction Skips
```toml
invalid_instructions = [
{ data = 0x00000000, size = 4 }, # Padding
{ data = 0x831B1C90, size = 8 }, # C++ Frame Handler
{ data = 0x8324B3BC, size = 8 }, # C Specific Frame Handler
{ data = 0x831C8B50, size = 8 },
{ data = 0x00485645, size = 44 } # End of .text
]
```
In the `invalid_instructions` property, you can define 32-bit integer values that instruct the recompiler to skip over certain bytes when it encounters them. For example, in Unleashed Recompiled, these are used to skip over exception handling data, which is placed between functions but is not valid code.
#### Mid-asm Hooks
```toml
[[midasm_hook]]
name = "IndexBufferLengthMidAsmHook"
address = 0x82E26244
registers = ["r3"]
```
```cpp
void IndexBufferLengthMidAsmHook(PPCRegister& r3)
{
// ...
}
```
You can define multiple mid-asm hooks in the TOML file, allowing the recompiler to insert function calls at specified addresses. When implementing them in your recompilation project, the linker will resolve the calls automatically.
Property|Description
-|-
name|Function name of the mid-asm hook. You can reuse function names to place the same implementation at multiple addresses. Otherwise, unique implementations must have unique names.
address|Address of the instruction where the function call will be placed. This does not overwrite the instruction at the specified address.
registers|Registers to pass as arguments to the mid-asm hook. This is a list of registers because the local variable optimization does not keep optimized registers within the PPC context struct.
return|Set to `true` to indicate that the function where the hook was inserted should immediately return after calling the mid-asm hook.
return_on_true|Set to `true` to indicate that the function should return if the mid-asm hook call returns `true`.
return_on_false|Set to `true` to indicate that the function should return if the mid-asm hook call returns `false`.
jump_address|The address to jump to immediately after calling the mid-asm hook. The address must be within the same function where the hook was placed.
jump_address_on_true|The address to jump to if the mid-asm hook returns `true`. The address must be within the same function where the hook was placed.
jump_address_on_false|The address to jump to if the mid-asm hook returns `false`. The address must be within the same function where the hook was placed.
after_instruction|Set to `true` to place the mid-asm hook immediately after the instruction, instead of before.
Certain properties are mutually exclusive. For example, you cannot use both `return` and `jump_address`, and direct or conditional returns/jumps cannot be mixed. The recompiler is going to show warnings if this is not followed.
### Tests
XenonRecomp can recompile Xenia's PPC tests and execute them through the XenonTests project in the repository. After building the tests using Xenia's build system, XenonRecomp can process the `src/xenia/cpu/ppc/testing/bin` directory as input, generating C++ files in the specified output directory:
```
XenonRecomp [input testing directory path] [input PPC context header file path] [output directory path]
```
Once the files are generated, refresh XenonTests' CMake cache to make them appear in the project. The tests can then be executed to compare the results of instructions against the expected values.
## Building
The project requires CMake 3.20 or later and Clang 18 or later to build. Since the repository includes submodules, ensure you clone it recursively.
Compilers other than Clang have not been tested and are not recommended, including for recompilation output. The project relies on compiler-specific intrinsics and techniques that may not function correctly on other compilers, and many optimization methods depend on Clang's code generation.
On Windows, you can use the clang-cl toolset and open the project in Visual Studio's CMake integration.
## Special Thanks
This project could not have been possible without the [Xenia](https://github.com/xenia-project/xenia) emulator, as many parts of the CPU code conversion process has been implemented by heavily referencing its PPC code translator. The project also uses code from [Xenia Canary](https://github.com/xenia-canary/xenia-canary) to patch XEX binaries.

View File

@ -172,32 +172,25 @@ void* SearchMask(const void* source, const uint32_t* compare, size_t compareCoun
return nullptr;
}
int main()
{
const auto file = LoadFile("private/default.xex");
auto image = Image::ParseImage(file.data(), file.size());
static std::string out;
std::string out;
auto println = [&]<class... Args>(fmt::format_string<Args...> fmt, Args&&... args)
{
template<class... Args>
static void println(fmt::format_string<Args...> fmt, Args&&... args)
{
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
out += '\n';
};
//for (const auto& section : image.sections)
//{
// image.symbols.emplace(section.name, section.base, section.size, Symbol_Section);
//}
};
// MakeMask((uint32_t*)image.Find(0x82C40D84), 6);
int main(int argc, char** argv)
{
if (argc < 3)
{
printf("Usage: XenonAnalyse [input XEX file path] [output jump table TOML file path]");
return EXIT_SUCCESS;
}
//auto data = "\x4D\x99\x00\x20";
//auto data2 = ByteSwap((2129));
//ppc_insn insn;
//ppc_insn insn2;
//ppc::Disassemble(data, 0, insn);
//ppc::Disassemble(&data2, 0, insn2);
//auto op = PPC_OP(insn.instruction);
//auto xop = PPC_XOP(insn.instruction);
const auto file = LoadFile(argv[1]);
auto image = Image::ParseImage(file.data(), file.size());
auto printTable = [&](const SwitchTable& table)
{
@ -217,27 +210,7 @@ int main()
std::vector<SwitchTable> switches{};
auto insertTable = [&](size_t base, size_t defaultLabel, size_t r, size_t nLabels, uint32_t type)
{
auto& sw = switches.emplace_back();
sw.base = base;
sw.defaultLabel = defaultLabel;
sw.r = r;
sw.labels.resize(nLabels);
sw.type = type;
};
println("# Generated by PowerAnalyse");
insertTable(0x830ADAD8, 0x830ADB28, 11, 0x1B, SWITCH_COMPUTED);
insertTable(0x830AE1B0, 0x830AE21C, 11, 0x1B, SWITCH_BYTEOFFSET);
insertTable(0x82CFE120, 0x82CFDE68, 11, 0x10, SWITCH_SHORTOFFSET);
println("# ---- MANUAL JUMPTABLE ----");
for (auto& table : switches)
{
ReadTable(image, table);
printTable(table);
}
println("# Generated by XenonAnalyse");
auto scanPattern = [&](uint32_t* pattern, size_t count, size_t type)
{
@ -332,122 +305,8 @@ int main()
scanPattern(offsetSwitch, std::size(offsetSwitch), SWITCH_BYTEOFFSET);
scanPattern(wordOffsetSwitch, std::size(wordOffsetSwitch), SWITCH_SHORTOFFSET);
FILE* f = fopen("out/switches.toml", "w");
fwrite(out.data(), 1, out.size(), f);
fclose(f);
std::ofstream f(argv[2]);
f.write(out.data(), out.size());
uint32_t cxxFrameHandler = ByteSwap(0x831B1C90);
uint32_t cSpecificFrameHandler = ByteSwap(0x8324B3BC);
image.symbols.emplace("__CxxFrameHandler", 0x831B1C90, 0x38, Symbol_Function);
image.symbols.emplace("__C_specific_handler", 0x8324B3BC, 0x38, Symbol_Function);
image.symbols.emplace("memcpy", 0x831B0ED0, 0x488, Symbol_Function);
image.symbols.emplace("memset", 0x831B0BA0, 0xA0, Symbol_Function);
image.symbols.emplace("blkmov", 0x831B1358, 0xA8, Symbol_Function);
image.symbols.emplace(fmt::format("sub_{:X}", 0x82EF5D78), 0x82EF5D78, 0x3F8, Symbol_Function);
// auto fnd = Function::Analyze(image.Find(0x82C40D58), image.size, 0x82C40D58);
std::vector<Function> functions;
auto& pdata = *image.Find(".pdata");
size_t count = pdata.size / sizeof(IMAGE_CE_RUNTIME_FUNCTION);
auto* pf = (IMAGE_CE_RUNTIME_FUNCTION*)pdata.data;
for (size_t i = 0; i < count; i++)
{
auto fn = pf[i];
fn.BeginAddress = ByteSwap(fn.BeginAddress);
fn.Data = ByteSwap(fn.Data);
auto& f = functions.emplace_back();
f.base = fn.BeginAddress;
f.size = fn.FunctionLength * 4;
if (f.base == 0x82BD7420)
{
__builtin_debugtrap();
}
image.symbols.emplace(fmt::format("sub_{:X}", f.base), f.base, f.size, Symbol_Function);
}
auto sym = image.symbols.find(0x82BD7420);
std::vector<Function> missingFunctions;
for (const auto& section : image.sections)
{
if (!(section.flags & SectionFlags_Code))
{
continue;
}
size_t base = section.base;
uint8_t* data = section.data;
uint8_t* dataEnd = section.data + section.size;
const Symbol* prevSymbol = nullptr;
while (data < dataEnd)
{
if (*(uint32_t*)data == 0)
{
data += 4;
base += 4;
continue;
}
if (*(uint32_t*)data == cxxFrameHandler || *(uint32_t*)data == cSpecificFrameHandler)
{
data += 8;
base += 8;
continue;
}
auto fnSymbol = image.symbols.find(base);
if (fnSymbol != image.symbols.end() && fnSymbol->type == Symbol_Function)
{
assert(fnSymbol->address == base);
prevSymbol = &*fnSymbol;
base += fnSymbol->size;
data += fnSymbol->size;
}
else
{
auto& missingFn = missingFunctions.emplace_back(Function::Analyze(data, dataEnd - data, base));
base += missingFn.size;
data += missingFn.size;
fmt::println("sub_{:X}", missingFn.base);
}
}
}
//ppc_insn insn;
//uint8_t c[4] = { 0x10, 0x00, 0x59, 0xC3 };
//ppc::Disassemble(c, 0x831D6C64, insn);
//fmt::println("{:20}{}", insn.opcode->name, insn.op_str);
const auto entrySymbol = image.symbols.find(image.entry_point);
assert(entrySymbol != image.symbols.end());
const auto entrySize = entrySymbol->size;
image.symbols.erase(entrySymbol);
image.symbols.emplace("_start", image.entry_point, entrySize, Symbol_Function);
fmt::println("FUNCTIONS");
for (const auto& fn : functions)
{
fmt::println("\tsub_{:X}", fn.base);
}
fmt::println("");
fmt::println("SECTIONS");
for (const auto& section : image.sections)
{
printf("Section %.8s\n", section.name.c_str());
printf("\t%X-%X\n", section.base, section.base + section.size);
}
fmt::println("");
return 0;
return EXIT_SUCCESS;
}

View File

@ -3,6 +3,14 @@
int main(int argc, char* argv[])
{
#ifndef XENON_RECOMP_CONFIG_FILE_PATH
if (argc < 3)
{
printf("Usage: XenonRecomp [input TOML file path] [PPC context header file path]");
return EXIT_SUCCESS;
}
#endif
const char* path =
#ifdef XENON_RECOMP_CONFIG_FILE_PATH
XENON_RECOMP_CONFIG_FILE_PATH
@ -40,5 +48,5 @@ int main(int argc, char* argv[])
TestRecompiler::RecompileTests(path, argv[2]);
}
return 0;
return EXIT_SUCCESS;
}

View File

@ -2578,7 +2578,11 @@ void Recompiler::SaveCurrentOutData(const std::string_view& name)
bool shouldWrite = true;
// Check if an identical file already exists first to not trigger recompilation
std::string filePath = fmt::format("{}/{}/{}", config.directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
std::string directoryPath = config.directoryPath;
if (!directoryPath.empty())
directoryPath += "/";
std::string filePath = fmt::format("{}{}/{}", directoryPath, config.outDirectoryPath, name.empty() ? cppName : name);
FILE* f = fopen(filePath.c_str(), "rb");
if (f)
{

View File

@ -51,7 +51,7 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
recompiler.println("#define PPC_CONFIG_H_INCLUDED");
recompiler.println("#include <ppc_context.h>\n");
recompiler.println("#define __debugbreak()\n");
recompiler.println("#define __builtin_debugtrap()\n");
for (auto& fn : recompiler.functions)
{
@ -102,8 +102,12 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
fmt::println(file, "#define PPC_CONFIG_H_INCLUDED");
fmt::println(file, "#include <ppc_context.h>");
fmt::println(file, "#ifdef _WIN32");
fmt::println(file, "#include <Windows.h>");
fmt::println(file, "#include <print>\n");
fmt::println(file, "#else");
fmt::println(file, "#include <sys/mman.h>");
fmt::println(file, "#endif");
fmt::println(file, "#include <fmt/core.h>\n");
fmt::println(file, "#define PPC_CHECK_VALUE_U(f, lhs, rhs) if (lhs != rhs) fmt::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{:X}}\", lhs)\n");
fmt::println(file, "#define PPC_CHECK_VALUE_F(f, lhs, rhs) if (lhs != rhs) fmt::println(#f \" \" #lhs \" EXPECTED \" #rhs \" ACTUAL {{}}\", lhs)\n");
@ -274,7 +278,11 @@ void TestRecompiler::RecompileTests(const char* srcDirectoryPath, const char* ds
}
fmt::println(file, "int main() {{");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
fmt::println(file, "#ifdef _WIN32");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(VirtualAlloc(nullptr, 0x100000000ull, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));");
fmt::println(file, "#else");
fmt::println(file, "\tuint8_t* base = reinterpret_cast<uint8_t*>(mmap(NULL, 0x100000000ull, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0));");
fmt::println(file, "#endif");
fwrite(main.data(), 1, main.size(), file);
fmt::println(file, "\treturn 0;");
fmt::println(file, "}}");

View File

@ -1 +0,0 @@
ppc_*

View File

@ -1,14 +0,0 @@
project("XenonSample")
add_compile_options(
"/D_HAS_EXCEPTIONS=0"
"/fp:strict"
"/GS-"
"/EHa-"
"-march=sandybridge"
"-fno-strict-aliasing")
file(GLOB RecompiledFiles *.cpp)
add_library(XenonSample ${RecompiledFiles})
target_precompile_headers(XenonSample PUBLIC "ppc_recomp_shared.h")

View File

View File

@ -1,13 +1,20 @@
project("XenonTests")
add_compile_options(
"-march=x86-64-v3"
file(GLOB TEST_FILES *.cpp)
if(TEST_FILES)
add_executable(XenonTests
${TEST_FILES}
)
target_link_libraries(XenonTests
PUBLIC
XenonUtils
fmt::fmt
)
target_compile_options(XenonTests
PRIVATE
"-march=sandybridge"
"-Wno-unused-label"
"-Wno-unused-variable")
file(GLOB TestFiles *.cpp)
if(TestFiles)
add_executable(XenonTests ${TestFiles})
target_link_libraries(XenonTests PUBLIC XenonUtils)
"-Wno-unused-variable"
)
endif()

View File

@ -1,10 +0,0 @@
int add(int a, int b)
{
int c = a + b;
return c == 0 ? 50000 : c;
}
extern "C" int _start()
{
return add(1, 2);
}

Binary file not shown.

View File

@ -1,9 +0,0 @@
int add(int a, int b)
{
return a + b;
}
extern "C" int _start()
{
return add(1, 2);
}

Binary file not shown.

View File

@ -1,15 +0,0 @@
int cond(int a)
{
int v = 2;
if (a == 1)
{
v += 5;
}
return v;
}
extern "C" int _start()
{
return cond(0);
}

Binary file not shown.

View File

@ -1,18 +0,0 @@
int cond(int a)
{
if (a == 1)
{
return 5;
}
else if (a == 4)
{
return 9;
}
return 0;
}
extern "C" int _start()
{
return cond(0);
}

Binary file not shown.

View File

@ -1,15 +0,0 @@
int loop(int a)
{
int result = 0;
for (int i = 0; i < a; i++)
{
result = result * 31 + i;
result *= result;
}
return result;
}
extern "C" int _start()
{
return loop(30);
}

Binary file not shown.

View File

@ -1,2 +0,0 @@
*
!.gitignore

View File

@ -1,5 +0,0 @@
@echo off
pushd XenonAnalyse
for %%f in (*.cpp) do call ..\compile.bat %%f
popd

View File

@ -1,3 +0,0 @@
@echo off
clang -target powerpc-unknown-linux-gnu -fuse-ld=lld -nostdlib -m32 -o %~n1.elf %1