Newsgroups: comp.sys.hp

> I'm trying to port a distributed system to a HP9000/710 running
> HP-UX 8.07. This system uses setjmp and longjmp for task switching.
> I need to access stackpointer and framepointer through the jmp_buf
> structure but nowhere in the HP documentation and also not in
> /usr/include/setjmp.h there is information on the position in 
> jmp_buf where the contents of the dumped registers are stored.

On PA, there is (usually) no framepointer.  Instead, stack frames
are a fixed size, and are created and destroyed on entry and exit.
I don't know if this is a problem for the package you're trying
to port.

> In the porting notes of the system is also read: 
> "It is not possible to use setjmp or longjmp on some systems
>  either because they contain bugs and fail to restore state
>  properly, or because they check that longjmp only returns to
>  a previously encountered stack frame, i.e setjmp was previously
>  executed on the current stack."

No problems here, though.

> So my questions are:
> - What registers are stored in what order in jmp_buf ?

rp and sp are saved in the first two words, respectively,
followed by the signal mask and an on-signal-stack flag.
Then the entry save registers (gr3-18) are saved.
Then gr19, sr3, fr12-15, a one-word flag indicating
whether the signal mask should be restored, a one-word
hole, fr16-21, rp-prime, and external-dp.

>   - If not how would assembly routines look like that could be 
>     used instead ?

I've attached below a simple user-level thread package that you might
be able to use.


Cary Coutant
Hewlett-Packard
California Language Lab


# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by Cary Coutant <cary@hpclpax> on Thu Dec 17 16:16:02 1992
#
# This archive contains:
#	threads.s	forks.c		Makefile	
#

LANG=""; export LANG
PATH=/bin:/usr/bin:$PATH; export PATH

echo x - threads.s
cat >threads.s <<'@EOF'
	.copyright "Hewlett Packard, 1992"

; Thread Control Block structure
;
; struct tcb {
;     long tcb_sp;
;     long tcb_stackbase;
;     long tcb_stacklimit;
;     /* anything else you may want */
;     }

TCB_SP		.equ	0
TCB_STACKBASE	.equ	4
TCB_STACKLIMIT	.equ	8


psp		.equ	64		; pseudo-register for previous sp



; long *init_thread_stack(
;		void (*code)(void),
;		void *base,
;		long size)
;
; Initializes a new thread's stack and returns the value of the
; stack pointer that the thread should initially use.
;
; <code> is the address of the function where the new thread will start.
;
; <base> is the base of the stack that has been allocated for the thread.
;
; <size> is the size of the stack.
;
; The stack frame is arranged so that it looks like the new thread
; has suspended execution at the beginning of __thread_init.  When
; the new thread is resumed, __thread_init (below) will then call
; the function <code>.

	.code

	.proc

	; ------------------------NOTE--------------------------
	; Use the first .callinfo for PA-RISC 1.0 machines.
	; Use the second for PA-RISC 1.1 machines.
	; .callinfo calls,save_rp,entry_gr=18,entry_fr=15,entry_sr=3
	; .callinfo calls,save_rp,entry_gr=18,entry_fr=21,entry_sr=3
	; ------------------------------------------------------

	.callinfo calls,save_rp,entry_gr=18,entry_fr=15,entry_sr=3
	.export init_thread_stack,entry

init_thread_stack
	.enter

	; Calculate far end of stack and align to 64-byte boundary
	add	%arg1,%arg2,%r22
	depi	0,31,6,%r22

	; Align base of stack to 64-byte boundary
	; and allocate a 64-byte stack frame
	; (This will be __thread_init's stack frame)
	ldo	64+63(%arg1),%arg1
	depi	0,31,6,%arg1

	; Store "magic number" at far end of stack for overflow detection
	mfctl	%cr16,%r21		; use interval timer as random no.
	uaddcm	%r0,%r21,%r20
	stw	%r21,-32(%r22) 		; Store Cr16 to AlgnLimit-32
	stw	%r20,-16(%r22)		; Store ~Cr16 to AlgnLimit-16

	; Mark the bottom of the new stack by storing zero in
	; the saved sp location (sp-4 of the new stack)
	stw	%r0,-4(%arg1)

	; Set the saved rp in __thread_init's stack frame so that execution
	; begins in the new thread at __thread_init
	ldil	L'__thread_init,%r1
	ldo	R'__thread_init(%r1),%r1
	stw	%r1,-20(%arg1)		; store at -20(new psp)

	; Save the <code> parameter in the new stack frame
	; (in __thread_init's parameter area)
	stw	%arg0,-36(%arg1)

	; Create a second stack frame on the new stack the same size
	; as our stack frame
	; (This will look like switch_thread's stack frame)
	; (The register save area will be garbage, but it doesn't matter)
	ldo	0(psp),%r19
	sub	%sp,%r19,%r20
	add	%r20,%arg1,%arg1

	; return the top of stack for the new thread
	copy	%arg1,%ret0

	.leave

	.procend



; __thread_init(void (*code)(void))
;
; Thread execution begins here upon return from switch_thread.
; The <code> parameter was saved in the parameter save area by
; init_thread_stack, so it must be picked up again from there.
;
; This is an internal routine.  It is never really "called";
; instead, it is "returned into" from switch_thread the first
; time that a thread is resumed.

	.proc
	.callinfo calls,save_sp
	.export	__thread_init,entry

__thread_init
	; No entry sequence -- the stack frame was already initialized
	; for us by init_thread_stack

	; Retrieve the <code> parameter from the stack frame
	ldw	-36(%sp),%arg0

	; Call <code> (in r3) to initiate new thread
	bl	.+8,%rp
	bv,n	0(%arg0)

	; control should never return here
	break	0,0

	.procend



; void switch_thread(struct tcb *new_thread)
;
; Transfers control from one thread to another.
; Saves minimal state of the old thread and restores
; minimal state for the new thread.
;
; init_thread_stack and switch_thread must have identical frame sizes.

	.proc

	; ------------------------NOTE--------------------------
	; Use the first .callinfo for PA-RISC 1.0 machines.
	; Use the second for PA-RISC 1.1 machines.
	; .callinfo calls,save_rp,entry_gr=18,entry_fr=15,entry_sr=3
	; .callinfo calls,save_rp,entry_gr=18,entry_fr=21,entry_sr=3
	; ------------------------------------------------------

	.callinfo calls,save_rp,entry_gr=18,entry_fr=15,entry_sr=3
	.export switch_thread,entry

switch_thread
	.enter

	; Get address of current thread's TCB from <cur_thread>
	.import cur_thread,data
	.import	$global$,data
	addil	L'cur_thread-$global$,%dp
	ldw	R'cur_thread-$global$(%r1),%r19

	; Check for stack overflow
	ldw	TCB_STACKLIMIT(%r19),%r20
	depi	0,31,6,%r20	
	ldw	-16(%r20),%r22
	ldw	-32(%r20),%r21
	uaddcm	%r0,%r22,%r22
	xor,=	%r22,%r21,%r0
	break	0,0

	; Switch stacks
	stw	%sp,TCB_SP(%r19)	; save old thread's sp in its TCB
	ldw	TCB_SP(%arg0),%sp	; get new thread's sp from <new_thread>

	; We continue here, now on the new thread's stack.
	; Put address of new TCB in <cur_thread>
	;addil	L'cur_thread-$global$,%dp ; (r1 is still live from above)
	stw	%arg0,R'cur_thread-$global$(%r1)
	
	.leave

	.procend

	.end
@EOF

chmod 644 threads.s

echo x - forks.c
cat >forks.c <<'@EOF'
#define STACKSIZE 65536

struct tcb {
    char *sp;
    char *base;
    char *limit;
    };

struct tcb tcb[3] = {0};
struct tcb *cur_thread = &tcb[0];

char stack1[STACKSIZE] = {0};
char stack2[STACKSIZE] = {0};
char stack3[STACKSIZE] = {0};

extern char *init_thread_stack(void (*proc)(void), char *base, int size);
extern void switch_thread(struct tcb *new_thread);

#pragma align 64
int limit[16] = {0};

int main(int argc, char **argv)
    {
    int i, ub;
    extern void fork1(void), fork2(void);

    if (argc > 1)
	ub = atoi(argv[1]);
    else
	ub = 20;

    tcb[0].limit = (char *)&limit[16];
    limit[12] = -1;

    tcb[1].sp = init_thread_stack(fork1, stack1, STACKSIZE);
    tcb[1].base = stack1;
    tcb[1].limit = stack1 + STACKSIZE;

    tcb[2].sp = init_thread_stack(fork2, stack2, STACKSIZE);
    tcb[2].base = stack2;
    tcb[2].limit = stack2 + STACKSIZE;

    for (i = 0; i < ub; i++) {
	switch_thread(&tcb[1]);
	switch_thread(&tcb[2]);
	}
    }

void fork1(void)
    {
    int i;
    extern void fork1b(void);

    for (i = 0; ; i++) {
	printf("%d\n", i);
	switch_thread(&tcb[0]);
	}
    }

void fork2(void)
    {
    int i, j, k;
    i = 0;
    j = 1;
    for (;;) {
	printf("\t%d\n", j);
	switch_thread(&tcb[0]);
	k = i + j;
	i = j;
	j = k;
	}
    }
@EOF

chmod 644 forks.c

echo x - Makefile
cat >Makefile <<'@EOF'
CFLAGS = -O -Aa
LDFLAGS = -Wl,-a,archive

OBJS = forks.o threads.o

forks: 	$(OBJS)
	$(CC) -o forks $(LDFLAGS) $(OBJS)
@EOF

chmod 644 Makefile

exit 0