/****************************************************************
 *								*
 * Copyright (c) 2011-2017 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 "gtm_string.h"

#include <rtnhdr.h>
#include "stack_frame.h"
#include "op.h"
#include "flush_jmp.h"
#include "dollar_zlevel.h"
#include "golevel.h"
#include <auto_zlink.h>
#include "error.h"
#include "gtmimagename.h"
#include "linktrc.h"
#ifdef UNIX
#include "gtm_unlink_all.h"
#endif
#ifdef GTM_TRIGGER
#include "gtm_trigger_trc.h"
#endif
#ifdef VMS
#include "pdscdef.h"
#include "proc_desc.h"
#endif

GBLREF	stack_frame	*frame_pointer;
#ifdef GTM_TRIGGER
GBLREF	boolean_t	goframes_unwound_trigger;
#endif

LITREF	gtmImageName 	gtmImageNames[];

error_def(ERR_LABELUNKNOWN);
error_def(ERR_RTNNAME);
error_def(ERR_ZGOCALLOUTIN);
error_def(ERR_ZGOTOINVLVL);
error_def(ERR_ZGOTOINVLVL2);
error_def(ERR_ZGOTOLTZERO);
error_def(ERR_ZGOTOTOOBIG);

void op_zgoto(mval *rtn_name, mval *lbl_name, int offset, int level)
{
	stack_frame	*fp, *fpprev;
	int4		curlvl;
	mval		rtnname, lblname;
	rhdtyp		*rtnhdr;
	lnr_tabent 	USHBIN_ONLY(*)*lnrptr;
	void		(*frame_func)();
	char		rtnname_buff[SIZEOF(mident_fixed)], lblname_buff[SIZEOF(mident_fixed)];
	DEBUG_ONLY(int4	dlevel;)
	DCL_THREADGBL_ACCESS;

	SETUP_THREADGBL_ACCESS;
	/* Validate level parm */
	curlvl = dollar_zlevel();
        if (0 > level)
	{	/* Negative level specified, means to use relative level change */
		if ((-level) > curlvl)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZGOTOLTZERO);
		level += curlvl;	/* Compute relative desired level */
	} else
	{	/* Else level is the level we wish to achieve - compute unrolls necessary */
		if (0 > (curlvl - level))
			/* Couldn't get to the level we were trying to unwind to */
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZGOTOTOOBIG);
	}
	/* Migrate mval parm contents to private buffers since the mvals could die as we unwind things */
	MV_FORCE_STR(rtn_name);
	MV_FORCE_STR(lbl_name);
	rtnname = *rtn_name;
	lblname = *lbl_name;
	memcpy(rtnname_buff, rtnname.str.addr, rtnname.str.len);
	rtnname.str.addr = rtnname_buff;
	memcpy(lblname_buff, lblname.str.addr, lblname.str.len);
	lblname.str.addr = lblname_buff;
	DBGEHND((stderr, "op_zgoto: rtnname: %.*s  lblname: %.*s  offset: %d  level: %d\n",
		 rtnname.str.len, rtnname.str.addr, lblname.str.len, lblname.str.addr, offset, level));
	/* Validate entryref before do any unwinding */
	if (0 == rtnname.str.len)
	{	/* If no routine name given, take it from the currently running routine unless the label name
		 * is also NULL. In that case, we must be doing an indirect routine name only and the supplied name
		 * was NULL.
		 */
		if ((0 == lblname.str.len) && !offset)
			rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_RTNNAME);
		rtnhdr = frame_pointer->rvector;
		ARLINK_ONLY(TADR(lnk_proxy)->rtnhdr_adr = rtnhdr);
		DBGINDCOMP((stderr, "op_zgoto: routine resolved to 0x"lvaddr" (1)\n", rtnhdr));
		if (0 == level)
		{	/* If doing unlink, recall name of routine as well as will need it later */
			memcpy(rtnname_buff, rtnhdr->routine_name.addr, rtnhdr->routine_name.len);
			rtnname.str.addr = rtnname_buff;
			rtnname.str.len = rtnhdr->routine_name.len;
		}
	} else
	{
#		ifdef AUTORELINK_SUPPORTED
		op_rhdaddr(&rtnname, -1);		/* Does an autozlink if necessary */
		rtnhdr = TADR(lnk_proxy)->rtnhdr_adr;
		DBGINDCOMP((stderr, "op_zgoto: routine resolved to 0x"lvaddr" (2)\n", rtnhdr));
#		else
		rtnhdr = op_rhdaddr(&rtnname, NULL);	/* Does an autozlink if necessary */
#		endif
#		ifdef VMS
		if (PDSC_FLAGS == ((proc_desc *)rtnhdr)->flags) /* it's a procedure descriptor, not a routine header */
		{
			rtnhdr = (rhdtyp *)(((proc_desc *)rtnhdr)->code_address);
			/* Make sure this if-test is sufficient to distinguish between procedure descriptors and routine headers */
			assert(PDSC_FLAGS != ((proc_desc *)rtnhdr)->flags);
		}
#		endif
	}
	assert(NULL != rtnhdr);
#	ifdef AUTORELINK_SUPPORTED
	op_labaddr(0, &lblname, offset);
	lnrptr = &TADR(lnk_proxy)->lnr_adr;
#	else
	lnrptr = op_labaddr(rtnhdr, &lblname, offset);
#	endif
	assert(NULL != lnrptr);
#	ifdef VMS
	/* VMS does not support unlink so any level 0 request that passes earlier checks just generates an error
	 * since the base frame cannot be rewritten as a GTM frame.
	 */
	if (0 == level)
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZGOTOINVLVL2);
#	endif
#	ifdef GTM_TRIGGER
	if (!IS_GTM_IMAGE && (1 >= level))
		/* In MUPIP (or other utility that gets trigger support in the future), levels 0 and 1 refer to
		 * the pseudo baseframe and initial stack levels which are not rewritable (in the case where an
		 * entry ref was coded) and are not resume-able (if no entry ref were specified) so we cannot
		 * permit ZGOTOs to these levels in a utility.
		 */
		rts_error_csa(CSA_ARG(NULL) VARLSTCNT(5) ERR_ZGOTOINVLVL, 3, GTMIMAGENAMETXT(image_type), level);
#	endif
#	ifdef UNIX
	/* One last check if we are unlinking, make sure no call-in frames exist on our stack */
	if (0 == level)
	{
		for (fp = frame_pointer; NULL != fp; fp = fpprev)
		{
			fpprev = fp->old_frame_pointer;
			if (!(fp->type & SFT_COUNT))
				continue;
			if (fp->flags & SFF_CI)
				/* We have a call-in frame - cannot do unlink */
				rts_error_csa(CSA_ARG(NULL) VARLSTCNT(1) ERR_ZGOCALLOUTIN);
			if (NULL == fpprev)
			{	/* Next frame is some sort of base frame */
#				ifdef GTM_TRIGGER
				if (fp->type & SFT_TRIGR)
				{	/* Have a trigger baseframe, pick up stack continuation frame_pointer stored by
					 *base_frame() */
					fpprev = *(stack_frame **)(fp + 1);
					continue;
				} else
#				endif
					break;			/* Some other base frame that stops us */
			}
		}
		/* Full unlink/unwind requested. First unlink everything, then relink our target entryref */
		gtm_unlink_all();
		frame_func = new_stack_frame;
		/* Now that everything is unwound, frame_pointer is the base frame. This frame contains the same rvector pointer
		 * that the level 1 stack frame routine has so it needs to also be rewritten once we link our new "1st routine".
		 */
		assert((NULL != frame_pointer) && (NULL == frame_pointer->old_frame_pointer));
#		ifdef AUTORELINK_SUPPORTED
		op_rhdaddr(&rtnname, -1);
		rtnhdr = TADR(lnk_proxy)->rtnhdr_adr;
#		else
		rtnhdr = op_rhdaddr(&rtnname, NULL);
#		endif
		assert(NULL != rtnhdr);
		frame_pointer->rvector = rtnhdr;
#		ifdef AUTORELINK_SUPPORTED
		op_labaddr(0, &lblname, offset);
		lnrptr = &TADR(lnk_proxy)->lnr_adr;
#		else
		lnrptr = op_labaddr(rtnhdr, &lblname, offset);
#		endif
		assert(NULL != lnrptr);
	} else
#	endif	/* UNIX */
	{	/* Unwind to our target level (UNIX if 0 != level  and VMS [all]) */
		GOLEVEL(level, TRUE);	/* Unwind the trigger base frame if we run into one */
		assert(level == dollar_zlevel());
		frame_func = flush_jmp;
	}
	/* Convert current stack frame (flush_jmp) to the frame for the entry ref we want to branch to, or create a new level 1
	 * frame (new_stack_frame) after ZGOTO 0.
	 */
	USHBIN_ONLY((* frame_func)(rtnhdr, (unsigned char *)LINKAGE_ADR(rtnhdr), LINE_NUMBER_ADDR(rtnhdr, *lnrptr)));
	/* on non-shared binary calculate the transfer address to be passed to flush_jmp as follows:
	 * 	1) get the number stored at lnrptr; this is the offset to the line number entry
	 *	2) add the said offset to the address of the routine header; this is the address of line number entry
	 *	3) dereference the said address to get the line number of the actual program
	 *	4) add the said line number to the address of the routine header
	 */
	NON_USHBIN_ONLY((* frame_func)(rtnhdr, (unsigned char *)LINKAGE_ADR(rtnhdr),
		(unsigned char *)((int)rtnhdr + *(int *)((int)rtnhdr + *lnrptr))));
	DBGEHND((stderr, "op_zgoto: Resuming at frame 0x"lvaddr" with type 0x%04lx\n", frame_pointer, frame_pointer->type));
#	ifdef GTM_TRIGGER
	if (goframes_unwound_trigger)
	{	/* If goframes() called by golevel unwound a trigger base frame, we must use MUM_TSTART to unroll the
		 * C stack before invoking the return frame. Otherwise we can just return and avoid the overhead that
		 * MUM_TSTART incurs.
		 */
		MUM_TSTART;
	}
#	endif
}
