/* ARM support code for fibers and multithreading.
   Copyright (C) 2019-2022 Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC 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 General Public License
for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "../common/threadasm.S"

#if defined(__ARM_EABI__)

/**
 * Performs a context switch.
 *
 * Parameters:
 * r0 - void** - ptr to old stack pointer
 * r1 - void*  - new stack pointer
 *
 * ARM EABI registers:
 * r0-r3   : argument/scratch registers
 * r4-r10  : callee-save registers
 * r11     : frame pointer (or a callee save register if fp isn't needed)
 * r12 =ip : inter procedure register. We can treat it like any other scratch
 *           register
 * r13 =sp : stack pointer
 * r14 =lr : link register, it contains the return address (belonging to the
 *           function which called us)
 * r15 =pc : program counter
 *
 * For floating point registers:
 * According to AAPCS (version 2.09, section 5.1.2) only the d8-d15 registers
 * need to be preserved across method calls. This applies to all ARM FPU
 * variants, whether they have 16 or 32 double registers NEON support or not,
 * half-float support or not and so on does not matter.
 *
 * Note: If this file was compiled with -mfloat-abi=soft but the code runs on a
 * softfp system with fpu the d8-d15 registers won't be saved (we do not know
 * that the system has got a fpu in that case) but the registers might actually
 * be used by other code if it was compiled with -mfloat-abi=softfp.
 *
 * Interworking is only supported on ARMv5+, not on ARM v4T as ARM v4t requires
 * special stubs when changing from thumb to arm mode or the other way round.
 */

    .text
#if defined(__ARM_PCS_VFP) || (defined(__ARM_PCS) && !defined(__SOFTFP__))
    .fpu vfp
#endif
    .global CSYM(fiber_switchContext)
    .type CSYM(fiber_switchContext), %function
    .align 4
CSYM(fiber_switchContext):
    .cfi_sections .debug_frame
    .cfi_startproc
    .fnstart
    push {r4-r11}
    // update the oldp pointer. Link register and floating point registers
    // stored later to prevent the GC from scanning them.
    str sp, [r0]
    // push r0 (or any other register) as well to keep stack 8byte aligned
    push {r0, lr}

     // ARM_HardFloat  || ARM_SoftFP
#if defined(__ARM_PCS_VFP) || (defined(__ARM_PCS) && !defined(__SOFTFP__))
    vpush {d8-d15}
    // now switch over to the new stack.
    // Need to subtract (8*8[d8-d15]+2*4[r0, lr]) to position stack pointer
    // below the last saved register. Remember we saved the SP before pushing
    // [r0, lr, d8-d15].
    sub sp, r1, #72
    vpop {d8-d15}
#else
    sub sp, r1, #8
#endif

    // we don't really care about r0, we only used that for padding.
    // r1 is now what used to be in the link register when saving.
    pop {r0, r1, r4-r11}
    /**
     * The link register for the initial jump to fiber_entryPoint must be zero:
     * The jump actually looks like a normal method call as we jump to the
     * start of the fiber_entryPoint function. Although fiber_entryPoint never
     * returns and therefore never accesses lr, it saves lr to the stack.
     * ARM unwinding will then look at the stack, find lr and think that
     * fiber_entryPoint was called by the function in lr! So if we have some
     * address in lr the unwinder will try to continue stack unwinding,
     * although it's already at the stack base and crash.
     * In all other cases the content of lr doesn't matter.
     * Note: If we simply loaded into lr above and then moved lr into pc, the
     * initial method call to fiber_entryPoint would look as if it was called
     * from fiber_entryPoint itself, as the fiber_entryPoint address is in lr
     * on the initial context switch.
     */
    mov lr, #0
    // return by writing lr into pc
    mov pc, r1
    .fnend
    .cfi_endproc
    .size CSYM(fiber_switchContext),.-CSYM(fiber_switchContext)

#endif
