uuUndo/QuickProfiler.c

1 line
10 KiB
C

//
// When __PROFILE is called, stack frame looks like this:
//
// sp+08 Caller's return address
// sp+04 Address of function name string
// sp Return address to start of function
//
// After we save registers and such, stack frame looks like this:
//
// sp+28 Caller's return address
// sp+24 Address of function name string
// sp+20 Return address to start of function
// sp+16 saved A0
// sp+12 saved A1
// sp+08 saved D0
// sp+04 saved D1
// sp saved D2
//
// Upon returning from __PROFILE, stack frame should look like this:
//
// sp Address of __PROFILEEND
//
//
#include <Profiler.h>
#include <Threads.h>
#include <Timer.h>
#include <stdio.h>
#include <string.h>
#pragma profile off
#if defined(powerc) || defined(__powerc)
extern void Profile_GetTime(UnsignedWide *time);
#else
#define Profile_GetTime(time) Microseconds(time)
#endif
#define THREAD_OFFSET 100
#define NAME_LENGTH 23
#define MAX_FUNCS 1000
#define MAX_THREADS 50
#define STACK_DEPTH 100
typedef struct {
char name[NAME_LENGTH+1];
int calls;
long address;
UnsignedWide totalTime;
} FunctionInfo;
typedef struct {
FunctionInfo *callerFunction;
long returnAddress;
} StackFrame;
typedef struct {
StackFrame stack[STACK_DEPTH];
StackFrame *stackPtr;
FunctionInfo *currentFunction;
UnsignedWide startTime;
} ThreadInfo;
static ThreadInfo gThread[MAX_THREADS];
static FunctionInfo gFunction[MAX_FUNCS];
static long gProfilerOn = false;
static ThreadInfo *gCurrentThread;
static FunctionInfo *gCallerFunction;
static long gSaveArea[5];
static FunctionInfo *GetIndex(char *functionName:__A0, long address:__D0):__A0;
static void StartTime(FunctionInfo *function:__A0, int call:__D0);
static void StopTime(int index:__D0);
static void GetThreadInfo(void);
static void PushFrame(long returnAddress:__D0);
static long PopFrame(void):__D0;
static asm pascal void __PROFILEEND(void);
#pragma parameter AddToWide(__A0, __A1)
void AddToWide(UnsignedWide *a, UnsignedWide *b) = {
0x2011, // move.l 0(a1),d0 ;d0 = hi
0x2229, 0x0004, // move.l 4(a1),d1 ;d1 = lo
0x2410, // move.l 0(a0),d2 ;d2 = hi
0xD2A8, 0x0004, // add.l 4(a0),d1 ;add lo parts
0xD580, // addx.l d0,d2 ;add hi parts with carry
0x2082, // move.l d2,0(a0) ;store high result
0x2141, 0x0004 // move.l d1,4(a0) ;store low result
};
#pragma parameter SubFromWide(__A1, __A0)
void SubFromWide(UnsignedWide *a, UnsignedWide *b) = {
0x2011, // move.l 0(a1),d0 ;d0 = hi
0x2229, 0x0004, // move.l 4(a1),d1 ;d1 = lo
0x2410, // move.l 0(a0),d2 ;d2 = hi
0x92A8, 0x0004, // sub.l 4(a0),d1 ;add lo parts
0x9580, // subx.l d0,d2 ;add hi parts with carry
0x2082, // move.l d2,0(a0) ;store high result
0x2141, 0x0004 // move.l d1,4(a0) ;store low result
};
pascal OSErr ProfilerInit(ProfilerCollectionMethod method, ProfilerTimeBase timeBase, short numFunctions, short stackDepth)
{
int i;
// initialize the function table
memset(gFunction, 0, sizeof(gFunction));
strcpy(gFunction[0].name, "Root_Function");
gFunction[0].address = -1;
gFunction[0].calls = 1;
// initialize the thread table
memset(gThread, 0, sizeof(gThread));
for (i = 0; i < MAX_THREADS; i++) gThread[i].stackPtr = gThread[i].stack;
Profile_GetTime(&gThread[0].startTime);
gThread[0].currentFunction = gFunction;
// make sure we're turned off
gProfilerOn = false;
return noErr;
}
pascal void ProfilerSetStatus(short on)
{
gProfilerOn = (on) ? true : false;
}
pascal OSErr ProfilerDump(StringPtr filename)
{
/*
static char cfile[256];
FunctionInfo *function;
double temp;
FILE *f;
int i;
BlockMoveData(filename + 1, cfile, *filename);
cfile[*filename] = 0;
f = fopen(cfile, "w");
if (f) {
for (i = 0, function = gFunction; i < MAX_FUNCS; i++, function++)
if (function->address) {
temp = ((double)function->totalTime.hi * 65536.0 * 65536.0) + (double)function->totalTime.lo;
fprintf(f, "%-40s %15.0lf ticks, %15.2lf ticks/call, %7d calls\n", function->name,
temp, temp/function->calls, function->calls);
}
fclose(f);
}
return noErr;
*/
}
pascal void ProfilerTerm(void)
{
gProfilerOn = false;
}
static void StartTime(FunctionInfo *function:__A0, int call:__D0)
{
UnsignedWide thisTime, diff;
FunctionInfo *last;
// get the current time
Profile_GetTime(&thisTime);
// if there was another function being timed, update that timer
last = gCurrentThread->currentFunction;
if (last) {
diff = gCurrentThread->startTime;
SubFromWide(&thisTime, &diff);
AddToWide(&last->totalTime, &diff);
}
// if we have a new function to track, reset the start time and increment the calling count
if (function) {
if (call) function->calls++;
gCurrentThread->startTime = thisTime;
}
// update the current thread's function pointer
gCurrentThread->currentFunction = function;
}
static FunctionInfo *GetIndex(char *functionName:__A0, long address:__D0):__A0
{
FunctionInfo *function;
register int i;
// loop over all functions, looking for an address match or an empty space
for (i = 0, function = gFunction; i < MAX_FUNCS; i++, function++) {
long a = function->address;
if (!a) break;
else if (a == address) return function;
}
// if we have space for a new function, create an entry
if (i < MAX_FUNCS) {
strncpy(function->name, functionName, NAME_LENGTH);
function->address = address;
return function;
}
// return nil if nothing could be found
return nil;
}
void GetThreadInfo(void)
{
// get a pointer to the current thread's info
ThreadID thread;
GetCurrentThread(&thread);
gCurrentThread = gThread + thread - THREAD_OFFSET;
}
void PushFrame(long returnAddress:__D0)
{
// get a pointer to the next empty stack frame, and increment the stack pointer
StackFrame *sp = gCurrentThread->stackPtr++;
// save the current function and return address
sp->callerFunction = gCurrentThread->currentFunction;
sp->returnAddress = returnAddress;
}
long PopFrame(void):__D0
{
// get a pointer to the next empty stack frame, and increment the stack pointer
StackFrame *sp = --gCurrentThread->stackPtr;
// save the current function and return address
gCallerFunction = sp->callerFunction;
return sp->returnAddress;
}
// When __PROFILE is called, stack frame looks like this:
//
// sp+0c Return space for Pascal function
// sp+08 Caller's return address
// sp+04 Address of function name string
// sp Return address to start of function
//
// After we save registers and such, stack frame looks like this:
//
// sp+32 Return space for Pascal function
// sp+28 Caller's return address
// sp+24 Address of function name string
// sp+20 Return address to start of function
// sp+16 saved A0
// sp+12 saved A1
// sp+08 saved D0
// sp+04 saved D1
// sp saved D2
//
// Upon returning from __PROFILE, stack frame should look like this:
//
// sp+04 Return space for Pascal function
// sp Address of __PROFILEEND
asm pascal void __PROFILE(char *functionName)
{
tst.l gProfilerOn // is the profiler on?
bne.s @doProfile // if so, go do the work
move.l (sp),4(sp) // otherwise, copy return address over parameter
addq.l #4,sp // adjust the stack pointer accordingly
rts // return
@doProfile:
_Debugger
clr.l gProfilerOn // clear the profiler flag -- we are not reentrant
movem.l a0/a1/d0-d2,-(sp) // save registers
jsr GetThreadInfo // get the current thread info into a global
move.l 28(sp),d0 // get the return address in d0
jsr PushFrame // and push it onto the stack
move.l 24(sp),a0 // now get the function name
move.l 20(sp),d0 // and the function address
move.l d0,24(sp) // (copy the address onto the stack for later)
jsr GetIndex // and return an index into our function table
moveq.l #1,d0 // set the call flag
jsr StartTime // start the timer for this baby (and stop the last one)
lea __PROFILEEND,a0 // point a0 to our profile end routine
move.l a0,28(sp) // store that in place of the real return address
movem.l (sp)+,a0/a1/d0-d2 // restore the registers
addq.l #4,sp // skip over stack space for the functionName parameter
move.l #1,gProfilerOn // turn the profiler flag back on
rts // and return
}
// sequence of events:
//
// call to __PROFILE
// clears profilerOn flag to prevent re-entrancy
// gets a pointer to the current thread info in gCurrentThread
// saves the currentFunction pointer and the real return address as a stack frame
// calculates a pointer to the new currentFunction
// stops the timer for the previous currentFunction and starts it for the new one
// replaces the real return address with a return into __PROFILEEND
// resets profilerOn flag
// returns to the beginning of the function
//
// return to __PROFILEEND
// clears profilerOn flag to prevent re-entrancy
// gets a pointer to the current thread info in gCurrentThread
// pops the stack frame, returning real return address and pointer to caller function
// saves real return address on the stack for returning
// stops the timer for the previous function and starts it back up for the caller function
// resets profilerOn flag
// returns to the calling function
//
// When __PROFILEEND is called, stack frame looks like this:
//
// sp Return space for Pascal function
//
// Upon returning from __PROFILE, stack frame should look like this:
//
// sp Address of __PROFILEEND
asm pascal void __PROFILEEND(void)
{
_Debugger
clr.l gProfilerOn // clear the profiler flag -- we are not reentrant
movem.l a0/d0,gSaveArea // save a0/d0
jsr GetThreadInfo // get the current thread info into a global
jsr PopFrame // pop the frame, returning the real return address
move.l d0,gSaveArea+8 // save return address in a safe place
move.l gCallerFunction,a0 // get pointer to new current function
clr.l d0 // clear the call flag
jsr StartTime // now start the timer
movem.l gSaveArea,a0/d0 // restore a0/d0
move.l gSaveArea+8,a1 // get return address in a1
move.l #1,gProfilerOn // turn the profiler flag back on
jmp (a1) // return to the caller
}