/*
 * Support code for Scout threads.  
 */
/* Patrick Bridges, August 12, 1997  */

#include "config.h"


#if defined(SCOUT_THREADS)

# include "gc_priv.h"
# include <sys/types.h>


bool GC_thr_initialized = FALSE;

void GC_thr_init(void)
{
    if (GC_thr_initialized)
	    return;
    GC_thr_initialized = TRUE;
}

/* The user level scout threads start with yields turned off to
 * avoid the GC yielding prior to thread initialization */
#if !defined(SCOUT)
int GC_scout_world_stopped = 1;
#else
int GC_scout_world_stopped = 0;
#endif

void 
GC_scout_no_yields()
{
    GC_scout_world_stopped++;
}

void 
GC_scout_allow_yields()
{
    GC_scout_world_stopped--;
}


static void preempt_on();
static void preempt_off();
/* Increments a counter, which is checked at the appropriate places.
 * If non-zero, the world is stopped. */
void 
GC_stop_world()
{
    preempt_off();
    GC_scout_no_yields();
    /* With the world stopped, we simply set a flag to prevent
     * any threadYield()-s being called from in the GC */
}

void 
GC_start_world()
{
    /* With the world stopped, we simply set a flag to prevent
     * any threadYield()-s being called from in the GC */
    GC_scout_allow_yields();
    preempt_on();
}

/* Non-kernel implmentations of certain primitives */
#ifndef SCOUT

# include <signal.h>
# include <sys/syscall.h>

/* Turn off preemption;  gross but effective.  		*/
/* Caller has allocation lock.				*/

static sigset_t old_mask;

static void 
preempt_off()
{
    sigset_t set;

    (void)sigfillset(&set);
    sigdelset(&set, SIGABRT);
    syscall(SYS_sigprocmask, SIG_SETMASK, &set, &old_mask);
}

static void 
preempt_on()
{
    syscall(SYS_sigprocmask, SIG_SETMASK, &old_mask, NULL);
}


#else /* !SCOUT */

/* Scout-kernel implementations of important routines */
#include <assert.h>

#include <scout/heap.h>
#include <scout/thread.h>
#include <machine/thread.h> /* for DEFAULT_STACK_SIZE */
typedef struct UList * UList;
struct UList {
    u_long      size;           /* size of this block (includes header size) */
    UList       next;           /* next free block */
    UList       prev;
#ifndef NDEBUG
    u_int       magic;
    u_long      owner;
    u_int       flag;
#endif
};

struct ThreadStack {
    union {
        void *sp;
        ThreadStack next;
    } u;
    void *stack_limit;
    /* More may follow here, which we ignore */
};

/* The list of blocks that the heap manager has "in use" */
extern struct UList in_use_list;
extern void kgdbBreakpoint();

void
GC_push_other_blocks()
{
    struct UList *heapBlock;
    Thread t, threadlist;

    extern ptr_t GC_approx_sp();
    extern bool GC_scout_is_foreign(ptr_t p);
    extern void GC_scout_add_foreign(ptr_t p);
    extern void GC_scout_remove_foreign(ptr_t p);

    /* We disallow yields in this routine, so that threads don't get 
     * retired */
    GC_scout_no_yields();

    /* Push the thread stacks, and add them to the list of foreign blocks
     * so that they doesn't get pushed later when we walk the in-use list. */
    threadlist = t = threadListThreads();
    while (t != 0) {
	if (t->stack) {
	    void *sp;
	    if (t == tSelf)
		sp = (void *)GC_approx_sp();
	    else
		sp = t->stack->u.sp;
	    GC_push_all_stack(sp, (long *)t->stack->stack_limit + 1);
	    /* Add this thread to the list of blocks we manage, so
	     * that we don't re-push the entire thread */
#ifdef PUSH_HEAP_BLOCKS
	    GC_scout_add_foreign((char *)t->stack->stack_limit - 
				 t->option.stacksize);
#endif

	}
	t = threadListNextThread(t);
    }

#ifdef PUSH_HEAP_BLOCKS

    /* Walk thread.c's free stack list, and add 
     * free stacks to the list of blocks that we manage, as we 
     * know they can contain lots of fake pointers */

#ifdef UNPUSH_FREE_STACKS /* XXX */
#endif 

    /* Walk the heap-manager's in-use list and push any other blocks 
     * that the garbage collector doesn't manage and hasn't yet seen */
    for (heapBlock = in_use_list.next; heapBlock != 0; 
	 heapBlock = heapBlock->next)
    {
	char *r;
	int isforeign;
	r = (char *)heapBlock + sizeof(struct UList);

	isforeign = GC_scout_is_foreign(r);

	/* If we don't manage this block on the in use list, push it. */
	if ( isforeign ) {
	    int blen;
	    blen = heapBlock->size - sizeof(struct UList);
	    GC_push_all_stack((ptr_t)r, (ptr_t)(r + blen));
	}
    }

    /* Re-walk the threadList, removing the threadStacks from the 
     * foreign list. XXX - Do I need to do this, since thread stacks
     * are recycled - perhaps I can just change DETACH_STACK() to set 
     * u.sp == stack_limit? */
    t = threadlist;
    while (t != 0) {
	if (t->stack) {
	    /* Add this thread to the list of blocks we manage, so
	     * that we don't re-push the entire thread */
	    GC_scout_remove_foreign((char *)t->stack->stack_limit - 
				    t->option.stacksize);
	}
	t = threadListNextThread(t);
    }

    /* Re-Walk thread.c's free stack list, and remove 
     * the free stacks from the list of blocks that we manage */

#ifdef UNPUSH_FREE_STACKS /* XXX */
#endif 

#endif /* PUSH_HEAP_BLOCKS */

    /* Re-allow yields */
    GC_scout_allow_yields();
}

/* We don't worry about preemption in the kernel now, since 
 * interrupt handlers don't do any allocation anyway */
static void 
preempt_off()
{
}

static void 
preempt_on()
{
}
#endif /* SCOUT */

#else /* SCOUT_THREADS */

# ifndef LINT
    int GC_no_scout_threads;
# endif

#endif
