[comp.sys.hp] HPPA Context switching code - still having problems

haines@debussy.cs.colostate.edu (Matt Haines) (06/30/90)

Hi again.

I'm still working on the HPPA context-switching code, and I'm still
having some problems.  I fixed my problem with doubles (the dynamic
storage allocator wasn't aligning things on full word boundaries),
but it still core dumps sometimes.  I say sometimes because for some
programs it seems to work fine.

Anyway, I have included the crux of the code.  There are two routines,
Save and Restore.  They both take an argument of an integer array
whose elements are used to save arguments 1 & 2, the PC and the SP, in
that order.  Two external variables are also used to save/restore the
SP and PC.

If there are any obvious errors in my assembly, please let me know.  I'm
having a helluva time getting this thing to run.  (BTW, I did the 
Decstation - Sparc - code in about a day and it worked fine; if anyone
needs that stuff, let me know).

Thanks in advance,
Matt.

-------------------------------------------------------------------------

		.CODE
Save
		.PROC				; declare a procedure
		.CALLINFO CALLER,FRAME=48
		.ENTRY
		LDO	96(30),30		; make room on the stack
						; for saved registers.
		;
		; save gr registers %gr19 .. %gr22
		;
		STW	19,-52(0,30)
		STW	20,-56(0,30)
		STW	21,-60(0,30)
		STW	22,-64(0,30)
		;
		; save fp registers %fr8 .. %fr11
		;
		LDO	-72(30),3
		FSTDS	%fr8,0(0,3)
		LDO	-80(30),3
		FSTDS	%fr9,0(0,3)
		LDO	-88(30),3
		FSTDS	%fr10,0(0,3)
		LDO	-96(30),3
		FSTDS	%fr11,0(0,3)
		;
		; Store the pc and sp to the argment array, elements 2 & 3.
		;
		STW	2,8(0,26)		; save the pc
		STW	30,12(0,26)		; save the sp
		;
		; Get new values for sp and pc from external variables
		;
		ADDIL	L%WorkerSP-$global$,27
		LDW	R%WorkerSP-$global$(0,1),30 ; restore the sp
		ADDIL	L%WorkerPC-$global$,27
		LDW	R%WorkerPC-$global$(0,1),2  ; restore the pc
		;
		; Ok, now go ...
		;
		BE	0(0,2)
		NOP
		.EXIT
		.PROCEND
		.IMPORT WorkerPC,DATA
		.IMPORT WorkerSP,DATA
		.IMPORT $global$,DATA
		.EXPORT Save

; *****************************************************************************

		.CODE
Restore
		.PROC				; declare a procedure
		.CALLINFO CALLER,FRAME=0
		.ENTRY
		;
		; Restore the pc and sp from the argument array, elements 2 & 3
		;
		LDW	8(0,26),2		; reset the pc
		LDW	12(0,26),30		; reset the sp
		;
		; restore gr registers %gr19 .. %gr22
		;
		LDW	-52(30),19
		LDW	-56(30),20
		LDW	-60(30),21
		LDW	-64(30),22
		;
		; restore fp registers %fr4 .. %fr11
		;
		LDO	-72(30),3
		FLDDS	0(0,3),%fr8
		LDO	-80(30),3
		FLDDS	0(0,3),%fr9
		LDO	-88(30),3
		FLDDS	0(0,3),%fr10
		LDO	-96(30),3
		FLDDS	0(0,3),%fr11
		;
		; Re-adjust the sp
		;
		LDO	-96(30),30
		;
		; Go ...
		;
		BE	0(0,2)			; start thread
		NOP
		.EXIT
		.PROCEND
		.EXPORT Restore

-----------------------------------------------------------------------------
--
Matt Haines <haines@cs.colostate.edu>   | "Don't take life too seriously ...
Colorado State University, CS Dept.     |     nobody gets out of here alive!"
503 University Services Center          |                     - Jim Morrison 
Ft. Collins, CO  80523 | (303) 491-1943 |

dhandly@hpcllz2.HP.COM (Dennis Handly) (07/01/90)

> Anyway, I have included the crux of the code.  There are two routines,
> Save and Restore.  They both take an argument of an integer array
> whose elements are used to save arguments 1 & 2, the PC and the SP, in
> that order.  Two external variables are also used to save/restore the
> SP and PC.
> Matt.

BTW, the correct name is now PA-RISC, not HP-PA.  But since you probably
paid good money for your machine, we'll let you get away with it this time.
:-)

I'm not sure what you are trying to do and whether you are mixing C or some
other high level language with assembly, or it is all assembly.

You appear to be violating some of the calling conventions.  I'll point
these out below.  I'll also include some suggestions and questions.

I assume you are somehow allocating several stacks that won't cause SP,
R30, to overlap?

-------------------------------------------------------------------------

		.CODE
Save
		.PROC				; declare a procedure
		.CALLINFO CALLER,FRAME=48
		.ENTRY
		LDO	96(30),30		; make room on the stack
						; for saved registers.
		;
		; save gr registers %gr19 .. %gr22
		;
Any reason why you are storing the registers in reverse order?
(this is not a problem except for caching??)
Any reason why you are storing these registers, caller save, instead of the
callee save registers??

		STW	19,-52(0,30)
		STW	20,-56(0,30)
		STW	21,-60(0,30)
		STW	22,-64(0,30)
		;
		; save fp registers %fr8 .. %fr11
		;
Any reason why you are storing the registers in reverse order?
(this is not a problem.)
Any reason why you are storing these registers, caller save, instead of the
callee save registers??
(Note you can eliminate 3 of the following LDOs by using an offset on the
FSTDS.  LDO  -80(30),3, then FSTDS 8,8(0,3)  FSTDS 9,0(0,3), etc.)

A possible problem here, you are using R3, which is a callee save register,
which means you must save and restore it before you exit.

		LDO	-72(30),3
		FSTDS	%fr8,0(0,3)
		LDO	-80(30),3
		FSTDS	%fr9,0(0,3)
		LDO	-88(30),3
		FSTDS	%fr10,0(0,3)
		LDO	-96(30),3
		FSTDS	%fr11,0(0,3)
		;
		; Store the pc and sp to the argment array, elements 2 & 3.
		;
		STW	2,8(0,26)		; save the pc
		STW	30,12(0,26)		; save the sp
		;
		; Get new values for sp and pc from external variables
		;
		ADDIL	L%WorkerSP-$global$,27
		LDW	R%WorkerSP-$global$(0,1),30 ; restore the sp
		ADDIL	L%WorkerPC-$global$,27
		LDW	R%WorkerPC-$global$(0,1),2  ; restore the pc
		;
		; Ok, now go ...
		;
How does the following work?  Do you mean BV  0(2) or  BE 0(4,2)?
Are you jumping back and forth between code and data spaces??
SR0 normally contains garbage.   ????
(Also you might want to nullify the instruction.)
		BE	0(0,2)
		NOP
		.EXIT
		.PROCEND
		.IMPORT WorkerPC,DATA
		.IMPORT WorkerSP,DATA
		.IMPORT $global$,DATA
		.EXPORT Save

; *****************************************************************************

		.CODE
Restore
		.PROC				; declare a procedure
		.CALLINFO CALLER,FRAME=0
		.ENTRY
		;
		; Restore the pc and sp from the argument array, elements 2 & 3
		;
		LDW	8(0,26),2		; reset the pc
		LDW	12(0,26),30		; reset the sp
		;
		; restore gr registers %gr19 .. %gr22
Any reason why you are restoring these registers, caller save, instead of the
callee save registers??
		;
		LDW	-52(30),19
		LDW	-56(30),20
		LDW	-60(30),21
		LDW	-64(30),22
		;
		; restore fp registers %fr4 .. %fr11
		;
Any reason why you are restoring these registers, caller save, instead of the
callee save registers??
(Note you can eliminate 3 of the following LDOs by using an offset on the
FLDDS.  LDO  -80(30),3, then FLDDS 8,8(0,3)  FLDDS 9,0(0,3), etc.)

A possible problem here, you are using R3, which is a callee save register,
which means you must save and restore it before you exit.
		LDO	-72(30),3
		FLDDS	0(0,3),%fr8
		LDO	-80(30),3
		FLDDS	0(0,3),%fr9
		LDO	-88(30),3
		FLDDS	0(0,3),%fr10
		LDO	-96(30),3
		FLDDS	0(0,3),%fr11
		;
		; Re-adjust the sp
		;
		LDO	-96(30),30
		;
		; Go ...
		;
How does the following work?  Do you mean BV  0(2) or  BE 0(4,2)?
Are you jumping back and forth between code and data spaces??
SR0 normally contains garbage.   ????
(Also you might want to put the LDO in the delay slot.)
		BE	0(0,2)			; start thread
		NOP
		.EXIT
		.PROCEND
		.EXPORT Restore

I hope the above comments help.  I would suspect your problem is with R3 and
the BE.

jmorris@hpsemc.HP.COM (John V. Morris) (07/03/90)

Here's some code for working with coroutines.  I've avoided the issues
of knowing which registers need saving by invoking setjmp/longjmp to save
and restore them.


John Morris
HP VAB Partners Lab
(408)725-3871


-------------------------------- Cut Here ----------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unwrap it by saving it in a file and typing "sh file".
#
# Wrapped by jmorris at hpsemc on Mon Jul  2 11:58:20 1990
# Contents:
#	Makefile 	coroutine.s 	test.c 		

PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:$PATH; export PATH
echo 'At the end, you should see the message "End of shell archive."'


echo Extracting Makefile
sed 's/^@//' >Makefile <<'@//E*O*F Makefile//'
all: coroutine.o  test

test: test.o coroutine.o
	cc -g test.o coroutine.o -o test

@.s.o:
	cc -c $*.s 
@//E*O*F Makefile//

set `wc -lwc <Makefile`
if test $1 -ne 7 -o $2 -ne 16 -o $3 -ne 103
then	echo ! Makefile should have 7 lines, 16 words, and 103 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 640 Makefile


echo Extracting coroutine.s
cat >coroutine.s <<'@//E*O*F coroutine.s//'
;*************************************************************************
;
;     #include <setjmp.h>
;     char stack[STACKSIZE];
;     jmp_buf env;
;     jmp_buf oldenv, newenv;
;
;     coroutine(env, routine, param, stack, STACKSIZE);
;
;     resume(oldenv, newenv)
;
; Coroutine() initializes a new co-routine.
;   Co-routines are very similar to processes, but they all take place
;   inside one UNIX process.  Since the operating system is not involved,
;   the context switch time ("resume" time) is very short.
;
; Resume() switches from one co-routine to another.  The current context
;   is saved in oldenv, and the new context is loaded from newenv.
;
; When a co-routine is activated the first time, the specified procedure
;   will be invoked with the specified parameter.   The procedure should
;   not return.  (It should resume to another coroutine, or exit).
;
; History
; 900702  John Morris  Using sigsetjmp() and siglongjmp(), the POSIX routines.
;
; 891031  John Morris  Changed call to $$dyncall to match conventions.
;                      Linker was blindly replacing the bl $$dyncall,2
;                      with ble $$dyncall, and return address was messed up.
;
; 880506  John Morris  Changed name to "coroutine" and included the resume
;                      function in same file. Also, added parameter that
;                      gets passed to coroutine the first time it is invoked.
;                      Use $$dyncall to do dynamic subroutine call instead
;                      of ble instruction.
; 
; 870000 John Morris   Originally written to satisfy various customer's needs
;                      at the HP Technology Access Center.
;**************************************************************************

; Should the signal mask be saved as part of the context?
#define SAVEMASK 0  /* 0 means don't save it,  1 means save it */

    .code
#define env arg0
#define routine arg1
#define param arg2
#define stack arg3
#define size r31

#define temp r31

    .import sigsetjmp
    .import siglongjmp
    .import $$dyncall

; void coroutine(env, routine, param, stack, size);
;************************************************************************
; coroutine creates a 'setjmp' environment with a new stack
;************************************************************************
coroutine
    .proc
    .callinfo caller, save_rp
    .export coroutine
    .enter

; Note that 'size' is not actually used by this routine. It is necessary
;   if equivalent routines are to be implemented on other architectures
;   (for example, if stack grows down instead of up).

; align the new stack area to an 8 byte boundary and allocate two frames
;    the frames will be referred to as 'current' and 'next'
    addi 103, stack, stack   ; add 7 to round up and add 96 for two frames.
    depi  0, 31, 3, stack    ; clear the low 3 bits, effect is to round up.

; save the local variables in the current stack frame
    stw r0, -68(stack)   ; set a zero return address so debugger trace stops
    stw sp, -48(stack)   ; save the current stack pointer so it can be restored
    stw routine, -88(stack)   ; save the routine address to invoke later on
    stw param, -84(stack) ; save the parameter to pass on

; switch to the new stack and save the environment
    copy stack,sp
    .call
    bl   sigsetjmp, rp
    ldi  SAVEMASK, arg1

; if we return to this point via 'longjmp' (ie. return value is non-zer0) ...
if1 comb,=,n  ret0, r0, else1     ; if return value is zero, skip ahead

    ; ... then call the routine with the argument given to 'longjmp'
        ldw -88(sp), r22     ; get the procedure address from stack
        ldw -84(sp), arg0    ; get the parameter we saved in the stack
        .call
        bl  $$dyncall, 31    ; invoke the procedure
        copy   31, rp        ; (Note: linker substitutes ble for bl)

    ; note: the routine should not return
        break

; otherwise, restore the stack pointer and do a normal return
else1
    ldw -48(sp), sp
    .leave
endif1
    .procend




; Note:  resume() could be written in C, but it is included here in
;  assembly in order to keep the two routines together.
;         if (sigsetjmp(oldenv, SAVEMASK) == 0)
;             siglongjmp(newenv,1);

; resume(oldenv, newenv)
;********************************************************************
; resume switches from one coroutine to another
;********************************************************************
    .proc
    .callinfo caller, frame=0, entry_gr=3, save_rp
    .export resume, entry
resume
    .enter

; save the current context with sigsetjmp
    copy  arg1, r3             ; save pointer to newenv
    bl    sigsetjmp, rp
    ldi   SAVEMASK, arg1

; if return value is zero, invoke the new co-routine
; (otherwise, another coroutine just invoked us, so just return)
if2 comib,<>,n   0, ret0, endif2      ; see if return value was zero
    bl           siglongjmp, rp       ; if so, invoke longjmp
    copy         r3, arg0             ; ... after restoring the parameter
endif2

; done
    .leave
    .procend
    .end
@//E*O*F coroutine.s//

set `wc -lwc <coroutine.s`
if test $1 -ne 141 -o $2 -ne 724 -o $3 -ne 5092
then	echo ! coroutine.s should have 141 lines, 724 words, and 5092 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 660 coroutine.s


echo Extracting test.c
cat >test.c <<'@//E*O*F test.c//'
/******************************************************************
Test program for coroutines.  

Creates a bunch of coroutines, and then switches among them
**********************************************************************/

#include <setjmp.h>
#define COUNT 10

typedef char stack_type[50000];

/* allocate a stack and a context for each coroutine */
/*   (also, allocate an extra context for the main program) */
jmp_buf env[COUNT+1];  
stack_type stack[COUNT];


main()
    {
    int i, proc();

    /* create the coroutines */
    for (i = 0; i<COUNT;  i++)
        coroutine(env[i], proc, i, stack[i], sizeof(stack[i]));

    /* invoke the first coroutine */
    resume(env[COUNT], env[0]);

    printf("Back to main program\n");

    /* do it a second time */
    resume(env[COUNT], env[0]);

    printf("Back to main program the second time\n");

    }


proc(n)
/******************************************************
proc is the initial coroutine procedure
********************************************************/
    int n;
    {
    printf("Co-routine %d was invoked\n", n);
    resume(env[n], env[n+1]);

    printf("Co-routine %d invoked a second time\n", n);
    resume(env[n], env[n+1]);
    }
@//E*O*F test.c//

set `wc -lwc <test.c`
if test $1 -ne 50 -o $2 -ne 129 -o $3 -ne 1218
then	echo ! test.c should have 50 lines, 129 words, and 1218 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 640 test.c

echo "End of shell archive."
exit 0

jmorris@hpsemc.HP.COM (John V. Morris) (07/03/90)

Here's another version of the coroutine stuff.  This version saves and restores
registers directly instead of using setjmp/longjmp.

John Morris
HP VAB Partners Lab
(408)725-3871

----------------------------- Cut Here -----------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before this line,
# then unwrap it by saving it in a file and typing "sh file".
#
# Wrapped by jmorris at hpsemc on Mon Jul  2 15:09:19 1990
# Contents:
#	Makefile 	coroutine.s 	test.c 		

PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:$PATH; export PATH
echo 'At the end, you should see the message "End of shell archive."'


echo Extracting Makefile
sed 's/^@//' >Makefile <<'@//E*O*F Makefile//'
all: coroutine.o  test

test: test.o coroutine.o
	cc -g test.o coroutine.o -o test

@.s.o:
	cc -c $*.s 
@//E*O*F Makefile//

set `wc -lwc <Makefile`
if test $1 -ne 7 -o $2 -ne 16 -o $3 -ne 103
then	echo ! Makefile should have 7 lines, 16 words, and 103 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 640 Makefile


echo Extracting coroutine.s
cat >coroutine.s <<'@//E*O*F coroutine.s//'
;*************************************************************************
;
;     char stack[STACKSIZE];
;     typedef int environment[1];
;     
;     environment env;
;     environment oldenv, newenv;
;
;     coroutine(env, routine, param, stack, STACKSIZE);
;
;     resume(oldenv, newenv)
;
; Coroutine() initializes a new co-routine.
;   Co-routines are very similar to processes, but they all take place
;   inside one UNIX process.  Since the operating system is not involved,
;   the context switch time ("resume" time) is very short.
;
; Resume() switches from one co-routine to another.  The current context
;   is saved in oldenv, and the new context is loaded from newenv.
;
; When a co-routine is activated the first time, the specified procedure
;   will be invoked with the specified parameter.   The procedure should
;   not return.  (It should resume to another coroutine, or exit).
;
; History
; 900702  John Morris  Anticipating shared libraries.  Code is now position
;                      independent and it saves gr19 as well.
;
; 900702  John Morris  Using the .enter and .leave macros to save and restore
;                      registers in the stack.  Environment only contains
;                      the stack pointer.
;
; 891103  John Morris  Now saving and restoring registers directly instead 
;                      of calling setjmp, longjmp.  Should work on MPE-XL now.
;
; 891031  John Morris  Changed call to $$dyncall to match conventions.
;                      Linker was blindly replacing the bl $$dyncall,2
;                      with ble $$dyncall, and return address was messed up.
;
; 880506  John Morris  Changed name to "coroutine" and included the resume
;                      function in same file. Also, added parameter that
;                      gets passed to coroutine the first time it is invoked.
;                      Use $$dyncall to do dynamic subroutine call instead
;                      of ble instruction.
; 
; 870000 John Morris   Originally written to satisfy various customer's needs
;                      at the HP Technology Access Center.
;**************************************************************************
    .code

#define env arg0
#define routine arg1
#define param arg2
#define stack arg3
#define size r31

#define temp r31

    .import $$dyncall

; void coroutine(env, routine, param, stack, size);
;************************************************************************
; coroutine creates a 'setjmp' environment with a new stack
;************************************************************************
coroutine
    .proc
    .callinfo
    .export coroutine
    .enter

; Note that 'size' is not actually used by this routine. It is necessary
;   if equivalent routines are to be implemented on other architectures
;   (for example, if stack grows down instead of up).

; align the new stack area to an 8 byte boundary
    addi  7, stack, stack    ; add 7 to round up 
    depi  0, 31, 3, stack    ; clear the low 3 bits, effect is to round up.

; define a dummy stack frame to serve as the "main"
    addi    48, stack, stack  ; allocate the smallest stack frame

; define the frame used during the "newstate" fragment.
    stw r0, -20(stack)        ; set a zero return address for debugger
    addi    56, stack, stack  ; allocate stack frame with two integers.
    stw routine, -56(stack)   ; save the routine address to invoke later on
    stw param, -52(stack)     ; save the parameter to pass on

; define the frame inside the "resume" program.  
    bl      .+8, temp                  ; get the address of newstate 
    ldo newstate-oldstate(temp), temp  ; (position independent in case we
oldstate                               ;  want to use it in shared libraries)
    stw	temp,-20(stack)        ; save "newstate" as the return address
    addi  112, stack, stack    ; MUST MATCH FRAME SIZE OF RESUME().
    ; initial values of registers don't matter.

; save the stack pointer in the environment variable
    stw	stack,0(env)            ; save the new stack pointer

; done
        .leave
	.procend



; void newstate()
;*******************************************************************
; newstate is the the first code executed when a new coroutine starts up
;*******************************************************************
; newstate takes care of the initial dynamic procedure call. Doing the
;   call indirectly solves a number of problems relating to cross-space
;   jumps.  It also makes it easy to pass a parameter to the starting
;   procedure.
;
; Note that newstate does not actually get called, but resume() "returns" 
;   to it the first time a coroutine gets started up.
;
    .proc
    .callinfo caller, frame=8
newstate

; call the initial routine, passing it the specified parameter
    ldw -56(sp), r22     ; get the procedure address from stack
    ldw -52(sp), arg0    ; get the parameter we saved in the stack
    .call
    bl  $$dyncall, 31    ; invoke the procedure
    copy   31, rp        ; (Note: linker substitutes ble for bl)

    ; the routine should not return
    break

    .procend




old     .equ  arg0
new     .equ  arg1

; resume(old, new)
;*********************************************************************
; resume switches context from one coroutine to another
;*********************************************************************
        .proc
        .callinfo save_rp, entry_gr=19, entry_fr=15, entry_sr=3
        .export resume
resume
        .enter

; save the old stack pointer and restore the new
        stw     sp, 0(old)
        ldw     0(new), sp

; done
        .leave
	.procend
@//E*O*F coroutine.s//

set `wc -lwc <coroutine.s`
if test $1 -ne 155 -o $2 -ne 767 -o $3 -ne 5641
then	echo ! coroutine.s should have 155 lines, 767 words, and 5641 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 660 coroutine.s


echo Extracting test.c
cat >test.c <<'@//E*O*F test.c//'
/******************************************************************
Test program for coroutines.  

Creates a bunch of coroutines, and then switches among them
**********************************************************************/

#define COUNT 10

typedef int environment[1];
typedef char stack_type[50000];

/* allocate a stack and a context for each coroutine */
/*   (also, allocate an extra context for the main program) */
environment env[COUNT+1];  
stack_type stack[COUNT];


main()
    {
    int i, proc();

    /* create the coroutines */
    for (i = 0; i<COUNT;  i++)
        coroutine(env[i], proc, i, stack[i], sizeof(stack[i]));

    /* invoke the first coroutine */
    resume(env[COUNT], env[0]);

    printf("Back to main program\n");

    /* do it a second time */
    resume(env[COUNT], env[0]);

    printf("Back to main program the second time\n");

    }


proc(n)
/******************************************************
proc is the initial coroutine procedure
********************************************************/
    int n;
    {
    printf("Co-routine %d was invoked\n", n);
    resume(env[n], env[n+1]);

    printf("Co-routine %d invoked a second time\n", n);
    resume(env[n], env[n+1]);
    }
@//E*O*F test.c//

set `wc -lwc <test.c`
if test $1 -ne 50 -o $2 -ne 130 -o $3 -ne 1230
then	echo ! test.c should have 50 lines, 130 words, and 1230 characters
	echo ! but has $1 lines, $2 words, and $3 characters
fi
chmod 640 test.c

echo "End of shell archive."
exit 0