// This file is a part of Julia. License is MIT: https://julialang.org/license

// Note that this file is `#include`d by "signals-unix.c"

#include <mach/clock.h>
#include <mach/clock_types.h>
#include <mach/clock_reply.h>
#include <mach/thread_state.h>
#include <mach/mach_traps.h>
#include <mach/task.h>
#include <mach/mig_errors.h>
#include <AvailabilityMacros.h>
#include <stdint.h>
#include "mig/mach_excServer.c"

#ifdef MAC_OS_X_VERSION_10_9
#include <sys/_types/_ucontext64.h>
#else
#define __need_ucontext64_t
#include <sys/_structs.h>
#endif

#include "julia_assert.h"

static struct {
    thread_state_flavor_t flavor;
    mach_msg_type_number_t count;  // in units of natural_t
} jl_mach_float_state_info = {0, 0};

// private keymgr stuff
#define KEYMGR_GCC3_DW2_OBJ_LIST 302
enum {
  NM_ALLOW_RECURSION = 1,
  NM_RECURSION_ILLEGAL = 2
};
extern void _keymgr_set_and_unlock_processwide_ptr(unsigned int key, void *ptr);
extern int _keymgr_unlock_processwide_ptr(unsigned int key);
extern void *_keymgr_get_and_lock_processwide_ptr(unsigned int key);
extern int _keymgr_get_and_lock_processwide_ptr_2(unsigned int key, void **result);
extern int _keymgr_set_lockmode_processwide_ptr(unsigned int key, unsigned int mode);

// private dyld3/dyld4 stuff
extern void _dyld_atfork_prepare(void) __attribute__((weak_import));
extern void _dyld_atfork_parent(void) __attribute__((weak_import));
//extern void _dyld_fork_child(void) __attribute__((weak_import));
extern void _dyld_dlopen_atfork_prepare(void) __attribute__((weak_import));
extern void _dyld_dlopen_atfork_parent(void) __attribute__((weak_import));
//extern void _dyld_dlopen_atfork_child(void) __attribute__((weak_import));

static void attach_exception_port(thread_port_t thread, int segv_only);

static mach_port_t segv_port = 0;

// Dedicated PROT_NONE page for the kernel-assisted restore trigger.
void *jl_mach_restore_page = NULL;

// Maximum float state count (in natural_t units) across all supported flavors.
#if defined(_CPU_X86_64_)
#define JL_MACH_FLOAT_STATE_MAX_COUNT x86_AVX512_STATE64_COUNT
#elif defined(_CPU_AARCH64_)
#define JL_MACH_FLOAT_STATE_MAX_COUNT ARM_NEON_STATE64_COUNT
#endif

static inline int jl_addr_is_restore_trigger(uintptr_t addr)
{
    uintptr_t page_addr = (uintptr_t)jl_mach_restore_page;
    return addr >= page_addr && addr < page_addr + jl_page_size;
}

#define HANDLE_MACH_ERROR(msg, retval) \
    if (retval != KERN_SUCCESS) { mach_error(msg XSTR(: __FILE__:__LINE__:), (retval)); abort(); }

void *mach_segv_listener(void *arg)
{
    (void)arg;
    int ret = mach_msg_server(mach_exc_server, 2048, segv_port, MACH_MSG_TIMEOUT_NONE);
    mach_error("mach_msg_server" XSTR(: __FILE__:__LINE__:), ret);
    abort();
}

static void init_mach_restore_trigger(void)
{
    // Allocate a PROT_NONE page for the kernel-assisted restore trigger.
    // Reading from this page causes EXC_BAD_ACCESS, which the exception
    // handler intercepts to restore full GP state via the kernel.
    jl_mach_restore_page = mmap(NULL, jl_getpagesize(), PROT_NONE,
                                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (jl_mach_restore_page == MAP_FAILED) {
        perror("mmap(jl_mach_restore_page)");
        abort();
    }

    // Initialize jl_mach_float_state_info
#if defined(_CPU_X86_64_)
    mach_port_t self = pthread_mach_thread_np(pthread_self());
    mach_msg_type_number_t count;
    kern_return_t ret;

    count = x86_AVX512_STATE64_COUNT;
    natural_t buf[x86_AVX512_STATE64_COUNT];
    ret = thread_get_state(self, x86_AVX512_STATE64, (thread_state_t)buf, &count);
    if (ret == KERN_SUCCESS) {
        jl_mach_float_state_info.flavor = x86_AVX512_STATE64;
        jl_mach_float_state_info.count = x86_AVX512_STATE64_COUNT;
    } else {
        count = x86_AVX_STATE64_COUNT;
        ret = thread_get_state(self, x86_AVX_STATE64, (thread_state_t)buf, &count);
        if (ret == KERN_SUCCESS) {
            jl_mach_float_state_info.flavor = x86_AVX_STATE64;
            jl_mach_float_state_info.count = x86_AVX_STATE64_COUNT;
        } else {
            jl_mach_float_state_info.flavor = x86_FLOAT_STATE64;
            jl_mach_float_state_info.count = x86_FLOAT_STATE64_COUNT;
            return;
        }
    }
#elif defined(_CPU_AARCH64_)
    jl_mach_float_state_info.flavor = ARM_NEON_STATE64;
    jl_mach_float_state_info.count = ARM_NEON_STATE64_COUNT;
#endif
}

static void allocate_mach_handler(void)
{
    // ensure KEYMGR_GCC3_DW2_OBJ_LIST is initialized, as this requires malloc
    // and thus can deadlock when used without first initializing it.
    // Apple caused this problem in their libunwind in 10.9 (circa keymgr-28)
    // when they removed this part of the code from keymgr.
    // Much thanks to Apple for providing source code, or this would probably
    // have simply remained unsolved forever on their platform.
    // This is similar to just calling checkKeyMgrRegisteredFDEs
    // (this is quite thread-unsafe)
    if (_keymgr_set_lockmode_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST, NM_ALLOW_RECURSION))
        jl_error("_keymgr_set_lockmode_processwide_ptr failed");

    // setup fault page and register save / restore info for exiting GC safepoint
    init_mach_restore_trigger();

    pthread_t thread;
    pthread_attr_t attr;
    kern_return_t ret;
    mach_port_t self = mach_task_self();
    ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, &segv_port);
    HANDLE_MACH_ERROR("mach_port_allocate",ret);
    ret = mach_port_insert_right(self, segv_port, segv_port, MACH_MSG_TYPE_MAKE_SEND);
    HANDLE_MACH_ERROR("mach_port_insert_right",ret);
    // Alright, create a thread to serve as the listener for exceptions
    if (pthread_attr_init(&attr) != 0) {
        jl_error("pthread_attr_init failed");
    }
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (pthread_create(&thread, &attr, mach_segv_listener, NULL) != 0) {
        jl_error("pthread_create failed");
    }
    pthread_attr_destroy(&attr);
}

#if defined(_CPU_X86_64_)
typedef x86_thread_state64_t host_thread_state_t;
typedef x86_exception_state64_t host_exception_state_t;
#define MACH_THREAD_STATE x86_THREAD_STATE64
#define MACH_THREAD_STATE_COUNT x86_THREAD_STATE64_COUNT
#define HOST_EXCEPTION_STATE x86_EXCEPTION_STATE64
#define HOST_EXCEPTION_STATE_COUNT x86_EXCEPTION_STATE64_COUNT

#elif defined(_CPU_AARCH64_)
typedef arm_thread_state64_t host_thread_state_t;
typedef arm_exception_state64_t host_exception_state_t;
#define MACH_THREAD_STATE ARM_THREAD_STATE64
#define MACH_THREAD_STATE_COUNT ARM_THREAD_STATE64_COUNT
#define HOST_EXCEPTION_STATE ARM_EXCEPTION_STATE64
#define HOST_EXCEPTION_STATE_COUNT ARM_EXCEPTION_STATE64_COUNT
#endif

#ifdef LLVMLIBUNWIND
volatile mach_port_t mach_profiler_thread = 0;
static kern_return_t profiler_segv_handler(
    mach_port_t exception_port,
    mach_port_t thread,
    mach_port_t task,
    exception_type_t exception,
    mach_exception_data_t code,
    mach_msg_type_number_t codeCnt,
    host_thread_state_t *state,
    mach_msg_type_number_t *new_stateCnt);
#endif

// Create a fake function that describes the register manipulations in jl_noreturn_call_in_state
// The callee-saved registers still may get smashed (by the cdecl fptr), since we didn't explicitly copy all of the
// state to the stack (to build a real sigreturn frame).
__attribute__((naked, used)) void jl_fake_signal_return(void)
{
#if defined(_CPU_X86_64_)
__asm__(
    "  .cfi_signal_frame\n"
    "  .cfi_def_cfa %rsp, 0\n" // CFA here uses %rsp directly
    "  .cfi_offset %rip, 0\n" // previous value of %rip at CFA
    "  .cfi_offset %rsp, 8\n" // previous value of %rsp at CFA
    "  ud2\n"
    "  ud2\n"
);
#elif defined(_CPU_AARCH64_)
__asm__(
    "  .cfi_signal_frame\n"
    "  .cfi_def_cfa sp, 0\n" // use sp as fp here
    // This is not quite valid, since the AArch64 DWARF spec lacks the ability to define how to restore the LR register correctly,
    // so normally libunwind implementations on linux detect this function specially and hack around the invalid info:
    // https://github.com/llvm/llvm-project/commit/c82deed6764cbc63966374baf9721331901ca958
    "  .cfi_offset lr, 0\n"
    "  .cfi_offset sp, 8\n"
    "  brk #1\n"
    "  brk #1\n"
);
#endif
}

// Optimized version of `jl_call_in_state` that avoids saving most CPU registers.
static void jl_noreturn_call_in_state(host_thread_state_t *state, void (*fptr)(void), uintptr_t arg0, uintptr_t arg1)
{
#ifdef _CPU_X86_64_
    uintptr_t sp = state->__rsp;
#elif defined(_CPU_AARCH64_)
    uintptr_t sp = state->__sp;
#endif
    sp = (sp - 256) & ~(uintptr_t)15; // redzone and re-alignment
    assert(sp % 16 == 0);
#ifdef _CPU_X86_64_
    // push {%rsp, %rip}
    sp -= sizeof(void*);
    *(uintptr_t*)sp = state->__rsp;
    sp -= sizeof(void*);
    *(uintptr_t*)sp = state->__rip;
    // pushq .jl_fake_signal_return + 1; aka call from jl_fake_signal_return
    sp -= sizeof(void*);
    *(uintptr_t*)sp = (uintptr_t)&jl_fake_signal_return + 1;
    state->__rsp = sp; // set stack pointer
    state->__rip = (uint64_t)fptr; // "call" the function
    state->__rdi = arg0;
    state->__rsi = arg1;
#elif defined(_CPU_AARCH64_)
    // push {%pc, %sp}
    sp -= sizeof(void*);
    *(uintptr_t*)sp = state->__sp;
    sp -= sizeof(void*);
    *(uintptr_t*)sp = (uintptr_t)state->__pc;
    state->__sp = sp; // x31
    state->__pc = (uint64_t)fptr; // pc
    state->__lr = (uintptr_t)&jl_fake_signal_return + 4; // x30
    state->__x[0] = arg0;
    state->__x[1] = arg1;
#else
#error "julia: throw-in-context not supported on this platform"
#endif
}

static void jl_longjmp_in_state(host_thread_state_t *state, jl_jmp_buf jmpbuf, int val)
{
    if (!jl_simulate_longjmp(jmpbuf, (bt_context_t*)state, val)) {
        // for sanitizer builds, fallback to calling longjmp on the original stack
        // (this will fail for stack overflow, but that is hardly sanitizer-legal anyways)
        jl_noreturn_call_in_state(state, (void (*)(void))longjmp, (uintptr_t)jmpbuf, val);
    }
}

#ifdef _CPU_X86_64_
int is_write_fault(host_exception_state_t exc_state) {
    return exc_reg_is_write_fault(exc_state.__err);
}
#elif defined(_CPU_AARCH64_)
int is_write_fault(host_exception_state_t exc_state) {
    return exc_reg_is_write_fault(exc_state.__esr);
}
#else
#warning Implement this query for consistent PROT_NONE handling
int is_write_fault(host_exception_state_t exc_state) {
    return 0;
}
#endif

static void jl_throw_in_state(jl_ptls_t ptls2, host_thread_state_t *state, jl_value_t *exception)
{
    if (ptls2->safe_restore) {
        jl_longjmp_in_state(state, *ptls2->safe_restore, 1);
    }
    else {
        assert(exception);
        ptls2->bt_size =
            rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, (bt_context_t *)state,
                            NULL /*current_task?*/);
        ptls2->sig_exception = exception;
        ptls2->io_wait = 0;
        jl_task_t *ct = jl_atomic_load_relaxed(&ptls2->current_task);
        // This redirect abandons every frame between the interrupted context
        // and the handler; clear a published reset context so no
        // cancellation sender consumes a buffer in the abandoned region
        // (jl_eh_restore_state republishes the outer one at the catch). The
        // same applies to a foreign-call cancellation-handler guard
        // published in an abandoned frame.
        jl_atomic_store_release(&ct->reset_ctx, NULL);
        jl_atomic_store_release(&ct->cancel_handler_ctx, NULL);
        jl_handler_t *eh = ct->eh;
        if (eh != NULL) {
            asan_unpoison_task_stack(ct, &eh->eh_ctx);
            jl_longjmp_in_state(state, eh->eh_ctx, 1);
        }
        else {
            jl_no_exc_handler(exception, ct);
        }
    }
}

// Trampoline that runs on the faulting thread after being hijacked by the
// Mach exception handler for a safepoint hit. This uses the same codepath
// as the Unix signal handler (jl_set_gc_and_wait), so the faulting thread
// participates in GC synchronization directly.
static void mach_safepoint_trampoline(jl_ptls_t ptls)
{
    // note: jl_current_task cannot be used here, since __thread variables
    //       may already be destroyed during thread teardown
    jl_task_t *ct = jl_atomic_load_relaxed(&ptls->current_task);
    if (ct == NULL)
        return; // thread is dead, just resume
    jl_set_gc_and_wait(ct);
    // (The sigint force-throw that lived here is gone: SIGINT is delivered
    // through the cancellation system - see jl_sigint_request_cancellation -
    // and nothing arms the sigint page anymore.)
}

#if defined(_CPU_AARCH64_)
__attribute__((naked, used)) static void jl_mach_restore_trigger(void)
{
    __asm__(
        "  adrp x16, _jl_mach_restore_page@PAGE\n"
        "  ldr x16, [x16, _jl_mach_restore_page@PAGEOFF]\n"
        "  ldr x17, [x16]\n"  // EXC_BAD_ACCESS here
        "  brk #1\n"          // should never reach here
    );
}
#elif defined(_CPU_X86_64_)
__attribute__((naked, used)) static void jl_mach_restore_trigger(void)
{
    __asm__(
        "  movq _jl_mach_restore_page(%rip), %r11\n"
        "  movq (%r11), %r11\n"  // EXC_BAD_ACCESS here
        "  ud2\n"                // should never reach here
    );
}
#endif

// Set up state to call fptr(arg0) on the target thread.
static void jl_call_in_state(host_thread_state_t *state, void (*fptr)(void),
                             uintptr_t arg0, const void *float_state)
{
    size_t float_size = jl_mach_float_state_info.count * sizeof(natural_t);
    size_t total_size = sizeof(host_thread_state_t) + float_size;
    // When fptr returns, the thread hits jl_mach_restore_trigger and faults
    // (again), so that we can request that the kernel restore all relevant
    // processor state.
#ifdef _CPU_X86_64_
    uintptr_t sp = state->__rsp;
    sp = (sp - 256) & ~(uintptr_t)15; // redzone and re-alignment
    sp -= total_size;
    sp &= ~(uintptr_t)15;
    memcpy((void*)sp, state, sizeof(host_thread_state_t));
    memcpy((void*)(sp + sizeof(host_thread_state_t)), float_state, float_size);
    // Push the return address for the restore trigger
    sp -= sizeof(void*);
    *(uintptr_t*)sp = (uintptr_t)&jl_mach_restore_trigger;
    state->__rsp = sp;
    state->__rip = (uint64_t)fptr;
    state->__rdi = arg0;
#elif defined(_CPU_AARCH64_)
    uintptr_t sp = state->__sp;
    sp = (sp - 256) & ~(uintptr_t)15;
    sp -= total_size;
    sp &= ~(uintptr_t)15;
    memcpy((void*)sp, state, sizeof(host_thread_state_t));
    memcpy((void*)(sp + sizeof(host_thread_state_t)), float_state, float_size);
    state->__sp = sp;
    state->__pc = (uint64_t)fptr;
    state->__lr = (uintptr_t)&jl_mach_restore_trigger;
    state->__x[0] = arg0;
#else
#error "julia: call-in-state not supported on this platform"
#endif
}

static void segv_handler(int sig, siginfo_t *info, void *context)
{
    assert(sig == SIGSEGV || sig == SIGBUS);
    jl_jmp_buf *saferestore = jl_get_safe_restore();
    if (saferestore) { // restarting jl_ or jl_unwind_stepn
        jl_longjmp_in_state((host_thread_state_t*)jl_to_bt_context(context), *saferestore, 1);
        return;
    }
    jl_task_t *ct = jl_get_current_task();
    if ((sig != SIGBUS || info->si_code == BUS_ADRERR) &&
    !(ct == NULL || ct->ptls == NULL || jl_atomic_load_relaxed(&ct->ptls->gc_state) == JL_GC_STATE_WAITING || ct->eh == NULL)
    && is_addr_on_stack(ct, info->si_addr)) { // stack overflow and not a BUS_ADRALN (alignment error)
        stack_overflow_warning();
    }
    sigdie_handler(sig, info, context);
}

// n.b. mach_exc_server expects us to define this symbol locally
/* The documentation for catch_exception_raise says: A return value of
 * KERN_SUCCESS indicates that the thread is to continue from the point of
 * exception. A return value of MIG_NO_REPLY indicates that the exception was
 * handled directly and the thread was restarted or terminated by the exception
 * handler. A return value of MIG_DESTROY_REQUEST causes the kernel to try
 * another exception handler (or terminate the thread). Any other value will
 * cause mach_msg_server to remove the task and thread port references.
 *
 * However MIG_DESTROY_REQUEST does not exist, nor does it appear the source
 * code for mach_msg_server ever destroy those references (only the message
 * itself).
 */
kern_return_t catch_mach_exception_raise_state_identity(
    mach_port_t exception_port,
    mach_port_t thread,
    mach_port_t task,
    exception_type_t exception,
    mach_exception_data_t code,
    mach_msg_type_number_t codeCnt,
    int *flavor,
    thread_state_t old_state,
    mach_msg_type_number_t old_stateCnt,
    thread_state_t new_state,
    mach_msg_type_number_t *new_stateCnt)
{
    host_thread_state_t *state = (host_thread_state_t*)new_state;
    assert(old_stateCnt >= MACH_THREAD_STATE_COUNT);
    // Copy old state to new state — we'll modify new_state in place
    memcpy(new_state, old_state, old_stateCnt * sizeof(natural_t));
    *new_stateCnt = old_stateCnt;
#ifdef LLVMLIBUNWIND
    if (thread == mach_profiler_thread) {
        return profiler_segv_handler(exception_port, thread, task, exception, code, codeCnt,
                                     state, new_stateCnt);
    }
#endif
    jl_ptls_t ptls2 = NULL;
    int nthreads = jl_atomic_load_acquire(&jl_n_threads);
    for (int16_t tid = 0; tid < nthreads; tid++) {
        jl_ptls_t _ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
        if (_ptls2 == NULL)
            continue;
        if (pthread_mach_thread_np(_ptls2->system_id) == thread) {
            ptls2 = _ptls2;
            break;
        }
    }
    if (!ptls2) {
        // We don't know about this thread, let the kernel try another handler
        // instead. This shouldn't actually happen since we only register the
        // handler for the threads we know about.
        jl_safe_printf("ERROR: Exception handler triggered on unmanaged thread.\n");
        return KERN_INVALID_ARGUMENT;
    }
    if (ptls2->safe_restore) {
        jl_throw_in_state(ptls2, state, NULL);
        return KERN_SUCCESS;
    }
    if (jl_atomic_load_acquire(&ptls2->gc_state) == JL_GC_STATE_WAITING)
        return KERN_FAILURE;
    if (exception == EXC_ARITHMETIC) {
        jl_throw_in_state(ptls2, state, jl_diverror_exception);
        return KERN_SUCCESS;
    }
    assert(exception == EXC_BAD_ACCESS); // SIGSEGV or SIGBUS
    if (codeCnt < 2 || code[0] != KERN_PROTECTION_FAILURE) // SEGV_ACCERR or BUS_ADRERR or BUS_ADRALN
        return KERN_FAILURE;
    uint64_t fault_addr = code[1];
    unsigned int exc_count = HOST_EXCEPTION_STATE_COUNT;
    host_exception_state_t exc_state;
    kern_return_t ret = thread_get_state(thread, HOST_EXCEPTION_STATE, (thread_state_t)&exc_state, &exc_count);
    HANDLE_MACH_ERROR("thread_get_state", ret);
    if (jl_addr_is_safepoint(fault_addr) && !is_write_fault(exc_state)) {
        // Save FP/SIMD state from the faulting thread
        mach_msg_type_number_t float_count = jl_mach_float_state_info.count;
        natural_t float_state[JL_MACH_FLOAT_STATE_MAX_COUNT];
        kern_return_t ret = thread_get_state(thread, jl_mach_float_state_info.flavor,
                                             (thread_state_t)float_state, &float_count);
        HANDLE_MACH_ERROR("thread_get_state", ret);
        assert(float_count == jl_mach_float_state_info.count);
        // Hijack the faulting thread to handle the safepoint on its own
        // stack, using the same jl_set_gc_and_wait() codepath as Unix signals.
        jl_call_in_state(state, (void (*)(void))&mach_safepoint_trampoline,
                         (uintptr_t)ptls2, float_state);
        return KERN_SUCCESS;
    } else if (jl_addr_is_restore_trigger(fault_addr)) {
        // This is a deliberate fault from jl_mach_restore_trigger, we're
        // returning from a `jl_call_in_state` (probably the one above).
#if defined(_CPU_X86_64_)
        old_state = (thread_state_t)state->__rsp;
#elif defined(_CPU_AARCH64_)
        old_state = (thread_state_t)state->__sp;
#endif
        // Restore all registers and return KERN_SUCCESS to resume the thread.
        memcpy(state, old_state, sizeof(host_thread_state_t));
        thread_state_t old_fp = (thread_state_t)((char*)old_state + sizeof(host_thread_state_t));
        kern_return_t ret = thread_set_state(thread, jl_mach_float_state_info.flavor,
                                             old_fp, jl_mach_float_state_info.count);
        HANDLE_MACH_ERROR("thread_set_state", ret);
        // A completed cancellation-handler delivery is consumed here (no-op
        // for other jl_call_in_state users); further deliveries may fire
        // again once the thread resumes.
        ptls2->cancel_handler_armed = 0;
        return KERN_SUCCESS;
    }
    if (jl_atomic_load_relaxed(&ptls2->current_task)->eh == NULL)
        return KERN_FAILURE;
    jl_value_t *excpt;
    if (is_addr_on_stack(jl_atomic_load_relaxed(&ptls2->current_task), (void*)fault_addr)) {
        stack_overflow_warning();
        excpt = jl_stackovf_exception;
    }
    else if (is_write_fault(exc_state)) // false for alignment errors
        excpt = jl_readonlymemory_exception;
    else
        return KERN_FAILURE;
    jl_throw_in_state(ptls2, state, excpt);
    return KERN_SUCCESS;
}

//mach_exc_server expects us to define this symbol locally
kern_return_t catch_mach_exception_raise_state(
    mach_port_t exception_port,
    exception_type_t exception,
    const mach_exception_data_t code,
    mach_msg_type_number_t codeCnt,
    int *flavor,
    const thread_state_t old_state,
    mach_msg_type_number_t old_stateCnt,
    thread_state_t new_state,
    mach_msg_type_number_t *new_stateCnt)
{
    return KERN_INVALID_ARGUMENT; // we only use EXCEPTION_STATE_IDENTITY
}

//mach_exc_server expects us to define this symbol locally
kern_return_t catch_mach_exception_raise(
    mach_port_t exception_port,
    mach_port_t thread,
    mach_port_t task,
    exception_type_t exception,
    mach_exception_data_t code,
    mach_msg_type_number_t codeCnt)
{
    return KERN_INVALID_ARGUMENT; // we only use EXCEPTION_STATE_IDENTITY
}

static void attach_exception_port(thread_port_t thread, int segv_only)
{
    kern_return_t ret;
    // https://www.opensource.apple.com/source/xnu/xnu-2782.1.97/osfmk/man/thread_set_exception_ports.html
    exception_mask_t mask = EXC_MASK_BAD_ACCESS;
    if (!segv_only)
        mask |= EXC_MASK_ARITHMETIC;
    ret = thread_set_exception_ports(thread, mask, segv_port, EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, MACH_THREAD_STATE);
    HANDLE_MACH_ERROR("thread_set_exception_ports", ret);
}

static int jl_thread_suspend_and_get_state2(int tid, host_thread_state_t *ctx) JL_NOTSAFEPOINT
{
    if (tid < 0 || tid >= jl_atomic_load_acquire(&jl_n_threads))
        return 0;
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL) // this thread is not alive
        return 0;
    jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL) // this thread is already dead
        return 0;

    mach_port_t thread = pthread_mach_thread_np(ptls2->system_id);

    kern_return_t ret = thread_suspend(thread);
    HANDLE_MACH_ERROR("thread_suspend", ret);

    // Do the actual sampling
    unsigned int count = MACH_THREAD_STATE_COUNT;
    memset(ctx, 0, sizeof(*ctx));

    // Get the state of the suspended thread
    ret = thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)ctx, &count);
    return 1;
}

static int jl_thread_suspend_and_get_state(int tid, int timeout, bt_context_t *ctx)
{
    (void)timeout;
    host_thread_state_t state;
    if (!jl_thread_suspend_and_get_state2(tid, &state)) {
        return 0;
    }
    *ctx = *(unw_context_t*)&state;
    return 1;
}

void jl_thread_resume(int tid)
{
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    mach_port_t thread = pthread_mach_thread_np(ptls2->system_id);
    kern_return_t ret = thread_resume(thread);
    HANDLE_MACH_ERROR("thread_resume", ret);
}

// Serializes every path that suspends a thread and rewrites its context
// (jl_send_cancellation_signal on any thread, and jl_send_abandon_signal) for
// its complete suspend/rewrite/resume sequence: two rewriters working from
// the same suspended snapshot would install conflicting continuations and
// task chains. (The profiler does not need it: it only reads contexts, and
// suspend counts nest.) It also prevents two threads delivering
// cancellations at each other from freezing both: a rewriter always takes
// this lock before suspending and never blocks while holding a suspension,
// so a suspended thread can never hold it.
static pthread_mutex_t ctx_rewrite_lock = PTHREAD_MUTEX_INITIALIZER;

// Runs on the interrupted thread, hijacked by jl_send_reset_signal via
// jl_call_in_state with the interrupted state saved on the stack below:
// invoke the registered cancellation handler with its arguments from the
// per-thread save area. Returning runs into jl_mach_restore_trigger, whose
// exception-handler branch restores the interrupted state (and disarms).
static void jl_mach_cancel_handler_trampoline(jl_ptls_t ptls)
{
    jl_cancel_handler_save_t *save = &ptls->cancel_handler_save;
    save->fn(save->state, save->sev);
}

// Suspend-based shootdown delivery (see jl_send_reset_signal in
// signals-win.c for the shared shape and locking rationale). Rather than
// simulating the longjmp into a GP-only thread state - which cannot carry
// the callee-saved SIMD registers the setjmp ABI requires - the frozen
// thread is redirected to call the real longjmp on its own stack. A
// foreign-call cancellation handler is delivered by hijacking the frozen
// thread to run fn(state, sev) via the resumable jl_call_in_state
// machinery, whose restore-trigger return path restores the complete
// interrupted state through the kernel. Best-effort: any failed check
// simply drops the request.
static void jl_send_reset_signal(int16_t tid, int reset_code) JL_NOTSAFEPOINT
{
    jl_value_t *bound;
    int bound_cancelled;
    jl_cancel_handler_ctx_t *hctx;
    if (tid < 0 || tid >= jl_atomic_load_acquire(&jl_n_threads))
        return;
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL)
        return;
    jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL)
        return;
    // Only proceed if the task has an interruptible-region context
    // published - a purely polling victim between cancellation points never
    // has one. Never self-suspend: a thread's own reset region is always
    // cleared here (the ccall reaching this function is itself an unsafe
    // point), but a protected runtime span (e.g. the GMP allocation hooks)
    // can reach this sender - through a finalizer running cancel! - with
    // its own handler context still published.
    if (pthread_equal(ptls2->system_id, pthread_self()))
        return;
    if (jl_atomic_load_relaxed(&ct2->reset_ctx) == NULL &&
        jl_atomic_load_relaxed(&ct2->cancel_handler_ctx) == NULL)
        return;
    pthread_mutex_lock(&ctx_rewrite_lock);
    // Thread teardown clears current_task while holding the profile write
    // lock (see jl_free_thread_gc_state): hold the read lock across the
    // liveness re-check and the suspension, so the thread cannot exit (and
    // its Mach port cannot die) in between. Suspension failure is still
    // treated as a best-effort delivery failure, never a fatal error.
    jl_lock_profile();
    ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL) {
        jl_unlock_profile();
        pthread_mutex_unlock(&ctx_rewrite_lock);
        return;
    }
    mach_port_t thread = pthread_mach_thread_np(ptls2->system_id);
    kern_return_t ret = thread_suspend(thread);
    jl_unlock_profile();
    if (ret != KERN_SUCCESS) {
        pthread_mutex_unlock(&ctx_rewrite_lock);
        return;
    }
    host_thread_state_t state;
    unsigned int count = MACH_THREAD_STATE_COUNT;
    memset(&state, 0, sizeof(state));
    if (thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, &count) != KERN_SUCCESS)
        goto resume;
    // Re-check now that the thread cannot run: the current task may have
    // switched before the freeze. Delivery is gated on an actual
    // cancellation of the task's bound token source, kept coherent with the
    // published regions by the exception-handler and finalizer save/restore
    // discipline.
    ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    bound = ct2 == NULL ? NULL :
        jl_atomic_load_relaxed(&ct2->bound_cancel_token);
    bound_cancelled = bound != NULL && bound != jl_nothing &&
        jl_atomic_load_relaxed(&((jl_cancel_source_t*)bound)->state) != 0;
    hctx = ct2 == NULL ? NULL :
        jl_atomic_load_acquire(&ct2->cancel_handler_ctx);
    if (hctx != NULL) {
        // Handler flavor: a published foreign-call cancellation-handler
        // guard takes priority over (and suppresses) the reset - its span
        // (e.g. a protected allocator) is exactly where a longjmp must not
        // land, and the handler can defer the cancellation and chain into
        // the reset on region exit. Hijack the frozen thread to run
        // fn(state, sev) on its own stack via the resumable
        // jl_call_in_state machinery - at most one delivery at a time per
        // thread (the save area holds one; skips recover level-triggered).
        if (!ptls2->cancel_handler_armed && bound_cancelled) {
            mach_msg_type_number_t float_count = jl_mach_float_state_info.count;
            natural_t float_state[JL_MACH_FLOAT_STATE_MAX_COUNT];
            if (thread_get_state(thread, jl_mach_float_state_info.flavor,
                                 (thread_state_t)float_state, &float_count) != KERN_SUCCESS)
                goto resume;
            jl_cancel_handler_save_t *save = &ptls2->cancel_handler_save;
            save->fn = hctx->fn;
            save->state = hctx->state;
            save->sev = jl_atomic_load_relaxed(&((jl_cancel_source_t*)bound)->state);
            ptls2->cancel_handler_armed = 1;
            jl_call_in_state(&state, (void (*)(void))&jl_mach_cancel_handler_trampoline,
                             (uintptr_t)ptls2, float_state);
            if (thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state,
                                 MACH_THREAD_STATE_COUNT) != KERN_SUCCESS)
                ptls2->cancel_handler_armed = 0;
        }
    }
    else if (ct2 != NULL &&
        // Reset flavor, additionally gated on the thread running Julia code
        // (gc_state == 0): a thread inside a GC-safe region may be raced by
        // a concurrent stop-the-world, and a redirect back into Julia code
        // would break that protocol.
        jl_atomic_load_relaxed(&ptls2->gc_state) == JL_GC_STATE_UNSAFE) {
        jl_reset_ctx_t *reset_ctx = jl_atomic_load_acquire(&ct2->reset_ctx);
        if (reset_ctx != NULL && reset_ctx->sp != 0 &&
            (reset_code == JL_RESET_CODE_PREEMPT || bound_cancelled)) {
            // Consume the reset point with an exchange (off-thread senders
            // may race each other, unlike the Unix in-handler consumer).
            reset_ctx = jl_atomic_exchange(&ct2->reset_ctx, NULL);
            if (reset_ctx != NULL && reset_ctx->sp != 0) {
                // Redirect the frozen thread to call longjmp on its own
                // stack (restoring callee-saved GP and SIMD state
                // natively), and rewind the task's gcstack/eh chains only
                // once the redirect is committed, so that any failure
                // resumes the thread exactly as it was, with the
                // (unconsumed) region republished.
                jl_noreturn_call_in_state(&state, (void (*)(void))longjmp,
                                          (uintptr_t)reset_ctx->mctx, reset_code);
                if (thread_set_state(thread, MACH_THREAD_STATE,
                                     (thread_state_t)&state,
                                     MACH_THREAD_STATE_COUNT) == KERN_SUCCESS) {
                    ct2->gcstack = reset_ctx->gcstack;
                    ct2->eh = reset_ctx->eh;
                }
                else {
                    jl_atomic_store_release(&ct2->reset_ctx, reset_ctx);
                }
            }
        }
    }
resume:
    if (thread_resume(thread) != KERN_SUCCESS)
        jl_safe_printf("error: thread_resume failed in cancellation delivery\n");
    pthread_mutex_unlock(&ctx_rewrite_lock);
}


// Switch the target thread's current (already committed) task to
// ptls->abandon_to (see jl_abandon_task_request): suspend the thread, validate the
// pending request against its frozen state, and on commit redirect it into
// the abandon callback. Holds the rewrite lock like every other
// suspend-and-rewrite path (see its definition above), and the profile read
// lock across the liveness re-check and suspension so the thread cannot
// exit in between.
void jl_send_abandon_signal(int16_t tid) JL_NOTSAFEPOINT
{
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
    if (ptls2 == NULL)
        return;
    pthread_mutex_lock(&ctx_rewrite_lock);
    jl_lock_profile();
    jl_task_t *ct2 = jl_atomic_load_relaxed(&ptls2->current_task);
    if (ct2 == NULL) {
        jl_unlock_profile();
        pthread_mutex_unlock(&ctx_rewrite_lock);
        return;
    }
    mach_port_t thread = pthread_mach_thread_np(ptls2->system_id);
    kern_return_t ret = thread_suspend(thread);
    jl_unlock_profile();
    if (ret != KERN_SUCCESS) {
        pthread_mutex_unlock(&ctx_rewrite_lock);
        return;
    }
    // The victim thread is suspended: fetch its context *before* deciding
    // anything, validate the pending request against its frozen state, and
    // only then redirect it into the abandon callback (which never
    // returns). A commit is rolled back to a refusal if the redirect
    // cannot be completed - the callback is what publishes the abandoned
    // task state, so an unredirected victim resumes untouched. On refusal
    // the requester observes the verdict and withdraws.
    host_thread_state_t state;
    unsigned int count = MACH_THREAD_STATE_COUNT;
    memset(&state, 0, sizeof(state));
    if (thread_get_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, &count) != KERN_SUCCESS) {
        // cannot redirect; leave the request pending for a retry/withdraw
    }
    else if (jl_abandon_try_commit(ptls2)) {
        jl_noreturn_call_in_state(&state, (void (*)(void))&jl_abandon_task_cb, 0, 0);
        if (thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count) != KERN_SUCCESS) {
            // Roll the commit back: nothing observable was published yet
            // (the task state is written by the callback).
            jl_atomic_store_release(&ptls2->abandon_state, JL_ABANDON_REFUSED);
        }
    }
    if (thread_resume(thread) != KERN_SUCCESS)
        jl_safe_printf("error: thread_resume failed in task abandonment\n");
    pthread_mutex_unlock(&ctx_rewrite_lock);
}

static void jl_exit_thread0_cb(int signo)
{
    jl_fprint_critical_error(ios_safe_stderr, signo, 0, NULL, jl_current_task);
    jl_atexit_hook(128);
    jl_raise(signo);
}

static void jl_exit_thread0(int signo, jl_bt_element_t *bt_data, size_t bt_size)
{
    jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[0];
    mach_port_t thread = pthread_mach_thread_np(ptls2->system_id);

    host_thread_state_t state;
    if (!jl_thread_suspend_and_get_state2(0, &state)) {
        // thread 0 is gone? just do the signal ourself
        jl_raise(signo);
    }

    // This aborts `sleep` and other syscalls.
    kern_return_t ret = thread_abort(thread);
    HANDLE_MACH_ERROR("thread_abort", ret);

    ptls2->bt_size = bt_size; // <= JL_MAX_BT_SIZE
    memcpy(ptls2->bt_data, bt_data, ptls2->bt_size * sizeof(bt_data[0]));

    jl_noreturn_call_in_state(&state, (void (*)(void))&jl_exit_thread0_cb, signo, 0);
    unsigned int count = MACH_THREAD_STATE_COUNT;
    ret = thread_set_state(thread, MACH_THREAD_STATE, (thread_state_t)&state, count);
    HANDLE_MACH_ERROR("thread_set_state", ret);

    ret = thread_resume(thread);
    HANDLE_MACH_ERROR("thread_resume", ret);
}

static int profile_started = 0;
mach_timespec_t timerprof;
static pthread_t profiler_thread;
clock_serv_t clk;
static mach_port_t profile_port = 0;

#ifdef LLVMLIBUNWIND
volatile static int forceDwarf = -2;
static unw_context_t profiler_uc;

static kern_return_t profiler_segv_handler(
    mach_port_t exception_port,
    mach_port_t thread,
    mach_port_t task,
    exception_type_t exception,
    mach_exception_data_t code,
    mach_msg_type_number_t codeCnt,
    host_thread_state_t *state,
    mach_msg_type_number_t *new_stateCnt)
{
    assert(thread == mach_profiler_thread);

    // Not currently unwinding. Raise regular segfault
    if (forceDwarf == -2)
        return KERN_FAILURE;

    if (forceDwarf == 0)
        forceDwarf = 1;
    else
        forceDwarf = -1;

#ifdef _CPU_X86_64_
    // don't change cs fs gs rflags
    uint64_t cs = state->__cs;
    uint64_t fs = state->__fs;
    uint64_t gs = state->__gs;
    uint64_t rflags = state->__rflags;
#elif defined(_CPU_AARCH64_)
    uint64_t cpsr = state->__cpsr;
#else
#error Unknown CPU
#endif

    memcpy(state, &profiler_uc, sizeof(*state));

#ifdef _CPU_X86_64_
    state->__cs = cs;
    state->__fs = fs;
    state->__gs = gs;
    state->__rflags = rflags;
#else
    state->__cpsr = cpsr;
#endif

    *new_stateCnt = MACH_THREAD_STATE_COUNT;
    return KERN_SUCCESS;
}
#endif

// WARNING: we are unable to handle sigsegv while the dlsymlock is held
static int jl_lock_profile_mach(int dlsymlock)
{
    jl_lock_profile();
    // workaround for old keymgr bugs
    void *unused = NULL;
    int keymgr_locked = _keymgr_get_and_lock_processwide_ptr_2(KEYMGR_GCC3_DW2_OBJ_LIST, &unused) == 0;
    // workaround for new dlsym4 bugs in the workaround for dlsym bugs: _dyld_atfork_prepare
    // acquires its locks in the wrong order, but fortunately we happen to able to guard it
    // with this call to force it to prevent that TSAN violation from causing a deadlock
    if (dlsymlock && _dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL)
        _dyld_dlopen_atfork_prepare();
    // workaround for new dlsym4 bugs (API and bugs introduced circa macOS 12.1)
    if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
        _dyld_atfork_prepare();
    return keymgr_locked;
}

static void jl_unlock_profile_mach(int dlsymlock, int keymgr_locked)
{
    if (dlsymlock && _dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
        _dyld_atfork_parent();
    if (dlsymlock && _dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL)
        _dyld_dlopen_atfork_parent();
    if (keymgr_locked)
        _keymgr_unlock_processwide_ptr(KEYMGR_GCC3_DW2_OBJ_LIST);
    jl_unlock_profile();
}

int jl_thread_suspend(int16_t tid, bt_context_t *ctx)
{
    int lockret = jl_lock_profile_mach(1);
    int success = jl_thread_suspend_and_get_state(tid, 1, ctx);
    jl_unlock_profile_mach(1, lockret);
    return success;
}

void jl_with_stackwalk_lock(void (*f)(void*), void *ctx)
{
    int lockret = jl_lock_profile_mach(1);
    f(ctx);
    jl_unlock_profile_mach(1, lockret);
}

// assumes holding `jl_lock_profile_mach`
void jl_profile_thread_mach(int tid)
{
    // if there is no space left, return early
    if (jl_profile_is_buffer_full()) {
        jl_profile_stop_timer();
        return;
    }
    if (_dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL)
        _dyld_dlopen_atfork_prepare();
    if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
        _dyld_atfork_prepare(); // briefly acquire the dlsym lock
    host_thread_state_t state;
    int valid_thread = jl_thread_suspend_and_get_state2(tid, &state);
    unw_context_t *uc = (unw_context_t*)&state;
    if (_dyld_atfork_prepare != NULL && _dyld_atfork_parent != NULL)
        _dyld_atfork_parent(); // quickly release the dlsym lock
    if (_dyld_dlopen_atfork_prepare != NULL && _dyld_dlopen_atfork_parent != NULL)
        _dyld_dlopen_atfork_parent();
    if (!valid_thread)
        return;
    if (profile_running) {
#ifdef LLVMLIBUNWIND
        /*
            *  Unfortunately compact unwind info is incorrectly generated for quite a number of
            *  libraries by quite a large number of compilers. We can fall back to DWARF unwind info
            *  in some cases, but in quite a number of cases (especially libraries not compiled in debug
            *  mode, only the compact unwind info may be available). Even more unfortunately, there is no
            *  way to detect such bogus compact unwind info (other than noticing the resulting segfault).
            *  What we do here is ugly, but necessary until the compact unwind info situation improves.
            *  We try to use the compact unwind info and if that results in a segfault, we retry with DWARF info.
            *  Note that in a small number of cases this may result in bogus stack traces, but at least the topmost
            *  entry will always be correct, and the number of cases in which this is an issue is rather small.
            *  Other than that, this implementation is not incorrect as the other thread is paused while we are profiling
            *  and during stack unwinding we only ever read memory, but never write it.
            */

        forceDwarf = 0;
        unw_getcontext(&profiler_uc); // will resume from this point if the next lines segfault at any point

        if (forceDwarf == 0) {
            // Save the backtrace
            profile_bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)profile_bt_data_prof + profile_bt_size_cur, profile_bt_size_max - profile_bt_size_cur - 1, uc, NULL);
        }
        else if (forceDwarf == 1) {
            profile_bt_size_cur += rec_backtrace_ctx_dwarf((jl_bt_element_t*)profile_bt_data_prof + profile_bt_size_cur, profile_bt_size_max - profile_bt_size_cur - 1, uc, NULL);
        }
        else if (forceDwarf == -1) {
            jl_safe_printf("WARNING: profiler attempt to access an invalid memory location\n");
        }

        forceDwarf = -2;
#else
        profile_bt_size_cur += rec_backtrace_ctx((jl_bt_element_t*)profile_bt_data_prof + profile_bt_size_cur, profile_bt_size_max - profile_bt_size_cur - 1, uc, NULL);
#endif
        jl_ptls_t ptls = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];

        // store threadid but add 1 as 0 is preserved to indicate end of block
        profile_bt_data_prof[profile_bt_size_cur++].uintptr = ptls->tid + 1;

        // store task id (never null)
        profile_bt_data_prof[profile_bt_size_cur++].jlvalue = (jl_value_t*)jl_atomic_load_relaxed(&ptls->current_task);

        // store cpu cycle clock
        profile_bt_data_prof[profile_bt_size_cur++].uintptr = cycleclock();

        // store whether thread is sleeping (don't ever encode a state as `0` since it is preserved to indicate end of block)
        int state = jl_atomic_load_relaxed(&ptls->sleep_check_state) == 0 ? PROFILE_STATE_THREAD_NOT_SLEEPING : PROFILE_STATE_THREAD_SLEEPING;
        profile_bt_data_prof[profile_bt_size_cur++].uintptr = state;

        // Mark the end of this block with two 0's
        profile_bt_data_prof[profile_bt_size_cur++].uintptr = 0;
        profile_bt_data_prof[profile_bt_size_cur++].uintptr = 0;
    }
    // We're done! Resume the thread.
    jl_thread_resume(tid);
}

void *mach_profile_listener(void *arg)
{
    (void)arg;
    const int max_size = 512;
    attach_exception_port(mach_thread_self(), 1);
#ifdef LLVMLIBUNWIND
    mach_profiler_thread = mach_thread_self();
#endif
    mig_reply_error_t *bufRequest = (mig_reply_error_t*)malloc_s(max_size);
    while (1) {
        kern_return_t ret = mach_msg(&bufRequest->Head, MACH_RCV_MSG,
                                     0, max_size, profile_port,
                                     MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
        HANDLE_MACH_ERROR("mach_msg", ret);
        // sample each thread, round-robin style in reverse order
        // (so that thread zero gets notified last)
        int keymgr_locked = jl_lock_profile_mach(0);
        int nthreads = jl_atomic_load_acquire(&jl_n_threads);
        if (profile_all_tasks) {
            // Don't take the stackwalk lock here since it's already taken in `jl_rec_backtrace`
            jl_profile_task();
        }
        else {
            int *randperm = profile_get_randperm(nthreads);
            for (int idx = nthreads; idx-- > 0; ) {
                // Stop the threads in random order.
                int i = randperm[idx];
                jl_profile_thread_mach(i);
            }
        }
        jl_unlock_profile_mach(0, keymgr_locked);
        if (profile_running) {
            jl_check_profile_autostop();
            // Reset the alarm
            kern_return_t ret = clock_alarm(clk, TIME_RELATIVE, timerprof, profile_port);
            HANDLE_MACH_ERROR("clock_alarm", ret)
        }
    }
}


JL_DLLEXPORT int jl_profile_start_timer(uint8_t all_tasks)
{
    kern_return_t ret;
    if (!profile_started) {
        mach_port_t self = mach_task_self();

        ret = host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, (clock_serv_t *)&clk);
        HANDLE_MACH_ERROR("host_get_clock_service", ret);

        ret = mach_port_allocate(self, MACH_PORT_RIGHT_RECEIVE, &profile_port);
        HANDLE_MACH_ERROR("mach_port_allocate", ret);

        // Alright, create a thread to serve as the listener for exceptions
        pthread_attr_t attr;
        if (pthread_attr_init(&attr) != 0) {
            jl_error("pthread_attr_init failed");
        }
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        if (pthread_create(&profiler_thread, &attr, mach_profile_listener, NULL) != 0) {
            jl_error("pthread_create failed");
        }
        pthread_attr_destroy(&attr);

        profile_started = 1;
    }

    timerprof.tv_sec = nsecprof/GIGA;
    timerprof.tv_nsec = nsecprof%GIGA;

    // hold the lock so that `jl_profile_init` cannot free the buffer while we transition to running
    uv_mutex_lock(&bt_data_prof_lock);
    profile_running = 1;
    profile_all_tasks = all_tasks;
    uv_mutex_unlock(&bt_data_prof_lock);
    // ensure the alarm is running
    ret = clock_alarm(clk, TIME_RELATIVE, timerprof, profile_port);
    HANDLE_MACH_ERROR("clock_alarm", ret);

    return 0;
}

JL_DLLEXPORT void jl_profile_stop_timer(void)
{
    uv_mutex_lock(&bt_data_prof_lock);
    profile_running = 0;
    profile_all_tasks = 0;
    uv_mutex_unlock(&bt_data_prof_lock);
}

// The mprotect implementation in signals-unix.c does not work on macOS/aarch64, as mentioned.
// This implementation comes from dotnet, but is similarly dependent on undocumented behavior of the OS.
// Copyright (c) .NET Foundation and Contributors
// MIT LICENSE
JL_DLLEXPORT void jl_membarrier(void) JL_NOTSAFEPOINT {
    uintptr_t sp;
    uintptr_t registerValues[128];
    kern_return_t machret;

    // Iterate through each of the threads in the list.
    int nthreads = jl_atomic_load_acquire(&jl_n_threads);
    for (int tid = 0; tid < nthreads; tid++) {
        jl_ptls_t ptls2 = jl_atomic_load_relaxed(&jl_all_tls_states)[tid];
        thread_act_t thread = pthread_mach_thread_np(ptls2->system_id);
        if (__builtin_available (macOS 10.14, iOS 12, tvOS 9, *))
        {
            // Request the threads pointer values to force the thread to emit a memory barrier
            size_t registers = 128;
            machret = thread_get_register_pointer_values(thread, &sp, &registers, registerValues);
        }
        else
        {
            // fallback implementation for older OS versions
#if defined(_CPU_X86_64_)
            x86_thread_state64_t threadState;
            mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
            machret = thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&threadState, &count);
#elif defined(_CPU_AARCH64_)
            arm_thread_state64_t threadState;
            mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT;
            machret = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&threadState, &count);
#else
            #error Unexpected architecture
#endif
        }

        if (machret == KERN_INSUFFICIENT_BUFFER_SIZE)
        {
            HANDLE_MACH_ERROR("thread_get_register_pointer_values()", machret);
        }
    }
}
