Merge pull request #9441 from skylersaleh/master
Apple M1 Support for MacOS
This commit is contained in:
commit
51671921c4
|
@ -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!")
|
|
@ -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.
|
@ -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
|
14
Readme.md
14
Readme.md
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue