/*
 * %CopyrightBegin%

 *
 * Copyright Ericsson AB 2001-2016. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

#define ASM
#include "hipe_x86_asm.h"
#include "hipe_literals.h"
#include "hipe_mode_switch.h"

/*
 * Enter Erlang from C.
 * Create a new frame on the C stack.
 * Save C callee-save registers in the frame.
 * Retrieve the process pointer from the C parameters.
 * SWITCH_C_TO_ERLANG.
 *
 * Our C frame includes:
 * - 4*4 == 16 bytes for saving %edi, %esi, %ebx, and %ebp
 * - 6*4 == 24 bytes of parameter area for recursive calls
 *   to C BIFs: actual parameters are moved to it, not pushed
 * - 8 bytes to pad the frame to a multiple of 16 bytes,
 *   minus 4 bytes for the return address pushed by the caller.
 *   OSX requires 16-byte alignment of %esp at calls (for SSE2).
 */
#define ENTER_FROM_C		\
	/* create stack frame and save C callee-save registers in it */ \
	subl	$44, %esp;	\
	movl	%edi, 28(%esp);	\
	movl	%esi, 32(%esp);	\
	movl	%ebx, 36(%esp);	\
	movl	%ebp, 40(%esp);	\
	/* get the process pointer */	\
	movl	48(%esp), P;	\
	/* switch to native stack */	\
	SWITCH_C_TO_ERLANG

	TEXT

/*
 * int x86_call_to_native(Process *p);
 * Emulated code recursively calls native code.
 */
	.align	4
	GLOBAL(CSYM(x86_call_to_native))
	GLOBAL(ASYM(nbif_return))
CSYM(x86_call_to_native):
	ENTER_FROM_C
	/* get argument registers */
	LOAD_ARG_REGS
	/* call the target */
	NSP_CALL(*P_NCALLEE(P))
/*
 * We export this return address so that hipe_mode_switch() can discover
 * when native code tailcalls emulated code.
 *
 * This is where native code returns to emulated code.
 */
ASYM(nbif_return):
	movl	%eax, P_ARG0(P)			/* save retval */
	movl	$HIPE_MODE_SWITCH_RES_RETURN, %eax
/* FALLTHROUGH to .flush_exit
 *
 * Return to the calling C function with result token in %eax.
 *
 * .nosave_exit saves no state
 * .flush_exit saves cached P state
 * .suspend_exit also saves RA
 */
.suspend_exit:
	/* save RA, no-op on x86 */
.flush_exit:
	/* flush cached P state */
	SAVE_CACHED_STATE
.nosave_exit:
	/* switch to C stack */
	SWITCH_ERLANG_TO_C_QUICK
	/* restore C callee-save registers, drop frame, return */
	movl	28(%esp), %edi
	movl	32(%esp), %esi	/* kills HP, if HP_IN_ESI is true */
	movl	36(%esp), %ebx
	movl	40(%esp), %ebp	/* kills P */
	addl	$44, %esp
	ret

/*
 * Native code calls emulated code via a linker-generated
 * stub (hipe_x86_loader.erl) which should look as follows:
 *
 * stub for f/N:
 *	movl	$<f's export entry address>, P_CALLEE_EXP(P)
 *	movb	$<N>, P_ARITY(P)
 *	jmp	nbif_callemu
 *
 * XXX: Different stubs for different number of register parameters?
 */
	.align	4
	GLOBAL(ASYM(nbif_callemu))
ASYM(nbif_callemu):
	STORE_ARG_REGS
	movl	$HIPE_MODE_SWITCH_RES_CALL_EXPORTED, %eax
	jmp	.suspend_exit

/*
 * nbif_apply
 */
	.align	4
	GLOBAL(ASYM(nbif_apply))
ASYM(nbif_apply):
	STORE_ARG_REGS
	movl	$HIPE_MODE_SWITCH_RES_APPLY, %eax
	jmp	.suspend_exit

/*
 * Native code calls an emulated-mode closure via a stub defined below.
 *
 * The closure is appended as the last actual parameter, and parameters
 * beyond the first few passed in registers are pushed onto the stack in
 * left-to-right order.
 * Hence, the location of the closure parameter only depends on the number
 * of parameters in registers, not the total number of parameters.
 */
#if X86_NR_ARG_REGS == 5
	.align	4
	GLOBAL(ASYM(nbif_ccallemu5))
ASYM(nbif_ccallemu5):
	movl	ARG4, P_ARG4(P)
	movl	4(NSP), ARG4
	/*FALLTHROUGH*/
#endif

#if X86_NR_ARG_REGS >= 4
	.align	4
	GLOBAL(ASYM(nbif_ccallemu4))
ASYM(nbif_ccallemu4):
	movl	ARG3, P_ARG3(P)
#if X86_NR_ARG_REGS > 4
	movl	ARG4, ARG3
#else
	movl	4(NSP), ARG3
#endif
	/*FALLTHROUGH*/
#endif

#if X86_NR_ARG_REGS >= 3
	.align	4
	GLOBAL(ASYM(nbif_ccallemu3))
ASYM(nbif_ccallemu3):
	movl	ARG2, P_ARG2(P)
#if X86_NR_ARG_REGS > 3
	movl	ARG3, ARG2
#else
	movl	4(NSP), ARG2
#endif
	/*FALLTHROUGH*/
#endif

#if X86_NR_ARG_REGS >= 2
	.align	4
	GLOBAL(ASYM(nbif_ccallemu2))
ASYM(nbif_ccallemu2):
	movl	ARG1, P_ARG1(P)
#if X86_NR_ARG_REGS > 2
	movl	ARG2, ARG1
#else
	movl	4(NSP), ARG1
#endif
	/*FALLTHROUGH*/
#endif

#if X86_NR_ARG_REGS >= 1
	.align	4
	GLOBAL(ASYM(nbif_ccallemu1))
ASYM(nbif_ccallemu1):
	movl	ARG0, P_ARG0(P)
#if X86_NR_ARG_REGS > 1
	movl	ARG1, ARG0
#else
	movl	4(NSP), ARG0
#endif
	/*FALLTHROUGH*/
#endif

	.align	4
	GLOBAL(ASYM(nbif_ccallemu0))
ASYM(nbif_ccallemu0):
	/* We use %eax not ARG0 here because ARG0 is not
	   defined when NR_ARG_REGS == 0. */
#if X86_NR_ARG_REGS == 0
	movl	4(NSP), %eax
#endif
	movl	%eax, P_CLOSURE(P)
	movl	$HIPE_MODE_SWITCH_RES_CALL_CLOSURE, %eax
	jmp	.suspend_exit

/*
 * This is where native code suspends.
 */
	.align	4
	GLOBAL(ASYM(nbif_suspend_0))
ASYM(nbif_suspend_0):
	movl	$HIPE_MODE_SWITCH_RES_SUSPEND, %eax
	jmp	.suspend_exit

/*
 * Suspend from a receive (waiting for a message)
 */
	.align	4
	GLOBAL(ASYM(nbif_suspend_msg))
ASYM(nbif_suspend_msg):
	movl	$HIPE_MODE_SWITCH_RES_WAIT, %eax
	jmp	.suspend_exit

/*
 * Suspend from a receive with a timeout (waiting for a message)
 *	if (!(p->flags & F_TIMO)) { suspend }
 *	else { return 0; }
 */
	.align	4
	GLOBAL(ASYM(nbif_suspend_msg_timeout))
ASYM(nbif_suspend_msg_timeout):
	movl	P_FLAGS(P), %eax
	/* this relies on F_TIMO (1<<2) fitting in a byte */
	testb	$F_TIMO, %al			/* F_TIMO set? */
	jz	.no_timeout			/* if not set, suspend */
	/* timeout has occurred */
	xorl	%eax, %eax			/* return 0 to signal timeout */
	NSP_RET0
.no_timeout:
	movl	$HIPE_MODE_SWITCH_RES_WAIT_TIMEOUT, %eax
	jmp	.suspend_exit

/*
 * int x86_return_to_native(Process *p);
 * Emulated code returns to its native code caller.
 */
	.align	4
	GLOBAL(CSYM(x86_return_to_native))
CSYM(x86_return_to_native):
	ENTER_FROM_C
	/* get return value */
	movl	P_ARG0(P), %eax
	/*
	 * Return using the stacked return address.
	 * The parameters were popped at the original native-to-emulated
	 * call (hipe_call_from_native_is_recursive), so a plain ret suffices.
	 */
	NSP_RET0

/*
 * int x86_tailcall_to_native(Process *p);
 * Emulated code tailcalls native code.
 */
	.align	4
	GLOBAL(CSYM(x86_tailcall_to_native))
CSYM(x86_tailcall_to_native):
	ENTER_FROM_C
	/* get argument registers */
	LOAD_ARG_REGS
	/* jump to the target label */
	jmp	*P_NCALLEE(P)

/*
 * int x86_throw_to_native(Process *p);
 * Emulated code throws an exception to its native code caller.
 */
	.align	4
	GLOBAL(CSYM(x86_throw_to_native))
CSYM(x86_throw_to_native):
	ENTER_FROM_C
	/* invoke the handler */
	jmp	*P_NCALLEE(P)		/* set by hipe_find_handler() */

/*
 * This is the default exception handler for native code.
 */
	.align	4
	GLOBAL(ASYM(nbif_fail))
ASYM(nbif_fail):
	movl	$HIPE_MODE_SWITCH_RES_THROW, %eax
	jmp	.flush_exit
	
	GLOBAL(nbif_0_gc_after_bif)
	GLOBAL(nbif_1_gc_after_bif)
	GLOBAL(nbif_2_gc_after_bif)
	GLOBAL(nbif_3_gc_after_bif)
	GLOBAL(nbif_4_gc_after_bif)
	.align	4
nbif_0_gc_after_bif:
	xorl	%edx, %edx
	jmp	.gc_after_bif
	.align	4
nbif_1_gc_after_bif:
	movl	$1, %edx
	jmp	.gc_after_bif
	.align	4
nbif_2_gc_after_bif:
	movl	$2, %edx
	jmp	.gc_after_bif
	.align	4
nbif_3_gc_after_bif:
	movl	$3, %edx
	jmp	.gc_after_bif
	.align	4
nbif_4_gc_after_bif:
	movl	$4, %edx
	/*FALLTHROUGH*/
	.align	4
.gc_after_bif:
	movl	%edx, P_NARITY(P)
	subl	$(32-4), %esp
	movl	P, (%esp)
	movl	%eax, 4(%esp)
	movl	$0, 8(%esp)		/* Pass NULL in regs */
	movl	$0, 12(%esp)		/* Pass 0 in arity */
	call	CSYM(erts_gc_after_bif_call)
	addl	$(32-4), %esp
	movl	$0, P_NARITY(P)
	ret

/*
 * We end up here when a BIF called from native signals an
 * exceptional condition.
 * The stack/heap registers were just read from P.
 */
	GLOBAL(nbif_0_simple_exception)
	GLOBAL(nbif_1_simple_exception)
	GLOBAL(nbif_2_simple_exception)
	GLOBAL(nbif_3_simple_exception)
	GLOBAL(nbif_4_simple_exception)
	.align	4
nbif_0_simple_exception:
	xorl	%eax, %eax
	jmp	.nbif_simple_exception
	.align	4
nbif_1_simple_exception:
	movl	$1, %eax
	jmp	.nbif_simple_exception
	.align	4
nbif_2_simple_exception:
	movl	$2, %eax
	jmp	.nbif_simple_exception
	.align	4
nbif_3_simple_exception:
	movl	$3, %eax
	jmp	.nbif_simple_exception
	.align	4
nbif_4_simple_exception:
	movl	$4, %eax
	/*FALLTHROUGH*/
	.align	4
.nbif_simple_exception:
	cmpl	$FREASON_TRAP, P_FREASON(P)
	je	.handle_trap
	/*
	 * Find and invoke catch handler (it must exist).
	 * The stack/heap registers were just read from P.
	 * - %eax should contain the current call's arity
	 */
	movl	%eax, P_NARITY(P)
	/* find and prepare to invoke the handler */
	SWITCH_ERLANG_TO_C_QUICK	/* The cached state is clean and need not be saved. */
	movl	P, (%esp)
	call	CSYM(hipe_handle_exception)	/* Note: hipe_handle_exception() conses */
	SWITCH_C_TO_ERLANG		/* %esp updated by hipe_find_handler() */
	/* now invoke the handler */
	jmp	*P_NCALLEE(P)		/* set by hipe_find_handler() */

	/*
	 * A BIF failed with freason TRAP:
	 * - the BIF's arity is in %eax
	 * - the native heap/stack/reds registers are saved in P
	 */
.handle_trap:
	movl	%eax, P_NARITY(P)
	movl	$HIPE_MODE_SWITCH_RES_TRAP, %eax
	jmp	.nosave_exit

/*
 * nbif_stack_trap_ra: trap return address for maintaining
 * the gray/white stack boundary
 */
	GLOBAL(ASYM(nbif_stack_trap_ra))
	.align	4
ASYM(nbif_stack_trap_ra):		/* a return address, not a function */
	/* This only handles a single return value. */
	/* If we have more, we need to save them in the PCB. */
	movl	%eax, TEMP_RV		/* save retval */
	SWITCH_ERLANG_TO_C_QUICK
	movl	P, (%esp)
	call	CSYM(hipe_handle_stack_trap)	/* must not cons; preserves TEMP_RV */
	movl	%eax, %edx		/* original RA */
	SWITCH_C_TO_ERLANG_QUICK
	movl	TEMP_RV, %eax		/* restore retval */
	jmp	*%edx			/* resume at original RA */

/*
 * nbif_inc_stack_0
 */
	.align	4
	GLOBAL(ASYM(nbif_inc_stack_0))
ASYM(nbif_inc_stack_0):
	SWITCH_ERLANG_TO_C_QUICK
	STORE_CALLER_SAVE
	movl	P, (%esp)
	/* hipe_inc_nstack reads and writes NSP and NSP_LIMIT, */
	/* but does not access HP or FCALLS (or the non-x86 NRA). */
	call	CSYM(hipe_inc_nstack)
	LOAD_CALLER_SAVE
	SWITCH_C_TO_ERLANG_QUICK
	NSP_RET0

#if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif
