Forráskód Böngészése

tcg: support JIT on Apple Silicon

https://developer.apple.com/documentation/apple_silicon/porting_just-in-time_compilers_to_apple_silicon

For < iOS 14, reverse engineered functions from libsystem_pthread.dylib is
implemented to handle APRR supported SoCs.

The following rules apply for JIT write protect:
  * JIT write-protect is enabled before tcg_qemu_tb_exec()
  * JIT write-protect is disabled after tcg_qemu_tb_exec() returns
  * JIT write-protect is disabled inside do_tb_phys_invalidate() but if it
    is called inside of tcg_qemu_tb_exec() then write-protect will be
    enabled again before returning.
  * JIT write-protect is disabled by cpu_loop_exit() for interrupt handling.
  * JIT write-protect is disabled everywhere else.
osy 5 éve
szülő
commit
0656dbe722

+ 2 - 0
accel/tcg/cpu-exec-common.c

@@ -64,6 +64,8 @@ void cpu_reloading_memory_map(void)
 
 
 void cpu_loop_exit(CPUState *cpu)
 void cpu_loop_exit(CPUState *cpu)
 {
 {
+    /* Unlock JIT write protect if applicable. */
+    tb_exec_unlock();
     /* Undo the setting in cpu_tb_exec.  */
     /* Undo the setting in cpu_tb_exec.  */
     cpu->can_do_io = 1;
     cpu->can_do_io = 1;
     siglongjmp(cpu->jmp_env, 1);
     siglongjmp(cpu->jmp_env, 1);

+ 2 - 0
accel/tcg/cpu-exec.c

@@ -169,7 +169,9 @@ static inline tcg_target_ulong cpu_tb_exec(CPUState *cpu, TranslationBlock *itb)
     }
     }
 #endif /* DEBUG_DISAS */
 #endif /* DEBUG_DISAS */
 
 
+    tb_exec_lock();
     ret = tcg_qemu_tb_exec(env, tb_ptr);
     ret = tcg_qemu_tb_exec(env, tb_ptr);
+    tb_exec_unlock();
     cpu->can_do_io = 1;
     cpu->can_do_io = 1;
     last_tb = (TranslationBlock *)(ret & ~TB_EXIT_MASK);
     last_tb = (TranslationBlock *)(ret & ~TB_EXIT_MASK);
     tb_exit = ret & TB_EXIT_MASK;
     tb_exit = ret & TB_EXIT_MASK;

+ 51 - 0
accel/tcg/translate-all.c

@@ -27,6 +27,9 @@
 #include "disas/disas.h"
 #include "disas/disas.h"
 #include "exec/exec-all.h"
 #include "exec/exec-all.h"
 #include "tcg/tcg.h"
 #include "tcg/tcg.h"
+#if defined(CONFIG_DARWIN)
+#include "tcg/tcg-apple-jit.h"
+#endif
 #if defined(CONFIG_USER_ONLY)
 #if defined(CONFIG_USER_ONLY)
 #include "qemu.h"
 #include "qemu.h"
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
@@ -75,6 +78,9 @@ extern kern_return_t mach_vm_remap(vm_map_t target_task,
                                   );
                                   );
 #endif
 #endif
 
 
+static bool tb_exec_is_locked(void);
+static void tb_exec_change(bool locked);
+
 /* #define DEBUG_TB_INVALIDATE */
 /* #define DEBUG_TB_INVALIDATE */
 /* #define DEBUG_TB_FLUSH */
 /* #define DEBUG_TB_FLUSH */
 /* make various TB consistency checks */
 /* make various TB consistency checks */
@@ -1242,6 +1248,7 @@ void tcg_exec_init(unsigned long tb_size, bool mirror_rwx)
     page_init();
     page_init();
     tb_htable_init();
     tb_htable_init();
     code_gen_alloc(tb_size, mirror_rwx);
     code_gen_alloc(tb_size, mirror_rwx);
+    tb_exec_unlock();
 #if defined(CONFIG_SOFTMMU)
 #if defined(CONFIG_SOFTMMU)
     /* There's no guest base to take into account, so go ahead and
     /* There's no guest base to take into account, so go ahead and
        initialize the prologue now.  */
        initialize the prologue now.  */
@@ -1518,8 +1525,11 @@ static void do_tb_phys_invalidate(TranslationBlock *tb, bool rm_from_page_list)
     PageDesc *p;
     PageDesc *p;
     uint32_t h;
     uint32_t h;
     tb_page_addr_t phys_pc;
     tb_page_addr_t phys_pc;
+    bool code_gen_locked;
 
 
     assert_memory_lock();
     assert_memory_lock();
+    code_gen_locked = tb_exec_is_locked();
+    tb_exec_unlock();
 
 
     /* make sure no further incoming jumps will be chained to this TB */
     /* make sure no further incoming jumps will be chained to this TB */
     qemu_spin_lock(&tb->jmp_lock);
     qemu_spin_lock(&tb->jmp_lock);
@@ -1532,6 +1542,7 @@ static void do_tb_phys_invalidate(TranslationBlock *tb, bool rm_from_page_list)
                      tb->trace_vcpu_dstate);
                      tb->trace_vcpu_dstate);
     if (!(tb->cflags & CF_NOCACHE) &&
     if (!(tb->cflags & CF_NOCACHE) &&
         !qht_remove(&tb_ctx.htable, tb, h)) {
         !qht_remove(&tb_ctx.htable, tb, h)) {
+        tb_exec_change(code_gen_locked);
         return;
         return;
     }
     }
 
 
@@ -1564,6 +1575,8 @@ static void do_tb_phys_invalidate(TranslationBlock *tb, bool rm_from_page_list)
 
 
     atomic_set(&tcg_ctx->tb_phys_invalidate_count,
     atomic_set(&tcg_ctx->tb_phys_invalidate_count,
                tcg_ctx->tb_phys_invalidate_count + 1);
                tcg_ctx->tb_phys_invalidate_count + 1);
+
+    tb_exec_change(code_gen_locked);
 }
 }
 
 
 static void tb_phys_invalidate__locked(TranslationBlock *tb)
 static void tb_phys_invalidate__locked(TranslationBlock *tb)
@@ -2797,3 +2810,41 @@ void tcg_flush_softmmu_tlb(CPUState *cs)
     tlb_flush(cs);
     tlb_flush(cs);
 #endif
 #endif
 }
 }
+
+#if defined(CONFIG_DARWIN) && !defined(CONFIG_TCG_INTERPRETER)
+static bool tb_exec_is_locked(void)
+{
+    return tcg_ctx->code_gen_locked;
+}
+
+static void tb_exec_change(bool locked)
+{
+#if defined(HAVE_PTHREAD_JIT_PROTECT)
+    if (__builtin_available(macOS 11, iOS 14, watchOS 7, tvOS 14, *)) {
+        pthread_jit_write_protect_np(locked);
+    } else
+#endif
+    if (jit_write_protect_supported()) {
+        jit_write_protect(locked);
+    }
+    tcg_ctx->code_gen_locked = locked;
+}
+#else // not needed on non-Darwin platforms
+static bool tb_exec_is_locked(void)
+{
+    return false;
+}
+
+static void tb_exec_change(bool locked) {}
+#endif
+
+void tb_exec_lock(void)
+{
+    // assumes sys_icache_invalidate already called
+    tb_exec_change(true);
+}
+
+void tb_exec_unlock(void)
+{
+    tb_exec_change(false);
+}

+ 19 - 0
configure

@@ -6588,6 +6588,21 @@ EOF
     fi
     fi
 fi
 fi
 
 
+##########################################
+# check for Apple Silicon JIT function
+
+if [ "$darwin" = "yes" ] ; then
+  cat > $TMPC << EOF
+#include <pthread.h>
+int main() { pthread_jit_write_protect_np(0); return 0; }
+EOF
+  if ! compile_prog ""; then
+    have_pthread_jit_protect='no'
+  else
+    have_pthread_jit_protect='yes'
+  fi
+fi
+
 
 
 ##########################################
 ##########################################
 # End of CC checks
 # End of CC checks
@@ -8021,6 +8036,10 @@ if test "$secret_keyring" = "yes" ; then
   fi
   fi
 fi
 fi
 
 
+if test "$have_pthread_jit_protect" = "yes" ; then
+  echo "HAVE_PTHREAD_JIT_PROTECT=y" >> $config_host_mak
+fi
+
 if test "$tcg_interpreter" = "yes"; then
 if test "$tcg_interpreter" = "yes"; then
   QEMU_INCLUDES="-iquote \$(SRC_PATH)/tcg/tci $QEMU_INCLUDES"
   QEMU_INCLUDES="-iquote \$(SRC_PATH)/tcg/tci $QEMU_INCLUDES"
 elif test "$ARCH" = "sparc64" ; then
 elif test "$ARCH" = "sparc64" ; then

+ 2 - 0
include/exec/exec-all.h

@@ -520,6 +520,8 @@ TranslationBlock *tb_htable_lookup(CPUState *cpu, target_ulong pc,
                                    target_ulong cs_base, uint32_t flags,
                                    target_ulong cs_base, uint32_t flags,
                                    uint32_t cf_mask);
                                    uint32_t cf_mask);
 void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr);
 void tb_set_jmp_target(TranslationBlock *tb, int n, uintptr_t addr);
+void tb_exec_lock(void);
+void tb_exec_unlock(void);
 
 
 /* GETPC is the true target of the return instruction that we'll execute.  */
 /* GETPC is the true target of the return instruction that we'll execute.  */
 #if defined(CONFIG_TCG_INTERPRETER)
 #if defined(CONFIG_TCG_INTERPRETER)

+ 76 - 0
include/tcg/tcg-apple-jit.h

@@ -0,0 +1,76 @@
+/*
+ * Apple Silicon APRR functions for JIT handling
+ *
+ * Copyright (c) 2020 osy
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Credits to: https://siguza.github.io/APRR/
+// Reversed from /usr/lib/system/libsystem_pthread.dylib
+
+#ifndef TCG_APPLE_JIT_H
+#define TCG_APPLE_JIT_H
+
+#if defined(__aarch64__) && defined(CONFIG_DARWIN)
+
+#define _COMM_PAGE_START_ADDRESS        (0x0000000FFFFFC000ULL) /* In TTBR0 */
+#define _COMM_PAGE_APRR_SUPPORT         (_COMM_PAGE_START_ADDRESS+0x10C)
+#define _COMM_PAGE_APPR_WRITE_ENABLE    (_COMM_PAGE_START_ADDRESS+0x110)
+#define _COMM_PAGE_APRR_WRITE_DISABLE   (_COMM_PAGE_START_ADDRESS+0x118)
+
+static __attribute__ ((__always_inline__)) bool jit_write_protect_supported(void)
+{
+    uint8_t aprr_support = *(volatile uint8_t *)_COMM_PAGE_APRR_SUPPORT;
+    return aprr_support > 0;
+}
+
+static __attribute__ ((__always_inline__)) void jit_write_protect(int enabled) // write protect enable = write disable
+{
+    uint8_t aprr_support = *(volatile uint8_t *)_COMM_PAGE_APRR_SUPPORT;
+    if (aprr_support == 0 || aprr_support > 3) {
+        return;
+    } else if (aprr_support == 1) {
+        __asm__ __volatile__ (
+            "mov x0, %0\n"
+            "ldr x0, [x0]\n"
+            "msr S3_4_c15_c2_7, x0\n"
+            "isb sy\n"
+            :: "r" (enabled ? _COMM_PAGE_APRR_WRITE_DISABLE : _COMM_PAGE_APPR_WRITE_ENABLE) : "memory", "x0"
+        );
+    } else {
+        __asm__ __volatile__ (
+            "mov x0, %0\n"
+            "ldr x0, [x0]\n"
+            "msr S3_6_c15_c1_5, x0\n"
+            "isb sy\n"
+            :: "r" (enabled ? _COMM_PAGE_APRR_WRITE_DISABLE : _COMM_PAGE_APPR_WRITE_ENABLE) : "memory", "x0"
+        );
+    }
+}
+
+#else // defined(__aarch64__) && defined(CONFIG_DARWIN)
+
+static __attribute__ ((__always_inline__)) bool jit_write_protect_supported(void)
+{
+    return false;
+}
+
+static __attribute__ ((__always_inline__)) void jit_write_protect(int enabled)
+{
+}
+
+#endif
+
+#endif // define TCG_APPLE_JIT_H

+ 3 - 0
include/tcg/tcg.h

@@ -630,6 +630,9 @@ struct TCGContext {
 #if defined(CONFIG_IOS_JIT)
 #if defined(CONFIG_IOS_JIT)
     ptrdiff_t code_rw_mirror_diff;
     ptrdiff_t code_rw_mirror_diff;
 #endif
 #endif
+#if defined(CONFIG_DARWIN) && !defined(CONFIG_TCG_INTERPRETER)
+    bool code_gen_locked; // on Darwin each thread tracks W^X flags
+#endif
 
 
     /* Threshold to flush the translated code buffer.  */
     /* Threshold to flush the translated code buffer.  */
     void *code_gen_highwater;
     void *code_gen_highwater;

+ 4 - 0
tcg/tcg.c

@@ -781,6 +781,8 @@ static void alloc_tcg_plugin_context(TCGContext *s)
 void tcg_register_thread(void)
 void tcg_register_thread(void)
 {
 {
     tcg_ctx = &tcg_init_ctx;
     tcg_ctx = &tcg_init_ctx;
+
+    tb_exec_unlock();
 }
 }
 #else
 #else
 void tcg_register_thread(void)
 void tcg_register_thread(void)
@@ -815,6 +817,8 @@ void tcg_register_thread(void)
     err = tcg_region_initial_alloc__locked(tcg_ctx);
     err = tcg_region_initial_alloc__locked(tcg_ctx);
     g_assert(!err);
     g_assert(!err);
     qemu_mutex_unlock(&region.lock);
     qemu_mutex_unlock(&region.lock);
+
+    tb_exec_unlock();
 }
 }
 #endif /* !CONFIG_USER_ONLY */
 #endif /* !CONFIG_USER_ONLY */