/*
            Copyright Oliver Kowalke 2009.
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENSE_1_0.txt or copy at
            http://www.boost.org/LICENSE_1_0.txt)
*/

/*
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/* modified by ruki
 *
 * - modify stack layout
 * - fix some bugs on macosx i386
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */

/**
 * translated from x86/context.asm by Alvin
 *
 * - support mingw/cygwin.. on windows
 */
#if defined(_WIN32)

function(tb_context_make)

    // save the stack top to eax
    movl 4(%esp), %eax
    addl 8(%esp), %eax
    movl %eax, %ecx

    // reserve space for first argument(from) and seh item of context-function
    leal -20(%eax), %eax

    // 16-align of the stack top address
    andl $-16, %eax

    // reserve space for context-data on context-stack
    leal -40(%eax), %eax

    // save top address of context stack as 'base'
    movl %ecx, 12(%eax)

    // save bottom address of context-stack as 'limit'
    movl 4(%esp), %ecx
    movl %ecx, 8(%eax)

    // save bottom address of context-stack as 'dealloction stack'
    movl %ecx, 4(%eax)

    // set fiber-storage as zero
    xorl %ecx, %ecx
    movl %ecx, (%eax)

    // context.ebx = func
    movl 12(%esp), %ecx
    movl %ecx, 28(%eax)

    // context.eip = __entry
    movl $__entry, %ecx
    movl %ecx, 36(%eax)

    // context.ebp = the address of label __end
    movl $__end, %ecx
    movl %ecx, 32(%eax)

    // load the current seh chain from TIB
    //assume fs:nothing
    movl %fs:(0x0), %ecx
    //assume fs:error

__walkchain:

    // if (sehitem.prev == 0xffffffff) (last?) goto __found
    movl (%ecx), %edx
    incl %edx
    jz __found

    // sehitem = sehitem.prev
    decl %edx
    xchgl %ecx, %edx
    jmp __walkchain

__found:
    
    // context.seh.handler = sehitem.handler
    movl 4(%ecx), %ecx
    movl %ecx, 60(%eax) 

    // context.seh.prev = 0xffffffff
    movl $0xffffffff, %ecx
    movl %ecx, 56(%eax)

    // context.seh = the address of context.seh.prev
    leal 56(%eax), %ecx
    movl %ecx, 16(%eax)

    // return pointer to context-data
    ret 

__entry:

    // pass old-context(context: eax, priv: edx) arguments to the context function
    movl %eax, (%esp)
    movl %edx, 4(%esp)

    // patch return address: __end
    pushl %ebp

    // jump to the context function entry(eip)
    jmp *%ebx

__end:

    // exit(0)
    xorl %eax, %eax
    movl %eax, (%esp)
    call __exit
    hlt

endfunc


function(tb_context_jump)

    // save registers and construct the current context
    pushl %ebp 
    pushl %ebx
    pushl %esi
    pushl %edi

    // load TIB to edx
    //assume fs:nothing
    movl %fs:(0x18), %edx
    //assume fs:error

    // load and save current seh exception list
    movl (%edx), %eax
    pushl %eax

    // load and save current stack base
    movl 0x04(%edx), %eax
    pushl %eax

    // load and save current stack limit
    movl 0x08(%edx), %eax
    pushl %eax

    // load and save current deallocation stack
    movl 0xe0c(%edx), %eax
    pushl %eax

    // load and save fiber local storage
    movl 0x10(%edx), %eax
    pushl %eax

    // save the old context(esp) to eax
    movl %esp, %eax

    // switch to the new context(esp) and stack
    movl 40(%esp), %ecx
    movl %ecx, %esp

    // load TIB to edx
    //assume fs:nothing
    movl %fs:(0x18), %edx
    //assume fs:error

    // restore fiber local storage (context.fiber)
    popl %ecx
    movl %ecx, 0x10(%edx)

    // restore current deallocation stack (context.dealloc)
    popl %ecx
    movl %ecx, 0xe0c(%edx)

    // restore current stack limit (context.limit)
    popl %ecx
    movl %ecx, 0x08(%edx)

    // restore current stack base (context.base)
    popl %ecx
    movl %ecx, 0x04(%edx)

    // restore current seh exception list (context.seh)
    popl %ecx
    movl %ecx, (%edx)

    // restore registers of the new context
    popl %edi 
    popl %esi
    popl %ebx 
    popl %ebp

    // restore the return or function address(ecx)
    popl %ecx

    // return from-context(context: eax, priv: edx) from jump 
    // edx = [eax + 44] = [esp_jump + 44] = jump.argument(priv)
    movl 44(%eax), %edx

    // jump to the return or function address(eip)
    jmp *%ecx

endfunc


//#if defined(TB_CONFIG_OS_MACOSX) || defined(TB_CONFIG_OS_IOS)
#elif defined(__APPLE__)

/* make context
 *
 *
 *             -----------------------------------------------------------------------------------------
 * stackdata: |                                                          |         context        |||||||
 *             -----------------------------------------------------------------------------------|-----
 *                                                                                       (16-align for macosx)
 *
 *
 *                                   func      __end   __entry      from
 *             --------------------------------------------------------------------------------
 * context:   |   edi   |   esi   |   ebx   |   ebp   |   eip   | context |  priv  |  padding  |
 *             --------------------------------------------------------------------------------
 *            0         4         8         12        16        20        24    arguments 
 *                                                              |         
 *                                                              | 16-align for macosx
 *                                                              |
 *                                                   esp when jump to function
 *
 * @param stackdata     the stack data (esp + 4)
 * @param stacksize     the stack size (esp + 8)
 * @param func          the entry function (esp + 12)
 *
 * @return              the context pointer (eax)
 */
function(tb_context_make)

    // save the stack top to eax
    movl 4(%esp), %eax
    addl 8(%esp), %eax

    // reserve space for first argument(from) of context-function
    leal -8(%eax), %eax

    // 16-align of the stack top address for macosx
    andl $-16, %eax

    // reserve space for context-data on context-stack 
    leal -20(%eax), %eax

    /* context.ebx = func
     *
     * @note ebp will be affected only when enter into the context function first
     */
    movl 12(%esp), %edx
    movl %edx, 8(%eax)

    // context.eip = the address of label __entry
    call 1f
1:  popl %ecx
    addl $__entry - 1b, %ecx
    movl %ecx, 16(%eax) 

    /* context.ebp = the address of label __end
     *
     * @note ebp will be affected only when enter into the context function first
     */
    call 2f
2:  popl %ecx
    addl $__end - 2b, %ecx
    movl %ecx, 12(%eax) 

    // return the context pointer
    ret

__entry:
    
    // pass old-arguments(context: eax, priv: edx) to the context function
    movl %eax, (%esp)
    movl %edx, 0x4(%esp)

    // patch __end, retval = the address of label __end
    pushl %ebp

    /* jump to the context function entry
     *
     *              patch __end
     *                  |
     *                  |        old-context
     *              ----|------------------------------------
     * context: .. | retval | context |   priv   |  padding  |  
     *              -----------------------------------------
     *             0        4     arguments 
     *             |        |
     *            esp    16-align
     *           (now)
     */
    jmp *%ebx

__end:
    // exit(0)
    xorl  %eax, %eax
    movl  %eax, (%esp)
#ifdef __ELF__
    call  _exit@PLT
#else
    call  __exit
#endif
    hlt

endfunc

/* jump context
 *
 * @param context       the to-context (esp + 4)
 * @param priv          the passed user private data (esp + 8)
 *
 * @return              the from-context (context: eax, priv: edx)
 */
function(tb_context_jump)

    // save registers and construct the current context
    pushl %ebp 
    pushl %ebx 
    pushl %esi 
    pushl %edi 

    // save the old context(esp) to eax
    movl %esp, %eax

    // ecx = argument(context)
    movl 20(%esp), %ecx

    // edx = argument(priv)
    movl 24(%esp), %edx

    // switch to the new context(esp) and stack
    movl %ecx, %esp

    // restore registers of the new context
    popl %edi 
    popl %esi
    popl %ebx
    popl %ebp

    // restore the return or function address(ecx)
    popl %ecx

    // return from-context(context: eax, priv: edx) from jump 
    // ...

    /* jump to the return or function address(eip)
     *
     *
     *                 old-context
     *              --------------------------------
     * context: .. | context |   priv   |  padding  |
     *              --------------------------------
     *             0         4    arguments 
     *             |         
     *            esp  16-align for macosx
     *           (now)
     */
    jmp *%ecx

endfunc

#else

/* make context (refer to boost.context)
 *
 *
 *             -----------------------------------------------------------------------------------------
 * stackdata: |                                                          |         context        |||||||
 *             -----------------------------------------------------------------------------------|-----
 *                                                                                           (16-align)
 *
 *
 *                                    func      __end   __entry                  from
 *             -----------------------------------------------------------------------------------------
 * context:   |   edi   |   esi   |   ebx   |   ebp   |   eip   | retval | context |  priv  |  padding  |
 *             -----------------------------------------------------------------------------------------
 *            0         4         8         12        16        20        24    arguments 
 *                                                              |         |
 *                                                              |      16-align
 *                                                              |
 *                                                   esp when jump to function
 *
 * @param stackdata     the stack data (esp + 4)
 * @param stacksize     the stack size (esp + 8)
 * @param func          the entry function (esp + 12)
 *
 * @return              the context pointer (eax)
 */
function(tb_context_make)

    // save the stack top to eax
    movl 4(%esp), %eax
    addl 8(%esp), %eax

    // reserve space for first argument(from) of context-function
    leal -8(%eax), %eax

    // 16-align of the stack top address for macosx
    andl $-16, %eax

    // reserve space for context-data on context-stack 
    leal -24(%eax), %eax

    /* context.ebx = func
     *
     * @note ebp will be affected only when enter into the context function first
     */
    movl 12(%esp), %edx
    movl %edx, 8(%eax)

    /* init retval = a writeable space (context)
     *
     * it will write context.edi and context.esi (unused) when jump to a new context function entry first
     */
    movl %eax, 20(%eax) 

    // context.eip = the address of label __entry
    call 1f
1:  popl %ecx
    addl $__entry - 1b, %ecx
    movl %ecx, 16(%eax) 

    /* context.ebp = the address of label __end
     *
     * @note ebp will be affected only when enter into the context function first
     */
    call 2f
2:  popl %ecx
    addl $__end - 2b, %ecx
    movl %ecx, 12(%eax) 

    // return the context pointer
    ret

__entry:
    
    /* pass arguments(context: eax, priv: edx) to the context function
     *
     *              patch __end
     *                  |
     *                  |        old-context
     *              ----|------------------------------------
     * context: .. | retval | context |   priv   |  padding  |  
     *              -----------------------------------------
     *             0        4     arguments 
     *             |        |
     *            esp    16-align
     *           (now)
     */
    movl %eax, (%esp)
    movl %edx, 0x4(%esp)

    // retval = the address of label __end
    pushl %ebp

    /* jump to the context function entry
     *
     * @note need not adjust stack pointer(+4) using 'ret $4' when enter into function first
     */
    jmp *%ebx

__end:
    // exit(0)
    xorl  %eax, %eax
    movl  %eax, (%esp)
#ifdef __ELF__
    call  _exit@PLT
#else
    call  __exit
#endif
    hlt

endfunc

/* jump context (refer to boost.context)
 *
 * @code
 *
 * subl $4, %esp  <---- padding (need ret $4) 
 * pushl priv
 * pushl context 
 * pushl retval
 * call tb_context_jump
 * addl $12, %esp
 *
 * tb_context_jump():
 *     ret 4
 *
 * @endcode
 *
 * @param retval        the from-context (esp + 4)
 * @param context       the to-context (esp + 8)
 * @param priv          the passed user private data (esp + 12)
 *
 * @return              the from-context (retval (esp + 4))
 */
function(tb_context_jump)

    // save registers and construct the current context
    pushl %ebp 
    pushl %ebx 
    pushl %esi 
    pushl %edi 

    // save the old context(esp) to eax
    movl %esp, %eax

    // ecx = argument(context)
    movl 24(%esp), %ecx

    // edx = argument(priv)
    movl 28(%esp), %edx

    // switch to the new context(esp) and stack
    movl %ecx, %esp

    // restore registers of the new context
    popl %edi 
    popl %esi
    popl %ebx
    popl %ebp

    /* return from-context(retval: [to_esp + 4](context: eax, priv: edx)) from jump 
     *
     * it will write context.edi and context.esi (unused) when jump to a new context function entry first
     */
    movl 4(%esp), %ecx
    movl %eax, (%ecx)
    movl %edx, 4(%ecx)

    /* jump to the return or entry address(eip)
     *
     * @note need adjust stack pointer(+4) when return from tb_context_jump()
     *
     *                           old-context
     *              ---------------------------------------------------
     * context: .. |   eip   | retval | context |   priv   |  padding  |  
     *              ---------------------------------------------------
     *             0         4        8     arguments 
     *             |                  |
     *            esp              16-align
     *           (now)
     */
    ret $4

endfunc

#endif

