/**************************************************************************/
/*                                                                        */
/*                                 OCaml                                  */
/*                                                                        */
/*             Xavier Leroy, projet Cristal, INRIA Rocquencourt           */
/*                                                                        */
/*   Copyright 1996 Institut National de Recherche en Informatique et     */
/*     en Automatique.                                                    */
/*                                                                        */
/*   All rights reserved.  This file is distributed under the terms of    */
/*   the GNU Lesser General Public License version 2.1, with the          */
/*   special exception on linking described in the file LICENSE.          */
/*                                                                        */
/**************************************************************************/

/* Asm part of the runtime system, Intel 386 processor */
/* Must be preprocessed by cpp */

#include "caml/m.h"

/* Linux/BSD with ELF binaries and Solaris do not prefix identifiers with _.
   Linux/BSD with a.out binaries and NextStep do. */

#if (defined(SYS_solaris) && !defined(__GNUC__))
#define CONCAT(a,b) a/**/b
#else
#define CONCAT(a,b) a##b
#endif

#if defined(SYS_linux_elf) || defined(SYS_bsd_elf) \
 || defined(SYS_solaris) || defined(SYS_beos) || defined(SYS_gnu)
#define G(x) x
#define LBL(x) CONCAT(.L,x)
#else
#define G(x) CONCAT(_,x)
#define LBL(x) CONCAT(L,x)
#endif

#if defined(SYS_linux_elf) || defined(SYS_bsd_elf) \
 || defined(SYS_solaris) || defined(SYS_beos) || defined(SYS_cygwin) \
 || defined(SYS_mingw) || defined(SYS_gnu)
#define FUNCTION_ALIGN 4
#else
#define FUNCTION_ALIGN 2
#endif

#if defined(FUNCTION_SECTIONS)
#if defined(SYS_macosx) || defined(SYS_mingw) || defined(SYS_cygwin)
#define TEXT_SECTION(name)
#else
#define TEXT_SECTION(name) .section .text.caml.##name,"ax",%progbits
#endif
#else
#define TEXT_SECTION(name)
#endif

#define FUNCTION(name) \
        TEXT_SECTION(name); \
        .globl G(name); \
        .align FUNCTION_ALIGN; \
        G(name):

#if defined(SYS_linux_elf) || defined(SYS_bsd_elf) || defined(SYS_gnu)
#define ENDFUNCTION(name) \
        .type name,@function; \
        .size name, . - name
#else
#define ENDFUNCTION(name)
#endif

#ifdef ASM_CFI_SUPPORTED
#define CFI_STARTPROC .cfi_startproc
#define CFI_ENDPROC .cfi_endproc
#define CFI_ADJUST(n) .cfi_adjust_cfa_offset n
#else
#define CFI_STARTPROC
#define CFI_ENDPROC
#define CFI_ADJUST(n)
#endif

#if !defined(SYS_mingw) && !defined(SYS_cygwin)
#define STACK_PROBE_SIZE 16384
#endif

        .set    domain_curr_field, 0
#define DOMAIN_STATE(c_type, name) \
        .equ    domain_field_caml_##name, domain_curr_field ; \
        .set    domain_curr_field, domain_curr_field + 1
#include "../runtime/caml/domain_state.tbl"
#undef DOMAIN_STATE

#define CAML_STATE(var,reg) 8*domain_field_caml_##var(reg)

/* PR#6038: GCC and Clang seem to require 16-byte alignment nowadays,
   even if only MacOS X's ABI formally requires it. */
#define ALIGN_STACK(amount) subl $ amount, %esp ; CFI_ADJUST(amount)
#define UNDO_ALIGN_STACK(amount) addl $ amount, %esp ; CFI_ADJUST(-amount)

        .text
#if defined(FUNCTION_SECTIONS)
        TEXT_SECTION(caml_hot__code_begin)
        .globl  G(caml_hot__code_begin)
G(caml_hot__code_begin):

        TEXT_SECTION(caml_hot__code_end)
        .globl  G(caml_hot__code_end)
G(caml_hot__code_end):
#endif

/* Allocation */
        TEXT_SECTION(caml_system__code_begin)
        .globl  G(caml_system__code_begin)
G(caml_system__code_begin):

FUNCTION(caml_call_gc)
        CFI_STARTPROC
LBL(caml_call_gc):
    /* Record lowest stack address and return address */
        movl    G(Caml_state), %ebx
        movl    (%esp), %eax
        movl    %eax, CAML_STATE(last_return_address, %ebx)
        leal    4(%esp), %eax
        movl    %eax, CAML_STATE(bottom_of_stack, %ebx)
#if !defined(SYS_mingw) && !defined(SYS_cygwin)
    /* Touch the stack to trigger a recoverable segfault
       if insufficient space remains */
        subl    $(STACK_PROBE_SIZE), %esp; CFI_ADJUST(STACK_PROBE_SIZE);
        movl    %eax, 0(%esp)
        addl    $(STACK_PROBE_SIZE), %esp; CFI_ADJUST(-STACK_PROBE_SIZE);
#endif
    /* Build array of registers, save it into Caml_state->gc_regs */
        pushl   %ebp; CFI_ADJUST(4)
        pushl   %edi; CFI_ADJUST(4)
        pushl   %esi; CFI_ADJUST(4)
        pushl   %edx; CFI_ADJUST(4)
        pushl   %ecx; CFI_ADJUST(4)
        pushl   %ebx; CFI_ADJUST(4)
        pushl   %eax; CFI_ADJUST(4)
        movl    %esp, CAML_STATE(gc_regs, %ebx)
        /* MacOSX note: 16-alignment of stack preserved at this point */
    /* Call the garbage collector */
        call    G(caml_garbage_collection)
    /* Restore all regs used by the code generator */
        popl    %eax; CFI_ADJUST(-4)
        popl    %ebx; CFI_ADJUST(-4)
        popl    %ecx; CFI_ADJUST(-4)
        popl    %edx; CFI_ADJUST(-4)
        popl    %esi; CFI_ADJUST(-4)
        popl    %edi; CFI_ADJUST(-4)
        popl    %ebp; CFI_ADJUST(-4)
    /* Return to caller. Returns young_ptr in %eax. */
        movl    CAML_STATE(young_ptr, %ebx), %eax
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_call_gc)

FUNCTION(caml_alloc1)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        movl    CAML_STATE(young_ptr, %ebx), %eax
        subl    $8, %eax
        movl    %eax, CAML_STATE(young_ptr, %ebx)
        cmpl    CAML_STATE(young_limit, %ebx), %eax
        jb      LBL(caml_call_gc)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_alloc1)

FUNCTION(caml_alloc2)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        movl    CAML_STATE(young_ptr, %ebx), %eax
        subl    $12, %eax
        movl    %eax, CAML_STATE(young_ptr, %ebx)
        cmpl    CAML_STATE(young_limit, %ebx), %eax
        jb      LBL(caml_call_gc)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_alloc2)

FUNCTION(caml_alloc3)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        movl    CAML_STATE(young_ptr, %ebx), %eax
        subl    $16, %eax
        movl    %eax, CAML_STATE(young_ptr, %ebx)
        cmpl    CAML_STATE(young_limit, %ebx), %eax
        jb      LBL(caml_call_gc)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_alloc3)

FUNCTION(caml_allocN)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        /* eax = size - Caml_state->young_ptr */
        subl    CAML_STATE(young_ptr, %ebx), %eax
        negl    %eax              /* eax = Caml_state->young_ptr - size */
        movl    %eax, CAML_STATE(young_ptr, %ebx)
        cmpl    CAML_STATE(young_limit, %ebx), %eax
        jb      LBL(caml_call_gc)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_allocN)

/* Call a C function from OCaml */

FUNCTION(caml_c_call)
        CFI_STARTPROC
    /* Record lowest stack address and return address */
    /* ecx and edx are destroyed at C call. Use them as temp. */
        movl    G(Caml_state), %ecx
        movl    (%esp), %edx
        movl    %edx, CAML_STATE(last_return_address, %ecx)
        leal    4(%esp), %edx
        movl    %edx, CAML_STATE(bottom_of_stack, %ecx)
#if !defined(SYS_mingw) && !defined(SYS_cygwin)
    /* Touch the stack to trigger a recoverable segfault
       if insufficient space remains */
        subl    $(STACK_PROBE_SIZE), %esp; CFI_ADJUST(STACK_PROBE_SIZE);
        movl    %eax, 0(%esp)
        addl    $(STACK_PROBE_SIZE), %esp; CFI_ADJUST(-STACK_PROBE_SIZE);
#endif
    /* Call the function (address in %eax) */
        jmp     *%eax
        CFI_ENDPROC
        ENDFUNCTION(caml_c_call)

/* Start the OCaml program */

FUNCTION(caml_start_program)
        CFI_STARTPROC
    /* Save callee-save registers */
        pushl   %ebx; CFI_ADJUST(4)
        pushl   %esi; CFI_ADJUST(4)
        pushl   %edi; CFI_ADJUST(4)
        pushl   %ebp; CFI_ADJUST(4)
    /* Initial entry point is caml_program */
        movl    $ G(caml_program), %esi
    /* Common code for caml_start_program and caml_callback* */
LBL(106):
        movl    G(Caml_state), %edi
    /* Build a callback link */
        pushl   CAML_STATE(gc_regs, %edi); CFI_ADJUST(4)
        pushl   CAML_STATE(last_return_address, %edi); CFI_ADJUST(4)
        pushl   CAML_STATE(bottom_of_stack, %edi); CFI_ADJUST(4)
        /* Note: 16-alignment preserved on MacOSX at this point */
    /* Build an exception handler */
        pushl   $ LBL(108); CFI_ADJUST(4)
        ALIGN_STACK(8)
        pushl   CAML_STATE(exception_pointer, %edi); CFI_ADJUST(4)
        movl    %esp, CAML_STATE(exception_pointer, %edi)
    /* Call the OCaml code */
        call    *%esi
LBL(107):
        movl    G(Caml_state), %edi
    /* Pop the exception handler */
        popl    CAML_STATE(exception_pointer, %edi); CFI_ADJUST(-4)
        addl    $12, %esp       ; CFI_ADJUST(-12)
LBL(109):
        movl    G(Caml_state), %edi /* Reload for LBL(109) entry */
    /* Pop the callback link, restoring the global variables */
        popl    CAML_STATE(bottom_of_stack, %edi); CFI_ADJUST(-4)
        popl    CAML_STATE(last_return_address, %edi); CFI_ADJUST(-4)
        popl    CAML_STATE(gc_regs, %edi); CFI_ADJUST(-4)
    /* Restore callee-save registers. */
        popl    %ebp; CFI_ADJUST(-4)
        popl    %edi; CFI_ADJUST(-4)
        popl    %esi; CFI_ADJUST(-4)
        popl    %ebx; CFI_ADJUST(-4)
    /* Return to caller. */
        ret
LBL(108):
    /* Exception handler*/
    /* Mark the bucket as an exception result and return it */
        orl     $2, %eax
        jmp     LBL(109)
        CFI_ENDPROC
        ENDFUNCTION(caml_start_program)

/* Raise an exception from OCaml */

FUNCTION(caml_raise_exn)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        testl   $1, CAML_STATE(backtrace_active, %ebx)
        jne     LBL(110)
        movl    CAML_STATE(exception_pointer, %ebx), %esp
        popl    CAML_STATE(exception_pointer, %ebx); CFI_ADJUST(-4)
        UNDO_ALIGN_STACK(8)
        ret
LBL(110):
        movl    %eax, %esi          /* Save exception bucket in esi */
        movl    CAML_STATE(exception_pointer, %ebx), %edi /* SP of handler */
        movl    0(%esp), %eax       /* PC of raise */
        leal    4(%esp), %edx       /* SP of raise */
        ALIGN_STACK(12)
        pushl   %edi; CFI_ADJUST(4)         /* arg 4: sp of handler */
        pushl   %edx; CFI_ADJUST(4)         /* arg 3: sp of raise */
        pushl   %eax; CFI_ADJUST(4)         /* arg 2: pc of raise */
        pushl   %esi; CFI_ADJUST(4)         /* arg 1: exception bucket */
        call    G(caml_stash_backtrace)
        movl    %esi, %eax              /* Recover exception bucket */
        movl    %edi, %esp
        popl    CAML_STATE(exception_pointer, %ebx); CFI_ADJUST(-4)
        UNDO_ALIGN_STACK(8)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_raise_exn)

/* Raise an exception from C */

FUNCTION(caml_raise_exception)
        CFI_STARTPROC
        movl    G(Caml_state), %ebx
        testl   $1, CAML_STATE(backtrace_active, %ebx)
        jne     LBL(112)
        movl    8(%esp), %eax
        movl    CAML_STATE(exception_pointer, %ebx), %esp
        popl    CAML_STATE(exception_pointer, %ebx); CFI_ADJUST(-4)
        UNDO_ALIGN_STACK(8)
        ret
LBL(112):
        movl    8(%esp), %esi          /* Save exception bucket in esi */
        ALIGN_STACK(12)
        /* 4: sp of handler */
        pushl   CAML_STATE(exception_pointer, %ebx); CFI_ADJUST(4)
        /* 3: sp of raise */
        pushl   CAML_STATE(bottom_of_stack, %ebx); CFI_ADJUST(4)
        /* 2: pc of raise */
        pushl   CAML_STATE(last_return_address, %ebx); CFI_ADJUST(4)
        /* 1: exception bucket */
        pushl   %esi; CFI_ADJUST(4)
        call    G(caml_stash_backtrace)
        movl    %esi, %eax              /* Recover exception bucket */
        movl    CAML_STATE(exception_pointer, %ebx), %esp
        popl    CAML_STATE(exception_pointer, %ebx); CFI_ADJUST(-4)
        UNDO_ALIGN_STACK(8)
        ret
        CFI_ENDPROC
        ENDFUNCTION(caml_raise_exception)

/* Callback from C to OCaml */

FUNCTION(caml_callback_asm)
        CFI_STARTPROC
    /* Save callee-save registers */
        pushl   %ebx; CFI_ADJUST(4)
        pushl   %esi; CFI_ADJUST(4)
        pushl   %edi; CFI_ADJUST(4)
        pushl   %ebp; CFI_ADJUST(4)
    /* Initial loading of arguments */
        movl    24(%esp), %ebx   /* arg2: closure */
        movl    28(%esp), %edi   /* arguments array */
        movl    0(%edi), %eax    /* arg1: argument */
        movl    0(%ebx), %esi    /* code pointer */
        jmp     LBL(106)
        CFI_ENDPROC
ENDFUNCTION(caml_callback_asm)

FUNCTION(caml_callback2_asm)
        CFI_STARTPROC
    /* Save callee-save registers */
        pushl   %ebx; CFI_ADJUST(4)
        pushl   %esi; CFI_ADJUST(4)
        pushl   %edi; CFI_ADJUST(4)
        pushl   %ebp; CFI_ADJUST(4)
    /* Initial loading of arguments */
        movl    24(%esp), %ecx   /* arg3: closure */
        movl    28(%esp), %edi   /* arguments array */
        movl    0(%edi), %eax    /* arg1: first argument */
        movl    4(%edi), %ebx    /* arg2: second argument */
        movl    $ G(caml_apply2), %esi   /* code pointer */
        jmp     LBL(106)
        CFI_ENDPROC
ENDFUNCTION(caml_callback2_asm)

FUNCTION(caml_callback3_asm)
        CFI_STARTPROC
    /* Save callee-save registers */
        pushl   %ebx; CFI_ADJUST(4)
        pushl   %esi; CFI_ADJUST(4)
        pushl   %edi; CFI_ADJUST(4)
        pushl   %ebp; CFI_ADJUST(4)
    /* Initial loading of arguments */
        movl    24(%esp), %edx   /* arg4: closure */
        movl    28(%esp), %edi   /* arguments array */
        movl    0(%edi), %eax    /* arg1: first argument */
        movl    4(%edi), %ebx    /* arg2: second argument */
        movl    8(%edi), %ecx    /* arg3: third argument */
        movl    $ G(caml_apply3), %esi   /* code pointer */
        jmp     LBL(106)
        CFI_ENDPROC
ENDFUNCTION(caml_callback3_asm)

FUNCTION(caml_ml_array_bound_error)
        CFI_STARTPROC
    /* Empty the floating-point stack */
        ffree   %st(0)
        ffree   %st(1)
        ffree   %st(2)
        ffree   %st(3)
        ffree   %st(4)
        ffree   %st(5)
        ffree   %st(6)
        ffree   %st(7)
    /* Record lowest stack address and return address */
        movl    G(Caml_state), %ebx
        movl    (%esp), %edx
        movl    %edx, CAML_STATE(last_return_address, %ebx)
        leal    4(%esp), %edx
        movl    %edx, CAML_STATE(bottom_of_stack, %ebx)
    /* Re-align the stack */
        andl    $-16, %esp
    /* Branch to [caml_array_bound_error_asm] (never returns) */
        call    G(caml_array_bound_error_asm)
        CFI_ENDPROC
        ENDFUNCTION(caml_ml_array_bound_error)

        TEXT_SECTION(caml_system__code_end)
        .globl  G(caml_system__code_end)
G(caml_system__code_end):

        .data
        .globl  G(caml_system__frametable)
G(caml_system__frametable):
        .long   1               /* one descriptor */
        .long   LBL(107)        /* return address into callback */
#ifndef SYS_solaris
        .word   -1              /* negative frame size => use callback link */
        .word   0               /* no roots here */
#else
        .value  -1              /* negative frame size => use callback link */
        .value  0               /* no roots here */
#endif

        .globl  G(caml_extra_params)
G(caml_extra_params):
#ifndef SYS_solaris
        .space  256
#else
        .zero   256
#endif

#if defined(SYS_linux_elf)
    /* Mark stack as non-executable, PR#4564 */
        .section .note.GNU-stack,"",%progbits
#endif
