;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  Microsoft Research Singularity
;;
;;  Copyright (c) Microsoft Corporation.  All rights reserved.
;;
;;  File:   halstack.asm
;;
;;  Note:
;;


include hal.inc
        
;; The code in this file runs as part of the priveleged runtime.  It is responsible for
;; calling into the ABI to allocate and free new stack segments, and to do all the low
;; level mucking to stitch the stacks together.
        

;; LinkStack is a glue routine to extend the stack.  Its job is to:
;; 
;;  1 - preserve the calling registers of the function it is being called from
;;  2 - set up arguments and call the appropriate ABI call to grow the stack
;;  3 - copy args and stack frame and switch execution to the resulting new stack segment
;;  4 - tail patch the function return so that Free is called on return
;; 
;; Its entry point calling conventions are delicate; essentially
;;      - rax contains the amount of new stack needed
;;      - all argument registers must be preserved
;;      - two values - (# of bytes) and (Unlink) are passed on the stack (from the stubs below)
;;
;; BUGBUG: This code doesn't maintain the required 16 byte alignment when running on 64-bit platforms
;;         This is not being fixed at the moment, since we're expecting to deprecate this code.

LinkFrame       struct
ifdef ISA_IX64
        _r9              dp ?    ; saved in LinkStack prolog
        _r8              dp ?    ; saved in LinkStack prolog
endif
        _dx              dp ?    ; saved in LinkStack prolog
        _cx              dp ?    ; saved in LinkStack prolog
        _alloc           dp ?    ; saved in LinkStack prolog from ax (set by grower prolog)
        _UnlinkFn        dp ?    ; pushed by LinkStackN stub
        _argStack        dp ?    ; pushed by LinkStackN stub
        ;; Continue address
        _continue        dp ?    ; pushed by call of LinkStack function
        ;; EBP frame
        _oldBp           dp ?    ; pushed by grower prolog
        ;; Return address
        _return          dp ?    ; pushed by call of grower
        ;; Stack Arguments (argStack*sizeof PWORD)
LinkFrame       ends
        
;
;  __checkStackOverflow
;
;  This function takes the bottom of the current frame in eax.  If we
;  detect a stack overflow, we make ourselves a real stack frame (for
;  debugging convenience) and then break.
;
; BUGBUG: This code doesn't maintain the required 16 byte alignment when running on 64-bit platforms
;         This is not being fixed at the moment, since we're expecting to deprecate this code.

        align 16
__checkStackOverflow proc
        push    PDX
        GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
        cmp     PAX, PDX
        jb      overflowHandler
        pop     PDX
        ret
overflowHandler:
        int     3
        pop     PDX
        ret
__checkStackOverflow endp


?c_LinkStackFunctionsBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

?c_LinkStackBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

;;; NOTE: If you change this function, please ensure that the necessary
;;; changes are made to CallStack.SkipLinkStackFrame

        align 16

LinkStack proc
        ;; Save allocation amount
        push    PAX
        
        ;; Save argument registers
        push    PCX
        push    PDX
ifdef ISA_IX64
        push    r8
        push    r9
endif

        ;; Call stack allocation ABI - this will update the current thread's stackBegin/Limit
        mov     PCX, [PSP].LinkFrame._alloc
        call    SYMFIX(?g_AllocateStackSegment@Struct_Microsoft_Singularity_V1_Services_StackService@@SIPAUuintPtr@@PAU2@@Z)

        ;; Switch over to new stack (keep old stack in PDX)
        mov     PDX, PSP
        mov     PSP, PAX

        GET_THREAD_CONTEXT_FIELD PAX, _stackLimit
        SET_THREAD_RECORD_FIELD _activeStackLimit, PAX

        ;; Now build up the copied/hijacked stack frame for the grower to continue on

        ;; Copy arguments to new stack
        mov     PCX, [PDX].LinkFrame._argStack
        jecxz   noargs
        lea     PAX, [PDX] + SIZEOF LinkFrame - SIZEOF PWORD
next:   
        push    [PAX+PCX*4]
        loop    next
noargs:
        
        ;; Push hijacked return address.  This will make the function return to our UnlinkStack routine
        ;; rather than its real return address.
        push    [PDX].LinkFrame._UnlinkFn

        ;; Push old ebp frame
        ;; Note that the exception unwind code expects this ebp frame to point to grower's original ebp frame.
        push    PBP

        ;; Reset ebp to the new stack segment.
        mov     PBP, PSP

        ;; Get continue address
        mov     PAX, [PDX].LinkFrame._continue

        ;; Restore arg registers
ifdef ISA_IX64
        mov     r8, [PDX].LinkFrame._r8
        mov     r9, [PDX].LinkFrame._r9
endif
        mov     PCX, [PDX].LinkFrame._cx
        mov     PDX, [PDX].LinkFrame._dx

        ;; Return.  This will resume execution in the prolog of the function which called us.  However
        ;; we are on the new stack segment, and the function has been tail patched so it will call back to
        ;; UnlinkStack when it returns.
        jmp     PAX
        
LinkStack endp

?c_LinkStackLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

?c_UnlinkStackBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

;; UnlinkStack is essentially a continuation of LinkStack; it is called from the hijacked return
;; pushed by LinkStack.  Because of the unlink stub, we are on the old stack; however we have
;; some cleanup to do.
;; 1. We must call Free on the stack we allocated before, and restore the thread's stack limits.
;; 2. We must update the activeStackLimit on the thread.

;; The stack state on entry to UnlinkStack is:
;; 1. We are back on the old stack
;; 2. Everything from the grower has been popped except its return address & args
;; 3. We have been called by the UnlinkStackN stub; it will execute the return for us

;;; NOTE: If you change this function, please ensure that the necessary
;;; changes are made to CallStack.SkipUnlinkStackFrame

        align 16

UnlinkStack proc

        ;; Save return value registers
        push    PDX
        push    PAX

        ;; HACK: set limit to zero to temporarily disable stack growth
        ;; We can get rid of this if we set activeStackLimit in asm code:
        ;;   ThreadContext *context = Isa.GetCurrentThread();
        ;;   StackHead *head = (StackHead *) (context->stackBegin - sizeof(StackHead));
        ;;   Isa.StackLimit = head->prevLimit;
        xor     PAX, PAX
        SET_THREAD_RECORD_FIELD _activeStackLimit, PAX
        
        ;; Call Free routine
        call    SYMFIX(?g_FreeStackSegment@Struct_Microsoft_Singularity_V1_Services_StackService@@SIXXZ)

        ;; Set active stack limit
        GET_THREAD_CONTEXT_FIELD PAX, _stackLimit
        SET_THREAD_RECORD_FIELD _activeStackLimit, PAX

        POP     PAX
        POP     PDX

        ;; Return to UnlinkStackN stub, which will execute ret N
        ret

UnlinkStack endp

?c_UnlinkStackLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

LINKNAME MACRO SIZE:REQ
        EXITM SYMFIX(?g_LinkStack&SIZE&@Class_Microsoft_Singularity_Memory_Stacks@@SIXXZ)
        ENDM
        
UNLINKNAME MACRO SIZE:REQ
        EXITM SYMFIX(?g_UnlinkStack&SIZE&@Class_Microsoft_Singularity_Memory_Stacks@@SIXXZ)
        ENDM
        
;; Implementations of LinkStackNn: push the appropriate number
;; and call the general-purpose LinkStack.

ifdef ISA_IX64        

; x64 does not have a PUSH OFFSET64 instruction, so there are two possible solutions.
;
; (1) LEA into a register and then push it
; (2) Use PUSH reg/mem64 (i.e. FF /6)
;
; Since stack link routines cannot polute any registers, (1) would require save/restore
; overhead as well, making (2) the more desirable solution.

;;; NOTE: If you change these function, please ensure that the necessary
;;; changes are made to CallStack.SkipLinkStackStubFrame

LINKSTACKSTUB MACRO SLOTS:REQ
        LOCAL unlink, uaddr
        align 16
LINKNAME(%SLOTS*8):
        push    SLOTS
        push    [uaddr]
        jmp     LinkStack
uaddr   dq      unlink         
unlink:
        mov     rsp, rbp
        pop     rbp
        call    UnlinkStack
        ret     SLOTS*8
        ENDM
        
elseifdef ISA_IX86
        
LINKSTACKSTUB MACRO SLOTS:REQ
        LOCAL unlink
        align 8
LINKNAME(%SLOTS*4):
        push    SLOTS           
        push    unlink
        jmp     LinkStack
unlink:
        mov     esp, ebp
        pop     ebp
        call    UnlinkStack
        ret     SLOTS*4
        ENDM

endif        

?c_LinkStackStubsBegin@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

        LINKSTACKSTUB 0        
        LINKSTACKSTUB 1        
        LINKSTACKSTUB 2        
        LINKSTACKSTUB 3        
        LINKSTACKSTUB 4        
        LINKSTACKSTUB 5        
        LINKSTACKSTUB 6        
        LINKSTACKSTUB 7
        LINKSTACKSTUB 8     
        LINKSTACKSTUB 9        
        LINKSTACKSTUB 10        
        LINKSTACKSTUB 11        
        LINKSTACKSTUB 12        
        LINKSTACKSTUB 13        
        LINKSTACKSTUB 14        
        LINKSTACKSTUB 15        
        LINKSTACKSTUB 16
        LINKSTACKSTUB 17        
        LINKSTACKSTUB 18        
        LINKSTACKSTUB 19        
        LINKSTACKSTUB 20        
        LINKSTACKSTUB 21        
        LINKSTACKSTUB 22        
        LINKSTACKSTUB 23        
        LINKSTACKSTUB 24        
        LINKSTACKSTUB 25        
        LINKSTACKSTUB 26        
        LINKSTACKSTUB 27        
        LINKSTACKSTUB 28        
        LINKSTACKSTUB 29        
        LINKSTACKSTUB 30        
        LINKSTACKSTUB 31        
        LINKSTACKSTUB 32        

?c_LinkStackStubsLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

;; CheckStackLimit is the routine that Bartok-generated code calls to check for
;; stack growth

align 16
PUBLIC  __checkStackLimit
__checkStackLimit       PROC
        ;; Note: expected stack growth limit is in PAX

        ;; Bartok doesn't allow quite enough overhead for the stack switch
        ;; now that it occurs in managed code.  So, we will pretend bartok
        ;; asked for more stack until it can be updated.  Note that this
        ;; is not quite sufficient; there are cases where bartok will not
        ;; even probe which we cannot alter.
        sub     eax, 32*sizeof(PWORD)
        
        ;; Free up a register
        push    PDX

ifdef PARANOID_CHECKS

        ;; Free up another register
        push    PCX
        
        ;; See if we are on the interrupt stack
        GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
        GET_CPU_RECORD_FIELD PCX, _interruptStackLimit
        cmp     PCX, PDX
        jne     not_interrupt_stack

        ;; Check for stack underflow
        GET_CPU_RECORD_FIELD PCX, _interruptStackBegin
        cmp     PCX, 0          ; kernel startup?
        je      stack_ok
        cmp     PSP, PCX        ; underflow?
        jl      stack_ok
        int     3

        ;; (Note: we will check for overflow below in the normal code)

not_interrupt_stack:
        GET_THREAD_CONTEXT_FIELD PCX, _stackLimit
        cmp     PCX, PDX
        jne     not_thread_stack

        ;; Check for stack underflow
        GET_THREAD_CONTEXT_FIELD PCX, _stackBegin
        cmp     PCX, 0          ; kernel startup?
        je      stack_ok
        cmp     PSP, PCX        ; underflow?
        jl      stack_ok
        int     3
        
not_thread_stack:
        ;; Check for kernel startup case
        cmp     PCX, 0
        je      stack_ok
        ;; Check for disabled stack limit checks case
        cmp     PDX, 0
        je      stack_ok
        ;; activeStackLimit appears to be bogus
        int     3

stack_ok:

        pop     PCX

endif ; PARANOID_CHECKS

        GET_THREAD_RECORD_FIELD PDX, _activeStackLimit
        cmp     PAX, PDX
        jb      grow
        pop     PDX
        ret

grow:
        ;; Since this is a slow path, always do some extra checks here.

        ;; Check for stack overflow
        cmp     PSP, PDX
        jg      no_overflow
        int     3

no_overflow: 
        ;; Now check to see if we are on the interrupt stack; we shouldn't be growing.
        GET_CPU_RECORD_FIELD PAX, _interruptStackLimit
        cmp     PDX, PAX
        jne     not_interrupt
        int     3

not_interrupt:  
        pop     PDX
        ;; this sets the status bit checked by "jb" above; our caller will be checking this
        stc                     
        ret

    __checkStackLimit       ENDP

?c_LinkStackFunctionsLimit@Class_Microsoft_Singularity_Memory_Stacks@@2EA byte 0

end
