/*  cstest.c -- context switch test
 *
 *  This program checks the MPD context switch routines in various ways.
 *  The expected output appears in cstest.stdout.
 *  Note that a "stack underflow" exit IS expected at the end.
 */


#include <ctype.h>
#include <stdio.h>
#include <math.h>
#include "../arch.h"
#include "../gen.h"

#define STACK_SIZE 16000	/* stack size; must be a multiple of 16 */
#define BOUNDARY 256		/* boundary size; must be multiple of 16 */

#define N_RECURSE 10		/* number of recursive calls */
#define N_STACK 5		/* number of stacks (also see code) */

#define CK_BEFORE 0xB4B4B4B4	/* check value for area below the stack */
#define CK_AFTER  0xAFAFAFAF	/* check value for area above the stack */

extern void mpd_build_context ();
extern void mpd_chg_context ();

struct envelope *spawn ();
double mul3 ();
void dotest(), ckenv(), err(), dstack(), dump();
void foo(), bar(), baz(), boo(), stir();
void mpd_check_stk(), mpd_stk_corrupted();
void mpd_stk_overflow(), mpd_stk_underflow();



/* enclose the stacks in a larger structure, to check for errors */

struct envelope {
    long before[BOUNDARY];	/* for boundary checks before */
    char stack[STACK_SIZE];	/* actual stack - must be mod-8 aligned */
    long after[BOUNDARY];	/* for boundary checks after */
};

struct envelope *env[N_STACK];	/* four stacks in envelopes */
char *s1, *s2, *s3, *s4, *s5;	/* actual context pointers */

int recursion_depth = 0;	/* current recursion depth */
int expect_error = 0;		/* is error expected? */



/*  main program.  */

main ()
{
    setbuf (stdout, (char *) NULL);	/* unbuffer stdout & stderr */
    setbuf (stderr, (char *) NULL);
    fprintf (stderr, "running context switch test, arch = %s\n", ARCH);
    dotest ((char *) NULL);
    err ("dotest() returned to main()");
    /*NOTREACHED*/
}



/*  create four contexts, then jump to the first.  */

void
dotest (context)
char *context;		/* caller's context */
{
    static char *p[] = { "one", "two", "three", "four" };

    env[0] = spawn(foo, "foo", (long)p[0], (long)0x111,(long)0x222,(long)0x333);
    env[1] = spawn(bar, "bar", (long)p[1], (long)p[2], (long)0x303,(long)0x404);
    env[2] = spawn(baz, "baz", (long)p[0], (long)p[1], (long)p[2], (long)0x300);
    env[3] = spawn(boo, "boo", (long)p[0], (long)p[1], (long)p[2], (long)p[3]);
    env[4] = spawn(stir,"stir",(long)0x501,(long)0x502,(long)0x503,(long)0x504);
    s1 = env[0]->stack;
    s2 = env[1]->stack;
    s3 = env[2]->stack;
    s4 = env[3]->stack;
    s5 = env[4]->stack;
    ckenv ();
    mpd_chg_context (s4, context);	/* should not return */
    err ("s4 returned to dotest()");
}



/*  create a stack and spawn a process.  */

struct envelope *
spawn (pc, name, arg1, arg2, arg3, arg4) 
void (*pc) ();
char *name;
long arg1, arg2, arg3, arg4;
{
    int i;
    struct envelope *r;

    printf ("creating stack for %s\n", name);
    r = (struct envelope *) malloc (sizeof (struct envelope));
    if (!r)
	err ("can't get memory for new stack");
    
    for (i = 0; i < BOUNDARY; i++) {
	r->before[i] = CK_BEFORE;
	r->after[i] = CK_AFTER;
    }
    for (i = 0; i < STACK_SIZE; i++)
	r->stack[i] = rand ();

    mpd_build_context (pc, r->stack, STACK_SIZE, arg1, arg2, arg3, arg4);
    return r;
}



/*  four coroutines for checking context switching.  */

/* foo is in context s1 */
void
foo (a)
char *a;
{
    printf (" 5. foo1(%s)\n", a);
    mpd_check_stk (s1);
    ckenv ();
    mpd_chg_context (s2, s1);
    printf (" 8. foo2(%s)\n", a);
    mpd_check_stk (s1);
    ckenv ();
    mpd_chg_context (s3, s1);
    printf ("XX. foom!!!!!!!\n");
    exit (1);
}

/* bar is in context s2 */
void
bar (a, b)
char *a, *b;
{
    double d;

    printf (" 6. bar1(%s,%s)\n", a, b);
    mpd_check_stk (s2);
    ckenv ();
    mpd_chg_context (s3, s2);
    printf ("10. bar2(%s,%s)\n", a, b);
    mpd_check_stk (s2);

    puts ("===== checking floating point =====");
    d = mul3 (mul3 (2., 3., 5., s2), mul3 (7., 11., 13., s2), 
	mul3 (17., 19., 23., s2), s2);
    if (d != 223092870.)
	printf ("oops -- product returned was %.20g\n", d);
    mpd_check_stk (s2);

    if (recursion_depth++ < N_RECURSE) {
	puts ("====== recursing ======");
	dotest (s2);
	err ("dotest() returned to bar()");
    }
    puts ("====== finishing up ======");
    printf ("now expect underflow error:\n");
    expect_error = 1;
    return;	/* should underflow and be caught */ 
}

/* baz is in context s3 */
void
baz (a, b, c)
char *a, *b, *c;
{
    printf (" 7. baz1(%s,%s,%s)\n", a, b, c);
    mpd_check_stk (s3);
    ckenv ();
    mpd_chg_context (s1, s3);
    printf (" 9. baz2(%s,%s,%s)\n", a, b, c);
    mpd_check_stk (s3);
    ckenv ();
    mpd_chg_context (s2, s3);
    printf ("XX. bazzzzzzzt!!!!!\n");
    exit (1);
}

/* boo is in context s4 */
void
boo (a, b, c, d)
char *a, *b, *c, *d;
{
    printf (" 1. boo1(%s,%s,%s,%s)\n", a, b, c, d);
    mpd_check_stk (s4);
    ckenv ();
    mpd_chg_context (s4, s4);
    printf (" 2. boo2(%s,%s,%s,%s)\n", a, b, c, d);
    mpd_check_stk (s4);
    ckenv ();
    mpd_chg_context (s4, s4);
    printf (" 3. boo3(%s,%s,%s,%s)\n", a, b, c, d);
    mpd_check_stk (s4);
    ckenv ();
    mpd_chg_context (s4, s4);
    printf (" 4. boo4(%s,%s,%s,%s)\n", a, b, c, d);
    mpd_check_stk (s4);
    ckenv ();
    mpd_chg_context (s1, s4);
    printf ("XX. boom!!!!!!!\n");
    exit (1);
}

/* stir is in context s5 */
void
stir ()		/* stir floating point registers, then switch to stack 2 */
{
    double x = 1.7, y = 3.2, z = 2.4;
    for (;;) {
	printf ("stirring...");
	x = sin ((y = cos (x + y + .4)) - (z = cos (x + z + .6)));
	mpd_chg_context (s2, s5);
    }
}



/*  multiply three doubles, with context switching  */

double mul3 (x, y, z, ctx)
double x, y, z;
char *ctx;		/* context */
{
    printf ("mul3...");
    mpd_chg_context (s5, ctx);		/* stir up the f.p. registers */
    printf ("returning\n");
    return x * y * z;
}



/*  check stack envelopes  */

void
ckenv ()
{
    int i, j;
    int nerrs = 0;
    struct envelope *e;

    for (i = 1; i <= N_STACK; i++) {
	e = env[i-1];
	for (j = 0; j < BOUNDARY; j++)
	    if (e->before[j] != CK_BEFORE) {
		printf ("error -- memory corrupted below stack %d\n", i);
		nerrs++;
		break;
	    }
	for (j = 0; j < BOUNDARY; j++)
	    if (e->after[j] != CK_AFTER) {
		printf ("error -- memory corrupted above stack %d\n", i);
		nerrs++;
		break;
	    }
    }
    if (nerrs)
	err ("aborting due to memory corruption");
}



/*  stack error handlers  */

void mpd_stk_corrupted () { err ("csw: stack corrupted"); }
void mpd_stk_overflow ()  { err ("csw: stack overflow"); }
void mpd_stk_underflow () { err ("csw: stack underflow"); }



/*  announce error, and exit.  */

void
err (s)
char *s;
{
    puts (s);
    if (expect_error)
	exit (0);
    else
	exit (1);
}



/*  dump stack s; this may help with debugging.  */
/*  NOT TESTED ON 64-BIT ARCHITECTURES.  */

void
dstack (s, label)
char *s, *label;
{
    dump (s, 0, 7, label);
    dump (s, STACK_SIZE - 100, STACK_SIZE - 1, "");
}



/*  dump(addr,m,n,label) -- dump addr+m through addr+n, with label.  */

void
dump (addr, m, n, label)
char *addr, *label;
int m, n;
{
    char c, *p = addr + m, *q = addr + n;  int i;
    if (!label)
	label = "";
    printf ("%s ---------------------------------------\n", label);
    while (p <= q)  {
	printf ("\t%p (%2d)  %08X  ", p, p - addr, i = * (int *) p);
	c = toascii (*p++);  putchar (isprint (c) ? c : '.');
	c = toascii (*p++);  putchar (isprint (c) ? c : '.');
	c = toascii (*p++);  putchar (isprint (c) ? c : '.');
	c = toascii (*p++);  putchar (isprint (c) ? c : '.');
	if (i != 0 && abs (i) <= 10000)
	    printf ("%6d\n", i);
	else
	    putchar ('\n');
    }
}
