/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2005-2011. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */


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

	.text
	.p2align 2

/*
 * Enter Erlang from C.
 * Create a new frame on the C stack.
 * Save C callee-save registers in the frame.
 * Do not clobber the C argument registers.
 * Retrieve the process pointer from the C argument registers.
 *
 * Our C frame includes:
 * - 9*4 == 36 bytes for saving r4-r11 and lr
 * - 2*4 == 8 bytes for calls to hipe_bs_put_{big_integer,small_float}.
 *   They take 5-6 parameter words: 4 in registers and 1-2 on the stack.
 *   (They take 5 regular parameters, and an additional P parameter on SMP.)
 * - 4 bytes to pad the frame size to a multiple of 8
 */
#define ENTER_FROM_C	\
	stmfd	sp!, {r4,r5,r6,r7,r8,r9,r10,r11,lr};	\
	sub	sp, sp, #12; \
	mov	P, r0;	\
	RESTORE_CACHED_STATE

/*
 * Return to the calling C function.
 * The return value is in r0.
 *
 * .nosave_exit saves no state
 * .flush_exit saves NSP and other cached P state.
 * .suspend_exit also saves RA.
 */
.suspend_exit:
	/* save RA, so we can be resumed */
	str	lr, [P, #P_NRA]
.flush_exit:
	/* flush cached P state */
	SAVE_CACHED_STATE
.nosave_exit:
	/* restore callee-save registers, drop frame, return */
	add	sp, sp, #12
	ldmfd	sp!, {r4,r5,r6,r7,r8,r9,r10,r11,pc}

/*
 * int hipe_arm_call_to_native(Process *p);
 * Emulated code recursively calls native code.
 */
	.global	hipe_arm_call_to_native
hipe_arm_call_to_native:
	ENTER_FROM_C
	/* get argument registers */
	LOAD_ARG_REGS
	/* call the target */
	mov	lr, pc
	ldr	pc, [P, #P_NCALLEE]
/* FALLTHROUGH
 *
 * 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.
 */
	.global	nbif_return
nbif_return:
	str	r0, [P, #P_ARG0]			/* save retval */
	mov	r0, #HIPE_MODE_SWITCH_RES_RETURN
	b	.flush_exit

/*
 * int hipe_arm_return_to_native(Process *p);
 * Emulated code returns to its native code caller.
 */
	.global	hipe_arm_return_to_native
hipe_arm_return_to_native:
	ENTER_FROM_C
	/* get return value */
	ldr	r0, [P, #P_ARG0]
	/*
	 * Return using the current return address.
	 * The parameters were popped at the original native-to-emulated
	 * call (hipe_call_from_native_is_recursive), so a plain ret suffices.
	 */
	ldr	pc, [P, #P_NRA]

/*
 * int hipe_arm_tailcall_to_native(Process *p);
 * Emulated code tailcalls native code.
 */
	.global	hipe_arm_tailcall_to_native
hipe_arm_tailcall_to_native:
	ENTER_FROM_C
	/* get argument registers */
	LOAD_ARG_REGS
	/* restore return address */
	ldr	lr, [P, #P_NRA]
	/* call the target */
	ldr	pc, [P, #P_NCALLEE]

/*
 * int hipe_arm_throw_to_native(Process *p);
 * Emulated code throws an exception to its native code caller.
 */
	.global	hipe_arm_throw_to_native
hipe_arm_throw_to_native:
	ENTER_FROM_C
	/* invoke the handler */
	ldr	pc, [P, #P_NCALLEE]	/* set by hipe_find_handler() */

/*
 * Native code calls emulated code via a stub
 * which should look as follows:
 *
 * stub for f/N:
 *	<set r8 to f's BEAM code address>
 *	<set r0 to N>
 *	b nbif_callemu
 *
 * XXX: Different stubs for different number of register parameters?
 */
	.global	nbif_callemu
nbif_callemu:
	str	r8, [P, #P_BEAM_IP]
	str	r0, [P, #P_ARITY]
	STORE_ARG_REGS
	mov	r0, #HIPE_MODE_SWITCH_RES_CALL
	b	.suspend_exit

/*
 * nbif_apply
 */
	.global	nbif_apply
nbif_apply:
	STORE_ARG_REGS
	mov	r0, #HIPE_MODE_SWITCH_RES_APPLY
	b	.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 NR_ARG_REGS >= 6
	.global	nbif_ccallemu6
nbif_ccallemu6:
	str	ARG5, [P, #P_ARG5]
#if NR_ARG_REGS > 6
	mov	ARG5, ARG6
#else
	ldr	ARG5, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

#if NR_ARG_REGS >= 5
	.global	nbif_ccallemu5
nbif_ccallemu5:
	str	ARG4, [P, #P_ARG4]
#if NR_ARG_REGS > 5
	mov	ARG4, ARG5
#else
	ldr	ARG4, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

#if NR_ARG_REGS >= 4
	.global	nbif_ccallemu4
nbif_ccallemu4:
	str	ARG3, [P, #P_ARG3]
#if NR_ARG_REGS > 4
	mov	ARG3, ARG4
#else
	ldr	ARG3, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

#if NR_ARG_REGS >= 3
	.global	nbif_ccallemu3
nbif_ccallemu3:
	str	ARG2, [P, #P_ARG2]
#if NR_ARG_REGS > 3
	mov	ARG2, ARG3
#else
	ldr	ARG2, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

#if NR_ARG_REGS >= 2
	.global	nbif_ccallemu2
nbif_ccallemu2:
	str	ARG1, [P, #P_ARG1]
#if NR_ARG_REGS > 2
	mov	ARG1, ARG2
#else
	ldr	ARG1, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

#if NR_ARG_REGS >= 1
	.global	nbif_ccallemu1
nbif_ccallemu1:
	str	ARG0, [P, #P_ARG0]
#if NR_ARG_REGS > 1
	mov	ARG0, ARG1
#else
	ldr	ARG0, [NSP, #0]
#endif
	/*FALLTHROUGH*/
#endif

	.global	nbif_ccallemu0
nbif_ccallemu0:
	/* We use r1 not ARG0 here because ARG0 is not
	   defined when NR_ARG_REGS == 0. */
#if NR_ARG_REGS == 0
	ldr	r1, [NSP, #0]		/* get the closure */
#endif
	str	r1, [P, #P_CLOSURE]	/* save the closure */
	mov	r0, #HIPE_MODE_SWITCH_RES_CALL_CLOSURE
	b	.suspend_exit

/*
 * This is where native code suspends.
 */
	.global	nbif_suspend_0
nbif_suspend_0:
	mov	r0, #HIPE_MODE_SWITCH_RES_SUSPEND
	b	.suspend_exit

/*
 * Suspend from a receive (waiting for a message)
 */
	.global	nbif_suspend_msg
nbif_suspend_msg:
	mov	r0, #HIPE_MODE_SWITCH_RES_WAIT
	b	.suspend_exit

/*
 * Suspend from a receive with a timeout (waiting for a message)
 *	if (!(p->flags & F_TIMO)) { suspend }
 *	else { return 0; }
 */
	.global	nbif_suspend_msg_timeout
nbif_suspend_msg_timeout:
	ldr	r1, [P, #P_FLAGS]
	mov	r0, #HIPE_MODE_SWITCH_RES_WAIT_TIMEOUT
	/* this relies on F_TIMO (1<<2) fitting in a uimm16 */
	tst	r1, #F_TIMO
	beq	.suspend_exit
	/* timeout has occurred */
	mov	r0, #0
	mov	pc, lr

/*
 * This is the default exception handler for native code.
 */
	.global	nbif_fail
nbif_fail:
	mov	r0, #HIPE_MODE_SWITCH_RES_THROW
	b	.flush_exit	/* no need to save RA */

	.global	nbif_0_gc_after_bif
	.global	nbif_1_gc_after_bif
	.global	nbif_2_gc_after_bif
	.global	nbif_3_gc_after_bif
nbif_0_gc_after_bif:
	mov	r1, #0
	b	.gc_after_bif
nbif_1_gc_after_bif:
	mov	r1, #1
	b	.gc_after_bif
nbif_2_gc_after_bif:
	mov	r1, #2
	b	.gc_after_bif
nbif_3_gc_after_bif:
	mov	r1, #3
	/*FALLTHROUGH*/
.gc_after_bif:
	str	r1, [P, #P_NARITY]
	str	TEMP_LR, [P, #P_NRA]
	str	NSP, [P, #P_NSP]
	mov	TEMP_LR, lr
	mov	r3, #0			/* Pass 0 in arity */
	mov	r2, #0			/* Pass NULL in regs */
	mov	r1, r0
	mov	r0, P
	bl	erts_gc_after_bif_call
	mov	lr, TEMP_LR
	ldr	TEMP_LR, [P, #P_NRA]
	mov	r1, #0
	str	r1, [P, #P_NARITY]
	mov	pc, lr

/*
 * We end up here when a BIF called from native signals an
 * exceptional condition.
 * HP was just read from P.
 * NSP has not been saved in P.
 * TEMP_LR contains a copy of LR
 */
	.global	nbif_0_simple_exception
nbif_0_simple_exception:
	mov	r1, #0
	b	.nbif_simple_exception
	.global	nbif_1_simple_exception
nbif_1_simple_exception:
	mov	r1, #1
	b	.nbif_simple_exception
	.global	nbif_2_simple_exception
nbif_2_simple_exception:
	mov	r1, #2
	b	.nbif_simple_exception
	.global	nbif_3_simple_exception
nbif_3_simple_exception:
	mov	r1, #3
	/*FALLTHROUGH*/
.nbif_simple_exception:
	ldr	r0, [P, #P_FREASON]
	cmp	r0, #FREASON_TRAP
	beq	.handle_trap
	/*
	 * Find and invoke catch handler (it must exist).
	 * HP was just read from P.
	 * NSP has not been saved in P.
	 * TEMP_LR should contain the current call's return address.
	 * r1 should contain the current call's arity.
	 */
	str	NSP, [P, #P_NSP]
	str	TEMP_LR, [P, #P_NRA]
	str	r1, [P, #P_NARITY]
	/* find and prepare to invoke the handler */
	mov	r0, P
	bl	hipe_handle_exception	/* Note: hipe_handle_exception() conses */
	RESTORE_CACHED_STATE		/* NSP updated by hipe_find_handler() */
	/* now invoke the handler */
	ldr	pc, [P, #P_NCALLEE]	/* set by hipe_find_handler() */

	/*
	 * A BIF failed with freason TRAP:
	 * - the BIF's arity is in r1
	 * - the native RA was saved in TEMP_LR before the BIF call
	 * - HP was just read from P
	 * - NSP has not been saved in P
	 */
.handle_trap:
	mov	r0, #HIPE_MODE_SWITCH_RES_TRAP
	str	NSP, [P, #P_NSP]
	str	r1, [P, #P_NARITY]
	str	TEMP_LR, [P, #P_NRA]
	b	.nosave_exit

/*
 * nbif_stack_trap_ra: trap return address for maintaining
 * the gray/white stack boundary
 */
	.global	nbif_stack_trap_ra
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.
	mov	TEMP_ARG0, r0		/* save retval */
	str	NSP, [P, #P_NSP]
	mov	r0, P
	bl	hipe_handle_stack_trap	/* must not cons */
	mov	lr, r0			/* original RA */
	mov	r0, TEMP_ARG0		/* restore retval */
	mov	pc, lr			/* resume at original RA */

/*
 * hipe_arm_inc_stack
 * Caller saved its LR in TEMP_LR (== TEMP1) before calling us.
 */
	.global	hipe_arm_inc_stack
hipe_arm_inc_stack:
	STORE_ARG_REGS
	mov	TEMP_ARG0, lr
	str	NSP, [P, #P_NSP]
	mov	r0, P
	# hipe_inc_nstack reads and writes NSP and NSP_LIMIT,
	# but does not access LR/RA, HP, or FCALLS.
	bl	hipe_inc_nstack
	ldr	NSP, [P, #P_NSP]
	LOAD_ARG_REGS
	# this relies on LOAD_ARG_REGS not clobbering TEMP_ARG0
	mov	pc, TEMP_ARG0

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