;; Licensed to the .NET Foundation under one or more agreements. ;; The .NET Foundation licenses this file to you under the MIT license. ;; See the LICENSE file in the project root for more information. #include "AsmMacros.h" #ifdef _DEBUG #define TRASH_SAVED_ARGUMENT_REGISTERS #endif #ifdef TRASH_SAVED_ARGUMENT_REGISTERS EXTERN RhpIntegerTrashValues EXTERN RhpFpTrashValues #endif ;; TRASH_SAVED_ARGUMENT_REGISTERS #define COUNT_ARG_REGISTERS (4) #define INTEGER_REGISTER_SIZE (4) #define ARGUMENT_REGISTERS_SIZE (COUNT_ARG_REGISTERS * INTEGER_REGISTER_SIZE) ;; Largest return block is 4 doubles #define RETURN_BLOCK_SIZE (32) #define COUNT_FLOAT_ARG_REGISTERS (8) #define FLOAT_REGISTER_SIZE (8) #define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE) #define PUSHED_LR_SIZE (4) #define PUSHED_R11_SIZE (4) ;; ;; From CallerSP to ChildSP, the stack frame is composed of the following adjacent regions: ;; ;; ARGUMENT_REGISTERS_SIZE ;; RETURN_BLOCK_SIZE ;; FLOAT_ARG_REGISTERS_SIZE ;; PUSHED_LR ;; PUSHED_R11 ;; #define DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK (PUSHED_R11_SIZE + PUSHED_LR_SIZE + FLOAT_ARG_REGISTERS_SIZE) TEXTAREA ;; ;; RhpUniversalTransition ;; ;; At input to this function, r0-3, d0-7 and the stack may contain any number of arguments. ;; ;; In addition, there are 2 extra arguments passed in the RED ZONE (8 byte negative space ;; off of sp). ;; sp-4 will contain the managed function that is to be called by this transition function ;; sp-8 will contain the pointer sized extra argument to the managed function ;; ;; When invoking the callee: ;; ;; r0 shall contain a pointer to the TransitionBlock ;; r1 shall contain the value that was in sp-8 at entry to this function ;; ;; Frame layout is: ;; ;; {StackPassedArgs} ChildSP+078 CallerSP+000 ;; {IntArgRegs (r0-r3) (0x10 bytes)} ChildSP+068 CallerSP-010 ;; {ReturnBlock (0x20 bytes)} ChildSP+048 CallerSP-030 ;; -- The base address of the Return block is the TransitionBlock pointer, the floating point args are ;; in the neg space of the TransitionBlock pointer. Note that the callee has knowledge of the exact ;; layout of all pieces of the frame that lie at or above the pushed floating point registers. ;; {FpArgRegs (d0-d7) (0x40 bytes)} ChildSP+008 CallerSP-070 ;; {PushedLR} ChildSP+004 CallerSP-074 ;; {PushedR11} ChildSP+000 CallerSP-078 ;; ;; NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure ;; must be updated as well. ;; ;; NOTE: The callee receives a pointer to the base of the ReturnBlock, and the callee has ;; knowledge of the exact layout of all pieces of the frame that lie at or above the pushed ;; FpArgRegs. ;; ;; NOTE: The stack walker guarantees that conservative GC reporting will be applied to ;; everything between the base of the ReturnBlock and the top of the StackPassedArgs. ;; MACRO UNIVERSAL_TRANSITION $FunctionName NESTED_ENTRY Rhp$FunctionName ;; Save argument registers (including floating point) and the return address. ;; NOTE: While we do that, capture the two arguments in the red zone into r12 and r3. PROLOG_NOP ldr r12, [sp, #-4] ; Capture first argument from red zone into r12 PROLOG_PUSH {r3} ; Push r3 PROLOG_NOP ldr r3, [sp, #-4] ; Capture second argument from red zone into r3 PROLOG_PUSH {r0-r2} ; Push the rest of the registers PROLOG_STACK_ALLOC RETURN_BLOCK_SIZE ; Save space a buffer to be used to hold return buffer data. PROLOG_VPUSH {d0-d7} ; Capture the floating point argument registers PROLOG_PUSH {r11,lr} ; Save caller's frame chain pointer and PC ;; Setup the arguments to the transition thunk. mov r1, r3 #ifdef TRASH_SAVED_ARGUMENT_REGISTERS ;; Before calling out, trash all of the argument registers except the ones (r0, r1) that ;; hold outgoing arguments. All of these registers have been saved to the transition ;; frame, and the code at the call target is required to use only the transition frame ;; copies when dispatching this call to the eventual callee. ldr r3, =RhpFpTrashValues vldr d0, [r3, #(0 * 8)] vldr d1, [r3, #(1 * 8)] vldr d2, [r3, #(2 * 8)] vldr d3, [r3, #(3 * 8)] vldr d4, [r3, #(4 * 8)] vldr d5, [r3, #(5 * 8)] vldr d6, [r3, #(6 * 8)] vldr d7, [r3, #(7 * 8)] ldr r3, =RhpIntegerTrashValues ldr r2, [r3, #(2 * 4)] ldr r3, [r3, #(3 * 4)] #endif // TRASH_SAVED_ARGUMENT_REGISTERS ;; Make the ReturnFromUniversalTransition alternate entry 4 byte aligned ALIGN 4 add r0, sp, #DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK ;; First parameter to target function is a pointer to the return block blx r12 EXPORT_POINTER_TO_ADDRESS PointerToReturnFrom$FunctionName ; We cannot make the label public as that tricks DIA stackwalker into thinking ; it's the beginning of a method. For this reason we export an auxiliary variable ; holding the address instead. ;; Move the result (the target address) to r12 so it doesn't get overridden when we restore the ;; argument registers. Additionally make sure the thumb2 bit is set. orr r12, r0, #1 ;; Restore caller's frame chain pointer and PC. EPILOG_POP {r11,lr} ;; Restore the argument registers. EPILOG_VPOP {d0-d7} EPILOG_STACK_FREE RETURN_BLOCK_SIZE ; pop return block conservatively reported area EPILOG_POP {r0-r3} ;; Tailcall to the target address. EPILOG_BRANCH_REG r12 NESTED_END Rhp$FunctionName MEND ; To enable proper step-in behavior in the debugger, we need to have two instances ; of the thunk. For the first one, the debugger steps into the call in the function, ; for the other, it steps over it. UNIVERSAL_TRANSITION UniversalTransition UNIVERSAL_TRANSITION UniversalTransition_DebugStepTailCall END