// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

#define cfi_adjust_cfa_offset(off)      .cfi_adjust_cfa_offset off
#define cfi_rel_offset(reg, off)        .cfi_rel_offset reg, off
#define cfi_restore(reg)                .cfi_restore reg
#define cfi_def_cfa_register(reg)       .cfi_def_cfa_register reg

////////////////////////////////////////////////////////////////////////////////
// MCC_C2NStub simply forwards arguments passed by runtime, i.e., arguments for compiled method are passed
// according to C/C++ calling convention, which usually means efficiency.
////////////////////////////////////////////////////////////////////////////////

#define StubFrameContextSize          (4 * 30 - 4 * 2)
#define StubCalleeSaveAreaSize        (4 * 6)
#define FuncAddrAndCpStacksizeOffset  (4 * 4)
#define SafeStateOffset               (4 * 2)

// R means runtime, while C means compiled method. XX indicates the return type of this method.

// On execution of "bl MCC_C2NStub", the frame layout of stack(growing downwards) looks like:
// x0~x7: hold the first 8 arguments arg0~arg7 if existed
// x30: return address of "bl MCC_C2NStub"
// callee funcAddr and cpStackSize are saved on the stack
// all on-stack arguments are addressable by SP as the frame layout shows.
//                 | ...          |
//                 | lr           | lr for the caller of MCC_C2NStub
// caller fp  -->  | r11          |
//                 | ...          |
//                 | arg11        |
//                 | arg10        |
//                 | arg9         |
//                 | arg8         |
//                 | cpStackSize  |
// caller sp  -->  | funcAddr     |

// the frame layout of stack(growing downwards) after MCC_C2NStub frame is built looks like:
//                 | ...          |
//                 | lr           | lr for the caller of MCC_C2NStub
// caller fp  -->  | r11          |
//                 | ...          |
//                 | arg11        |
//                 | arg10        |
//                 | arg9         |
// caller sp  -->  | arg8         |
// callee saved    | d7(high)     | <== MCC_C2NStub frame starts from here
//                 | d7(low)      |
//                 | d6(high)     |
//                 | d6(low)      |
//                 | d5(high)     |
//                 | d5(low)      |
//                 | d4(high)     |
//                 | d4(low)      |
//                 | d3(high)     |
//                 | d3(low)      |
//                 | d2(high)     |
//                 | d2(low)      |
//                 | d1(high)     |
//                 | d1(low)      |
//                 | d0(high)     |
//                 | d0(low)      |
//                 | null         |
//                 | r10          |
//                 | r9           |
//                 | r8           |
//                 | r7           |
//                 | r6           |
//                 | r5           |
// callee saved    | r4           |
//                 | cpStackSize  |
//                 | calleeAddr   |
//                 | null         |
//                 | entersafe    |
//                 | lr           |
//   stub fp  -->  | r11 callerfp |
//                 | ...          | <== copy caller Stack start here
//                 | arg11        |
//                 | arg10        |
//                 | arg9         |
//   stub sp  -->  | arg8         | <== MCC_C2NStub frame ends at here

#if defined(ENABLE_BACKWARD_PTRAUTH_CFI)
    .global C2NStubFrameSize
C2NStubFrameSize:
    .quad StubFrameContextSize
#endif

    .text
    .align 2
    .global CJ_MCC_C2NStub
    .type CJ_MCC_C2NStub, %function
CJ_MCC_C2NStub:
    .cfi_startproc
    // actual stack size is StubFrameContextSize + 8
    // 8 means the size has been pre allocated: calleeAddr and cpStackSize
    sub  sp, sp, #StubFrameContextSize

    // save calleeAddr and cpStackSize
    ldr  r12, [sp, #StubFrameContextSize]  // calleeAddr
    str  r12, [sp, #FuncAddrAndCpStacksizeOffset]
    ldr  r12, [sp, #StubFrameContextSize+4]  // cpStackSize
    str  r12, [sp, #FuncAddrAndCpStacksizeOffset+4]

    str  r11, [sp]
    str  lr, [sp, #4]
    cfi_adjust_cfa_offset (StubFrameContextSize)
    cfi_rel_offset (r11, 0)
    cfi_rel_offset (lr, 4)

    mov  r11, sp
    cfi_def_cfa_register (r11)

    // save all used callee-saved registers
    str  r4, [sp, #StubCalleeSaveAreaSize]
    str  r5, [sp, #StubCalleeSaveAreaSize+4]
    str  r6, [sp, #StubCalleeSaveAreaSize+8]
    str  r7, [sp, #StubCalleeSaveAreaSize+12]
    str  r8, [sp, #StubCalleeSaveAreaSize+16]
    str  r9, [sp, #StubCalleeSaveAreaSize+20]
    str  r10, [sp, #StubCalleeSaveAreaSize+24]
    cfi_rel_offset (r4, StubCalleeSaveAreaSize)
    cfi_rel_offset (r5, StubCalleeSaveAreaSize+4)
    cfi_rel_offset (r6, StubCalleeSaveAreaSize+8)
    cfi_rel_offset (r7, StubCalleeSaveAreaSize+12)
    cfi_rel_offset (r8, StubCalleeSaveAreaSize+16)
    cfi_rel_offset (r9, StubCalleeSaveAreaSize+20)
    cfi_rel_offset (r10, StubCalleeSaveAreaSize+24)

    // save double arg registers
    vstr  d0, [sp, #StubCalleeSaveAreaSize+32]
    vstr  d1, [sp, #StubCalleeSaveAreaSize+40]
    vstr  d2, [sp, #StubCalleeSaveAreaSize+48]
    vstr  d3, [sp, #StubCalleeSaveAreaSize+56]
    vstr  d4, [sp, #StubCalleeSaveAreaSize+64]
    vstr  d5, [sp, #StubCalleeSaveAreaSize+72]
    vstr  d6, [sp, #StubCalleeSaveAreaSize+80]
    vstr  d7, [sp, #StubCalleeSaveAreaSize+88]
    cfi_rel_offset (d0, StubCalleeSaveAreaSize+32)
    cfi_rel_offset (d1, StubCalleeSaveAreaSize+40)
    cfi_rel_offset (d2, StubCalleeSaveAreaSize+48)
    cfi_rel_offset (d3, StubCalleeSaveAreaSize+56)
    cfi_rel_offset (d4, StubCalleeSaveAreaSize+64)
    cfi_rel_offset (d5, StubCalleeSaveAreaSize+72)
    cfi_rel_offset (d6, StubCalleeSaveAreaSize+80)
    cfi_rel_offset (d7, StubCalleeSaveAreaSize+88)

    mov  r5, r0
    mov  r6, r1
    mov  r7, r2
    mov  r8, r3
    ldr  r4, [sp, #FuncAddrAndCpStacksizeOffset+4]  // cpStackSize

    bl CJ_CJThreadStackGuardGet
    add  r0, r4, r0
    mov  r1, sp
    cmp  r1, r0
    bls  .L_stack_check_fail

    bl   MRT_GetThreadLocalData
    mov  r2, r0

    // r10 <- previous sp
    add  r10, sp, #StubFrameContextSize
    add  r10, r10, #8  // cpStakSize slot + calleeAddr slot

    // r4 <- previous sp + cpStackSize
    add  r4, r10, r4

    // save last managed context
    mov  r1, r11
    adr  r0, .L_current_pc
.L_current_pc:
    .global unwindPCForC2NStub
unwindPCForC2NStub:

    bl  MRT_C2N_Enter                // merged: SaveC2NContext + EnterSaferegion
    str  r0, [sp, #SafeStateOffset]
#ifdef __OHOS__
    mov  r0, sp
    bl   MRT_UpdateStackTop        // merged: GetUIThreadStackTop + PushUIThreadStackTop
    str  r0, [sp, #SafeStateOffset+0x4]
#endif
    
    vldr  d0, [sp, #StubCalleeSaveAreaSize+32]
    vldr  d1, [sp, #StubCalleeSaveAreaSize+40]
    vldr  d2, [sp, #StubCalleeSaveAreaSize+48]
    vldr  d3, [sp, #StubCalleeSaveAreaSize+56]
    vldr  d4, [sp, #StubCalleeSaveAreaSize+64]
    vldr  d5, [sp, #StubCalleeSaveAreaSize+72]
    vldr  d6, [sp, #StubCalleeSaveAreaSize+80]
    vldr  d7, [sp, #StubCalleeSaveAreaSize+88]
    cfi_restore (d0)
    cfi_restore (d1)
    cfi_restore (d2)
    cfi_restore (d3)
    cfi_restore (d4)
    cfi_restore (d5)
    cfi_restore (d6)
    cfi_restore (d7)

#ifdef __OHOS__
    cmp  r0, #0
    beq  .L_copy
    mov  sp, r0
#endif
    // copy arg8, arg9, arg10, ... (if existed)
.L_copy:
    cmp  r4, r10
    bls  .L_copy_end
    sub  r4, r4, #8
    ldr  r1, [r4]
    ldr  r2, [r4, #4]
    // SP is always 8 byte-aligned.
    sub  sp, sp, #8
    str  r1, [sp]
    str  r2, [sp, #4]
    b .L_copy
.L_copy_end:

    // prepare arguments for invoking target method
    mov  r0, r5
    mov  r1, r6
    mov  r2, r7
    mov  r3, r8
    ldr  r9, [r11, #FuncAddrAndCpStacksizeOffset]  // calleeAddr
    blx  r9

    // keep potential return value
    mov  r6, r0
    mov  r7, r1

    vstr  d0, [r11, #StubCalleeSaveAreaSize+32]
    vstr  d1, [r11, #StubCalleeSaveAreaSize+40]
    cfi_rel_offset(d0, StubCalleeSaveAreaSize+32)
    cfi_rel_offset(d1, StubCalleeSaveAreaSize+40)

    // restoring the SP Value. the stack which extended for invoking c method is useless now.
    mov  sp, r11
    cfi_def_cfa_register (sp)
    // Call merged leave function: (PopUIThreadStackTop) + LeaveSaferegion + DeleteC2NContext + ThrowPendingException
    ldr  r0, [r11, #SafeStateOffset]
    ldr  r1, [r11, #SafeStateOffset+4]
    bl   MRT_C2N_Leave

#if defined(ENABLE_BACKWARD_PTRAUTH_CFI)
    .global unwindPCForC2NStubExceptionReturn
unwindPCForC2NStubExceptionReturn:
#endif

    /* set potential return value */
    mov  r0, r6
    mov  r1, r7

    vldr  d0, [sp, #StubCalleeSaveAreaSize+32]
    vldr  d1, [sp, #StubCalleeSaveAreaSize+40]
    cfi_restore (d0)
    cfi_restore (d1)

.L_stack_restore:
    // restore all used callee-saved registers
    ldr  r4, [sp, #StubCalleeSaveAreaSize]
    ldr  r5, [sp, #StubCalleeSaveAreaSize+4]
    ldr  r6, [sp, #StubCalleeSaveAreaSize+8]
    ldr  r7, [sp, #StubCalleeSaveAreaSize+12]
    ldr  r8, [sp, #StubCalleeSaveAreaSize+16]
    ldr  r9, [sp, #StubCalleeSaveAreaSize+20]
    ldr  r10, [sp, #StubCalleeSaveAreaSize+24]
    cfi_restore (r4)
    cfi_restore (r5)
    cfi_restore (r6)
    cfi_restore (r7)
    cfi_restore (r8)
    cfi_restore (r9)
    cfi_restore (r10)

    ldr  r11, [sp]
    ldr  lr, [sp, 4]
    add  sp, sp, #StubFrameContextSize
    cfi_adjust_cfa_offset (-StubFrameContextSize)
    cfi_restore (r11)
    cfi_restore (lr)
    // restore for calleeAddr and cpStackSize slot in stack
    add  sp, sp, #8
    cfi_adjust_cfa_offset (-8)
    bx lr

.L_stack_check_fail:
    bl  MCC_ThrowStackOverflowError
    b   .L_stack_restore
    .cfi_endproc
    .size CJ_MCC_C2NStub, .-CJ_MCC_C2NStub