/****************************************************************
 *								*
 * Copyright (c) 2001-2018 Fidelity National Information	*
 * Services, Inc. and/or its subsidiaries. All rights reserved.	*
 *								*
 *	This source code contains the intellectual property	*
 *	of its copyright holder(s), and is made available	*
 *	under a license.  If you do not know the terms of	*
 *	the license, please stop and do not read further.	*
 *								*
 ****************************************************************/

#include "mdef.h"

#include "gtm_stdio.h"

#include <rtnhdr.h>
#include "stack_frame.h"
#include "mv_stent.h"
#include "tp_frame.h"
#include "cache.h"
#include "cache_cleanup.h"
#include "objlabel.h"
#include "op.h"
#include "error_trap.h"
#include "error.h"
#include "tp_timeout.h"
#include "compiler.h"
#include "parm_pool.h"
#include "opcode.h"
#include "glvn_pool.h"
#include "gdsroot.h"
#include "gdsblk.h"
#include "gdsbt.h"
#include "gdsfhead.h"
#include "alias.h"
#include "zr_unlink_rtn.h"
#ifdef GTM_TRIGGER
# include "gtm_trigger_trc.h"
#endif
#include "xfer_enum.h"
#include "deferred_events.h"
#include "deferred_events_queue.h"
#include "ztimeout_routines.h"
#include "jobinterrupt_event.h"

GBLREF	void			(*unw_prof_frame_ptr)(void);
GBLREF	stack_frame		*frame_pointer, *zyerr_frame;
GBLREF	unsigned char		*msp, *stackbase, *stacktop;
GBLREF	mv_stent		*mv_chain;
GBLREF	tp_frame		*tp_pointer;
GBLREF	boolean_t		is_tracing_on;
GBLREF	boolean_t		skip_error_ret;
GBLREF	stack_frame		*error_frame;
GBLREF	mval			*alias_retarg;
GBLREF	boolean_t		tp_timeout_deferred;
GBLREF	dollar_ecode_type	dollar_ecode;
GBLREF	boolean_t		ztrap_explicit_null;
GBLREF	boolean_t		dollar_zininterrupt;
GBLREF	boolean_t		dollar_truth;

error_def(ERR_STACKUNDERFLO);
error_def(ERR_TPQUIT);

/* This has to be maintained in parallel with unw_retarg(), the unwind with a return argument (extrinisic quit) routine. */
void op_unwind(void)
{
	rhdtyp			*rtnhdr;
	mv_stent		*mvc;
	int4			event_type, param_val;
	void (*set_fn)(int4 param);

	DBGEHND_ONLY(stack_frame *prevfp;)
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	assert((frame_pointer < frame_pointer->old_frame_pointer) || (NULL == frame_pointer->old_frame_pointer));
	if (frame_pointer->type & SFT_COUNT)
	{	/* If unwinding a counted frame, make sure we clean up any alias return argument in flight */
		if (NULL != alias_retarg)
			CLEAR_ALIAS_RETARG;
	}
	DBGEHND_ONLY(prevfp = frame_pointer);
	if (tp_pointer && tp_pointer->fp <= frame_pointer)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_TPQUIT);
	/* Note that error_ret() should be invoked only after the rts_error() of TPQUIT.
	 * This is so the TPQUIT error gets noted down in $ECODE (which will not happen if error_ret() is called before).
	 */
	if (!skip_error_ret)
	{
		INVOKE_ERROR_RET_IF_NEEDED;
	} else
	{
		if (NULL != error_frame)
		{
			assert(error_frame >= frame_pointer);
			if (error_frame <= frame_pointer)
				NULLIFY_ERROR_FRAME;	/* ZGOTO to frame level lower than primary error level cancels error mode */
		}
		skip_error_ret = FALSE;	/* reset at the earliest point although caller (goframes()) does reset it just in
					 * case an error occurs before we return to the caller
					 */
	}
	assert(msp <= stackbase && msp > stacktop);
	assert(mv_chain <= (mv_stent *)stackbase && mv_chain > (mv_stent *)stacktop);
	assert(frame_pointer <= (stack_frame*)stackbase && frame_pointer > (stack_frame *)stacktop);
	/* See if unwinding an indirect frame */
	IF_INDR_FRAME_CLEANUP_CACHE_ENTRY(frame_pointer);
	for (mvc = mv_chain; mvc < (mv_stent *)frame_pointer; )
	{
		unw_mv_ent(mvc);
		mvc = (mv_stent *)(mvc->mv_st_next + (char *)mvc);
	}
	if (0 <= frame_pointer->dollar_test)		/* get dollar_test if it has been set */
		dollar_truth = frame_pointer->dollar_test;
	if (is_tracing_on GTMTRIG_ONLY( && !(frame_pointer->type & SFT_TRIGR)))
		(*unw_prof_frame_ptr)();
	mv_chain = mvc;
	msp = (unsigned char *)frame_pointer + SIZEOF(stack_frame);
	if (msp > stackbase)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKUNDERFLO);
	if (SSF_NORET_VIA_MUMTSTART & frame_pointer->type)
		DBGTRIGR((stderr, "op_unwind: Unwinding frame 0x"lvaddr" with type %d which has SSF_NORET_VIA_MUMTSTART enabled\n",
			  frame_pointer, frame_pointer->type));
	DRAIN_GLVN_POOL_IF_NEEDED;
	PARM_ACT_UNSTACK_IF_NEEDED;
	USHBIN_ONLY(rtnhdr = frame_pointer->rvector);	/* Save rtnhdr for cleanup call below */
	frame_pointer = frame_pointer->old_frame_pointer;
	DBGEHND((stderr, "op_unwind: Stack frame 0x"lvaddr" unwound - frame 0x"lvaddr" now current - New msp: 0x"lvaddr"\n",
		 prevfp, frame_pointer, msp));
	if (NULL != zyerr_frame && frame_pointer > zyerr_frame)
		zyerr_frame = NULL;	/* If we have unwound past zyerr_frame, clear it */
	if (frame_pointer)
	{
		if ((frame_pointer < (stack_frame *)msp)
				|| (frame_pointer > (stack_frame *)stackbase) || (frame_pointer < (stack_frame *)stacktop))
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_STACKUNDERFLO);
		assert((frame_pointer < frame_pointer->old_frame_pointer) || (NULL == frame_pointer->old_frame_pointer));
	}
	USHBIN_ONLY(CLEANUP_COPIED_RECURSIVE_RTN(rtnhdr));
	/* We just unwound a frame. May have been either a zintrupt frame and/or may have unwound a NEW'd ZTRAP or even cleared
	 * our error state. If we have a deferred timeout and none of the deferral conditions are anymore in effect, release
	 * the hounds.
	 */
	if ((TREF(save_xfer_root)))
	{
		/*If TP timeout or ztimeout , check conditions before popping out */
		if (((TREF(save_xfer_root))->set_fn == tptimeout_set) || ((TREF(save_xfer_root))->set_fn == ztimeout_set))
		{
			if ((tp_timeout_deferred || TREF(ztimeout_deferred))
				UNIX_ONLY(&& !dollar_zininterrupt) && ((0 == dollar_ecode.index)
									|| !(ETRAP_IN_EFFECT)))
			{
				DBGDFRDEVNT((stderr, "op_unwind1: Calling pop_reset_xfer from op_unwind\n"));
				POP_XFER_ENTRY(&event_type, &set_fn, &param_val);
				xfer_set_handlers(event_type, set_fn, param_val, TRUE);
			}
		} else if (((TREF(save_xfer_root))->set_fn == jobinterrupt_set) UNIX_ONLY(&& !dollar_zininterrupt))
		{
			DBGDFRDEVNT((stderr, "op_unwind2: Calling pop_reset_xfer from op_unwind\n"));
			POP_XFER_ENTRY(&event_type, &set_fn, &param_val);
			xfer_set_handlers(event_type, set_fn, param_val, TRUE);
		}
	}
	return;
}
