mirror of
https://github.com/Liondarkz/issn_pc.git
synced 2026-11-07 04:36:01 -10:00
Merge PR #16 (Sanvtto): macOS on Apple silicon, x86_64 under Rosetta 2 with MoltenVK (experimental)
Merged into the work branch as an experimental platform: the changes are behind __APPLE__ or type-correct casts; the Linux build passes with them. Not yet run with the game on a Mac. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Eij6gW2nnTUK9BowHuBVZZ
This commit is contained in:
30 files changed
+820
-38
No files matched your search
@@ -48,6 +48,31 @@ MINGW*|MSYS*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
# Linux defaults; macOS replaces them below.
|
||||
no_pie=(-no-pie) export_dynamic=-rdynamic origin='$ORIGIN' heap=() cmake_platform=()
|
||||
# macOS: an x86_64 build that Rosetta 2 runs, Vulkan through MoltenVK. Dependencies come from
|
||||
# packaging/macos/build-deps-x86_64.sh (ISSN_MAC_DEPS), never from the arm64 Homebrew.
|
||||
if [[ $(uname -s) == Darwin ]]; then
|
||||
deps=${ISSN_MAC_DEPS:-$PWD/out/macos-deps}
|
||||
[[ -f $deps/lib/libMoltenVK.dylib ]] || { echo "macOS dependencies missing: run packaging/macos/build-deps-x86_64.sh" >&2; exit 1; }
|
||||
export MACOSX_DEPLOYMENT_TARGET=13.0 PATH="$deps/bin:$PATH"
|
||||
export PKG_CONFIG_LIBDIR="$deps/lib/pkgconfig:$deps/share/pkgconfig"
|
||||
printf '#!/bin/sh\nexec clang -arch x86_64 -mmacosx-version-min=13.0 "$@"\n' > out/cc-x86_64
|
||||
chmod +x out/cc-x86_64
|
||||
CC=$PWD/out/cc-x86_64
|
||||
# The guest packs pointers into 40 bits: mimalloc, patched to map in 32-64 GiB, replaces
|
||||
# the system malloc (near 128 TiB). It must come first on the link line.
|
||||
no_pie=() export_dynamic=-Wl,-export_dynamic,-rpath,"$deps/lib" origin=@loader_path
|
||||
heap=("$deps/lib/mimalloc-3.5/mimalloc.o" src/compat_mac.c)
|
||||
# shadPS4 points the Vulkan loader at the executable's directory (VK_DRIVER_FILES).
|
||||
printf '{"file_format_version":"1.0.0","ICD":{"library_path":"%s","api_version":"1.4.0","is_portability_driver":true}}\n' \
|
||||
"$deps/lib/libMoltenVK.dylib" > out/MoltenVK_icd.json
|
||||
boost=$(ls -d /opt/homebrew/lib/cmake/Boost-* 2>/dev/null | tail -1)
|
||||
cmake_platform=(-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0
|
||||
-DCMAKE_PREFIX_PATH="$deps" -DCMAKE_IGNORE_PREFIX_PATH=/opt/homebrew
|
||||
-DBoost_DIR="$boost" -DCMAKE_POLICY_VERSION_MINIMUM=3.5)
|
||||
ISSN_PGO=${ISSN_PGO:-off} ISSN_LTO=${ISSN_LTO:-OFF}
|
||||
fi
|
||||
if [[ -z ${CC:-} ]]; then
|
||||
CC=$(command -v cc || command -v gcc || true)
|
||||
if [[ -z $CC ]]; then
|
||||
@@ -93,7 +118,7 @@ for patch in gpu/patches/fsr-vulkan/*.patch; do
|
||||
fi
|
||||
done
|
||||
cmake -S gpu -B out/gpu -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DISSN_PGO="$pgo" \
|
||||
-DISSN_LTO="${ISSN_LTO:-ON}" -DISSN_PGO_DIR="$PWD/pgo" >/dev/null
|
||||
-DISSN_LTO="${ISSN_LTO:-ON}" -DISSN_PGO_DIR="$PWD/pgo" ${cmake_platform[@]+"${cmake_platform[@]}"} >/dev/null
|
||||
echo "GPU library: PGO $pgo, LTO ${ISSN_LTO:-ON}"
|
||||
# A failed GPU build must stop here: an older libissngpu.so would otherwise be used silently.
|
||||
if ! ninja -C out/gpu issngpu > out/gpu-build.log 2>&1; then
|
||||
@@ -101,7 +126,7 @@ if ! ninja -C out/gpu issngpu > out/gpu-build.log 2>&1; then
|
||||
echo 'GPU library build failed (full log: out/gpu-build.log)' >&2; exit 1
|
||||
fi
|
||||
# $ORIGIN/gpu: packaged copies keep the library next to the binary without patching it.
|
||||
gpu=(-Lout/gpu -lissngpu -Wl,-rpath,'$ORIGIN/gpu' -Wl,-rpath,"$PWD/out/gpu" -rdynamic)
|
||||
gpu=(-Lout/gpu -lissngpu -Wl,-rpath,"$origin/gpu" -Wl,-rpath,"$PWD/out/gpu" "$export_dynamic")
|
||||
runtime=(src/runtime*.c)
|
||||
# Third-party decoders: compiled once, without this project's -Werror policy.
|
||||
atrac9=(third_party/LibAtrac9/C/src/*.c)
|
||||
@@ -112,10 +137,10 @@ if [[ ! -f out/libatrac9.a || -n $(find third_party/LibAtrac9/C/src -newer out/l
|
||||
fi
|
||||
# isa_emulate runs inside guest code: no XMM/YMM.
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -mgeneral-regs-only -c src/isa_emulate.c -o out/isa_emulate.o
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread -no-pie "${includes[@]}" -I. -Isrc src/probe.c "${runtime[@]}" src/cpu_check.c src/vulkan_smoke.c out/isa_emulate.o src/isa_rewrite.c out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/issn-probe
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread ${no_pie[@]+"${no_pie[@]}"} "${includes[@]}" -I. -Isrc ${heap[@]+"${heap[@]}"} src/probe.c "${runtime[@]}" src/cpu_check.c src/vulkan_smoke.c out/isa_emulate.o src/isa_rewrite.c out/libatrac9.a -lm "${gpu[@]}" "${libraries[@]}" -o out/issn-probe
|
||||
echo "Built $PWD/out/issn-probe"
|
||||
# GPU check for run.sh (live_resolution=auto): links only the Vulkan loader.
|
||||
"$CC" -std=c11 -O2 -Wall -Wextra -Werror -Igpu/shadps4 tools/gpu_capabilities.c "${libraries[@]}" -o out/issn-gpu-capabilities
|
||||
"$CC" -std=c11 -O2 -Wall -Wextra -Werror "${includes[@]}" -Igpu/shadps4 tools/gpu_capabilities.c "${libraries[@]}" -o out/issn-gpu-capabilities
|
||||
if [[ ${1:-} == --test ]]; then
|
||||
"$CC" -std=c11 -O2 -g -Wall -Wextra -Werror -pthread "${includes[@]}" -I. -Isrc tests/test_pad.c "${libraries[@]}" -lm -o out/pad-test
|
||||
out/pad-test
|
||||
|
||||
+7
-2
@@ -82,7 +82,8 @@ endif()
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(XXHASH REQUIRED IMPORTED_TARGET libxxhash)
|
||||
pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
|
||||
if (NOT WIN32)
|
||||
# macOS presents through MoltenVK's Metal surface (SDL), with no X11.
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
|
||||
endif()
|
||||
find_package(miniz QUIET)
|
||||
@@ -173,7 +174,9 @@ target_include_directories(issngpu PRIVATE shim shadps4 ${SHADER_INCLUDE} third_
|
||||
target_compile_definitions(issngpu PRIVATE BOOST_ASIO_STANDALONE NDEBUG)
|
||||
target_compile_options(issngpu PRIVATE -Wno-deprecated-declarations -Wno-missing-field-initializers)
|
||||
# Runtime symbols (runtime_memory_*, runtime_process_*) resolve against issn-probe.
|
||||
if (NOT WIN32)
|
||||
if (APPLE)
|
||||
target_link_options(issngpu PRIVATE -Wl,-undefined,dynamic_lookup)
|
||||
elseif (NOT WIN32)
|
||||
target_link_options(issngpu PRIVATE -Wl,--allow-shlib-undefined)
|
||||
endif()
|
||||
# glslang's own CMake package: SPIRV and the default resource limits are separate targets in
|
||||
@@ -193,6 +196,8 @@ if (WIN32)
|
||||
target_compile_definitions(issngpu PUBLIC NOMINMAX WIN32_LEAN_AND_MEAN)
|
||||
target_compile_options(issngpu PRIVATE -include ${CMAKE_CURRENT_SOURCE_DIR}/shim/issn_win_compat.h)
|
||||
target_link_libraries(issngpu PUBLIC Vulkan::Vulkan ws2_32 bcrypt ntdll synchronization winmm pdh)
|
||||
elseif (APPLE)
|
||||
target_link_libraries(issngpu PUBLIC Vulkan::Vulkan)
|
||||
else()
|
||||
target_link_libraries(issngpu PUBLIC PkgConfig::X11)
|
||||
endif()
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -780,7 +783,11 @@ struct PM4CmdWriteData {
|
||||
|
||||
template <typename T>
|
||||
T Address() const {
|
||||
return reinterpret_cast<T>(addr64);
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
return static_cast<T>(addr64);
|
||||
} else {
|
||||
return reinterpret_cast<T>(static_cast<uintptr_t>(addr64));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -811,7 +818,12 @@ struct PM4CmdEventWriteEos {
|
||||
|
||||
template <typename T = u32*>
|
||||
T Address() const {
|
||||
return reinterpret_cast<T>(address_lo | u64(address_hi) << 32);
|
||||
const u64 address = address_lo | u64(address_hi) << 32;
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
return static_cast<T>(address);
|
||||
} else {
|
||||
return reinterpret_cast<T>(static_cast<uintptr_t>(address));
|
||||
}
|
||||
}
|
||||
|
||||
u32 DataDWord() const {
|
||||
|
||||
@@ -634,7 +634,7 @@ static std::pair<VAddr, u64> WidenReadFault(const Buffer& buffer, VAddr device_a
|
||||
return {device_addr, size};
|
||||
}
|
||||
const VAddr begin = std::max(Common::AlignDown(device_addr, window), buffer.CpuAddr());
|
||||
const VAddr end = std::min(Common::AlignUp(device_addr + size, window),
|
||||
const VAddr end = std::min<VAddr>(Common::AlignUp(device_addr + size, window),
|
||||
buffer.CpuAddr() + buffer.SizeBytes());
|
||||
return {begin, end - begin};
|
||||
}
|
||||
@@ -1699,7 +1699,7 @@ bool BufferCache::DownloadWindowMultiBuffer(VAddr window_addr, u64 window_size)
|
||||
}
|
||||
seen.push_back(id);
|
||||
const VAddr lo = std::max(window_addr, buffer.CpuAddr());
|
||||
const VAddr hi = std::min(window_addr + window_size, buffer.CpuAddr() + buffer.SizeBytes());
|
||||
const VAddr hi = std::min<VAddr>(window_addr + window_size, buffer.CpuAddr() + buffer.SizeBytes());
|
||||
if (hi <= lo) {
|
||||
return;
|
||||
}
|
||||
@@ -2148,7 +2148,7 @@ BufferCache::FaultTicket BufferCache::PrepareFaultDownload(VAddr device_addr, u6
|
||||
seen.push_back(id);
|
||||
const VAddr lo = std::max(addr, window_buffer.CpuAddr());
|
||||
const VAddr hi =
|
||||
std::min(addr + bytes, window_buffer.CpuAddr() + window_buffer.SizeBytes());
|
||||
std::min<VAddr>(addr + bytes, window_buffer.CpuAddr() + window_buffer.SizeBytes());
|
||||
if (hi <= lo) {
|
||||
return;
|
||||
}
|
||||
@@ -3258,7 +3258,7 @@ u64 BufferCache::NewestAscWriter(std::span<const FaultCopy> copies, u64 done_bel
|
||||
VAddr clo = ~VAddr{0}, chi = 0; // q11e: the copies' bounding box
|
||||
for (const FaultCopy& copy : copies) {
|
||||
clo = std::min(clo, copy.addr);
|
||||
chi = std::max(chi, copy.addr + copy.size);
|
||||
chi = std::max<VAddr>(chi, copy.addr + copy.size);
|
||||
}
|
||||
for (u64 v = top; v != 0 && v > done_below && top - v < asc_write_hist.size(); --v) {
|
||||
const size_t i = v % asc_write_hist.size();
|
||||
|
||||
@@ -21,7 +21,11 @@
|
||||
|
||||
#ifndef _WIN64
|
||||
#include <sys/mman.h>
|
||||
#ifdef __APPLE__
|
||||
#include <sys/ucontext.h>
|
||||
#else
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
#include "common/adaptive_mutex.h"
|
||||
#ifdef ENABLE_USERFAULTFD
|
||||
#include <thread>
|
||||
@@ -212,7 +216,11 @@ static bool ReadFaultRipEnabled() {
|
||||
static void SnapshotReadFaultPage(VAddr) {}
|
||||
static void LogReadFaultRip(void*, VAddr) {}
|
||||
static u64 FaultRip(void* context) {
|
||||
#ifdef __APPLE__
|
||||
return static_cast<u64>(static_cast<ucontext_t*>(context)->uc_mcontext->__ss.__rip);
|
||||
#else
|
||||
return static_cast<u64>(static_cast<ucontext_t*>(context)->uc_mcontext.gregs[REG_RIP]);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -284,6 +284,11 @@ bool Instance::CreateDevice() {
|
||||
return false;
|
||||
};
|
||||
|
||||
#ifdef __APPLE__
|
||||
// port: MoltenVK is a portability implementation, which must have this enabled when it
|
||||
// offers it (the name is a beta extension's, so spelled out).
|
||||
add_extension("VK_KHR_portability_subset");
|
||||
#endif
|
||||
// Required
|
||||
ASSERT_MSG(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME),
|
||||
"Required Vulkan extension unavailable: {}", VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_compile_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_serialization.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include <immintrin.h> // _mm_pause
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
|
||||
@@ -222,6 +222,11 @@ std::vector<const char*> GetInstanceExtensions(Frontend::WindowSystemType window
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
// port: the loader only lists MoltenVK (a portability driver) when the instance asks for it.
|
||||
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||
#endif
|
||||
|
||||
// SHADPS4_DLSS=1: Streamline starts here, before vkCreateInstance, and adds what it needs.
|
||||
Dlss::AddInstanceExtensions(extensions);
|
||||
// issn-axy: ISSN_XESS=1 - libxess loads here and adds what it needs (issn_xess.h).
|
||||
@@ -424,6 +429,9 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e
|
||||
|
||||
vk::StructureChain<vk::InstanceCreateInfo, vk::LayerSettingsCreateInfoEXT> instance_ci_chain = {
|
||||
vk::InstanceCreateInfo{
|
||||
#ifdef __APPLE__
|
||||
.flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR,
|
||||
#endif
|
||||
.pApplicationInfo = &application_info,
|
||||
.enabledLayerCount = static_cast<u32>(layers.size()),
|
||||
.ppEnabledLayerNames = layers.data(),
|
||||
|
||||
@@ -46,6 +46,18 @@ asm(".section .rdata,\"dr\"\n"
|
||||
".global issn_font_ttf_end\n"
|
||||
"issn_font_ttf_end:\n"
|
||||
".text\n");
|
||||
#elif defined(__APPLE__)
|
||||
// Mach-O: C symbols carry a leading underscore, read-only data is __TEXT,__const.
|
||||
asm(".section __TEXT,__const\n"
|
||||
".p2align 4\n"
|
||||
".private_extern _issn_font_ttf\n"
|
||||
".globl _issn_font_ttf\n"
|
||||
"_issn_font_ttf:\n"
|
||||
".incbin \"" ISSN_FONT_PATH "\"\n"
|
||||
".private_extern _issn_font_ttf_end\n"
|
||||
".globl _issn_font_ttf_end\n"
|
||||
"_issn_font_ttf_end:\n"
|
||||
".text\n");
|
||||
#else
|
||||
asm(".section .rodata\n"
|
||||
".balign 16\n"
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
#include <vector>
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <cstring>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/sysinfo.h>
|
||||
@@ -123,6 +127,52 @@ void Run(const Vulkan::Instance* instance) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// port: macOS measurements. CPU from getrusage, RAM from the Mach task and hw.memsize, VRAM from
|
||||
// VK_EXT_memory_budget (MoltenVK); GPU load stays "n/a".
|
||||
uint64_t ProcessMicros() { // user + system time of the whole process
|
||||
rusage r{};
|
||||
if (getrusage(RUSAGE_SELF, &r) != 0) return 0;
|
||||
return uint64_t(r.ru_utime.tv_sec + r.ru_stime.tv_sec) * 1000000u +
|
||||
uint64_t(r.ru_utime.tv_usec + r.ru_stime.tv_usec);
|
||||
}
|
||||
|
||||
void Run(const Vulkan::Instance* instance) {
|
||||
const unsigned cores = std::max(1u, std::thread::hardware_concurrency());
|
||||
constexpr double GiB = 1024.0 * 1024.0 * 1024.0;
|
||||
uint64_t ram_total = 0;
|
||||
size_t length = sizeof(ram_total);
|
||||
if (sysctlbyname("hw.memsize", &ram_total, &length, nullptr, 0) != 0) ram_total = 0;
|
||||
uint64_t last_process = 0;
|
||||
auto last_wall = std::chrono::steady_clock::now();
|
||||
while (true) {
|
||||
Sample s;
|
||||
s.gpu_name = std::string(instance->GetModelName());
|
||||
const uint64_t process = ProcessMicros();
|
||||
const auto wall = std::chrono::steady_clock::now();
|
||||
if (last_process) {
|
||||
const uint64_t wall_us = uint64_t(std::chrono::duration<double, std::micro>(wall - last_wall).count());
|
||||
s.cpu_percent = CpuPercent(process - last_process, wall_us, cores);
|
||||
}
|
||||
last_process = process;
|
||||
last_wall = wall;
|
||||
mach_task_basic_info_data_t task{};
|
||||
mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
|
||||
if (task_info(mach_task_self(), MACH_TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&task), &count) == KERN_SUCCESS)
|
||||
s.ram_used_gb = double(task.resident_size) / GiB;
|
||||
if (ram_total) s.ram_total_gb = double(ram_total) / GiB;
|
||||
if (instance->CanReportMemoryUsage()) {
|
||||
s.vram_used_gb = double(instance->GetDeviceMemoryUsage()) / GiB;
|
||||
s.vram_total_gb = double(instance->GetTotalMemoryBudget()) / GiB;
|
||||
}
|
||||
s.gpu_percent = -1.0f;
|
||||
{
|
||||
std::scoped_lock lock{mutex};
|
||||
latest = std::move(s);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
}
|
||||
}
|
||||
#else
|
||||
// port: Linux measurements. CPU and RAM from /proc, VRAM from VK_EXT_memory_budget, GPU load from
|
||||
// NVML (NVIDIA, loaded at run time) or the amdgpu sysfs counter; anything else stays "n/a".
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#ifdef _WIN32
|
||||
#include <bit>
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#include <sys/qos.h>
|
||||
#else
|
||||
#include <sched.h>
|
||||
#include <sys/resource.h>
|
||||
@@ -31,6 +34,17 @@ inline unsigned Available() {
|
||||
inline void MakeBackground() {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
/// Hardware threads available to the process (macOS has no affinity masks).
|
||||
inline unsigned Available() {
|
||||
return std::max(1u, std::thread::hardware_concurrency());
|
||||
}
|
||||
|
||||
/// The calling thread runs at background QoS: on Apple silicon, the efficiency cores, so
|
||||
/// it never takes time from the game's threads.
|
||||
inline void MakeBackground() {
|
||||
pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0);
|
||||
}
|
||||
#else
|
||||
|
||||
/// Hardware threads available to the process.
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#undef near
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
@@ -61,7 +65,15 @@ void Note(std::uint64_t address, const void* data, std::uint64_t size, Source so
|
||||
}
|
||||
|
||||
void Record(std::uint64_t address, const void* data, std::uint64_t size, Source source) {
|
||||
#ifdef __APPLE__
|
||||
static thread_local const std::uint32_t tid = [] {
|
||||
std::uint64_t id = 0;
|
||||
pthread_threadid_np(nullptr, &id);
|
||||
return static_cast<std::uint32_t>(id);
|
||||
}();
|
||||
#else
|
||||
static thread_local const std::uint32_t tid = static_cast<std::uint32_t>(gettid());
|
||||
#endif
|
||||
Entry e{address, size, 0, __rdtsc(), source, tid};
|
||||
std::memcpy(&e.first, data, size < 8 ? size : 8);
|
||||
Push(ring, head, e);
|
||||
@@ -93,6 +105,9 @@ extern "C" void issngpu_dump_guest_writes(void* ucontext) {
|
||||
// The loader passes EXCEPTION_POINTERS (see signal_context.cpp).
|
||||
const CONTEXT* c = static_cast<const EXCEPTION_POINTERS*>(ucontext)->ContextRecord;
|
||||
const std::uint64_t regs[] = {c->Rax, c->Rbx, c->Rcx, c->Rdx, c->Rsi, c->Rdi, c->R14, c->R15};
|
||||
#elif defined(__APPLE__)
|
||||
const auto& s = static_cast<const ucontext_t*>(ucontext)->uc_mcontext->__ss;
|
||||
const std::uint64_t regs[] = {s.__rax, s.__rbx, s.__rcx, s.__rdx, s.__rsi, s.__rdi, s.__r14, s.__r15};
|
||||
#else
|
||||
const auto* uc = static_cast<const ucontext_t*>(ucontext);
|
||||
const auto* g = uc->uc_mcontext.gregs;
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <SDL3/SDL.h>
|
||||
#ifdef __APPLE__
|
||||
#include <SDL3/SDL_metal.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -116,6 +119,12 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, const char* title) : width{width_}
|
||||
window_info.type = WindowSystemType::Windows;
|
||||
window_info.render_surface = SDL_GetPointerProperty(wp, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
|
||||
} else
|
||||
#elif defined(__APPLE__)
|
||||
// port: MoltenVK draws into the CAMetalLayer of an SDL Metal view.
|
||||
if (driver && !std::strcmp(driver, "cocoa")) {
|
||||
window_info.type = WindowSystemType::Metal;
|
||||
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
|
||||
} else
|
||||
#endif
|
||||
if (driver && !std::strcmp(driver, "x11")) {
|
||||
window_info.type = WindowSystemType::X11;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
issn on macOS (Apple silicon) - TEST BUILD
|
||||
==========================================
|
||||
|
||||
This is an early test build. The port compiles and its checks pass on an M4
|
||||
(Vulkan through MoltenVK: command submission + readback PASS; CPU: complete
|
||||
but SSE4A, which the port emulates as on Intel). It has NOT yet been run with
|
||||
the game: every report helps.
|
||||
|
||||
What you need
|
||||
- A Mac with Apple silicon, macOS 13 or newer, Rosetta 2
|
||||
(softwareupdate --install-rosetta).
|
||||
- Python 3 (python3; macOS offers to install it on first use).
|
||||
- Your own dump of the game, prepared as described in the main README.
|
||||
This package contains no game files.
|
||||
|
||||
Running
|
||||
1. Unzip, open Terminal in the folder, and run: bash run.sh
|
||||
(the same arguments and environment variables as on Linux).
|
||||
2. macOS may refuse the unsigned binaries the first time: run
|
||||
xattr -dr com.apple.quarantine . in the folder, then try again.
|
||||
|
||||
How it works on a Mac
|
||||
- The build is x86_64 and runs under Rosetta 2: the game's own x86-64 code
|
||||
runs as on a PC. issn-probe restarts itself with ROSETTA_ADVERTISE_AVX=1,
|
||||
which Rosetta needs before it reports AVX/AVX2/F16C/BMI in CPUID.
|
||||
- Graphics: Vulkan through MoltenVK (bin/libMoltenVK.dylib) onto Metal.
|
||||
- Memory: the guest packs pointers into 40 bits, so the whole process heap
|
||||
must stay below 1 TiB. A patched mimalloc (packaging/macos) keeps it in
|
||||
32-64 GiB instead of macOS's usual ~128 TiB.
|
||||
|
||||
Reporting
|
||||
Please send: the Mac model, the macOS version, what happened, and the whole
|
||||
terminal output (and out/*.log if there are any).
|
||||
|
||||
Building it yourself: see packaging/macos/README.md.
|
||||
@@ -0,0 +1,35 @@
|
||||
# issn on macOS
|
||||
|
||||
The port builds on macOS as an **x86_64** program that Rosetta 2 runs on Apple silicon, with
|
||||
Vulkan through MoltenVK. It is not a GPTK/Wine build: the same sources as on Linux, with a
|
||||
`__APPLE__` branch where the systems differ. Status: builds, `issn-probe --vulkan-only` and
|
||||
`--cpu-features` pass on an M4; not yet run with the game.
|
||||
|
||||
## Building
|
||||
|
||||
The repository path must not contain spaces (FFmpeg's configure does not accept them).
|
||||
|
||||
```sh
|
||||
brew install cmake ninja pkg-config boost # host tools; Boost is header-only here
|
||||
bash packaging/macos/build-deps-x86_64.sh # x86_64 dependencies into out/macos-deps (once)
|
||||
bash build.sh # out/issn-probe, out/gpu/libissngpu.dylib
|
||||
bash packaging/macos/package.sh # optional: dist/issn-macos-x86_64.zip for testers
|
||||
```
|
||||
|
||||
`ISSN_MAC_DEPS` points both scripts at another dependency prefix. The arm64 Homebrew is never
|
||||
linked: only Boost's headers are taken from it.
|
||||
|
||||
## What differs from Linux
|
||||
|
||||
| Linux | macOS |
|
||||
|---|---|
|
||||
| guest TLS: `arch_prctl(ARCH_SET_GS)` | a pthread key, read as `gs:[8*key]`; the `fs:` sites are rewritten as on Windows (`patch_tcb_loads`) |
|
||||
| host heap below 1 TiB: non-PIE brk heap, `mallopt` | mimalloc 3.5.3 patched to map only in 32-64 GiB (`mimalloc-os-window.patch`), linked first |
|
||||
| `ucontext` `gregs` | `uc_mcontext->__ss` |
|
||||
| `process_vm_readv`, `/proc/self/task` | `mach_vm_read_overwrite`, `task_threads` |
|
||||
| `memfd_create` | `shm_open` + immediate unlink |
|
||||
| timed mutex/rwlock waits, `CLOCK_MONOTONIC` waits | try + short sleep (`compat_mac.c`); `CLOCK_REALTIME` (as on Windows) |
|
||||
| `SCHED_IDLE` helpers | `QOS_CLASS_BACKGROUND` |
|
||||
| X11/Wayland surface | SDL Metal view, `VK_EXT_metal_surface` |
|
||||
| — | `VK_KHR_portability_enumeration` + `VK_KHR_portability_subset` (MoltenVK) |
|
||||
| — | Rosetta reports AVX in CPUID only with `ROSETTA_ADVERTISE_AVX=1`: `issn-probe` restarts itself with it |
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env bash
|
||||
# Dependências x86_64 (Intel, rodam no M4 via Rosetta 2) para o port issn_pc no macOS.
|
||||
# Instala tudo em out/macos-deps (ou ISSN_MAC_DEPS) sem tocar no Homebrew ARM (/opt/homebrew).
|
||||
# Idempotente: cada etapa concluída deixa um carimbo em $P/.stamps e é pulada na próxima vez.
|
||||
# O caminho do repositório não pode ter espaços (o configure do FFmpeg não aceita).
|
||||
set -euo pipefail
|
||||
|
||||
D="$(cd -- "$(dirname -- "$0")" && pwd)" # packaging/macos (o patch do mimalloc)
|
||||
ROOT="$(cd -- "$D/../.." && pwd)"
|
||||
P="${ISSN_MAC_DEPS:-$ROOT/out/macos-deps}" # prefixo de instalação (x86_64)
|
||||
S="$ROOT/out/macos-deps-src" # códigos-fonte
|
||||
mkdir -p "$P/.stamps" "$S"
|
||||
|
||||
export MACOSX_DEPLOYMENT_TARGET=13.0
|
||||
ARCH="-arch x86_64 -mmacosx-version-min=$MACOSX_DEPLOYMENT_TARGET"
|
||||
JOBS=$(sysctl -n hw.ncpu)
|
||||
|
||||
CMAKE_COMMON=(-G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET
|
||||
-DCMAKE_INSTALL_PREFIX="$P" -DCMAKE_PREFIX_PATH="$P"
|
||||
-DCMAKE_IGNORE_PREFIX_PATH=/opt/homebrew # nunca pegar biblioteca ARM do Homebrew
|
||||
-DCMAKE_POLICY_VERSION_MINIMUM=3.5)
|
||||
|
||||
step() { echo; echo "=================== $1 ($(date +%H:%M:%S))"; }
|
||||
done_() { touch "$P/.stamps/$1"; echo "OK: $1"; }
|
||||
have() { [[ -f "$P/.stamps/$1" ]]; }
|
||||
fetch() { # fetch <dir> <url> <tag> [--recursive]
|
||||
[[ -d "$S/$1/.git" ]] || git clone --depth 1 --branch "$3" ${4:-} "$2" "$S/$1" -q
|
||||
}
|
||||
cm() { # cm <name> <srcdir> [cmake args...]
|
||||
local name=$1 src=$2; shift 2
|
||||
cmake -S "$src" -B "$S/build-$name" "${CMAKE_COMMON[@]}" "$@" > "$S/build-$name.cmake.log"
|
||||
cmake --build "$S/build-$name" -j "$JOBS" > "$S/build-$name.build.log"
|
||||
cmake --install "$S/build-$name" > "$S/build-$name.install.log"
|
||||
}
|
||||
|
||||
if ! have vulkan-headers; then step "Vulkan-Headers"
|
||||
fetch Vulkan-Headers https://github.com/KhronosGroup/Vulkan-Headers.git vulkan-sdk-1.4.363.0
|
||||
cm vulkan-headers "$S/Vulkan-Headers"; done_ vulkan-headers; fi
|
||||
|
||||
if ! have vulkan-loader; then step "Vulkan-Loader"
|
||||
fetch Vulkan-Loader https://github.com/KhronosGroup/Vulkan-Loader.git vulkan-sdk-1.4.363.0
|
||||
cm vulkan-loader "$S/Vulkan-Loader" -DVULKAN_HEADERS_INSTALL_DIR="$P" -DBUILD_TESTS=OFF
|
||||
done_ vulkan-loader; fi
|
||||
|
||||
if ! have moltenvk; then step "MoltenVK v1.4.2 (binário universal oficial da Khronos)"
|
||||
mkdir -p "$S/moltenvk" && cd "$S/moltenvk"
|
||||
[[ -f MoltenVK-macos.tar ]] || curl -sSL -o MoltenVK-macos.tar \
|
||||
https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.2/MoltenVK-macos.tar
|
||||
rm -rf x && mkdir x && tar -xf MoltenVK-macos.tar -C x
|
||||
dylib=$(find x -name libMoltenVK.dylib -path '*macOS*' | head -1)
|
||||
[[ -n $dylib ]] || dylib=$(find x -name libMoltenVK.dylib | head -1)
|
||||
lipo -info "$dylib"
|
||||
mkdir -p "$P/lib" "$P/share/vulkan/icd.d"
|
||||
cp "$dylib" "$P/lib/libMoltenVK.dylib"
|
||||
printf '{\n "file_format_version": "1.0.0",\n "ICD": {\n "library_path": "../../../lib/libMoltenVK.dylib",\n "api_version": "1.4.0",\n "is_portability_driver": true\n }\n}\n' \
|
||||
> "$P/share/vulkan/icd.d/MoltenVK_icd.json"
|
||||
cd "$D"; done_ moltenvk; fi
|
||||
|
||||
if ! have vma; then step "VulkanMemoryAllocator (só cabeçalho)"
|
||||
fetch VMA https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git v3.5.0
|
||||
cm vma "$S/VMA"; done_ vma; fi
|
||||
|
||||
if ! have robin-map; then step "tsl-robin-map (só cabeçalho)"
|
||||
fetch robin-map https://github.com/Tessil/robin-map.git v1.4.1
|
||||
cm robin-map "$S/robin-map"; done_ robin-map; fi
|
||||
|
||||
if ! have fmt; then step "fmt 12.2.0"
|
||||
fetch fmt https://github.com/fmtlib/fmt.git 12.2.0
|
||||
cm fmt "$S/fmt" -DFMT_TEST=OFF -DFMT_DOC=OFF; done_ fmt; fi
|
||||
|
||||
if ! have xxhash; then step "xxHash 0.8.4"
|
||||
fetch xxHash https://github.com/Cyan4973/xxHash.git v0.8.4
|
||||
make -C "$S/xxHash" -j"$JOBS" CC="clang $ARCH" PREFIX="$P" lib > "$S/build-xxhash.log"
|
||||
make -C "$S/xxHash" CC="clang $ARCH" PREFIX="$P" install >> "$S/build-xxhash.log"
|
||||
done_ xxhash; fi
|
||||
|
||||
if ! have zydis; then step "Zydis 4.1.1"
|
||||
fetch zydis https://github.com/zyantific/zydis.git v4.1.1 --recursive
|
||||
cm zydis "$S/zydis" -DZYDIS_BUILD_TOOLS=OFF -DZYDIS_BUILD_EXAMPLES=OFF -DZYDIS_BUILD_SHARED_LIB=OFF
|
||||
done_ zydis; fi
|
||||
|
||||
if ! have zycore; then step "Zycore (a dependência do Zydis, instalada à parte para o zydis-config)"
|
||||
cm zycore "$S/zydis/dependencies/zycore" -DZYCORE_BUILD_SHARED_LIB=OFF
|
||||
done_ zycore; fi
|
||||
|
||||
if ! have mimalloc; then step "mimalloc 3.5.3 com janela de endereços abaixo de 1 TiB"
|
||||
# O guest do PS4 guarda ponteiros em 40 bits; o malloc do macOS fica perto de 128 TiB.
|
||||
# O patch (mimalloc-os-window.patch) põe toda memória do processo em 32–64 GiB.
|
||||
fetch mimalloc https://github.com/microsoft/mimalloc.git v3.5.3
|
||||
git -C "$S/mimalloc" apply --reverse --check "$D/mimalloc-os-window.patch" 2>/dev/null ||
|
||||
git -C "$S/mimalloc" apply "$D/mimalloc-os-window.patch"
|
||||
cm mimalloc "$S/mimalloc" -DMI_OVERRIDE=ON -DMI_OSX_ZONE=ON -DMI_OSX_INTERPOSE=ON \
|
||||
-DMI_BUILD_SHARED=OFF -DMI_BUILD_STATIC=ON -DMI_BUILD_OBJECT=ON -DMI_BUILD_TESTS=OFF \
|
||||
"-DCMAKE_C_FLAGS=-DMI_OS_WINDOW_START=0x800000000ULL -DMI_OS_WINDOW_END=0x1000000000ULL"
|
||||
done_ mimalloc; fi
|
||||
|
||||
if ! have sdl3; then step "SDL 3.4.18"
|
||||
fetch SDL https://github.com/libsdl-org/SDL.git release-3.4.18
|
||||
cm sdl3 "$S/SDL" -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -DSDL_EXAMPLES=OFF
|
||||
done_ sdl3; fi
|
||||
|
||||
if ! have glslang; then step "glslang 16.6.0"
|
||||
fetch glslang https://github.com/KhronosGroup/glslang.git 16.6.0
|
||||
cm glslang "$S/glslang" -DENABLE_OPT=OFF -DGLSLANG_TESTS=OFF -DBUILD_SHARED_LIBS=OFF \
|
||||
-DENABLE_GLSLANG_BINARIES=ON -DGLSLANG_ENABLE_INSTALL=ON
|
||||
done_ glslang; fi
|
||||
|
||||
if ! have ffmpeg; then step "FFmpeg 9.0.2 (só as bibliotecas que o port usa)"
|
||||
fetch FFmpeg https://github.com/FFmpeg/FFmpeg.git n9.0.2
|
||||
cd "$S/FFmpeg"
|
||||
./configure --prefix="$P" --arch=x86_64 --target-os=darwin --enable-cross-compile \
|
||||
--cc="clang $ARCH" --cxx="clang++ $ARCH" --nm=nm --ar=ar --ranlib=ranlib \
|
||||
--enable-shared --disable-static --disable-programs --disable-doc \
|
||||
--disable-autodetect --disable-network --disable-avdevice --disable-avfilter \
|
||||
> "$S/build-ffmpeg.configure.log"
|
||||
make -j"$JOBS" > "$S/build-ffmpeg.build.log" 2>&1
|
||||
make install > "$S/build-ffmpeg.install.log"
|
||||
cd "$D"; done_ ffmpeg; fi
|
||||
|
||||
step "Verificação"
|
||||
for f in "$P"/lib/*.dylib; do [[ -L $f ]] || printf '%-34s %s\n' "$(basename "$f")" "$(lipo -archs "$f")"; done
|
||||
echo "pkg-config:"; PKG_CONFIG_LIBDIR="$P/lib/pkgconfig:$P/share/pkgconfig" pkg-config --modversion sdl3 libxxhash libavcodec vulkan 2>&1
|
||||
echo; echo "TODAS AS DEPENDÊNCIAS PRONTAS ($(date +%H:%M:%S))"
|
||||
@@ -0,0 +1,38 @@
|
||||
diff --git a/src/prim/unix/prim.c b/src/prim/unix/prim.c
|
||||
index 25c2ade..d5fa76c 100644
|
||||
--- a/src/prim/unix/prim.c
|
||||
+++ b/src/prim/unix/prim.c
|
||||
@@ -305,7 +305,33 @@ static int unix_madvise(void* addr, size_t size, int advice) {
|
||||
#endif
|
||||
}
|
||||
|
||||
+#if defined(MI_OS_WINDOW_START) && defined(MI_OS_WINDOW_END)
|
||||
+// Optional address window: every non-fixed OS allocation is placed in
|
||||
+// [MI_OS_WINDOW_START, MI_OS_WINDOW_END). issn_pc builds mimalloc with a window below 1 TiB on
|
||||
+// macOS, where the system heap lives near 128 TiB: heap pointers then stay small, like a Linux
|
||||
+// brk heap (its PS4 guest code packs pointers into 40 bits).
|
||||
+static uintptr_t mi_os_window_next = MI_OS_WINDOW_START;
|
||||
+static void* unix_mmap_window(size_t size, int protect_flags, int flags, int fd) {
|
||||
+ const uintptr_t align = (uintptr_t)64 << 20; // 64 MiB covers the arena and page alignments
|
||||
+ for (;;) {
|
||||
+ uintptr_t at = __atomic_load_n(&mi_os_window_next, __ATOMIC_RELAXED);
|
||||
+ const uintptr_t start = (at + align - 1) & ~(align - 1);
|
||||
+ if (start + size > (uintptr_t)MI_OS_WINDOW_END || start + size < start) { errno = ENOMEM; return MAP_FAILED; }
|
||||
+ if (!__atomic_compare_exchange_n(&mi_os_window_next, &at, start + size, true, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) continue;
|
||||
+ void* p = mmap((void*)start, size, protect_flags, flags, fd, 0);
|
||||
+ if (p == (void*)start) return p;
|
||||
+ if (p != MAP_FAILED) munmap(p, size); // that range is taken: the kernel placed it elsewhere
|
||||
+ }
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
static void* unix_mmap_prim(void* addr, size_t size, int protect_flags, int flags, int fd) {
|
||||
+ #if defined(MI_OS_WINDOW_START) && defined(MI_OS_WINDOW_END)
|
||||
+ if ((flags & MAP_FIXED) == 0 &&
|
||||
+ ((uintptr_t)addr < (uintptr_t)MI_OS_WINDOW_START || (uintptr_t)addr >= (uintptr_t)MI_OS_WINDOW_END)) {
|
||||
+ return unix_mmap_window(size, protect_flags, flags, fd);
|
||||
+ }
|
||||
+ #endif
|
||||
void* p = mmap(addr, size, protect_flags, flags, fd, 0 /* offset */);
|
||||
#if defined(__linux__) && defined(PR_SET_VMA)
|
||||
if (p!=MAP_FAILED && p!=NULL) {
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds dist/issn-macos-x86_64.zip: the port's current macOS build (run build.sh first), its
|
||||
# scripts and every non-system library it loads, with relative install names, so a tester
|
||||
# needs neither Homebrew nor the dependency build. Unzip, then: bash run.sh (ISSN_PREBUILT=1).
|
||||
set -euo pipefail
|
||||
cd -- "$(dirname -- "$0")/../.."
|
||||
deps=${ISSN_MAC_DEPS:-$PWD/out/macos-deps}
|
||||
[[ -f out/issn-probe && -f out/gpu/libissngpu.dylib ]] || { echo 'Build first: bash build.sh' >&2; exit 1; }
|
||||
name=issn-macos-x86_64
|
||||
stage=dist/$name
|
||||
rm -rf "$stage" "dist/$name.zip" && mkdir -p "$stage/bin/gpu"
|
||||
cp out/issn-probe out/issn-gpu-capabilities "$stage/bin/"
|
||||
cp out/gpu/libissngpu.dylib "$stage/bin/gpu/"
|
||||
cp "$deps/lib/libMoltenVK.dylib" "$stage/bin/"
|
||||
# shadPS4 points the Vulkan loader at the executable's directory; the path is the file's own.
|
||||
printf '{"file_format_version":"1.0.0","ICD":{"library_path":"./libMoltenVK.dylib","api_version":"1.4.0","is_portability_driver":true}}\n' \
|
||||
> "$stage/bin/MoltenVK_icd.json"
|
||||
|
||||
# The libraries of $deps a Mach-O file loads, by install name (absolute or @rpath/).
|
||||
deps_of() {
|
||||
otool -L "$1" | tail -n +2 | awk '{print $1}' | while read -r dep; do
|
||||
case $dep in
|
||||
"$deps"/lib/*|@rpath/*) [[ $dep == */libissngpu.dylib ]] || echo "$dep" ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
# Copies them into bin/ until nothing new appears (FFmpeg's libraries load each other).
|
||||
queue=("$stage/bin/issn-probe" "$stage/bin/issn-gpu-capabilities" "$stage/bin/gpu/libissngpu.dylib")
|
||||
while ((${#queue[@]})); do
|
||||
file=${queue[0]} queue=("${queue[@]:1}")
|
||||
for dep in $(deps_of "$file"); do
|
||||
base=$(basename "$dep")
|
||||
if [[ ! -f $stage/bin/$base ]]; then
|
||||
cp -L "$deps/lib/$base" "$stage/bin/$base"
|
||||
chmod u+w "$stage/bin/$base"
|
||||
install_name_tool -id "@rpath/$base" "$stage/bin/$base" 2>/dev/null
|
||||
queue+=("$stage/bin/$base")
|
||||
fi
|
||||
install_name_tool -change "$dep" "@rpath/$base" "$file" 2>/dev/null
|
||||
done
|
||||
done
|
||||
# Search paths: bin/ for everything, never the build machine's directories.
|
||||
for file in "$stage"/bin/issn-probe "$stage"/bin/issn-gpu-capabilities "$stage"/bin/gpu/libissngpu.dylib "$stage"/bin/*.dylib; do
|
||||
for path in $(otool -l "$file" | awk '/LC_RPATH/{getline; getline; print $2}'); do
|
||||
[[ $path == @* ]] || install_name_tool -delete_rpath "$path" "$file" 2>/dev/null
|
||||
done
|
||||
done
|
||||
install_name_tool -add_rpath @loader_path "$stage/bin/issn-probe" 2>/dev/null || true
|
||||
install_name_tool -add_rpath @loader_path "$stage/bin/issn-gpu-capabilities" 2>/dev/null || true
|
||||
install_name_tool -add_rpath @loader_path/.. "$stage/bin/gpu/libissngpu.dylib" 2>/dev/null || true
|
||||
install_name_tool -add_rpath @loader_path "$stage/bin/gpu/libissngpu.dylib" 2>/dev/null || true
|
||||
# install_name_tool invalidates signatures; Apple silicon (Rosetta included) needs one: ad hoc.
|
||||
for file in "$stage"/bin/issn-probe "$stage"/bin/issn-gpu-capabilities "$stage"/bin/gpu/*.dylib "$stage"/bin/*.dylib; do
|
||||
codesign --force --sign - "$file" 2>/dev/null
|
||||
done
|
||||
|
||||
cp -R run.sh run.py scripts launcher patches LICENSE "$stage/"
|
||||
cp packaging/macos/README-macOS.txt "$stage/"
|
||||
# run.sh on a packaged build: nothing is built (ISSN_PREBUILT).
|
||||
sed -i '' 's/^data=\${ISSN_DATA_DIR:-.}$/data=${ISSN_DATA_DIR:-.}; export ISSN_PREBUILT=${ISSN_PREBUILT:-1}/' "$stage/run.sh"
|
||||
(cd dist && ditto -c -k --keepParent "$name" "$name.zip")
|
||||
ls -lh "dist/$name.zip"
|
||||
@@ -0,0 +1,83 @@
|
||||
/* macOS implementations of compat_mac.h. */
|
||||
#ifdef __APPLE__
|
||||
#include "compat_mac.h"
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int expired(const struct timespec *deadline) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
return now.tv_sec > deadline->tv_sec || (now.tv_sec == deadline->tv_sec && now.tv_nsec >= deadline->tv_nsec);
|
||||
}
|
||||
/* Polls the try variant with a short sleep: the guest's timed waits are rare and short, and
|
||||
* a deadline in the past still gets one attempt (POSIX). */
|
||||
static int timed_try(int (*attempt)(void *), void *object, const struct timespec *deadline) {
|
||||
if (deadline->tv_nsec < 0 || deadline->tv_nsec >= 1000000000) return EINVAL;
|
||||
for (;;) {
|
||||
int e = attempt(object);
|
||||
if (e != EBUSY) return e;
|
||||
if (expired(deadline)) return ETIMEDOUT;
|
||||
struct timespec nap = {0, 200000};
|
||||
nanosleep(&nap, NULL);
|
||||
}
|
||||
}
|
||||
static int try_mutex(void *m) { return pthread_mutex_trylock(m); }
|
||||
static int try_read(void *l) { return pthread_rwlock_tryrdlock(l); }
|
||||
static int try_write(void *l) { return pthread_rwlock_trywrlock(l); }
|
||||
int compat_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *deadline) {
|
||||
return timed_try(try_mutex, mutex, deadline);
|
||||
}
|
||||
int compat_rwlock_timedrdlock(pthread_rwlock_t *lock, const struct timespec *deadline) {
|
||||
return timed_try(try_read, lock, deadline);
|
||||
}
|
||||
int compat_rwlock_timedwrlock(pthread_rwlock_t *lock, const struct timespec *deadline) {
|
||||
return timed_try(try_write, lock, deadline);
|
||||
}
|
||||
|
||||
int compat_memfd_create(const char *name, unsigned flags) {
|
||||
(void)name;
|
||||
static unsigned counter;
|
||||
char path[32]; /* PSHMNAMLEN is 31 */
|
||||
for (int attempt = 0; attempt < 100; ++attempt) {
|
||||
snprintf(path, sizeof(path), "/issn.%d.%u", (int)getpid(),
|
||||
__atomic_fetch_add(&counter, 1, __ATOMIC_RELAXED));
|
||||
int fd = shm_open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
if (fd >= 0) {
|
||||
shm_unlink(path);
|
||||
if (flags & MFD_CLOEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
return fd;
|
||||
}
|
||||
if (errno != EEXIST) return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void compat_close_from(int first) {
|
||||
struct rlimit limit;
|
||||
int last = 65536;
|
||||
if (!getrlimit(RLIMIT_NOFILE, &limit) && limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < (rlim_t)last)
|
||||
last = (int)limit.rlim_cur;
|
||||
for (int fd = first; fd < last; ++fd) close(fd);
|
||||
}
|
||||
|
||||
void compat_rosetta_advertise_avx(char **argv) {
|
||||
int translated = 0;
|
||||
size_t size = sizeof translated;
|
||||
if (sysctlbyname("sysctl.proc_translated", &translated, &size, NULL, 0) || !translated) return;
|
||||
if (getenv("ROSETTA_ADVERTISE_AVX")) return; /* already set, or chosen by the user */
|
||||
char path[PATH_MAX];
|
||||
uint32_t length = sizeof path;
|
||||
if (_NSGetExecutablePath(path, &length)) return;
|
||||
setenv("ROSETTA_ADVERTISE_AVX", "1", 1);
|
||||
execv(path, argv);
|
||||
unsetenv("ROSETTA_ADVERTISE_AVX"); /* execv failed: carry on, cpu_check reports AVX */
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,69 @@
|
||||
/* macOS (Apple clang, x86_64 under Rosetta 2) counterparts of the Linux calls the runtime
|
||||
* uses. Included through runtime.h after the C library headers, like compat_win.h, so the
|
||||
* macros below only replace calls in this project's own sources. <pthread.h> and <time.h>
|
||||
* come first so later includes of them cannot see the macros. */
|
||||
#ifndef ISSN_COMPAT_MAC_H
|
||||
#define ISSN_COMPAT_MAC_H
|
||||
#ifdef __APPLE__
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* struct stat's nanosecond times are st_*timespec on macOS (st_*tim on Linux). */
|
||||
#define st_atim st_atimespec
|
||||
#define st_mtim st_mtimespec
|
||||
#define st_ctim st_ctimespec
|
||||
|
||||
#ifndef CLOCK_REALTIME_COARSE
|
||||
#define CLOCK_REALTIME_COARSE CLOCK_REALTIME
|
||||
#endif
|
||||
#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
|
||||
#define PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PTHREAD_RECURSIVE_MUTEX_INITIALIZER
|
||||
#endif
|
||||
|
||||
/* Kernel thread id (Linux gettid): the 64-bit id macOS gives each thread. */
|
||||
static inline pid_t compat_gettid(void) {
|
||||
uint64_t id = 0;
|
||||
pthread_threadid_np(NULL, &id);
|
||||
return (pid_t)id;
|
||||
}
|
||||
#define gettid compat_gettid
|
||||
|
||||
/* Only the calling thread can name itself on macOS. */
|
||||
#define pthread_setname_np(thread, name) ((void)(thread), pthread_setname_np(name))
|
||||
|
||||
/* macOS condition variables wait on CLOCK_REALTIME only (no pthread_condattr_setclock), as
|
||||
* with winpthreads: runtime_sema.c and runtime_eventflag.c pick WAIT_CLOCK = CLOCK_REALTIME. */
|
||||
#define pthread_condattr_setclock(attr, clock) ((void)(attr), (clock) == CLOCK_REALTIME ? 0 : EINVAL)
|
||||
|
||||
/* No timed mutex/rwlock acquisition on macOS: try until the CLOCK_REALTIME deadline. */
|
||||
int compat_mutex_timedlock(pthread_mutex_t *mutex, const struct timespec *deadline);
|
||||
int compat_rwlock_timedrdlock(pthread_rwlock_t *lock, const struct timespec *deadline);
|
||||
int compat_rwlock_timedwrlock(pthread_rwlock_t *lock, const struct timespec *deadline);
|
||||
#define pthread_mutex_timedlock compat_mutex_timedlock
|
||||
#define pthread_rwlock_timedrdlock compat_rwlock_timedrdlock
|
||||
#define pthread_rwlock_timedwrlock compat_rwlock_timedwrlock
|
||||
|
||||
/* Guest memory backing (Linux memfd_create): an unlinked POSIX shared memory object. */
|
||||
#define MFD_CLOEXEC 1u
|
||||
int compat_memfd_create(const char *name, unsigned flags);
|
||||
#define memfd_create compat_memfd_create
|
||||
|
||||
/* Returning pages to the pool (Linux punch hole) has no shared-memory counterpart: released
|
||||
* pages stay committed until the guest reuses them. */
|
||||
#define FALLOC_FL_KEEP_SIZE 1
|
||||
#define FALLOC_FL_PUNCH_HOLE 2
|
||||
#define fallocate(fd, mode, offset, length) ((void)(fd), (void)(mode), (void)(offset), (void)(length), 0)
|
||||
|
||||
/* Linux close_range(first, ~0u, 0): closes every descriptor from first up. */
|
||||
void compat_close_from(int first);
|
||||
|
||||
/* Rosetta 2 runs AVX/AVX2 but only reports them in CPUID to a process started with
|
||||
* ROSETTA_ADVERTISE_AVX=1: a translated process without it starts itself again with it. */
|
||||
void compat_rosetta_advertise_avx(char **argv);
|
||||
|
||||
#endif
|
||||
#endif
|
||||
+91
-20
@@ -23,14 +23,24 @@
|
||||
#include <signal.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#ifdef __APPLE__
|
||||
#include <malloc/malloc.h>
|
||||
#include <sys/ucontext.h>
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
/* <mach/vm_param.h> defines round_page/trunc_page macros; this file has its own round_page. */
|
||||
#undef round_page
|
||||
#undef trunc_page
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#include <ucontext.h>
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <fcntl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
|
||||
@@ -111,7 +121,7 @@ static ABI __attribute__((noreturn)) void unresolved(uint32_t id, uintptr_t argu
|
||||
uintptr_t caller=(uintptr_t)__builtin_return_address(0)-(uintptr_t)image;
|
||||
for (uint64_t m=0;m<module_count;++m)
|
||||
if (caller>=modules[m].base && caller-modules[m].base<modules[m].size)
|
||||
printf("Caller in linked module %" PRIu64 " (%s): +0x%" PRIxPTR "\n",m,m==0 ? "libc.prx" : "system module",caller-modules[m].base);
|
||||
printf("Caller in linked module %" PRIu64 " (%s): +0x%" PRIxPTR "\n",m,m==0 ? "libc.prx" : "system module",(uintptr_t)(caller-modules[m].base));
|
||||
runtime_report();
|
||||
puts(entered_game ? "Original guest entry instructions executed; game initialization is incomplete." :
|
||||
"Native libc initialization is incomplete; game entry has not run.");
|
||||
@@ -121,7 +131,12 @@ static ABI __attribute__((noreturn)) void unresolved(uint32_t id, uintptr_t argu
|
||||
#ifndef _WIN32
|
||||
/* enter_on_stack(entry, arg0, arg1, stack_top): call entry(arg0,arg1) on a new stack. */
|
||||
void enter_on_stack(void *entry, void *arg0, void *arg1, void *top);
|
||||
__asm__(".text\n.globl enter_on_stack\nenter_on_stack:\n"
|
||||
#ifdef __APPLE__
|
||||
#define ENTER_ON_STACK "_enter_on_stack" /* Mach-O symbols carry a leading underscore */
|
||||
#else
|
||||
#define ENTER_ON_STACK "enter_on_stack"
|
||||
#endif
|
||||
__asm__(".text\n.globl " ENTER_ON_STACK "\n" ENTER_ON_STACK ":\n"
|
||||
" push %rbp\n mov %rsp,%rbp\n and $-16,%rcx\n mov %rcx,%rsp\n"
|
||||
" mov %rdi,%rax\n mov %rsi,%rdi\n mov %rdx,%rsi\n call *%rax\n"
|
||||
" mov %rbp,%rsp\n pop %rbp\n ret\n");
|
||||
@@ -272,8 +287,11 @@ static LONG CALLBACK vectored_fault(EXCEPTION_POINTERS *info) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
static LONG WINAPI unhandled_fault(EXCEPTION_POINTERS *info) { return fatal_exception(info); }
|
||||
#endif
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
/* Guest code reads its TCB with `mov rax, gs:[0]` (link_*.py rewrote fs:[0]); on Windows
|
||||
* the displacement becomes the TEB TLS slot that holds the guest TCB (runtime_thread.c). */
|
||||
* the displacement becomes the TEB TLS slot that holds the guest TCB, on macOS the pthread
|
||||
* key's slot in the thread's TSD array (runtime_thread.c). */
|
||||
uint32_t runtime_thread_tcb_offset(void);
|
||||
/* `mov r64, gs:[0]` (link_modules.py rewrote the fs: of each `mov r64, fs:[0]`): 65, REX.W[R],
|
||||
* 8B, ModRM mod=00 rm=100 (any register), SIB 25 (disp32 only), displacement 0. issn: any
|
||||
@@ -296,21 +314,39 @@ static uint64_t patch_tcb_loads(const Segment *segments, uint64_t count) {
|
||||
}
|
||||
#endif
|
||||
#ifndef _WIN32
|
||||
/* ucontext gregs of IsaRegs.gpr (x86 numbering). */
|
||||
/* Saved registers of a signal context, in IsaRegs.gpr's x86 numbering: Linux keeps them in
|
||||
* uc_mcontext.gregs[REG_*], macOS in the x86_thread_state64 that uc_mcontext points to. */
|
||||
#ifdef __APPLE__
|
||||
typedef __uint64_t ctx_reg_t;
|
||||
static ctx_reg_t *ctx_gpr(ucontext_t *uc, int index) {
|
||||
_STRUCT_X86_THREAD_STATE64 *s = &uc->uc_mcontext->__ss;
|
||||
ctx_reg_t *const r[16] = {&s->__rax, &s->__rcx, &s->__rdx, &s->__rbx, &s->__rsp, &s->__rbp, &s->__rsi, &s->__rdi,
|
||||
&s->__r8, &s->__r9, &s->__r10, &s->__r11, &s->__r12, &s->__r13, &s->__r14, &s->__r15};
|
||||
return r[index];
|
||||
}
|
||||
#define CTX_RIP(uc) ((uc)->uc_mcontext->__ss.__rip)
|
||||
#define CTX_RFLAGS(uc) ((uc)->uc_mcontext->__ss.__rflags)
|
||||
#else
|
||||
typedef greg_t ctx_reg_t;
|
||||
static const int isa_gregs[16]={REG_RAX,REG_RCX,REG_RDX,REG_RBX,REG_RSP,REG_RBP,REG_RSI,REG_RDI,
|
||||
REG_R8,REG_R9,REG_R10,REG_R11,REG_R12,REG_R13,REG_R14,REG_R15};
|
||||
static ctx_reg_t *ctx_gpr(ucontext_t *uc, int index) { return &uc->uc_mcontext.gregs[isa_gregs[index]]; }
|
||||
#define CTX_RIP(uc) ((uc)->uc_mcontext.gregs[REG_RIP])
|
||||
#define CTX_RFLAGS(uc) ((uc)->uc_mcontext.gregs[REG_EFL])
|
||||
#endif
|
||||
enum { CTX_RBP = 5, CTX_RDI = 7 }; /* ctx_gpr indices */
|
||||
static void fault(int sig, siginfo_t *info, void *context) {
|
||||
if (sig == SIGTRAP && isa_table) {
|
||||
/* An int3 ISA site (RIP is past the CC byte); any other trap ends in the report below. */
|
||||
greg_t *g = ((ucontext_t *)context)->uc_mcontext.gregs;
|
||||
ucontext_t *trap = context;
|
||||
IsaRegs regs;
|
||||
uintptr_t rip;
|
||||
for (int i = 0; i < 16; ++i) regs.gpr[i] = (uint64_t)g[isa_gregs[i]];
|
||||
regs.rflags = (uint64_t)g[REG_EFL];
|
||||
if (isa_rewrite_trap(isa_table, (uintptr_t)g[REG_RIP] - 1, ®s, &rip)) {
|
||||
for (int i = 0; i < 16; ++i) g[isa_gregs[i]] = (greg_t)regs.gpr[i];
|
||||
g[REG_EFL] = (greg_t)regs.rflags;
|
||||
g[REG_RIP] = (greg_t)rip;
|
||||
for (int i = 0; i < 16; ++i) regs.gpr[i] = (uint64_t)*ctx_gpr(trap, i);
|
||||
regs.rflags = (uint64_t)CTX_RFLAGS(trap);
|
||||
if (isa_rewrite_trap(isa_table, (uintptr_t)CTX_RIP(trap) - 1, ®s, &rip)) {
|
||||
for (int i = 0; i < 16; ++i) *ctx_gpr(trap, i) = (ctx_reg_t)regs.gpr[i];
|
||||
CTX_RFLAGS(trap) = (ctx_reg_t)regs.rflags;
|
||||
CTX_RIP(trap) = (ctx_reg_t)rip;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -328,7 +364,7 @@ static void fault(int sig, siginfo_t *info, void *context) {
|
||||
}
|
||||
/* The process is terminating: dladdr/snprintf are acceptable here. */
|
||||
ucontext_t *uc = context;
|
||||
uintptr_t rip = (uintptr_t)uc->uc_mcontext.gregs[REG_RIP];
|
||||
uintptr_t rip = (uintptr_t)CTX_RIP(uc);
|
||||
char line[512];
|
||||
Dl_info where;
|
||||
int site = isa_table ? isa_rewrite_site_of_stub(isa_table, rip) : -1;
|
||||
@@ -389,20 +425,30 @@ static void write_hex(char *out, uint64_t v) {
|
||||
const char digits[] = "0123456789abcdef";
|
||||
for (int i = 0; i < 16; ++i) out[i] = digits[(v >> (60 - i * 4)) & 15];
|
||||
}
|
||||
/* Reads a frame record without faulting on a bad rbp. */
|
||||
static int read_frame(uintptr_t frame[2], uintptr_t rbp) {
|
||||
#ifdef __APPLE__
|
||||
mach_vm_size_t got=0;
|
||||
return mach_vm_read_overwrite(mach_task_self(),(mach_vm_address_t)rbp,2*sizeof(uintptr_t),
|
||||
(mach_vm_address_t)frame,&got)==KERN_SUCCESS && got==2*sizeof(uintptr_t);
|
||||
#else
|
||||
struct iovec local={frame,2*sizeof(uintptr_t)}, remote={(void *)rbp,2*sizeof(uintptr_t)};
|
||||
return process_vm_readv(getpid(),&local,1,&remote,1,0)==(ssize_t)(2*sizeof(uintptr_t));
|
||||
#endif
|
||||
}
|
||||
static void dump_frames(ucontext_t *uc) {
|
||||
char line[] = " tid=0000000000000000 rip=0000000000000000 image-relative=0000000000000000 host-relative=0000000000000000\n";
|
||||
uintptr_t rip=(uintptr_t)uc->uc_mcontext.gregs[REG_RIP], rbp=(uintptr_t)uc->uc_mcontext.gregs[REG_RBP];
|
||||
uintptr_t rip=(uintptr_t)CTX_RIP(uc), rbp=(uintptr_t)*ctx_gpr(uc,CTX_RBP);
|
||||
uint64_t tid=(uint64_t)gettid();
|
||||
/* First argument register: the lock address when a thread waits on a futex. */
|
||||
char arg[]=" tid=0000000000000000 rdi=0000000000000000\n";
|
||||
write_hex(arg+6,tid); write_hex(arg+27,(uint64_t)uc->uc_mcontext.gregs[REG_RDI]);
|
||||
write_hex(arg+6,tid); write_hex(arg+27,(uint64_t)*ctx_gpr(uc,CTX_RDI));
|
||||
{ ssize_t written_=write(2,arg,sizeof(arg)-1); (void)written_; }
|
||||
for (int depth=0; depth<24; ++depth) {
|
||||
write_hex(line+6,tid); write_hex(line+27,rip); write_hex(line+59,rip-(uintptr_t)image); write_hex(line+90,rip-exe_base);
|
||||
{ ssize_t written_=write(2,line,sizeof(line)-1); (void)written_; }
|
||||
uintptr_t frame[2];
|
||||
struct iovec local={frame,sizeof(frame)}, remote={(void *)rbp,sizeof(frame)};
|
||||
if (!rbp || process_vm_readv(getpid(),&local,1,&remote,1,0)!=(ssize_t)sizeof(frame)) break;
|
||||
if (!rbp || !read_frame(frame,rbp)) break;
|
||||
if (frame[0]<=rbp) break;
|
||||
rbp=frame[0]; rip=frame[1];
|
||||
}
|
||||
@@ -413,6 +459,17 @@ static void watchdog(int sig, siginfo_t *info, void *context) {
|
||||
const char head[]="STOP: watchdog timeout; thread stacks:\n";
|
||||
{ ssize_t written_=write(2,head,sizeof(head)-1); (void)written_; }
|
||||
dump_frames(context);
|
||||
#ifdef __APPLE__
|
||||
/* Every other thread dumps its own stack (SIGUSR2): enumerated through the Mach task. */
|
||||
thread_act_array_t list;
|
||||
mach_msg_type_number_t count=0;
|
||||
mach_port_t self=mach_thread_self();
|
||||
if (task_threads(mach_task_self(),&list,&count)==KERN_SUCCESS)
|
||||
for (mach_msg_type_number_t i=0; i<count; ++i) {
|
||||
pthread_t thread=pthread_from_mach_thread_np(list[i]);
|
||||
if (thread && list[i]!=self) { pthread_kill(thread,SIGUSR2); usleep(20000); }
|
||||
}
|
||||
#else
|
||||
int dir=open("/proc/self/task",O_RDONLY|O_DIRECTORY);
|
||||
char buffer[4096];
|
||||
long n;
|
||||
@@ -424,6 +481,7 @@ static void watchdog(int sig, siginfo_t *info, void *context) {
|
||||
if (tid>0 && tid!=self) { syscall(SYS_tgkill,getpid(),tid,SIGUSR2); usleep(20000); }
|
||||
at+=d->reclen;
|
||||
}
|
||||
#endif
|
||||
usleep(100000);
|
||||
watchdog_extra();
|
||||
_exit(128 + sig);
|
||||
@@ -840,7 +898,11 @@ void runtime_restart(void) {
|
||||
TerminateProcess(GetCurrentProcess(),0);
|
||||
#else
|
||||
const char *command = getenv("ISSN_RESTART_COMMAND");
|
||||
#ifdef __APPLE__
|
||||
compat_close_from(3);
|
||||
#else
|
||||
syscall(SYS_close_range, 3u, ~0u, 0u);
|
||||
#endif
|
||||
if (command && *command) {
|
||||
puts("Runtime: restarting through ISSN_RESTART_COMMAND");
|
||||
execl("/bin/sh", "sh", "-c", command, (char *)NULL);
|
||||
@@ -872,11 +934,16 @@ static int cpu_supported(char *missing, size_t size) {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
/* Keep host heap objects handed to the guest (thread handles, TLS) in the
|
||||
non-PIE brk heap, i.e. below 1 TiB: the guest packs pointers into 40 bits. */
|
||||
non-PIE brk heap, i.e. below 1 TiB: the guest packs pointers into 40 bits.
|
||||
macOS has no brk heap: the build links mimalloc with an address window below
|
||||
1 TiB instead (packaging/macos). */
|
||||
mallopt(M_ARENA_MAX,1);
|
||||
mallopt(M_MMAP_THRESHOLD,32*1024*1024);
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
compat_rosetta_advertise_avx(argv);
|
||||
#endif
|
||||
if (argc == 2 && !strcmp(argv[1], "--vulkan-only")) return vulkan_smoke();
|
||||
char missing[128];
|
||||
@@ -896,6 +963,8 @@ int main(int argc, char **argv) {
|
||||
* TCB needs one of the 64 TEB TLS slots before DLLs (Vulkan drivers) take them. */
|
||||
runtime_memory_reserve();
|
||||
runtime_thread_tcb_offset();
|
||||
#elif defined(__APPLE__)
|
||||
runtime_thread_tcb_offset(); /* the pthread key that holds the guest TCB (runtime_thread.c) */
|
||||
#endif
|
||||
int cpu_only = 0, strict_imports = 0, check_imports = 0;
|
||||
unsigned timeout_seconds = 10;
|
||||
@@ -1206,6 +1275,8 @@ int main(int argc, char **argv) {
|
||||
if (gpu_enabled) issngpu_apply_game_patches();
|
||||
#ifdef _WIN32
|
||||
printf("Guest TCB loads redirected to the TEB TLS slot: %" PRIu64 "\n", patch_tcb_loads(segments, ns));
|
||||
#elif defined(__APPLE__)
|
||||
printf("Guest TCB loads redirected to the pthread TSD slot: %" PRIu64 "\n", patch_tcb_loads(segments, ns));
|
||||
#endif
|
||||
if (isa_table) {
|
||||
IsaReport r = isa_rewrite_apply(isa_table, image, stubs, isa_sets);
|
||||
|
||||
@@ -10,6 +10,9 @@ typedef issn_jmp_buf sigjmp_buf;
|
||||
#define sigsetjmp(buffer, save) issn_setjmp(buffer)
|
||||
#else
|
||||
#include <setjmp.h>
|
||||
#ifdef __APPLE__
|
||||
#include "compat_mac.h"
|
||||
#endif
|
||||
#endif
|
||||
/* Recovery point for speculative guest memory reads on this thread (probe.c fault handler). */
|
||||
extern __thread sigjmp_buf *runtime_fault_recover;
|
||||
|
||||
@@ -55,6 +55,13 @@ static void sleep_until(uint64_t deadline) {
|
||||
#ifdef _WIN32
|
||||
/* winpthreads' clock_nanosleep only takes CLOCK_REALTIME (EINVAL otherwise). */
|
||||
for (uint64_t now=now_ns(); now<deadline; now=now_ns()) compat_sleep_ns(deadline-now);
|
||||
#elif defined(__APPLE__)
|
||||
/* No clock_nanosleep on macOS: sleep what is left of the CLOCK_MONOTONIC deadline. */
|
||||
for (uint64_t now=now_ns(); now<deadline; now=now_ns()) {
|
||||
const uint64_t left=deadline-now;
|
||||
struct timespec t={(time_t)(left/1000000000u),(long)(left%1000000000u)};
|
||||
nanosleep(&t,NULL);
|
||||
}
|
||||
#else
|
||||
struct timespec t={(time_t)(deadline/1000000000u),(long)(deadline%1000000000u)};
|
||||
while (clock_nanosleep(CLOCK_MONOTONIC,TIMER_ABSTIME,&t,NULL)) {}
|
||||
|
||||
@@ -34,8 +34,8 @@ static Flag *flags;
|
||||
static uint64_t next_id=1;
|
||||
static size_t created, waits, timeouts;
|
||||
|
||||
/* As runtime_sema.c: winpthreads condition variables accept only CLOCK_REALTIME. */
|
||||
#ifdef _WIN32
|
||||
/* As runtime_sema.c: winpthreads and macOS condition variables accept only CLOCK_REALTIME. */
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
#define WAIT_CLOCK CLOCK_REALTIME
|
||||
#else
|
||||
#define WAIT_CLOCK CLOCK_MONOTONIC
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach_init.h>
|
||||
#include <mach/mach_port.h>
|
||||
#include <mach/thread_act.h>
|
||||
#endif
|
||||
#include <x86intrin.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/random.h>
|
||||
@@ -245,6 +250,22 @@ static ABI int32_t guest_getrusage(int who,GuestRusage *out) {
|
||||
compat_cpu_times(who,&user,&kernel);
|
||||
out->utime=(GuestTimeval){user/1000000,user%1000000};
|
||||
out->stime=(GuestTimeval){kernel/1000000,kernel%1000000};
|
||||
#elif defined(__APPLE__)
|
||||
if (who==0) {
|
||||
struct rusage r;
|
||||
getrusage(RUSAGE_SELF,&r);
|
||||
out->utime=(GuestTimeval){r.ru_utime.tv_sec,r.ru_utime.tv_usec};
|
||||
out->stime=(GuestTimeval){r.ru_stime.tv_sec,r.ru_stime.tv_usec};
|
||||
} else { /* no RUSAGE_THREAD: the calling thread's times come from Mach */
|
||||
thread_basic_info_data_t info;
|
||||
mach_msg_type_number_t count=THREAD_BASIC_INFO_COUNT;
|
||||
mach_port_t self=mach_thread_self();
|
||||
if (thread_info(self,THREAD_BASIC_INFO,(thread_info_t)&info,&count)==KERN_SUCCESS) {
|
||||
out->utime=(GuestTimeval){info.user_time.seconds,info.user_time.microseconds};
|
||||
out->stime=(GuestTimeval){info.system_time.seconds,info.system_time.microseconds};
|
||||
}
|
||||
mach_port_deallocate(mach_task_self(),self);
|
||||
}
|
||||
#else
|
||||
struct rusage r;
|
||||
getrusage(who==0 ? RUSAGE_SELF : RUSAGE_THREAD,&r);
|
||||
@@ -260,6 +281,8 @@ static ABI int32_t guest_sysctl(const int32_t *name,uint32_t namelen,void *old,u
|
||||
if (!old || !oldlen) return fail_posix(EINVAL);
|
||||
#ifdef _WIN32
|
||||
if (compat_random(old,(size_t)*oldlen)) return fail_posix(EIO);
|
||||
#elif defined(__APPLE__)
|
||||
arc4random_buf(old,(size_t)*oldlen);
|
||||
#else
|
||||
if (getrandom(old,(size_t)*oldlen,0)<0) return fail_posix(errno);
|
||||
#endif
|
||||
|
||||
@@ -762,7 +762,13 @@ void *runtime_low_map(size_t size, int prot) {
|
||||
#else
|
||||
void *p=MAP_FAILED;
|
||||
while (low_next+size<=USER_MIN) {
|
||||
#ifdef __APPLE__
|
||||
/* No MAP_FIXED_NOREPLACE: map with the address as a hint and keep it only there. */
|
||||
p=mmap((void *)low_next,size,prot,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
|
||||
if (p!=MAP_FAILED && p!=(void *)low_next) { munmap(p,size); p=MAP_FAILED; }
|
||||
#else
|
||||
p=mmap((void *)low_next,size,prot,MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED_NOREPLACE,-1,0);
|
||||
#endif
|
||||
low_next+=size+PAGE; /* unmapped gap catches overruns */
|
||||
if (p!=MAP_FAILED) break;
|
||||
}
|
||||
|
||||
+3
-2
@@ -35,8 +35,9 @@ static void host_check(int e) {
|
||||
if (e) { fprintf(stderr,"STOP: semaphore host error %d\n",e); exit(21); }
|
||||
}
|
||||
/* Waits use this clock for their deadlines. winpthreads condition variables accept only
|
||||
* CLOCK_REALTIME (pthread_condattr_setclock(CLOCK_MONOTONIC) fails with EINVAL). */
|
||||
#ifdef _WIN32
|
||||
* CLOCK_REALTIME (pthread_condattr_setclock(CLOCK_MONOTONIC) fails with EINVAL), and macOS
|
||||
* has no pthread_condattr_setclock at all. */
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
#define WAIT_CLOCK CLOCK_REALTIME
|
||||
#else
|
||||
#define WAIT_CLOCK CLOCK_MONOTONIC
|
||||
|
||||
+22
-1
@@ -4,7 +4,9 @@
|
||||
* keeps FS. Priorities/affinity are recorded, not enforced by a PS4 scheduler.
|
||||
* Windows: GS is the TEB and cannot be moved. The guest TCB lives in a TLS slot of the TEB
|
||||
* instead, and the loader rewrites the load to `mov rax, gs:[0x1480+8*slot]` (TlsSlots),
|
||||
* the same 9-byte instruction with another displacement. */
|
||||
* the same 9-byte instruction with another displacement.
|
||||
* macOS (x86_64 under Rosetta 2): GS is the thread's TSD array and cannot be moved either.
|
||||
* The guest TCB lives in a pthread key, which macOS stores at gs:[8*key]: same rewrite. */
|
||||
#define _GNU_SOURCE
|
||||
#include "runtime.h"
|
||||
#include <stdio.h>
|
||||
@@ -17,6 +19,8 @@
|
||||
#include <unistd.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/mman.h>
|
||||
#else
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/mman.h>
|
||||
@@ -85,6 +89,23 @@ static void set_gs(void *base) {
|
||||
if (!TlsSetValue(tls_slot,base)) { fprintf(stderr,"STOP: TlsSetValue failed (%lu)\n",GetLastError()); exit(21); }
|
||||
}
|
||||
#define tls_block_alloc(size) _aligned_malloc(size,64)
|
||||
#elif defined(__APPLE__)
|
||||
/* pthread_getspecific(key) is `mov rax, gs:[8*key]` on x86_64 macOS: the guest's TCB load
|
||||
* reads the key directly. */
|
||||
static pthread_key_t tcb_key;
|
||||
static pthread_once_t tcb_key_once=PTHREAD_ONCE_INIT;
|
||||
static void create_tcb_key(void) {
|
||||
if (pthread_key_create(&tcb_key,NULL)) { fputs("STOP: pthread_key_create failed\n",stderr); exit(21); }
|
||||
}
|
||||
uint32_t runtime_thread_tcb_offset(void) {
|
||||
pthread_once(&tcb_key_once,create_tcb_key);
|
||||
return 8*(uint32_t)tcb_key;
|
||||
}
|
||||
static void set_gs(void *base) {
|
||||
runtime_thread_tcb_offset();
|
||||
if (pthread_setspecific(tcb_key,base)) { fputs("STOP: pthread_setspecific failed\n",stderr); exit(21); }
|
||||
}
|
||||
#define tls_block_alloc(size) aligned_alloc(64,size)
|
||||
#else
|
||||
static void set_gs(void *base) {
|
||||
if (syscall(SYS_arch_prctl,ARCH_SET_GS,(unsigned long)base)) { perror("STOP: arch_prctl(ARCH_SET_GS)"); exit(21); }
|
||||
|
||||
@@ -3,12 +3,29 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef __APPLE__
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <string.h>
|
||||
#endif
|
||||
#define CHECK(call) do { VkResult r = (call); if (r != VK_SUCCESS) { \
|
||||
fprintf(stderr, "Vulkan: %s returned %d\n", #call, r); exit(1); } } while (0)
|
||||
|
||||
int vulkan_smoke(void) {
|
||||
VkApplicationInfo app = {.sType=VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName="inFAMOUS Second Son native probe", .apiVersion=VK_API_VERSION_1_0};
|
||||
VkInstanceCreateInfo ici = {.sType=VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo=&app};
|
||||
#ifdef __APPLE__
|
||||
/* As the GPU library does: the MoltenVK ICD file sits next to the executable. */
|
||||
char path[PATH_MAX]; uint32_t length = sizeof path;
|
||||
if (!getenv("VK_DRIVER_FILES") && !_NSGetExecutablePath(path, &length)) {
|
||||
char *slash = strrchr(path, '/');
|
||||
if (slash) { *slash = 0; setenv("VK_DRIVER_FILES", path, 1); }
|
||||
}
|
||||
/* MoltenVK is a portability driver: the loader only lists it when asked to. */
|
||||
const char *portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
ici.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||
ici.enabledExtensionCount = 1; ici.ppEnabledExtensionNames = &portability;
|
||||
#endif
|
||||
VkInstance instance; CHECK(vkCreateInstance(&ici, NULL, &instance));
|
||||
uint32_t count = 0; CHECK(vkEnumeratePhysicalDevices(instance, &count, NULL));
|
||||
if (!count) { fprintf(stderr, "Vulkan: no physical devices\n"); vkDestroyInstance(instance, NULL); return 1; }
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
#ifdef __APPLE__
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#include "video_core/amdgpu/issn_msaa_default.h"
|
||||
|
||||
@@ -73,8 +77,27 @@ int main(int argc, char **argv) {
|
||||
.pApplicationName = "issn scene scaling probe",
|
||||
.apiVersion = VK_API_VERSION_1_3,
|
||||
};
|
||||
#ifdef __APPLE__
|
||||
/* As the GPU library does: the MoltenVK ICD file sits next to the executable, and the
|
||||
loader only lists a portability driver when the instance asks for it. */
|
||||
char path[PATH_MAX];
|
||||
uint32_t length = sizeof path;
|
||||
if (!getenv("VK_DRIVER_FILES") && !_NSGetExecutablePath(path, &length)) {
|
||||
char *slash = strrchr(path, '/');
|
||||
if (slash) {
|
||||
*slash = 0;
|
||||
setenv("VK_DRIVER_FILES", path, 1);
|
||||
}
|
||||
}
|
||||
const char *portability = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
|
||||
#endif
|
||||
const VkInstanceCreateInfo create = {
|
||||
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
||||
#ifdef __APPLE__
|
||||
.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR,
|
||||
.enabledExtensionCount = 1,
|
||||
.ppEnabledExtensionNames = &portability,
|
||||
#endif
|
||||
.pApplicationInfo = &app,
|
||||
};
|
||||
VkInstance instance = VK_NULL_HANDLE;
|
||||
|
||||
Reference in new issue
Block a user