/**********************************************************************
 * Copyright (c) 2005, 2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id: stack.c,v 1.2 2007/04/06 00:53:58 jkubasta Exp $
 * 
 * Contributors: 
 * IBM - Initial API and implementation
 **********************************************************************/

#include <assert.h>
#include "stack.h"
#include "RADataTransfer.h"
#include "RAComm.h"
#include "options.h"
#include "utility.h"
#include "JvmpiWriter.h"

extern void getStackFrameStructureLock();	/* for pre-aggregation. Belongs in a header file */
extern void releaseStackFrameStructureLock();	/* for pre-aggregation. Belongs in a header file */

static unsigned long _staticThreadIdCount = 0;

/* Allocates an entry in the symbol table for the object */
HashEntry * jvmpiAgent_CreateStack(JNIEnv * env_id)
{
 HashEntry *hashEntry;
 ThreadPrivateStorage *tps = (ThreadPrivateStorage *)jvmpiAgent_Calloc(sizeof(ThreadPrivateStorage));
 jvmpiAgent_initializeSegmentedValue(&tps->ticket, 0);
 tps->currentStackSize = ORIGINAL_STACK_SIZE;
 tps->tos = 0;
 tps->threadStartEventSeen = 0;
 tps->staticThreadId = ++_staticThreadIdCount;
 tps->env=env_id;
 tps->threadStackKnown=0;
 tps->boundaryDepth=-1;

 /*##MW There is a race condition between the increment and the copy that must be fixed */
 jvmpiAgent_incrementSegmentedValue(&_jvmpiAgent_collation, 0);
 jvmpiAgent_copySegmentedValue(&tps->collation, &_jvmpiAgent_collation);

 tps->stackEntry = (StackEntry *)jvmpiAgent_Calloc(tps->currentStackSize * sizeof(StackEntry));
 tps->buffer=(char *)ra_allocateMessageBlock(ORIGINAL_MESSAGE_BUFFER_SIZE);
#ifdef __OS400__ 
 tps->buffer2=(char *)ra_allocateMessageBlock(ORIGINAL_MESSAGE_BUFFER_SIZE+1);
#endif
 hashEntry = jvmpiAgent_CreateThreadSymbol(env_id);
 hashEntry->entry = tps;
 return hashEntry;
}

/** DESTROY_STACK  ************************************************************
  *
  */
void jvmpiAgent_DestroyStack(JNIEnv *env_id) {
 ThreadLocalStorage *tps;
 tps = jvmpiAgent_getThreadLocalStorage(env_id);
 if (tps)
 {
  HashEntry *hashEntry;
  hashEntry = jvmpiAgent_FindThreadSymbol(env_id);
  free(tps->stackEntry);
  ra_freeMessageBlock((unsigned char *)tps->buffer);
#ifdef __OS400__ 
  ra_freeMessageBlock((unsigned char *)tps->buffer2);
#endif
  jvmpiAgent_DeleteSymbol(hashEntry, Thread_t);
 }
}

/** PUSH  ***********************************************************************
  */
StackEntry * jvmpiAgent_Push(ThreadPrivateStorage *tps, JNIEnv *env_id, HashEntry *methodHashEntry, HashEntry * objectHashEntry, timestamp_t timestamp, timestamp_t cpu_timestamp)
{
 if (!tps) return 0;
 tps->tos++;

 /* If the Stack is full, increment it's size by STACK_INCREMENT_SIZE */
 if(tps->tos == tps->currentStackSize)
 {
   StackEntry *oldStack=tps->stackEntry;
   int newStackSize=tps->currentStackSize+STACK_INCREMENT_SIZE;
   tps->stackEntry=(StackEntry *)jvmpiAgent_Calloc(newStackSize * sizeof(StackEntry));
   memcpy(tps->stackEntry, oldStack, tps->currentStackSize * sizeof(StackEntry));
   free(oldStack);
   tps->currentStackSize=newStackSize;
 }

 jvmpiAgent_incrementSegmentedValue(&tps->ticket, 0);
 jvmpiAgent_copySegmentedValue(&(tps->stackEntry[tps->tos].ticket), &tps->ticket);
 tps->stackEntry[tps->tos].objectHashEntry = objectHashEntry;

 if (tps->stackEntry[tps->tos].methodHashEntry = methodHashEntry) /* Note: Intentional assignment rather than compare */
 {
  METHOD_ENTRY(methodHashEntry)->methodCount++;
 }
 /* Get the timestamp information if necessary */
 if(_jvmpiAgent_Options.timestamp)
 {
   tps->stackEntry[tps->tos].timestamp = timestamp;
 }
 if(_jvmpiAgent_Options.cpuTime) {
    tps->stackEntry[tps->tos].cpuTime=cpu_timestamp; 
 }
 else {
    tps->stackEntry[tps->tos].cpuTime=0;
 }
 tps->stackEntry[tps->tos].printed = 0;
 tps->stackEntry[tps->tos].tps = tps;
 tps->stackEntry[tps->tos].entryEventSeen=1;

 /* If pre-aggregating, initialize the StackEntry's preagg fields */
 if(_jvmpiAgent_Options.compressLevel==CompressAggregate) {
	 StackEntry* new_entry = &tps->stackEntry[tps->tos];
	 new_entry->baseTime = 0;
	 new_entry->baseCPUTime = 0;
	 new_entry->lastEntryTime = 0;
	 new_entry->lastEntryCPUTime = 0;
	 new_entry->realFrame = 0;
     new_entry->stackFrame = NULL;   /* 136721 (permanent) */
 }

 if(tps->tos==1) {
	tps->stackEntry[tps->tos].currentBoundaryDepth=0;
 }
 else {
     if(tps->stackEntry[tps->tos-1].currentBoundaryDepth==0) {
		 tps->stackEntry[tps->tos].currentBoundaryDepth=0;
	 }
	 else {
		 tps->stackEntry[tps->tos].currentBoundaryDepth=tps->stackEntry[tps->tos-1].currentBoundaryDepth-1;
	 }
 }
 if(_jvmpiAgent_Options.compressLevel==CompressAggregate)  /* 136721 (permanent) */ 
 {
	/* pre-aggregation: initialize the StackFrame of this StackEntry.
	 *
	 * This code sets things up for a frame that's filtered out:
	 * stackFrame is set to NULL for the first StackEntry on a stack, 
	 * or to the same frame that the previous StackEntry points to.
	 * In either case, realFrame is set to zero (false).
	 *
	 * A StackEntry whose stackFrame points to its caller's stackFrame
	 * and whose "realFrame" flag is zero indicates a frame that's filtered out.
	 *
	 * If the entry being pushed is NOT filtered out, the caller of Push
	 * (usually handleMethodEntry) will call CreateStackFrame to change 
	 * these pointers and values.
	 */

	if (tps->tos == 1) {
		/* The newly-pushed frame is the only frame on the stack */
		tps->stackEntry[tps->tos].stackFrame = NULL;
	}
	else {
		/* There is a previous entry, a caller */
		StackEntry * caller = &tps->stackEntry[tps->tos - 1];
		tps->stackEntry[tps->tos].stackFrame = caller->stackFrame;
	}
	tps->stackEntry[tps->tos].realFrame = 0;
 }
 
 return &tps->stackEntry[tps->tos];
}

/* for pre-aggregation (that is, CompressAggregate) */
/*
 * Execute a "pop" for the aggregated method information.
 * This is called from jvmpiAgent_Pop (normal method exit).
 * It is also called from thread death, JVM shutdown,  and
 * suspendTracing processing: in those cases, we have to
 * account for the time in frames that are currently live
 * at thread death time or JVM shutdown time.
 *
 * Before calling this, the caller (especially the
 * thread-death and shutdown callers) need to update
 * baseTime of each stackEntry on the live stack(s).
 *
 * Note that a stackEntry can only be agPop'ed once. If it
 * is agPop'ed a second time, nothing happens. This is forced
 * by clearing the stackEntry->stackFrame field. This is a 
 * good practice generally and is used particularly in 
 * suspendTracing where an agRollUpStack is done followed by
 * a stackCleaner, both of which trigger agPops directly or
 * indirectly respectively.  (bug 134635)
 *
 * If I weren't such a chicken, I'd change the thread-death
 * and JVM shutdown handlers to simulate method-exit for all
 * active frames, and let that automatically handle the
 * bookkeeping. But I don't want to expose myself to breakage
 * because of (e.g.) method calls that arrive after thread death
 * or shutdown notification (from VMs with bad JVMPI impls).
 */

void jvmpiAgent_agPop(StackEntry* stackEntry) 
{
	StackFrame* frame = stackEntry->stackFrame;
	if (frame != NULL) {
		timestamp_t baseTime = stackEntry->baseTime;
		timestamp_t baseCPUTime = stackEntry->baseCPUTime;
		if (stackEntry->realFrame) {
			/* This is a "real" frame, one for a non-filtered method. */
			/* Update the times for this method along this call chain. */
			timestamp_t totalBaseTime ;
			timestamp_t totalBaseCPUTime ;

			/* 
			 * Do not acquire stackFrameStructureLock here, despite the 
			 * possibility that another thread is dumping data right now.
			 * It's just too expensive, and the risk of damage
			 * is limited to improper rolled-up data; you won't crash.
			 */
			totalBaseTime = frame->filteredMethodsBaseTime + baseTime;
			DBG_CHK_AND_REPORT_TIME_OVERFLOW(totalBaseTime, "Time overflowed?! (agPop: totalBaseTime)\n"); /* 134577 */

			if (frame->maxTime < totalBaseTime )
				frame->maxTime = totalBaseTime ;
			if (frame->minTime == 0 || frame->minTime > totalBaseTime )
				frame->minTime = totalBaseTime ;

			frame->baseTime += totalBaseTime ;
			DBG_CHK_AND_REPORT_TIME_OVERFLOW(frame->baseTime, "Time overflowed?! (agPop: frame->baseTime)\n"); /* 134577 */

			frame->filteredMethodsBaseTime = 0;

			/* Do it again for CPU time - just base time, no min/max */
			totalBaseCPUTime = frame->filteredMethodsBaseCPUTime + baseCPUTime;
			frame->baseCPUTime += totalBaseCPUTime;
			frame->filteredMethodsBaseCPUTime = 0;
			frame->numCalls ++ ;
		}
		else {
			/* This is a filtered method. Accumulate basetime
			   into the filteredMethodsBaseTime of this non-real frame
			   so we can roll it up to the real frame when we reach one. */
			frame->filteredMethodsBaseTime += baseTime;
			DBG_CHK_AND_REPORT_TIME_OVERFLOW(frame->filteredMethodsBaseTime, 
			                                 "Time overflowed?! (agPop: frame->filteredMethodsBaseTime)\n"); /* 134577 */			
			frame->filteredMethodsBaseCPUTime += baseCPUTime;
		}
		stackEntry->stackFrame = NULL;  /* 134635 */
	}
}

/** POP  ************************************************************************
  */
void jvmpiAgent_Pop(ThreadPrivateStorage *tps)
{
	if (tps != NULL  &&  tps->tos == 0)  {
		/* RKD:  We are underflowing here.  Mark the stack as invalid and return */
		tps->threadStackKnown=0;
		return;
	}
 assert(tps && tps->tos);
#ifdef SEGMENTED_VALUE_MULTIWORD
 free(tps->stackEntry[tps->tos].ticket.values);
#endif

  /* if pre-aggregating, roll up this frame's data to its StackFrame */
  if(_jvmpiAgent_Options.compressLevel==CompressAggregate)
	jvmpiAgent_agPop(&tps->stackEntry[tps->tos]);

 tps->tos--;
}

/**********************************************************
	jvmpiAgent_CreateStackFrame()
	Creates a stack frame for a stack entry. Called if this method is being
	tracked, then it searches the caller's stack frames, if not found 
	creates a new entry. Marks the StackEntry "real" to say it
	represents a tracked method, not one that's filtered out.
	
***************************************************************/
void jvmpiAgent_CreateStackFrame(ThreadPrivateStorage * tps, 
								 HashEntry* methodHashEntry,
								 StackEntry* caller)
{

	StackFrame * callerStackFrame = NULL;
	StackFrame * frame = NULL;
	StackFrame * it = NULL;
	StackFrame** base = NULL;
	StackFrame** prev = NULL;

	/*
	 * Search through the caller's stackframes to see if we already have
	 * a matching one. The "caller" might be the thread itself if the thread 
	 * stack is empty.
	 * 
	 * During this search, set "base" to point to the "calledList" that the
	 * frame if found on, or the one it belongs on if it's not found.
	 *
	 * Also, set "prev" to point to the "next" pointer that pointed to the
	 * found frame, if one is found; we use this to unlink the frame
	 * and move it to the base of the list it is on, as an optimization.
	 */	

	if( caller == NULL || caller->stackFrame == NULL) {
 		/* no caller; use the thread's list of top-level functions */
		callerStackFrame = NULL;
		base = &(tps->calledList);
		prev = base;
		it = *base;
	}
	else {
 		/* There is a caller: use that frame's list of called functions */
		callerStackFrame = caller->stackFrame;
		base = &(callerStackFrame->calledList);
		prev = base;
		it = *base;
	}

	/* now "it" is the first entry on the proper calledList */

	while ( it != NULL ) {
		if( it->methodHashEntry == methodHashEntry ) {
			/* we've seen this call before: re-use the existing frame */
			frame = it;
			/* Optimization opportunity here: 
			 * if the "it" frame isn't first, make it first, on the theory
			 * that the caller might call the same callee again soon,
			 * and it will make the search time shorter.
			 * Tradeoff: it will mean acquiring stackFrameStructureLock,
			 * which might wipe out the benefit. See notes elsewhere
			 * about stackFrameStructureLock, talking about why it's needed 
			 * and what the no-lock alternatives might be.
			 */
			break;
		}

		/* no match. update "prev" and "it" to walk to the next link in the chain */
		prev = &(it->next);
		it = *prev;
	}

	if ( frame == NULL ) {
		/* This is a new child of its caller; create a stack frame */

		/* Optimization opportunity: use a chunky allocator for StackFrame objects.
		 * They persist without being freed until they all get freed at once when this thread dies.
		 * Then again, malloc is usually pretty optimized, and jvmpiAgent_Calloc attempts some
		 * out-of-memory error reporting.
		 */
		frame = (StackFrame *)jvmpiAgent_Calloc(sizeof(StackFrame));
		frame->methodHashEntry = methodHashEntry;

		/* 
		 * Prepend this new frame to the proper calledList.
		 * Remember, "base" is a pointer to either tps->calledList or
		 * the caller's calledList. We prepend because it's quicker and
		 * because it could be a slight optimization for locality,
		 * making searches shorter. (Imagine a function call in a loop.)
		 *
		 * Get stackFrameStructureLock here because another thread might be 
		 * dumping a snapshot right now. We only use this when adding a new 
		 * unique frame, not on every entry/exit. See notes elsewhere
		 * about no-lock alternatives to stackFrameStructureLock.
		 */
		getStackFrameStructureLock();
		{
			frame->caller = callerStackFrame;
			frame->next = *base;
			*base = frame;
		}
		releaseStackFrameStructureLock();
#if defined (_DEBUG) && !defined (MVS) && !defined (__OS400__)
		printf("jvmpiAgent_CreateStackFrame: tps, &stackframe, &methodHashEntry, MethodIdRef= %8x, %8x, %8x, %5d\n",
			    (void *)tps, (void *)frame, (void*)methodHashEntry, (jint)((METHOD_ENTRY(methodHashEntry))->static_id)); 
		fflush(stdout);
#endif
	}

	tps->stackEntry[tps->tos].stackFrame = frame;
	tps->stackEntry[tps->tos].realFrame = 1;
}


/** PEEK  ***********************************************************************
  */
StackEntry * jvmpiAgent_Peek(ThreadPrivateStorage *tps, int offset)
{
 assert(tps);
 if((int)tps->tos <= offset)
	 return NULL;
 return &tps->stackEntry[tps->tos-offset];
}

/** TOP_OF_STACK  ***************************************************************
  */
unsigned long jvmpiAgent_TOS(ThreadPrivateStorage *tps)
{
 return tps ? tps->tos : 0;
}

/** STACK_DEPTH  ***************************************************************
  */
unsigned long jvmpiAgent_StackDepth(ThreadPrivateStorage *tps) {
	return tps->tos;
}
