Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ endif()

option(ANYPS5_ENABLE_SPIRV_TOOLS "Enable SPIRV-Tools validation and optimization" OFF)
option(ANYPS5_ENABLE_ASAN "Build Linux targets with AddressSanitizer and use an ASan-compatible guest arena range" OFF)
option(ANYPS5_ENABLE_TSAN "Build Linux targets with ThreadSanitizer" OFF)
option(APS5_ENABLE_TIMING_LOG "Write frame timings asynchronously to frame-timing.log" OFF)
option(APS5_ENABLE_TRACY "Build the Tracy profiler client and report performance timer scopes as Tracy zones" OFF)
option(ANYPS5_USE_SYSTEM_DEPS "Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the bundled submodules" OFF)
Expand All @@ -76,6 +77,18 @@ if(NOT ANYPS5_CXX_LINKS_STANDARD_LIBRARY)
"Supported toolchains are listed in docs/dev/BUILD.md.")
endif()

if(ANYPS5_ENABLE_ASAN AND ANYPS5_ENABLE_TSAN)
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN and ANYPS5_ENABLE_TSAN cannot be combined")
endif()
if(ANYPS5_ENABLE_TSAN)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "ANYPS5_ENABLE_TSAN is supported only on Linux")
endif()
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang)$")
message(FATAL_ERROR "ANYPS5_ENABLE_TSAN requires GCC or Clang")
endif()
endif()

if(ANYPS5_ENABLE_ASAN)
if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(FATAL_ERROR "ANYPS5_ENABLE_ASAN is supported only on Linux")
Expand All @@ -86,6 +99,10 @@ if(ANYPS5_ENABLE_ASAN)
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
add_compile_definitions(ANYPS5_ENABLE_ASAN=1)
elseif(ANYPS5_ENABLE_TSAN)
add_compile_options(-fsanitize=thread -fno-omit-frame-pointer)
add_link_options(-fsanitize=thread)
add_compile_definitions(ANYPS5_ENABLE_TSAN=1)
elseif(NOT APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
endif()
Expand Down
10 changes: 5 additions & 5 deletions core/libs/tests/GuestMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,7 @@ static void CheckAliasRefusesPagesThatAreNotSharedViews() {
}
#endif

#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
static std::size_t LockedKilobytes() {
std::ifstream status("/proc/self/status");
std::string line;
Expand All @@ -1593,7 +1593,7 @@ static void CheckMlock() {
Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0);
auto* bytes = static_cast<unsigned char*>(mapped);
Require(sceKernelMlock_nid_postfix(mapped, 0) == 0);
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
const auto lockedBefore = LockedKilobytes();
#endif
Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0);
Expand All @@ -1604,7 +1604,7 @@ static void CheckMlock() {
Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum);
Require(VirtualUnlock(mapped, length));
Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED);
#elif defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
#elif defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
Require(LockedKilobytes() - lockedBefore == length / 1024);
#endif
Require(sceKernelMlock_nid_postfix(mapped, length) == 0);
Expand All @@ -1613,12 +1613,12 @@ static void CheckMlock() {
Require(sceKernelMlock_nid_postfix(reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max() - page + 1), page * 2) == invalid);
void* reserved = nullptr;
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
#ifndef ANYPS5_ENABLE_ASAN
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory);
#endif
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(reserved), page) == 0);
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), length) == 0);
#ifndef ANYPS5_ENABLE_ASAN
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory);
#endif
}
Expand Down
6 changes: 3 additions & 3 deletions core/libs/tests/GuestMlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static void RequireLockAllUnsupported(int flags) {
Require(rejected);
}

#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN)
#if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
#define HAS_LOCKED_KILOBYTES
static std::size_t LockedKilobytes() {
std::ifstream status("/proc/self/status");
Expand Down Expand Up @@ -125,7 +125,7 @@ int main() {

void* reserved = nullptr;
Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0);
#ifndef ANYPS5_ENABLE_ASAN
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
*__error_nid_postfix() = 0;
Require(mlock_nid_postfix(reserved, page) == -1 && *__error_nid_postfix() == 12);
#endif
Expand All @@ -135,7 +135,7 @@ int main() {
Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(reserved), page) == 0);

Require(sceKernelMunmap(reinterpret_cast<std::uint64_t>(mapped), length) == 0);
#ifndef ANYPS5_ENABLE_ASAN
#if !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN)
*__error_nid_postfix() = 0;
Require(mlock_nid_postfix(mapped, page) == -1 && *__error_nid_postfix() == 12);
*__error_nid_postfix() = 0;
Expand Down
5 changes: 5 additions & 0 deletions docs/dev/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Project switches accept `ON` or `OFF`:
| `-DANYPS5_BUILD_WASM=ON` | `OFF` | Build only the browser relinker (`relinker_web` target). Requires `emcmake` from Emscripten and `BUILD_TESTING=OFF`. |
| `-DANYPS5_ENABLE_SPIRV_TOOLS=ON` | `OFF` | Enable SPIR-V validation and optimization. |
| `-DANYPS5_ENABLE_ASAN=ON` | `OFF` | Build with AddressSanitizer on Linux and move the guest arena out of ASan's shadow gap. |
| `-DANYPS5_ENABLE_TSAN=ON` | `OFF` | Build with ThreadSanitizer on Linux and link executables dynamically. Cannot be combined with `-DANYPS5_ENABLE_ASAN=ON`. |
| `-DANYPS5_USE_SYSTEM_DEPS=ON` | `OFF` | Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the submodules. |
| `-DAPS5_ENABLE_TIMING_LOG=ON` | `OFF` | Compile frame timing logging. |
| `-DAPS5_ENABLE_TRACY=ON` | `OFF` | Build the [Tracy](#tracy) profiler client into the agc driver. |
Expand All @@ -134,6 +135,10 @@ SDL and FreeType settings forced by the root `CMakeLists.txt` cannot be overridd

With `ANYPS5_USE_SYSTEM_DEPS=ON` those libraries come from the system (on Debian and Ubuntu: `libsdl2-dev`, `libfreetype-dev`, `libvulkan-dev`, `spirv-headers`, `glslang-dev`, `glslang-tools` and `spirv-tools`). A missing package, Vulkan-Headers older than 1.4.310 (the agc driver uses `VK_KHR_maintenance8`), or an SDL2 without the HIDAPI joystick driver (sdl2-compat over SDL3, or a build with HIDAPI disabled, which breaks the pad and disk-audio tests), fails the configure with an error and never falls back to the submodule. The forced SDL and FreeType settings from the root `CMakeLists.txt` do not apply with the flag ON: the system SDL2 comes with whatever its distribution enabled. The prx libraries are shared objects, so SDL2 is linked from the system as a shared library; the default build keeps its own static SDL2. `ffmpeg-core`, `libjpeg-turbo`, `stb`, `VulkanMemoryAllocator`, `LibAtrac9`, `ogg` and `vorbis` always stay bundled and still need `git submodule update --init`.

## ThreadSanitizer

A ThreadSanitizer build runs its tests with `TSAN_OPTIONS=suppressions=$PWD/tools/tsan-suppressions.txt`, which silences reports from Mesa's lavapipe, the LLVM it compiles shaders with, and PulseAudio: those system libraries are not instrumented, so ThreadSanitizer cannot see their locking. Guest memory written by the emulated GPU while a guest thread polls it, such as labels and fences, is shared without C++ atomics on purpose, as on the console, so ThreadSanitizer also reports those accesses.

## Quick syntax checks

After configuring a build directory, use `tools/check_compile.py` to check the given source files. The tool reads their compile commands from `compile_commands.json` or Ninja, preserving compilation options while dropping object and dependency output options. It handles `-MF`, `-MT`, `-MQ` and `-MJ` with separate or attached values so that unused output options do not cause false failures under `-Werror`.
Expand Down
5 changes: 5 additions & 0 deletions tools/tsan-suppressions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
called_from_lib:libvulkan_lvp.so
called_from_lib:libLLVM
called_from_lib:libpulsecommon
race:libLLVM
deadlock:libLLVM
Loading