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