Merge pull request #9441 from skylersaleh/master

Apple M1 Support for MacOS
This commit is contained in:
Léo Lam 2021-05-24 12:39:01 +02:00 committed by GitHub
commit 51671921c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 598 additions and 38 deletions

330
BuildMacOSUniversalBinary.py Executable file
View File

@ -0,0 +1,330 @@
#!/usr/bin/env python3
"""
The current tooling supported in CMake, Homebrew, and Qt5 are insufficient for
creating macOS universal binaries automatically for applications like Dolphin
which have more complicated build requirements (like different libraries, build
flags and source files for each target architecture).
So instead, this script manages the configuration and compilation of distinct
builds and project files for each target architecture and then merges the two
binaries into a single universal binary.
Running this script will:
1) Generate Xcode project files for the ARM build (if project files don't
already exist)
2) Generate Xcode project files for the x64 build (if project files don't
already exist)
3) Build the ARM project for the selected build_target
4) Build the x64 project for the selected build_target
5) Generate universal .app packages combining the ARM and x64 packages
6) Use the lipo tool to combine the binary objects inside each of the
packages into universal binaries
7) Code sign the final universal binaries using the specified
codesign_identity
"""
import argparse
import filecmp
import glob
import json
import multiprocessing
import os
import shutil
import subprocess
# The config variables listed below are the defaults, but they can be
# overridden by command line arguments see parse_args(), or run:
# BuildMacOSUniversalBinary.py --help
DEFAULT_CONFIG = {
# Location of destination universal binary
"dst_app": "universal/",
# Build Target (dolphin-emu to just build the emulator and skip the tests)
"build_target": "ALL_BUILD",
# Location for CMake to search for files (default is for homebrew)
"arm64_cmake_prefix": "/opt/homebrew",
"x86_64_cmake_prefix": "/usr/local",
# Locations to qt5 directories for arm and x64 libraries
# The default values of these paths are taken from the default
# paths used for homebrew
"arm64_qt5_path": "/opt/homebrew/opt/qt5",
"x86_64_qt5_path": "/usr/local/opt/qt5",
# Identity to use for code signing. "-" indicates that the app will not
# be cryptographically signed/notarized but will instead just use a
# SHA checksum to verify the integrity of the app. This doesn't
# protect against malicious actors, but it does protect against
# running corrupted binaries and allows for access to the extended
# permisions needed for ARM builds
"codesign_identity": "-",
# Entitlements file to use for code signing
"entitlements": "../Source/Core/DolphinQt/DolphinEmu.entitlements",
# Minimum macOS version for each architecture slice
"arm64_mac_os_deployment_target": "11.0.0",
"x86_64_mac_os_deployment_target": "10.12.0",
# CMake Generator to use for building
"generator": "Unix Makefiles",
"build_type": "Release",
}
# Architectures to build for. This is explicity left out of the command line
# config options for several reasons:
# 1) Adding new architectures will generally require more code changes
# 2) Single architecture builds should utilize the normal generated cmake
# project files rather than this wrapper script
ARCHITECTURES = ["x86_64", "arm64"]
def parse_args(conf=DEFAULT_CONFIG):
"""
Parses the command line arguments into a config dictionary.
"""
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(
"--target",
help="Build target in generated project files",
default=conf["build_target"],
dest="build_target")
parser.add_argument(
"-G",
help="CMake Generator to use for creating project files",
default=conf["generator"],
dest="generator")
parser.add_argument(
"--build_type",
help="CMake build type [Debug, Release, RelWithDebInfo, MinSizeRel]",
default=conf["build_type"],
dest="build_type")
parser.add_argument(
"--dst_app",
help="Directory where universal binary will be stored",
default=conf["dst_app"])
parser.add_argument(
"--entitlements",
help="Path to .entitlements file for code signing",
default=conf["entitlements"])
parser.add_argument(
"--codesign",
help="Code signing identity to use to sign the applications",
default=conf["codesign_identity"],
dest="codesign_identity")
for arch in ARCHITECTURES:
parser.add_argument(
f"--{arch}_cmake_prefix",
help="Folder for cmake to search for packages",
default=conf[arch+"_cmake_prefix"],
dest=arch+"_cmake_prefix")
parser.add_argument(
f"--{arch}_qt5_path",
help=f"Install path for {arch} qt5 libraries",
default=conf[arch+"_qt5_path"])
parser.add_argument(
f"--{arch}_mac_os_deployment_target",
help=f"Deployment architecture for {arch} slice",
default=conf[arch+"_mac_os_deployment_target"])
return vars(parser.parse_args())
def lipo(path0, path1, dst):
if subprocess.call(["lipo", "-create", "-output", dst, path0, path1]) != 0:
print(f"WARNING: {path0} and {path1} cannot be lipo'd")
shutil.copy(path0, dst)
def recursive_merge_binaries(src0, src1, dst):
"""
Merges two build trees together for different architectures into a single
universal binary.
The rules for merging are:
1) Files that exist in either src tree are copied into the dst tree
2) Files that exist in both trees and are identical are copied over
unmodified
3) Files that exist in both trees and are non-identical are lipo'd
4) Symlinks are created in the destination tree to mirror the hierarchy in
the source trees
"""
# Check that all files present in the folder are of the same type and that
# links link to the same relative location
for newpath0 in glob.glob(src0+"/*"):
filename = os.path.basename(newpath0)
newpath1 = os.path.join(src1, filename)
if not os.path.exists(newpath1):
continue
if os.path.islink(newpath0) and os.path.islink(newpath1):
if os.path.relpath(newpath0, src0) == os.path.relpath(newpath1, src1):
continue
if os.path.isdir(newpath0) and os.path.isdir(newpath1):
continue
# isfile() can be true for links so check that both are not links
# before checking if they are both files
if (not os.path.islink(newpath0)) and (not os.path.islink(newpath1)):
if os.path.isfile(newpath0) and os.path.isfile(newpath1):
continue
raise Exception(f"{newpath0} and {newpath1} cannot be " +
"merged into a universal binary because they are of " +
"incompatible types. Perhaps the installed libraries" +
" are from different versions for each architecture")
for newpath0 in glob.glob(src0+"/*"):
filename = os.path.basename(newpath0)
newpath1 = os.path.join(src1, filename)
new_dst_path = os.path.join(dst, filename)
if os.path.islink(newpath0):
# Symlinks will be fixed after files are resolved
continue
if not os.path.exists(newpath1):
if os.path.isdir(newpath0):
shutil.copytree(newpath0, new_dst_path)
else:
shutil.copy(newpath0, new_dst_path)
continue
if os.path.isdir(newpath1):
os.mkdir(new_dst_path)
recursive_merge_binaries(newpath0, newpath1, new_dst_path)
continue
if filecmp.cmp(newpath0, newpath1):
shutil.copy(newpath0, new_dst_path)
else:
lipo(newpath0, newpath1, new_dst_path)
# Loop over files in src1 and copy missing things over to dst
for newpath1 in glob.glob(src1+"/*"):
filename = os.path.basename(newpath1)
newpath0 = os.path.join(src0, filename)
new_dst_path = os.path.join(dst, filename)
if (not os.path.exists(newpath0)) and (not os.path.islink(newpath1)):
if os.path.isdir(newpath1):
shutil.copytree(newpath1, new_dst_path)
else:
shutil.copy(newpath1, new_dst_path)
# Fix up symlinks for path0
for newpath0 in glob.glob(src0+"/*"):
filename = os.path.basename(newpath0)
new_dst_path = os.path.join(dst, filename)
if os.path.islink(newpath0):
relative_path = os.path.relpath(os.path.realpath(newpath0), src0)
os.symlink(relative_path, new_dst_path)
# Fix up symlinks for path1
for newpath1 in glob.glob(src1+"/*"):
filename = os.path.basename(newpath1)
new_dst_path = os.path.join(dst, filename)
newpath0 = os.path.join(src0, filename)
if os.path.islink(newpath1) and not os.path.exists(newpath0):
relative_path = os.path.relpath(os.path.realpath(newpath1), src1)
os.symlink(relative_path, new_dst_path)
def build(config):
"""
Builds the project with the parameters specified in config.
"""
print("Building config:")
print(json.dumps(config, indent=4))
# Configure and build single architecture builds for each architecture
for arch in ARCHITECTURES:
if not os.path.exists(arch):
os.mkdir(arch)
env = os.environ.copy()
env["Qt5_DIR"] = config[arch+"_qt5_path"]
env["CMAKE_OSX_ARCHITECTURES"] = arch
env["CMAKE_PREFIX_PATH"] = config[arch+"_cmake_prefix"]
# Add the other architecture's prefix path to the ignore path so that
# CMake doesn't try to pick up the wrong architecture's libraries when
# cross compiling.
ignore_path = ""
for a in ARCHITECTURES:
if a != arch:
ignore_path = config[a+"_cmake_prefix"]
subprocess.check_call([
"cmake", "../../", "-G", config["generator"],
"-DCMAKE_BUILD_TYPE=" + config["build_type"],
'-DCMAKE_CXX_FLAGS="-DMACOS_UNIVERSAL_BUILD=1"',
'-DCMAKE_C_FLAGS="-DMACOS_UNIVERSAL_BUILD=1"',
# System name needs to be specified for CMake to use
# the specified CMAKE_SYSTEM_PROCESSOR
"-DCMAKE_SYSTEM_NAME=Darwin",
"-DCMAKE_PREFIX_PATH="+config[arch+"_cmake_prefix"],
"-DCMAKE_SYSTEM_PROCESSOR="+arch,
"-DCMAKE_IGNORE_PATH="+ignore_path,
"-DCMAKE_OSX_DEPLOYMENT_TARGET="
+ config[arch+"_mac_os_deployment_target"],
"-DMACOS_CODE_SIGNING_IDENTITY="
+ config["codesign_identity"],
"-DMACOS_CODE_SIGNING_IDENTITY_UPDATER="
+ config["codesign_identity"],
'-DMACOS_CODE_SIGNING="ON"'
],
env=env, cwd=arch)
threads = multiprocessing.cpu_count()
subprocess.check_call(["cmake", "--build", ".",
"--config", config["build_type"],
"--parallel", f"{threads}"], cwd=arch)
dst_app = config["dst_app"]
if os.path.exists(dst_app):
shutil.rmtree(dst_app)
# Create and codesign the universal binary/
os.mkdir(dst_app)
# Source binary trees to merge together
src_app0 = ARCHITECTURES[0]+"/Binaries/"
src_app1 = ARCHITECTURES[1]+"/Binaries/"
recursive_merge_binaries(src_app0, src_app1, dst_app)
for path in glob.glob(dst_app+"/*"):
if os.path.isdir(path) and os.path.splitext(path)[1] != ".app":
continue
subprocess.check_call([
"codesign",
"-d",
"--force",
"-s",
config["codesign_identity"],
"--options=runtime",
"--entitlements", config["entitlements"],
"--deep",
"--verbose=2",
path])
if __name__ == "__main__":
conf = parse_args()
build(conf)
print("Built Universal Binary successfully!")

View File

@ -2,13 +2,17 @@
# General setup
#
cmake_minimum_required(VERSION 3.10)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
# Minimum OS X version.
# This is inserted into the Info.plist as well.
# MacOS prior to 10.12 did not fully support C++17, which is used to
# handle configuration options
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12.0" CACHE STRING "")
# MacOS prior to 10.14 did not support aligned alloc which is used to implement
# std::unique_ptr in the arm64 C++ standard library. x86_64 builds can override
# this to 10.12.0 using -DCMAKE_OSX_DEPLOYMENT_TARGET="10.12.0" without issue.
# This is done in the universal binary building script to build a binary that
# runs on 10.12 on x86_64 computers, while still containing an arm64 slice.
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14.0" CACHE STRING "")
set(CMAKE_USER_MAKE_RULES_OVERRIDE "CMake/FlagsOverride.cmake")
@ -68,8 +72,12 @@ else()
endif()
if(APPLE)
option(OSX_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF)
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
option(MACOS_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF)
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
# Enable adhoc code signing by default (otherwise makefile builds on ARM will not work)
option(MACOS_CODE_SIGNING "Enable codesigning" ON)
set(MACOS_CODE_SIGNING_IDENTITY "-" CACHE STRING "The identity used for codesigning.")
set(MACOS_CODE_SIGNING_IDENTITY_UPDATER "-" CACHE STRING "The identity used for codesigning, for the updater.")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
@ -290,7 +298,7 @@ else()
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
if(NOT OSX_USE_DEFAULT_SEARCH_PATH)
if(NOT MACOS_USE_DEFAULT_SEARCH_PATH)
# Hack up the path to prioritize the path to built-in OS libraries to
# increase the chance of not depending on a bunch of copies of them
# installed by MacPorts, Fink, Homebrew, etc, and ending up copying
@ -306,9 +314,10 @@ if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(CMAKE_XCODE_ATTRIBUTE_GCC_STRICT_ALIASING NO)
# Specify target CPUs.
check_and_add_flag(HAVE_MSSSE3 -mssse3)
check_and_add_flag(HAVE_ARCH_CORE2 -march=core2)
if(_ARCH_64 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
check_and_add_flag(HAVE_MSSSE3 -mssse3)
check_and_add_flag(HAVE_ARCH_CORE2 -march=core2)
endif()
# Linker flags.
# Drop unreachable code and data.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-dead_strip,-dead_strip_dylibs")

Binary file not shown.

View File

@ -1 +1 @@
MoltenVK from https://github.com/KhronosGroup/MoltenVK, commit b9b78def172074872bfbb1015ccf75eeec554ae2
MoltenVK from https://vulkan.lunarg.com/sdk/home#mac, version 1.2.170.0

View File

@ -65,6 +65,8 @@ missing packages yourself.
### macOS Build Steps:
A binary supporting a single architecture can be built using the following steps:
1. `mkdir build`
2. `cd build`
3. `cmake ..`
@ -72,6 +74,18 @@ missing packages yourself.
An application bundle will be created in `./Binaries`.
A script is also provided to build universal binaries supporting both x64 and ARM in the same
application bundle using the following steps:
1. `mkdir build`
2. `cd build`
3. `python ../BuildMacOSUniversalBinary.py`
4. Universal binaries will be available in the `universal` folder
Doing this is more complex as it requires installation of library dependencies for both x64 and ARM (or universal library
equivalents) and may require specifying additional arguments to point to relevant library locations.
Execute BuildMacOSUniversalBinary.py --help for more details.
### Linux Global Build Steps:
To install to your system.

View File

@ -21,6 +21,9 @@
#ifdef _WIN32
#include <Windows.h>
#endif
#ifdef __APPLE__
#include <libkern/OSCacheControl.h>
#endif
namespace Arm64Gen
{
@ -342,7 +345,7 @@ void ARM64XEmitter::FlushIcacheSection(u8* start, u8* end)
if (start == end)
return;
#if defined(IOS)
#if defined(IOS) || defined(__APPLE__)
// Header file says this is equivalent to: sys_icache_invalidate(start, end - start);
sys_cache_control(kCacheFunctionPrepareForExecution, start, end - start);
#elif defined(WIN32)

View File

@ -8,7 +8,7 @@
#include <string>
#include <thread>
#ifndef _WIN32
#if !defined(_WIN32) && !defined(__APPLE__)
#ifndef __FreeBSD__
#include <asm/hwcap.h>
#endif
@ -71,7 +71,17 @@ void CPUInfo::Detect()
vendor = CPUVendor::ARM;
bFlushToZero = true;
#ifdef _WIN32
#ifdef __APPLE__
num_cores = std::thread::hardware_concurrency();
// M-series CPUs have all of these
bFP = true;
bASIMD = true;
bAES = true;
bSHA1 = true;
bSHA2 = true;
bCRC32 = true;
#elif defined(_WIN32)
num_cores = std::thread::hardware_concurrency();
// Windows does not provide any mechanism for querying the system registers on ARMv8, unlike Linux

View File

@ -16,6 +16,7 @@
#include <windows.h>
#include "Common/StringUtil.h"
#else
#include <pthread.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
@ -38,9 +39,15 @@ void* AllocateExecutableMemory(size_t size)
#if defined(_WIN32)
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
#else
void* ptr =
mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
int map_flags = MAP_ANON | MAP_PRIVATE;
#if defined(__APPLE__)
// This check is in place to prepare for x86_64 MAP_JIT support. While MAP_JIT did exist
// prior to 10.14, it had restrictions on the number of JIT allocations that were removed
// in 10.14.
if (__builtin_available(macOS 10.14, *))
map_flags |= MAP_JIT;
#endif
void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, map_flags, -1, 0);
if (ptr == MAP_FAILED)
ptr = nullptr;
#endif
@ -50,6 +57,79 @@ void* AllocateExecutableMemory(size_t size)
return ptr;
}
// This function is used to provide a counter for the JITPageWrite*Execute*
// functions to enable nesting. The static variable is wrapped in a a function
// to allow those functions to be called inside of the constructor of a static
// variable portably.
//
// The variable is thread_local as the W^X mode is specific to each running thread.
static int& JITPageWriteNestCounter()
{
static thread_local int nest_counter = 0;
return nest_counter;
}
// Certain platforms (Mac OS on ARM) enforce that a single thread can only have write or
// execute permissions to pages at any given point of time. The two below functions
// are used to toggle between having write permissions or execute permissions.
//
// The default state of these allocations in Dolphin is for them to be executable,
// but not writeable. So, functions that are updating these pages should wrap their
// writes like below:
// JITPageWriteEnableExecuteDisable();
// PrepareInstructionStreamForJIT();
// JITPageWriteDisableExecuteEnable();
// These functions can be nested, in which case execution will only be enabled
// after the call to the JITPageWriteDisableExecuteEnable from the top most
// nesting level. Example:
// [JIT page is in execute mode for the thread]
// JITPageWriteEnableExecuteDisable();
// [JIT page is in write mode for the thread]
// JITPageWriteEnableExecuteDisable();
// [JIT page is in write mode for the thread]
// JITPageWriteDisableExecuteEnable();
// [JIT page is in write mode for the thread]
// JITPageWriteDisableExecuteEnable();
// [JIT page is in execute mode for the thread]
// Allows a thread to write to executable memory, but not execute the data.
void JITPageWriteEnableExecuteDisable()
{
#if defined(_M_ARM_64) && defined(__APPLE__)
if (JITPageWriteNestCounter() == 0)
{
if (__builtin_available(macOS 11.0, *))
{
pthread_jit_write_protect_np(0);
}
}
#endif
JITPageWriteNestCounter()++;
}
// Allows a thread to execute memory allocated for execution, but not write to it.
void JITPageWriteDisableExecuteEnable()
{
JITPageWriteNestCounter()--;
// Sanity check the NestCounter to identify underflow
// This can indicate the calls to JITPageWriteDisableExecuteEnable()
// are not matched with previous calls to JITPageWriteEnableExecuteDisable()
if (JITPageWriteNestCounter() < 0)
PanicAlertFmt("JITPageWriteNestCounter() underflowed");
#if defined(_M_ARM_64) && defined(__APPLE__)
if (JITPageWriteNestCounter() == 0)
{
if (__builtin_available(macOS 11.0, *))
{
pthread_jit_write_protect_np(1);
}
}
#endif
}
void* AllocateMemoryPages(size_t size)
{
@ -128,7 +208,10 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute)
DWORD oldValue;
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
PanicAlertFmt("WriteProtectMemory failed!\nVirtualProtect: {}", GetLastErrorString());
#else
#elif !(defined(_M_ARM_64) && defined(__APPLE__))
// MacOS 11.2 on ARM does not allow for changing the access permissions of pages
// that were marked executable, instead it uses the protections offered by MAP_JIT
// for write protection.
if (mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ) != 0)
PanicAlertFmt("WriteProtectMemory failed!\nmprotect: {}", LastStrerrorString());
#endif
@ -140,7 +223,10 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute)
DWORD oldValue;
if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &oldValue))
PanicAlertFmt("UnWriteProtectMemory failed!\nVirtualProtect: {}", GetLastErrorString());
#else
#elif !(defined(_M_ARM_64) && defined(__APPLE__))
// MacOS 11.2 on ARM does not allow for changing the access permissions of pages
// that were marked executable, instead it uses the protections offered by MAP_JIT
// for write protection.
if (mprotect(ptr, size,
allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ) != 0)
{

View File

@ -10,6 +10,23 @@
namespace Common
{
void* AllocateExecutableMemory(size_t size);
// These two functions control the executable/writable state of the W^X memory
// allocations. More detailed documentation about them is in the .cpp file.
// In general where applicable the ScopedJITPageWriteAndNoExecute wrapper
// should be used to prevent bugs from not pairing up the calls properly.
// Allows a thread to write to executable memory, but not execute the data.
void JITPageWriteEnableExecuteDisable();
// Allows a thread to execute memory allocated for execution, but not write to it.
void JITPageWriteDisableExecuteEnable();
// RAII Wrapper around JITPageWrite*Execute*(). When this is in scope the thread can
// write to executable memory but not execute it.
struct ScopedJITPageWriteAndNoExecute
{
ScopedJITPageWriteAndNoExecute() { JITPageWriteEnableExecuteDisable(); }
~ScopedJITPageWriteAndNoExecute() { JITPageWriteDisableExecuteEnable(); }
};
void* AllocateMemoryPages(size_t size);
void FreeMemoryPages(void* ptr, size_t size);
void* AllocateAlignedMemory(size_t size, size_t alignment);

View File

@ -289,11 +289,17 @@ void DolphinAnalytics::MakeBaseBuilder()
s64 minor_version; // NSInteger minorVersion
s64 patch_version; // NSInteger patchVersion
};
// Under arm64, we need to call objc_msgSend to recieve a struct.
// On x86_64, we need to explicitly call objc_msgSend_stret for a struct.
#if _M_ARM_64
#define msgSend objc_msgSend
#else
#define msgSend objc_msgSend_stret
#endif
// NSOperatingSystemVersion version = [processInfo operatingSystemVersion]
OSVersion version = reinterpret_cast<OSVersion (*)(id, SEL)>(objc_msgSend_stret)(
OSVersion version = reinterpret_cast<OSVersion (*)(id, SEL)>(msgSend)(
processInfo, sel_getUid("operatingSystemVersion"));
#undef msgSend
builder.AddData("osx-ver-major", version.major_version);
builder.AddData("osx-ver-minor", version.minor_version);
builder.AddData("osx-ver-bugfix", version.patch_version);

View File

@ -67,6 +67,12 @@ typedef x86_thread_state64_t SContext;
#define CTX_R14 __r14
#define CTX_R15 __r15
#define CTX_RIP __rip
#elif _M_ARM_64
typedef arm_thread_state64_t SContext;
#define CTX_REG(x) __x[x]
#define CTX_LR __x[30]
#define CTX_SP __sp
#define CTX_PC __pc
#else
#error No context definition for architecture
#endif

View File

@ -25,6 +25,20 @@
#include <unistd.h> // Needed for _POSIX_VERSION
#endif
#if defined(__APPLE__)
#ifdef _M_X86_64
#define THREAD_STATE64_COUNT x86_THREAD_STATE64_COUNT
#define THREAD_STATE64 x86_THREAD_STATE64
#define thread_state64_t x86_thread_state64_t
#elif defined(_M_ARM_64)
#define THREAD_STATE64_COUNT ARM_THREAD_STATE64_COUNT
#define THREAD_STATE64 ARM_THREAD_STATE64
#define thread_state64_t arm_thread_state64_t
#else
#error Unsupported architecture
#endif
#endif
namespace EMM
{
#ifdef _WIN32
@ -123,7 +137,7 @@ static void ExceptionThread(mach_port_t port)
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[x86_THREAD_STATE64_COUNT];
natural_t old_state[THREAD_STATE64_COUNT];
mach_msg_trailer_t trailer;
} msg_in;
@ -134,7 +148,7 @@ static void ExceptionThread(mach_port_t port)
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[x86_THREAD_STATE64_COUNT];
natural_t new_state[THREAD_STATE64_COUNT];
} msg_out;
#pragma pack()
memset(&msg_in, 0xee, sizeof(msg_in));
@ -165,13 +179,13 @@ static void ExceptionThread(mach_port_t port)
return;
}
if (msg_in.flavor != x86_THREAD_STATE64)
if (msg_in.flavor != THREAD_STATE64)
{
PanicAlertFmt("unknown flavor {} (expected {})", msg_in.flavor, x86_THREAD_STATE64);
PanicAlertFmt("unknown flavor {} (expected {})", msg_in.flavor, THREAD_STATE64);
return;
}
x86_thread_state64_t* state = (x86_thread_state64_t*)msg_in.old_state;
thread_state64_t* state = (thread_state64_t*)msg_in.old_state;
bool ok = JitInterface::HandleFault((uintptr_t)msg_in.code[1], state);
@ -184,9 +198,9 @@ static void ExceptionThread(mach_port_t port)
if (ok)
{
msg_out.RetCode = KERN_SUCCESS;
msg_out.flavor = x86_THREAD_STATE64;
msg_out.new_stateCnt = x86_THREAD_STATE64_COUNT;
memcpy(msg_out.new_state, msg_in.old_state, x86_THREAD_STATE64_COUNT * sizeof(natural_t));
msg_out.flavor = THREAD_STATE64;
msg_out.new_stateCnt = THREAD_STATE64_COUNT;
memcpy(msg_out.new_state, msg_in.old_state, THREAD_STATE64_COUNT * sizeof(natural_t));
}
else
{
@ -218,7 +232,7 @@ void InstallExceptionHandler()
// Debuggers set the task port, so we grab the thread port.
CheckKR("thread_set_exception_ports",
thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port,
EXCEPTION_STATE | MACH_EXCEPTION_CODES, x86_THREAD_STATE64));
EXCEPTION_STATE | MACH_EXCEPTION_CODES, THREAD_STATE64));
// ...and get rid of our copy so that MACH_NOTIFY_NO_SENDERS works.
CheckKR("mach_port_mod_refs",
mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1));

View File

@ -73,6 +73,8 @@ void JitArm64::Init()
bool JitArm64::HandleFault(uintptr_t access_address, SContext* ctx)
{
// Ifdef this since the exception handler runs on a separate thread on macOS (ARM)
#if !(defined(__APPLE__) && defined(_M_ARM_64))
// We can't handle any fault from other threads.
if (!Core::IsCPUThread())
{
@ -80,6 +82,7 @@ bool JitArm64::HandleFault(uintptr_t access_address, SContext* ctx)
DoBacktrace(access_address, ctx);
return false;
}
#endif
bool success = false;
@ -124,6 +127,7 @@ void JitArm64::ClearCache()
m_handler_to_loc.clear();
blocks.Clear();
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
ClearCodeSpace();
farcode.ClearCodeSpace();
UpdateMemoryOptions();
@ -596,6 +600,7 @@ void JitArm64::Jit(u32)
{
ClearCache();
}
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
std::size_t block_size = m_code_buffer.size();
const u32 em_address = PowerPC::ppcState.pc;

View File

@ -59,11 +59,11 @@ void JitArm64BlockCache::WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit,
void JitArm64BlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest)
{
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
u8* location = source.exitPtrs;
ARM64XEmitter emit(location);
WriteLinkBlock(emit, source, dest);
emit.FlushIcache();
}
@ -71,9 +71,8 @@ void JitArm64BlockCache::WriteDestroyBlock(const JitBlock& block)
{
// Only clear the entry points as we might still be within this block.
ARM64XEmitter emit(block.checkedEntry);
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
while (emit.GetWritableCodePtr() <= block.normalEntry)
emit.BRK(0x123);
emit.FlushIcache();
}

View File

@ -289,6 +289,7 @@ bool JitArm64::HandleFastmemFault(uintptr_t access_address, SContext* ctx)
if ((const u8*)ctx->CTX_PC - fault_location > fastmem_area_length)
return false;
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
ARM64XEmitter emitter((u8*)fault_location);
emitter.BL(slow_handler_iter->second.slowmem_code);
@ -300,6 +301,7 @@ bool JitArm64::HandleFastmemFault(uintptr_t access_address, SContext* ctx)
m_fault_to_handler.erase(slow_handler_iter);
emitter.FlushIcache();
ctx->CTX_PC = reinterpret_cast<std::uintptr_t>(fault_location);
return true;
}

View File

@ -25,6 +25,8 @@ using namespace Arm64Gen;
void JitArm64::GenerateAsm()
{
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
// This value is all of the callee saved registers that we are required to save.
// According to the AACPS64 we need to save R19 ~ R30 and Q8 ~ Q15.
const u32 ALL_CALLEE_SAVED = 0x7FF80000;

View File

@ -71,9 +71,8 @@ CPUCoreBase* InitJitCore(PowerPC::CPUCore core)
break;
default:
PanicAlertFmtT("The selected CPU emulation core ({0}) is not available. "
"Please select a different CPU emulation core in the settings.",
core);
// Under this case the caller overrides the CPU core to the default and logs that
// it performed the override.
g_jit = nullptr;
return nullptr;
}

View File

@ -473,6 +473,8 @@ if(APPLE)
set_target_properties(dolphin-emu PROPERTIES
MACOSX_BUNDLE true
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/DolphinEmu.entitlements"
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--deep --options=runtime"
OUTPUT_NAME Dolphin
)
@ -516,6 +518,22 @@ if(APPLE)
POST_BUILD COMMAND
${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks/"
$<TARGET_FILE:dolphin-emu>)
if(MACOS_CODE_SIGNING)
# Code sign make file builds
add_custom_command(TARGET dolphin-emu
POST_BUILD COMMAND
/usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Dolphin.app" || true)
# Code sign builds for build systems that do have release/debug variants (Xcode)
add_custom_command(TARGET dolphin-emu
POST_BUILD COMMAND
/usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/Dolphin.app" || true)
add_custom_command(TARGET dolphin-emu
POST_BUILD COMMAND
/usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY}" --deep --options=runtime --entitlements "${CMAKE_SOURCE_DIR}/Source/Core/DolphinQt/DolphinEmu.entitlements" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/Dolphin.app" || true)
endif()
else()
install(TARGETS dolphin-emu RUNTIME DESTINATION ${bindir})
endif()

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- Needed for GameCube microphone emulation -->
<key>com.apple.security.device.audio-input</key>
<true/>
<!-- TODO: It is likely this requirement is coming from Qt, but should confirm -->
<key>com.apple.security.automation.apple-events</key>
<true/>
<!-- This is needed to use adhoc signed linked libraries -->
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@ -13,6 +13,8 @@ set(SOURCES
add_executable(MacUpdater ${SOURCES})
set(MacUpdater_NAME "Dolphin Updater")
set(MacUpdater_BIN_DIR ${CMAKE_BINARY_DIR}/Binaries)
set(MacUpdater_BUNDLE_PATH ${MacUpdater_BIN_DIR}/${MacUpdater_NAME}.app)
set_target_properties(MacUpdater PROPERTIES
MACOSX_BUNDLE true
@ -53,8 +55,24 @@ foreach(sb ${STORYBOARDS})
add_custom_command(TARGET MacUpdater POST_BUILD
COMMAND ${IBTOOL} --errors --warnings --notices --output-format human-readable-text
--compile ${MacUpdater_BIN_DIR}/${MacUpdater_NAME}.app/Contents/Resources/${sb}c
--compile ${MacUpdater_BUNDLE_PATH}/Contents/Resources/${sb}c
${CMAKE_CURRENT_SOURCE_DIR}/${sb}
COMMENT "Compiling Storyboard ${sb}...")
endforeach()
if(MACOS_CODE_SIGNING)
if (MACOS_CODE_SIGNING_IDENTITY_UPDATER STREQUAL "")
set(MACOS_CODE_SIGNING_IDENTITY_UPDATER "${MACOS_CODE_SIGNING_IDENTITY}")
endif()
# Make file build code sign
add_custom_command(TARGET MacUpdater POST_BUILD
COMMAND test ${MacUpdater_BUNDLE_PATH} || /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY_UPDATER}" --deep --options runtime ${MacUpdater_BUNDLE_PATH})
# Xcode build code sign
add_custom_command(TARGET MacUpdater POST_BUILD
COMMAND test "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/${MacUpdater_NAME}.app" || /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY_UPDATER}" --deep --options runtime "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG}/${MacUpdater_NAME}.app")
add_custom_command(TARGET MacUpdater POST_BUILD
COMMAND test "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/${MacUpdater_NAME}.app" || /usr/bin/codesign -f -s "${MACOS_CODE_SIGNING_IDENTITY_UPDATER}" --deep --options runtime "${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE}/${MacUpdater_NAME}.app")
endif()

View File

@ -140,7 +140,11 @@ static std::string GetPlatformID()
#if defined _WIN32
return "win";
#elif defined __APPLE__
#if defined(MACOS_UNIVERSAL_BUILD)
return "macos-universal";
#else
return "macos";
#endif
#else
return "unknown";
#endif

View File

@ -54,6 +54,7 @@ VertexLoaderARM64::VertexLoaderARM64(const TVtxDesc& vtx_desc, const VAT& vtx_at
: VertexLoaderBase(vtx_desc, vtx_att), m_float_emit(this)
{
AllocCodeSpace(4096);
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
ClearCodeSpace();
GenerateVertexLoader();
WriteProtect();