code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/*
 * This file (pa-risc.h) is part of the port of QuickThreads for the
 * PA-RISC 1.1 architecture.  This file is a machine dependent header
 * file.  It was written in 1994 by Uwe Reder
 * (`uereder@cip.informatik.uni-erlangen.de') for the Operating Systems
 * Department (IMMD4) at the University of Erlangen/Nuernberg Germany.
 */

#ifndef QT_PA_RISC_H
#define QT_PA_RISC_H

#include <qt.h>

/* size of an integer-register (32 bit) */
typedef unsigned long qt_word_t;

/* PA-RISC's stack grows up */
#define QT_GROW_UP

/* Stack layout on PA-RISC according to PA-RISC Procedure Calling Conventions:

    Callee-save registers are: gr3-gr18, fr12-fr21.
    Also save gr2, return pointer.

    +---
    | fr12          Each floating register is a double word (8 bytes).
    | fr13          Floating registers are only saved if `qt_block' is
    | fr14          called, in which case it saves the floating-point
    | fr15          registers then calls `qt_blocki' to save the integer
    | fr16	    registers.
    | fr17
    | fr18
    | fr19
    | fr20
    | fr21
    | <arg word 3>  fixed arguments (must be allocated; may remain unused)
    | <arg word 2>
    | <arg word 1>
    | <arg word 0>
    | <LPT>         frame marker
    | <LPT'>
    | <RP'>
    | <Current RP>
    | <Static Link>
    | <Clean Up>
    | <RP''>
    | <Previous SP>
    +---
    | gr3           word each (4 bytes)
    | gr4
    | gr5
    | gr6
    | gr7
    | gr8
    | gr9
    | gr10
    | gr11
    | gr12
    | gr13
    | gr14
    | gr15
    | gr16
    | gr17
    | gr18
    | <16 bytes filled in (sp has to be 64-bytes aligned)>
    | <arg word 3>  fixed arguments (must be allocated; may remain unused)
    | <arg word 2>
    | <arg word 1>
    | <arg word 0>
    | <LPT>         frame marker
    | <LPT'>
    | <RP'>
    | <Current RP>
    | <Static Link>
    | <Clean Up>
    | <RP''>
    | <Previous SP>
    +---            <--- sp
*/

/* When a never-before-run thread is restored, the return pc points
   to a fragment of code that starts the thread running.  For
   non-vargs functions, it just calls the client's `only' function.
   For varargs functions, it calls the startup, user, and cleanup
   functions. */

/* Note: Procedue Labels on PA-RISC

   <--2--><-------28---------><1-><1->
   -----------------------------------
   | SID |    Adress Part    | L | X |
   -----------------------------------

   On HP-UX the L field is used to flag wheather the procedure
   label (plabel) is a pointer to an LT entry or to the entry point
   of the procedure (PA-RISC Procedure Calling Conventions Reference
   Manual, 5.3.2 Procedure Labels and Dynamic Calls). */

#define QT_PA_RISC_READ_PLABEL(plabel) \
    ( (((int)plabel) & 2) ? \
        ( (*((int *)(((int)plabel) & 0xfffffffc)))) : ((int)plabel) )

/* Stack must be 64 bytes aligned. */
#define QT_STKALIGN (64)

/* Internal helper for putting stuff on stack (negative index!). */
#define QT_SPUT(top, at, val)   \
    (((qt_word_t *)(top))[-(at)] = (qt_word_t)(val))

/* Offsets of various registers which are modified on the stack.
   rp (return-pointer) has to be stored in the frame-marker-area
   of the "older" stack-segment. */

#define QT_crp  (12+4+16+5)
#define QT_15   (12+4+4)
#define QT_16   (12+4+3)
#define QT_17   (12+4+2)
#define QT_18   (12+4+1)

/** This stuff is for NON-VARARGS. **/

/* Stack looks like this (2 stack frames):

    <--- 64-bytes aligned --><------- 64-bytes aligned ------------>
   |                        ||                                      |
    <--16--><------48-------><----16*4-----><--16-><------48------->
   ||      |                ||             |      |                ||
   ||filler|arg|frame-marker||register-save|filler|arg|frame-marker||
   ------------------------------------------------------------------
 */

#define QT_STKBASE  (16+48+(16*sizeof(qt_word_t))+16+48)

/* The index, relative to sp, of where to put each value. */
#define QT_ONLY_INDEX   (QT_15)
#define QT_USER_INDEX   (QT_16)
#define QT_ARGT_INDEX   (QT_17)
#define QT_ARGU_INDEX   (QT_18)

extern void qt_start(void);
#define QT_ARGS_MD(sp)  \
    (QT_SPUT (sp, QT_crp, QT_PA_RISC_READ_PLABEL(qt_start)))

/** This is for VARARGS. **/

#define QT_VARGS_DEFAULT

/* Stack looks like this (2 stack frames):

    <------ 64-bytes aligned -------><--------- 64-bytes aligned ---------->
   |                                ||                                      |
    <---?--><--?---><16><----32-----><----16*4-----><-16--><16><----32----->
   ||      |       |   |            ||             |      |   |            ||
   ||filler|varargs|arg|frame-marker||register-save|filler|arg|frame-marker||
   --------------------------------------------------------------------------
 */

/* Sp is moved to the end of the first stack frame. */
#define QT_VARGS_MD0(sp, vasize) \
    ((qt_t *)(((char *)sp) + QT_STKROUNDUP(vasize + 4*4 + 32)))

/* To reach the arguments from the end of the first stack frame use 32
   as a negative adjustment. */
#define QT_VARGS_ADJUST(sp)	((qt_t *)(((char *)sp) - 32))

/* Offset to reach the end of the second stack frame. */
#define QT_VSTKBASE	((16*sizeof(qt_word_t)) + 16 + 4*4 + 32)

extern void qt_vstart(void);
#define QT_VARGS_MD1(sp) \
    (QT_SPUT (sp, QT_crp, QT_PA_RISC_READ_PLABEL(qt_vstart)))

#define QT_VARGT_INDEX      (QT_15)
#define QT_VSTARTUP_INDEX   (QT_16)
#define QT_VUSERF_INDEX     (QT_17)
#define QT_VCLEANUP_INDEX   (QT_18)

#endif /* ndef QT_PA_RISC_H */


code:
; pa-risc.s -- assembly support.

; QuickThreads -- Threads-building toolkit.
; Copyright (c) 1993 by David Keppel
;
; Permission to use, copy, modify and distribute this software and
; its documentation for any purpose and without fee is hereby
; granted, provided that the above copyright notice and this notice
; appear in all copies.  This software is provided as a
; proof-of-concept and for demonstration purposes; there is no
; representation about the suitability of this software for any
; purpose.

; This file (pa-risc.s) is part of the port of QuickThreads for
; PA-RISC 1.1 architecture.  This file implements context switches
; and thread startup.  It was written in 1994 by Uwe Reder
; (`uereder@cip.informatik.uni-erlangen.de') for the Operating
; Systems Department (IMMD4) at the University of Erlangen/Nuernberg
; Germany.

; Callee saves general registers gr3..gr18,
;              floating-point registers fr12..fr21.

            .CODE

            .IMPORT $$dyncall, MILLICODE
            .IMPORT qt_error, CODE

            .EXPORT qt_blocki, ENTRY
            .EXPORT qt_block, ENTRY
            .EXPORT qt_abort, ENTRY
            .EXPORT qt_start, ENTRY
            .EXPORT qt_vstart, ENTRY

; arg0: ptr to function (helper) to call once curr is suspended
;       and control is on arg3's stack.
; arg1: 1'th arg to *arg0.
; arg2: 2'th arg to *arg0.
; arg3: sp of new thread.

qt_blocki
            .PROC
            .CALLINFO   CALLER, FRAME=0, SAVE_RP, ENTRY_GR=18
            .ENTRY

            stw         %rp,-20(%sp)    ; save rp to old frame-marker

            stwm        %r3,128(%sp)    ; save callee-saves general registers
            stw         %r4,-124(%sp)
            stw         %r5,-120(%sp)
            stw         %r6,-116(%sp)
            stw         %r7,-112(%sp)
            stw         %r8,-108(%sp)
            stw         %r9,-104(%sp)
            stw         %r10,-100(%sp)
            stw         %r11,-96(%sp)
            stw         %r12,-92(%sp)
            stw         %r13,-88(%sp)
            stw         %r14,-84(%sp)
            stw         %r15,-80(%sp)
            stw         %r16,-76(%sp)
            stw         %r17,-72(%sp)
            stw         %r18,-68(%sp)

qt_abort
            copy        %arg0,%r22      ; helper to be called by $$dyncall
            copy        %sp,%arg0       ; pass current sp as arg0 to helper
            copy        %arg3,%sp       ; set new sp

            .CALL
            bl          $$dyncall,%mrp  ; call helper
            copy        %mrp,%rp

            ldw         -68(%sp),%r18   ; restore general registers
            ldw         -72(%sp),%r17
            ldw         -76(%sp),%r16
            ldw         -80(%sp),%r15
            ldw         -84(%sp),%r14
            ldw         -88(%sp),%r13
            ldw         -92(%sp),%r12
            ldw         -96(%sp),%r11
            ldw         -100(%sp),%r10
            ldw         -104(%sp),%r9
            ldw         -108(%sp),%r8
            ldw         -112(%sp),%r7
            ldw         -116(%sp),%r6
            ldw         -120(%sp),%r5
            ldw         -124(%sp),%r4

            ldw         -148(%sp),%rp   ; restore return-pointer

            bv          %r0(%rp)        ; return to caller
            ldwm        -128(%sp),%r3

            .EXIT
            .PROCEND

qt_block
            .PROC
            .CALLINFO   CALLER, FRAME=0, SAVE_RP, ENTRY_FR=21
            .ENTRY

            stw         %rp,-20(%sp)    ; save rp to old frame-marker

            fstds,ma    %fr12,8(%sp)    ; save callee-saves float registers
            fstds,ma    %fr13,8(%sp)
            fstds,ma    %fr14,8(%sp)
            fstds,ma    %fr15,8(%sp)
            fstds,ma    %fr16,8(%sp)
            fstds,ma    %fr17,8(%sp)
            fstds,ma    %fr18,8(%sp)
            fstds,ma    %fr19,8(%sp)
            fstds,ma    %fr20,8(%sp)
            fstds,ma    %fr21,8(%sp)

            .CALL
            bl          qt_blocki,%rp
            ldo         48(%sp),%sp

            ldo         -48(%sp),%sp

            fldds,mb    -8(%sp),%fr21   ; restore callee-saves float registers
            fldds,mb    -8(%sp),%fr20
            fldds,mb    -8(%sp),%fr19
            fldds,mb    -8(%sp),%fr18
            fldds,mb    -8(%sp),%fr17
            fldds,mb    -8(%sp),%fr16
            fldds,mb    -8(%sp),%fr15
            fldds,mb    -8(%sp),%fr14
            fldds,mb    -8(%sp),%fr13

            ldw         -28(%sp),%rp    ; restore return-pointer

            bv          %r0(%rp)        ; return to caller.
            fldds,mb    -8(%sp),%fr12

            .EXIT
            .PROCEND

qt_start
            .PROC
            .CALLINFO   CALLER, FRAME=0
            .ENTRY

            copy        %r18,%arg0      ; set user arg `pu'.
            copy        %r17,%arg1      ; ... user function pt.
            copy        %r16,%arg2      ; ... user function userf.
                                        ; %r22 is a caller-saves register
            copy        %r15,%r22       ; function to be called by $$dyncall

            .CALL                       ; in=%r22
            bl          $$dyncall,%mrp  ; call `only'.
            copy        %mrp,%rp

            bl,n        qt_error,%r0    ; `only' erroniously returned.

            .EXIT
            .PROCEND

; Varargs
;
; First, call `startup' with the `pt' argument.
;
; Next, call the user's function with all arguments.
; We don't know whether arguments are integers, 32-bit floating-points or
; even 64-bit floating-points, so we reload all the registers, possibly
; with garbage arguments.  The thread creator provided non-garbage for
; the arguments that the callee actually uses, so the callee never gets
; garbage.
;
;            -48    -44    -40    -36    -32
;             | arg3 | arg2 | arg1 | arg0 |
;             -----------------------------
; integers:     arg3   arg2   arg1   arg0
; 32-bit fps:  farg3  farg2  farg1  farg0
; 64-bit fps:  <---farg3-->  <---farg1-->
;
; Finally, call `cleanup' with the `pt' argument and with the return value
; from the user's function.  It is an error for `cleanup' to return.

qt_vstart
            .PROC
            .CALLINFO   CALLER, FRAME=0
            .ENTRY

            ; Because the startup function may damage the fixed arguments
            ; on the stack (PA-RISC Procedure Calling Conventions Reference
            ; Manual, 2.4 Fixed Arguments Area), we allocate a seperate
            ; stack frame for it.
            ldo         64(%sp),%sp

            ; call: void startup(void *pt)

            copy        %r15,%arg0      ; `pt' is arg0 to `startup'.
            copy        %r16,%r22
            .CALL
            bl          $$dyncall,%mrp  ; Call `startup'.
            copy        %mrp,%rp

            ldo         -64(%sp),%sp

            ; call: void *qt_vuserf_t(...)

            ldw         -36(%sp),%arg0  ; Load args to integer registers.
            ldw         -40(%sp),%arg1
            ldw         -44(%sp),%arg2
            ldw         -48(%sp),%arg3
            ; Index of fld[w|d]s only ranges from -16 to 15, so we
            ; take r22 to be our new base register.
            ldo         -32(%sp),%r22
            fldws       -4(%r22),%farg0 ; Load args to floating-point registers.
            fldds       -8(%r22),%farg1
            fldws       -12(%r22),%farg2
            fldds       -16(%r22),%farg3
            copy        %r17,%r22
            .CALL
            bl          $$dyncall,%mrp  ; Call `userf'.
            copy        %mrp,%rp

            ; call: void cleanup(void *pt, void *vuserf_return)

            copy        %r15,%arg0      ; `pt' is arg0 to `cleanup'.
            copy        %ret0,%arg1     ; Return-value is arg1 to `cleanup'.
            copy        %r18,%r22
            .CALL
            bl          $$dyncall,%mrp  ; Call `cleanup'.
            copy        %mrp,%rp

            bl,n        qt_error,%r0

            .EXIT
            .PROCEND

license: unknown


code:
; QuickThreads -- Threads-building toolkit.
; Copyright (c) 1993 by David Keppel

; Permission to use, copy, modify and distribute this software and
; its documentation for any purpose and without fee is hereby
; granted, provided that the above copyright notice and this notice
; appear in all copies.  This software is provided as a
; proof-of-concept and for demonstration purposes; there is no
; representation about the suitability of this software for any
; purpose.

; This file (pa-risc_b.s) is part of the port of QuickThreads for
; PA-RISC 1.1 architecture.  It contains assembly-level support for
; raw processor performance measurement.  It was written in 1994 by
; Uwe Reder (`uereder@cip.informatik.uni-erlangen.de')
; for the Operating Systems Department (IMMD4) at the
; University of Erlangen/Nuernberg Germany.

; Note that the number of instructions in the measurement-loops, differ
; from implementation to implementation. I took eight instructions in a loop
; for every test (execute eight instructions and loop to the start).

            .CODE

            .IMPORT $global$,DATA
            .IMPORT $$dyncall,MILLICODE
            .EXPORT b_call_reg
            .EXPORT b_call_imm
            .EXPORT b_add
            .EXPORT b_load

; Just do nothing, only return to caller. This procedure is called by
; `b_call_reg' and `b_call_imm'.

b_null
            .PROC
            .CALLINFO   NO_CALLS, FRAME=0
            .ENTRY

            bv,n        %r0(%rp)        ; just return

            .EXIT
            .PROCEND

; Call the procedure `b_null' with function pointer in a register.

b_call_reg
            .PROC
            .CALLINFO   CALLER, FRAME=0
            .ENTRY

            stwm        %r3,64(%sp)     ; store r3 (may be used by caller)
            stw         %rp,-20(%sp)    ; save return-pointer to frame-marker

            addil       LR'to_call-$global$,%r27
            ldw         RR'to_call-$global$(%r1),%r3

_loop0
            copy        %r3,%r22        ; copy the procedure label to r22, ...
            .CALL                       ; ...this is the input to $$dyncall
            bl          $$dyncall,%mrp  ; call $$dyncall (millicode function)
            copy        %mrp,%rp        ; remember the return-pointer

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            copy        %r3,%r22
            .CALL
            bl          $$dyncall,%mrp
            copy        %mrp,%rp

            addibf,<=   -8,%arg0,_loop0 ; decrement counter by 8 and loop
            nop

            ldw         -20(%sp),%rp    ; restore return-pointer
            bv          %r0(%rp)        ; return to caller
            ldwm        -64(%sp),%r3    ; resore r3 and remove stack frame

            .EXIT
            .PROCEND

; Call the procedure `b_null' immediate.

b_call_imm
            .PROC
            .CALLINFO   CALLER, FRAME=0, SAVE_RP
            .ENTRY

            ldo         64(%sp),%sp     ; caller needs a stack-frame
            stw         %rp,-20(%sp)    ; save return-pointer to frame-marker

_loop1
            bl          b_null,%rp      ; call `b_null' immediate (8 times)
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop
            bl          b_null,%rp
            nop

            addibf,<=   -8,%arg0,_loop1 ; decrement counter by 8 and loop
            nop

            ldw         -20(%sp),%rp    ; restore return-pointer
            bv          %r0(%rp)        ; return to caller
            ldo         -64(%sp),%sp    ; remove stack-frame

            .EXIT
            .PROCEND

; Copy register-to-register.
; On PA-RISC this is implemented with an `or'.
; The `or' is hidden by a pseudo-operation called `copy'.

b_add
            .PROC
            .CALLINFO   NO_CALLS, FRAME=0
            .ENTRY

_loop2
            copy        %r19,%r20       ; copy register-to-register
            copy        %r20,%r21       ; use caller-saves registers
            copy        %r21,%r22
            copy        %r22,%r21
            copy        %r21,%r20
            copy        %r20,%r19
            copy        %r19,%r20
            copy        %r20,%r21

            addibf,<=   -8,%arg0,_loop2 ; decrement counter by 8 and loop
            nop

            bv,n        %r0(%rp)

            .EXIT
            .PROCEND

; Load memory to a register.

b_load
            .PROC
            .CALLINFO   NO_CALLS, FRAME=0
            .ENTRY

_loop3
            ldw         -4(%sp),%r22    ; load data from frame-marker
            ldw         -8(%sp),%r22    ; use a caller-saves register
            ldw         -12(%sp),%r22
            ldw         -16(%sp),%r22
            ldw         -20(%sp),%r22
            ldw         -24(%sp),%r22
            ldw         -28(%sp),%r22
            ldw         -32(%sp),%r22

            addibf,<=   -8,%arg0,_loop3 ; decrement counter by 8 and loop
            nop

            bv,n        %r0(%rp)

            .EXIT
            .PROCEND

            .ALIGN 8
to_call
            .WORD       b_null


code:
Note that some machines want labels to have leading underscores,
while others (e.g. System V) do not.  Thus, several labels appear
duplicated except for the leading underscore, e.g.

	_qt_cswap:
	qt_cswap:


code:
;; i386.asm -- assembly support.

;;
;; QuickThreads -- Threads-building toolkit.
;; Copyright (c) 2001, 2006 Free Software Foundation, Inc.
;;
;; Permission to use, copy, modify and distribute this software and
;; its documentation for any purpose and without fee is hereby
;; granted, provided that the above copyright notice and this notice
;; appear in all copies.  This software is provided as a
;; proof-of-concept and for demonstration purposes; there is no
;; representation about the suitability of this software for any
;; purpose.

;; NOTE: double-labeled `_name' and `name' for System V compatability.
;; NOTE: Comment lines start like this one, or with '//' ONLY.  Sorry!

;; Callee-save: %esi, %edi, %ebx, %ebp
;; Caller-save: %eax, %ecx
;; Can't tell: %edx (seems to work w/o saving it.)
;;
;; Assignment:
;;
;; See ``i386.h'' for the somewhat unconventional stack layout.

	.386p
	.model flat
	.code

	public _qt_abort
	public qt_abort
	public _qt_block
	public qt_block
	public _qt_blocki
	public qt_blocki

;; These all have the type signature
;;
;;	void *blocking (helper, arg0, arg1, new)
;;
;; On procedure entry, the helper is at 4(sp), args at 8(sp) and
;; 12(sp) and the new thread's sp at 16(sp).  It *appears* that the
;; calling convention for the 8X86 requires the caller to save all
;; floating-point registers, this makes our life easy.

;; Halt the currently-running thread.  Save it's callee-save regs on
;; to the stack, 32 bytes.  Switch to the new stack (next == 16+32(sp))
;; and call the user function (f == 4+32(sp) with arguments: old sp
;; arg1 (8+32(sp)) and arg2 (12+32(sp)).  When the user function is
;; done, restore the new thread's state and return.
;;
;; `qt_abort' is (currently) an alias for `qt_block' because most of
;; the work is shared.  We could save the insns up to `qt_common' by
;; replicating, but w/o replicating we need an inital subtract (to
;; offset the stack as if it had been a qt_block) and then a jump
;; to qt_common.  For the cost of a jump, we might as well just do
;; all the work.
;;
;; The helper function (4(sp)) can return a void* that is returned
;; by the call to `qt_blockk{,i}'.  Since we don't touch %eax in
;; between, we get that ``for free''.

_qt_abort:
qt_abort:
_qt_block:
qt_block:
_qt_blocki:
qt_blocki:
	push ebp		; Save callee-save, sp-=4.
	push esi		; Save callee-save, sp-=4.
	push edi		; Save callee-save, sp-=4.
	push ebx		; Save callee-save, sp-=4.
	mov eax, esp		; Remember old stack pointer.

qt_common:
	mov esp, [esp+32]	; Move to new thread.
	push [eax+28]		; Push arg 2.
	push [eax+24]		; Push arg 1.
	push eax		; Push arg 0.
	mov ebx, [eax+20]	; Get function to call.
	call ebx		; Call f.
	add esp, 12		; Pop args.

	pop ebx			; Restore callee-save, sp+=4.
	pop edi			; Restore callee-save, sp+=4.
	pop esi			; Restore callee-save, sp+=4.
	pop ebp			; Restore callee-save, sp+=4.
	ret			; Resume the stopped function.
	hlt

;; Start a varargs thread.

	public _qt_vstart
	public qt_vstart

_qt_vstart:
qt_vstart:
	push edi		; Push `pt' arg to `startup'.
	call ebp		; Call `startup'.
	pop eax			; Clean up the stack.

	call ebx		; Call the user's function.

	push eax		; Push return from user's.
	push edi		; Push `pt' arg to `cleanup'.
	call esi		; Call `cleanup'.

	hlt			; `cleanup' never returns.

	end


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_386_H
#define QT_386_H

typedef unsigned long qt_word_t;

/* Thread's initial stack layout on the i386:

   non-varargs:

   +---
   | arg[2]	=== `userf' on startup
   | arg[1]	=== `pt' on startup
   | arg[0]	=== `pu' on startup
   +---
   | ret pc === qt_error
   +---
   | ret pc	=== `only' on startup
   +---
   | %ebp
   | %esi
   | %edi
   | %ebx				<--- qt_t.sp
   +---

   When a non-varargs thread is started, it ``returns'' directly to
   the client's `only' function.

   varargs:

   +---
   | arg[n-1]
   | ..
   | arg[0]
   +---
   | ret pc	=== `qt_vstart'
   +---
   | %ebp	=== `startup'
   | %esi	=== `cleanup'
   | %edi	=== `pt'
   | %ebx	=== `vuserf'		<--- qt_t.sp
   +---

   When a varargs thread is started, it ``returns'' to the `qt_vstart'
   startup code.  The startup code calls the appropriate functions. */

/* What to do to start a varargs thread running. */
QT_API void qt_vstart (void);

/* Hold 4 saved regs plus two return pcs (qt_error, qt_start) plus
   three args. */
#define QT_STKBASE	(9 * 4)

/* Hold 4 saved regs plus one return pc (qt_vstart). */
#define QT_VSTKBASE	(5 * 4)

/* Stack must be 4-byte aligned. */
#define QT_STKALIGN	(4)

/* Where to place various arguments. */
#define QT_ONLY_INDEX	(QT_PC)
#define QT_USER_INDEX	(QT_ARG2)
#define QT_ARGT_INDEX	(QT_ARG1)
#define QT_ARGU_INDEX	(QT_ARG0)

#define QT_VSTARTUP_INDEX	(QT_EBP)
#define QT_VUSERF_INDEX		(QT_EBX)
#define QT_VCLEANUP_INDEX	(QT_ESI)
#define QT_VARGT_INDEX		(QT_EDI)

#define QT_EBX	0
#define QT_EDI	1
#define QT_ESI	2
#define QT_EBP	3
#define QT_PC	4
/* The following are defined only for non-varargs. */
#define QT_RPC	5
#define QT_ARG0	6
#define QT_ARG1	7
#define QT_ARG2	8

/* Stack grows down.  The top of the stack is the first thing to
   pop off (preincrement, postdecrement). */
#define QT_GROW_DOWN

QT_API void qt_error (void);

/* Push on the error return address. */
#define QT_ARGS_MD(sto) \
  (QT_SPUT (sto, QT_RPC, qt_error))

/* When varargs are pushed, allocate space for all the args. */
#define QT_VARGS_MD0(sto, nbytes) \
  ((qt_t *)(((char *)(sto)) - QT_STKROUNDUP(nbytes)))

#define QT_VARGS_MD1(sto) \
  (QT_SPUT (sto, QT_PC, qt_vstart))

#define QT_VARGS_DEFAULT

#endif /* QT_386_H */


code:
/* i386.s -- assembly support. */

/*
// QuickThreads -- Threads-building toolkit.
// Copyright (c) 1993 by David Keppel
//
// Permission to use, copy, modify and distribute this software and
// its documentation for any purpose and without fee is hereby
// granted, provided that the above copyright notice and this notice
// appear in all copies.  This software is provided as a
// proof-of-concept and for demonstration purposes; there is no
// representation about the suitability of this software for any
// purpose. */

/* NOTE: double-labeled `_name' and `name' for System V compatability.  */
/* NOTE: Comment lines start like this one, or with '//' ONLY.  Sorry! */

/* Callee-save: %esi, %edi, %ebx, %ebp
// Caller-save: %eax, %ecx
// Can't tell: %edx (seems to work w/o saving it.)
//
// Assignment:
//
// See ``i386.h'' for the somewhat unconventional stack layout.  */

	.text
	.align 2

	.globl _qt_abort
	.globl qt_abort
	.globl _qt_block
	.globl qt_block
	.globl _qt_blocki
	.globl qt_blocki

/* These all have the type signature
//
//	void *blocking (helper, arg0, arg1, new)
//
// On procedure entry, the helper is at 4(sp), args at 8(sp) and
// 12(sp) and the new thread's sp at 16(sp).  It *appears* that the
// calling convention for the 8X86 requires the caller to save all
// floating-point registers, this makes our life easy.  */

/* Halt the currently-running thread.  Save it's callee-save regs on
// to the stack, 32 bytes.  Switch to the new stack (next == 16+32(sp))
// and call the user function (f == 4+32(sp) with arguments: old sp
// arg1 (8+32(sp)) and arg2 (12+32(sp)).  When the user function is
// done, restore the new thread's state and return.
//
// `qt_abort' is (currently) an alias for `qt_block' because most of
// the work is shared.  We could save the insns up to `qt_common' by
// replicating, but w/o replicating we need an inital subtract (to
// offset the stack as if it had been a qt_block) and then a jump
// to qt_common.  For the cost of a jump, we might as well just do
// all the work.
//
// The helper function (4(sp)) can return a void* that is returned
// by the call to `qt_blockk{,i}'.  Since we don't touch %eax in
// between, we get that ``for free''.  */

_qt_abort:
qt_abort:
_qt_block:
qt_block:
_qt_blocki:
qt_blocki:
	pushl %ebp		/* Save callee-save, sp-=4. */
	pushl %esi		/* Save callee-save, sp-=4. */
	pushl %edi		/* Save callee-save, sp-=4. */
	pushl %ebx		/* Save callee-save, sp-=4. */
	movl %esp, %eax		/* Remember old stack pointer. */

qt_common:
	movl 32(%esp), %esp	/* Move to new thread. */
	pushl 28(%eax)		/* Push arg 2. */
	pushl 24(%eax)		/* Push arg 1. */
	pushl %eax		/* Push arg 0. */
	movl 20(%eax), %ebx	/* Get function to call. */
	call *%ebx		/* Call f. */
	addl $12, %esp		/* Pop args. */

	popl %ebx		/* Restore callee-save, sp+=4. */
	popl %edi		/* Restore callee-save, sp+=4. */
	popl %esi		/* Restore callee-save, sp+=4. */
	popl %ebp		/* Restore callee-save, sp+=4. */
	ret			/* Resume the stopped function. */
	hlt

/* Start a varargs thread. */

	.globl _qt_vstart
	.globl qt_vstart
_qt_vstart:
qt_vstart:
	pushl %edi		/* Push `pt' arg to `startup'. */
	call *%ebp		/* Call `startup'. */
	popl %eax		/* Clean up the stack. */

	call *%ebx		/* Call the user's function. */

	pushl %eax		/* Push return from user's. */
	pushl %edi		/* Push `pt' arg to `cleanup'. */
	call *%esi		/* Call `cleanup'. */

	hlt			/* `cleanup' never returns. */


code:
/*
// QuickThreads -- Threads-building toolkit.
// Copyright (c) 1993 by David Keppel
//
// Permission to use, copy, modify and distribute this software and
// its documentation for any purpose and without fee is hereby
// granted, provided that the above copyright notice and this notice
// appear in all copies.  This software is provided as a
// proof-of-concept and for demonstration purposes; there is no
// representation about the suitability of this software for any
// purpose.  */

	.globl _b_call_reg
	.globl b_call_reg
	.globl _b_call_imm
	.globl b_call_imm
	.globl _b_add
	.globl b_add
	.globl _b_load
	.globl b_load

_b_call_reg:
b_call_reg:
_b_call_imm:
b_call_imm:
_b_add:
b_add:
_b_load:
b_load:
	hlt


code:
#
# KSR1 configuration.
#
CC	      = cc -ansi


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_KSR1_H
#define QT_KSR1_H

/*
   Stack layout:

   Registers are saved in strictly low to high order, FPU regs first
   (only if qt_block is called), CEU regs second, IPU regs next, with no
   padding between the groups.

   Callee-save:  f16..f63; c15..c30; i12..i30.
   Args passed in i2..i5.

   Note: c31 is a private data pointer.  It is not changed on thread
   swaps with the assumption that it represents per-processor rather
   than per-thread state.

   Note: i31 is an instruction count register that is updated by the
   context switch routines.  Like c31, it is not changed on context
   switches.

   This is what we want on startup:

   +------		<-- BOS: Bottom of stack (grows down)
   | 80 (128 - 48) bytes of padding to a 128-byte boundary
   +---
   | only
   | userf
   | t
   | u
   | qt_start$TXT
   | (empty)     	<-- qt.sp
   +------		<-- (BOS - 128)

   This is why we want this on startup:

   A thread begins running when the restore procedure switches thread stacks
   and pops a return address off of the top of the new stack (see below
   for the reason why we explicitly store qt_start$TXT).  The
   block procedure pushes two jump addresses on a thread's stack before
   it switches stacks.  The first is the return address for the block
   procedure, and the second is a restore address.  The return address
   is used to jump back to the thread that has been switched to;  the
   restore address is a jump within the block code to restore the registers.
   Normally, this is just a jump to the next address.  However, on thread
   startup, this is a jump to qt_start$TXT.  (The block procedure stores
   the restore address at an offset of 8 bytes from the top of the stack,
   which is also the offset at which qt_start$TXT is stored on the stacks
   of new threads.  Hence, when the block procedure switches to a new
   thread stack, it will initially jump to qt_start$TXT; thereafter,
   it jumps to the restore code.)

   qt_start$TXT, after it has read the initial data on the new thread's
   stack and placed it in registers, pops the initial stack frame
   and gives the thread the entire stack to use for execution.

   The KSR runtime system has an unusual treatment of pointers to
   functions.  From C, taking the `name' of a function yields a
   pointer to a _constant block_ and *not* the address of the
   function.  The zero'th entry in the constant block is a pointer to
   the function.

   We have to be careful: the restore procedure expects a return
   address on the top of the stack (pointed to by qt.sp).  This is not
   a problem when restoring a thread that has run before, since the
   block routine would have stored the return address on top of the
   stack.  However, when ``faking up'' a thread start (bootstrapping a
   thread stack frame), the top of the stack needs to contain a
   pointer to the code that will start the thread running.

   The pointer to the startup code is *not* `qt_start'.  It is the
   word *pointed to* by `qt_start'.  Thus, we dereference `qt_start',
   see QT_ARGS_MD below.

   On varargs startup (still unimplemented):

   | padding to 128 byte boundary
   | varargs		<-- padded to a 128-byte-boundary
   +---
   | caller's frame, 16 bytes
   | 80 bytes of padding (frame padded to a 128-byte boundary)
   +---
   | cleanup
   | vuserf
   | startup
   | t
   +---
   | qt_start		<-- qt.sp
   +---

   Of a suspended thread:

   +---
   | caller's frame, 16 bytes
   | fpu registers 47 regs * 8 bytes/reg 376 bytes
   | ceu registers 16 regs * 8 bytes/reg 128 bytes
   | ipu registers 19 regs * 8 bytes/reg 152 bytes
   |  :
   | 80 bytes of padding
   |  :
   | qt_restore		<-- qt.sp
   +---

   */

#define QT_STKALIGN	128
#define QT_GROW_DOWN
typedef unsigned long qt_word_t;

#define QT_STKBASE	QT_STKALIGN
#define QT_VSTKBASE	QT_STKBASE

extern void qt_start(void);
/*
 * See the discussion above for what indexing into a procedure ptr
 * does for us (it's lovely, though, isn't it?).
 *
 * This assumes that the address of a procedure's code is the
 * first word in a procedure's constant block.  That's how the manual
 * says it will be arranged.
 */
#define QT_ARGS_MD(sp)	(QT_SPUT (sp, 1, ((qt_word_t *)qt_start)[0]))

/*
 * The *index* (positive offset) of where to put each value.
 * See the picture of the stack above that explains the offsets.
 */
#define QT_ONLY_INDEX	(5)
#define QT_USER_INDEX	(4)
#define QT_ARGT_INDEX	(3)
#define QT_ARGU_INDEX	(2)

#define QT_VARGS_DEFAULT
#define QT_VARGS(sp, nb, vargs, pt, startup, vuserf, cleanup) \
      (qt_vargs (sp, nbytes, &vargs, pt, startup, vuserf, cleanup))

#define QT_VARGS_MD0(sp, vabytes) \
  ((qt_t *)(((char *)(sp)) - 4*8 - QT_STKROUNDUP(vabytes)))

extern void qt_vstart(void);
#define QT_VARGS_MD1(sp)	(QT_SPUT (sp, 0, ((qt_word_t *)qt_vstart)[0]))

#define QT_VCLEANUP_INDEX	(4)
#define QT_VUSERF_INDEX		(3)
#define QT_VSTARTUP_INDEX	(2)
#define QT_VARGT_INDEX		(1)

#endif /* def QT_KSR1_H */


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.file	"ksr1.s"
	.def	.debug;	.endef

	.align 128
	.globl qt_blocki
	.globl qt_blocki$TXT
	.globl qt_block
	.globl qt_block$TXT
	.globl qt_start$TXT
        .globl qt_start
	.globl qt_abort$TXT
	.globl qt_abort
	.globl qt_vstart
	.globl qt_vstart$TXT

#
# KSR convention: on procedure calls, load both the procedure address
# and a pointer to a constant block.  The address of function `f' is
# `f$TXT', and the constant block address is `f'.  The constant block
# has several reserved values:
#
#	8 bytes fpu register save mask
#	4 bytes ipu register save mask
#	4 bytes ceu register save mask
#   f:  f$TXT
#	... whatever you want ... (not quite...read on)
#
# Note, by the way, that a pointer to a function is passed as a
# pointer to the constant area, and the constant area has the text
# address.
#

#
# Procedures that do not return structures prefix their code with
#
# proc$TXT:
#   finop; cxnop
#   finop; cxnop
#   <proc code>
#
# Calls to those procedures branch to a 16 byte offset (4 instrs) in
# to the procedure to skip those instructions.
#
# Procedures that return structures use a different code prefix:
#
# proc$TXT:
#   finop; beq.qt %rc, %rc, 24		# return value entry
#   finop; cxnop
#   finop; movi8 0, %rc			# no return value entry
#   <proc code>
#
# Calls that want the returned structure branch directly to the
# procedure address.  Callers that don't want (or aren't expecting) a
# return value branche 16 bytes in to the procedure, which will zero
# %rc, telling the called procedure not to return a structure.
#

#
# On entry:
#   %i2 -- control block of helper function to run
#          (dereference to get helper)
#   %i3 -- a1
#   %i4 -- a2
#   %i5 -- sp of new to run
#

        .data
	.half 0x0, 0x0, 0x7ffff000, 0x7fff8000
qt_blocki:
qt_abort:
	.word qt_blocki$TXT
	.word qt_restore$TXT

	.text
qt_abort$TXT:
qt_blocki$TXT:
	finop			; cxnop			# entry prefix
	finop			; cxnop			# entry prefix
	add8.ntr 75,%i31,%i31	; movi8 512,%c5		# ICR; stk adjust
	finop			; ssub8.ntr 0,%sp,%c5,%sp
	finop			; st8 %fp,504(%sp)	# Save caller's fp
	finop			; st8 %cp,496(%sp)	# Save caller's cp
	finop			; ld8 8(%c10),%c5	# ld qt_restore$TXT
	finop			; st8 %c14,0(%sp)	# Save special ret addr
	finop			; mov8_8 %c10, %cp	# Our cp
	finop			; sadd8.ntr 0,%sp,%c5,%fp # Our frame ptr
	finop			; st8 %c5,8(%sp)	# st qt_restore$TXT
#
# CEU registers %c15-%c24, %c26-%c30 (%c14 we restore later)
#
	finop			; st8  %c15,456(%sp)
	finop			; st8  %c16,448(%sp)
	finop			; st8  %c17,440(%sp)
	finop			; st8  %c18,432(%sp)
	finop			; st8  %c19,424(%sp)
	finop			; st8  %c20,416(%sp)
	finop			; st8  %c21,408(%sp)
	finop			; st8  %c22,400(%sp)
	finop			; st8  %c23,392(%sp)
	finop			; st8  %c24,384(%sp)
#
# %c25 is the Enclosing Frame Pointer (EFP) -- since C doesn't
# use nested procedures, we ignore it (leaving a gap, though)
#
	finop			; st8 %c26,368(%sp)
	finop			; st8 %c27,360(%sp)
	finop			; st8 %c28,352(%sp)
	finop			; st8 %c29,344(%sp)
	finop			; st8 %c30,336(%sp)
#
# IPU registers %i12-%i30
#
	finop			; st8 %i12,328(%sp)
	finop			; st8 %i13,320(%sp)
	finop			; st8 %i14,312(%sp)
	finop			; st8 %i15,304(%sp)
# (gap to get alignment for st64)
# -- Doesn't work on version 1.1.3 of the OS
#	finop			; st64 %i16,256(%sp)

	finop			; st8 %i16,256(%sp)
	finop			; st8 %i17,248(%sp)
	finop			; st8 %i18,240(%sp)
	finop			; st8 %i19,232(%sp)
	finop			; st8 %i20,224(%sp)
	finop			; st8 %i21,216(%sp)
	finop			; st8 %i22,208(%sp)
	finop			; st8 %i23,200(%sp)
	finop			; st8 %i24,192(%sp)
	finop			; st8 %i25,184(%sp)
	finop			; st8 %i26,176(%sp)
	finop			; st8 %i27,168(%sp)
	finop			; st8 %i28,160(%sp)
	finop			; st8 %i29,152(%sp)
	finop			; st8 %i30,144(%sp)
#
# FPU already saved, or saving not necessary
#

#
# Switch to the stack passed in as fourth argument to the block
# routine (%i5) and call the helper routine passed in as the first
# argument (%i2).  Note that the address of the helper's constant
# block is passed in, so we must derefence it to get the helper's text
# address.
#
	finop			; movb8_8 %i2,%c10	# helper's ConstBlock
	finop			; cxnop			# Delay slot, fill w/
	finop			; cxnop			# .. 2 st8 from above
	finop			; ld8 0(%c10),%c4	# load addr of helper
	finop			; movb8_8 %sp, %i2	# 1st arg to helper
							# is this stack; other
							# args remain in regs
	finop			; movb8_8 %i5,%sp	# switch stacks
	finop			; jsr %c14,16(%c4)	# call helper
	movi8 3, %i0		; movi8 0,%c8		# nargs brain dmg
	finop			; cxnop
	finop			; cxnop
#
# Here is where behavior differs for threads being restored and threads
# being started.  Blocked threads have a pointer to qt_restore$TXT on
# the top of their stacks; manufactured stacks have a pointer to qt_start$TXT
# on the top of their stacks.  With this setup, starting threads
# skip the (unecessary) restore operations.
#
# We jump to an offset of 16 to either (1) skip past the two noop pairs
# at the start of qt_start$TXT, or (2) skip past the two noop pairs
# after qt_restore$TXT.
#
	finop			; ld8 8(%sp),%c4
	finop			; cxnop
	finop			; cxnop
	finop			; jmp 16(%c4)
qt_restore$TXT:
	finop			; cxnop
	finop			; cxnop
#
# Point of Restore:
#
# The helper funtion will return here.  Any result it has placed in
# a return register (most likely %i0) will not get overwritten below
# and will consequently be the return value of the blocking routine.
#

#
# CEU registers %c15-%c24, %c26-%c30 (%c14 we restore later)
#
	finop			; ld8  456(%sp),%c15
	finop			; ld8  448(%sp),%c16
	finop			; ld8  440(%sp),%c17
	finop			; ld8  432(%sp),%c18
	finop			; ld8  424(%sp),%c19
	finop			; ld8  416(%sp),%c20
	finop			; ld8  408(%sp),%c21
	finop			; ld8  400(%sp),%c22
	finop			; ld8  392(%sp),%c23
	finop			; ld8  384(%sp),%c24
#
# %c25 is the Enclosing Frame Pointer (EFP) -- since C doesn't
# use nested procedures, we ignore it (leaving a gap, though)
#
	finop			; ld8 368(%sp),%c26
	finop			; ld8 360(%sp),%c27
	finop			; ld8 352(%sp),%c28
	finop			; ld8 344(%sp),%c29
	finop			; ld8 336(%sp),%c30
#
# IPU registers %i12-%i30
#
	finop			; ld8 328(%sp),%i12
	finop			; ld8 320(%sp),%i13
	finop			; ld8 312(%sp),%i14
	finop			; ld8 304(%sp),%i15
# (gap to get alignment for ld64)
# -- Doesn't work on version 1.1.3 of the OS
#	finop			; ld64 256(%sp),%i16

	finop			; ld8 256(%sp),%i16
	finop			; ld8 248(%sp),%i17
	finop			; ld8 240(%sp),%i18
	finop			; ld8 232(%sp),%i19
	finop			; ld8 224(%sp),%i20
	finop			; ld8 216(%sp),%i21
	finop			; ld8 208(%sp),%i22
	finop			; ld8 200(%sp),%i23
	finop			; ld8 192(%sp),%i24
	finop			; ld8 184(%sp),%i25
	finop			; ld8 176(%sp),%i26
	finop			; ld8 168(%sp),%i27
	finop			; ld8 160(%sp),%i28
	finop			; ld8 152(%sp),%i29
	finop			; ld8 144(%sp),%i30

#
# FPU registers don't need to be loaded, or will be loaded by an
# enclosing scope (e.g., if this is called by qt_block).
#

#
# Load the special registers.  We don't load the stack ptr because
# the new stack is passed in as an argument, we don't load the EFP
# because we don't use it, and we load the return address specially
# off the top of the stack.
#
	finop			; ld8 0(%sp),%c14	# return addr
	finop			; ld8 496(%sp),%cp
	finop			; ld8 504(%sp),%fp

	finop			; jmp 32(%c14)		# jump back to thread
	finop			; movi8 512,%c5		# stack adjust
	finop			; sadd8.ntr 0,%sp,%c5,%sp

        .data
	.half 0x0, 0x0, 0x7ffff000, 0x7fff8000
qt_block:
	.word	qt_block$TXT
	.word	qt_error
	.word	qt_error$TXT
	.word	qt_blocki
#
# Handle saving and restoring the FPU regs, relying on qt_blocki
# to save and restore the remaining registers.
#
        .text
qt_block$TXT:
	finop			; cxnop			# entry prefix
	finop			; cxnop			# entry prefix

	add8.ntr 29,%i31,%i31	; movi8 512,%c5		# ICR; stk adjust
	finop			; ssub8.ntr 0,%sp,%c5,%sp
	finop			; st8 %fp,504(%sp)	# Save caller's fp
	finop			; st8 %cp,496(%sp)	# Save caller's cp
	finop			; st8 %c14,488(%sp)	# store ret addr
	finop			; sadd8.ntr 0,%sp,%c5,%fp # Our frame ptr
	finop			; mov8_8 %c10, %cp	# Our cp

#
# Store 8 registers at once...destination must be a multiple of 64
#
	finop			; st64 %f16,384(%sp)
	finop			; st64 %f24,320(%sp)
	finop			; st64 %f32,256(%sp)
	finop			; st64 %f40,192(%sp)
	finop			; st64 %f48,128(%sp)
	finop			; st64 %f56,64(%sp)

#
# Call the integer blocking routine, passing the arguments passed to us
#
	finop			; ld8 24(%cp), %c10
	finop			; cxnop
	finop			; jsr %c14, qt_blocki$TXT
	finop			; cxnop
	finop			; cxnop
	movi8 4,%i0		; movi8 0,%c8		# nargs brain dmg

#
# Load 8 registers at once...source must be a multiple of 64
#
	finop			; ld64 64(%sp),%f56
	finop			; ld64 128(%sp),%f48
	finop			; ld64 192(%sp),%f40
	finop			; ld64 256(%sp),%f32
	finop			; ld64 320(%sp),%f24
	finop			; ld64 384(%sp),%f16

	finop			; ld8 488(%sp),%c14
	finop			; ld8 496(%sp),%cp
	finop			; ld8 504(%sp),%fp
	finop			; jmp 32(%c14)		# jump back to thread
	finop			; movi8 512,%c5		# stack adjust
	finop			; sadd8.ntr 0,%sp,%c5,%sp

        .data
	.half 0x0, 0x0, 0x7ffff000, 0x7fff8000
qt_start:
	.word qt_start$TXT
#
# A new thread is set up to "appear" as if it were executing code at
# the beginning of qt_start and then it called a blocking routine
# (qt_blocki).  So when a new thread starts to run, it gets unblocked
# by the code above and "returns" to `qt_start$TXT' in the
# restore step of the switch.  Blocked threads jump to 16(qt_restore$TXT),
# and starting threads jump to 16(qt_start$TXT).
#
        .text
qt_start$TXT:
	finop			; cxnop			#
	finop			; cxnop			#
	finop			; ld8 40(%sp),%c10	# `only' constant block
	finop			; ld8 32(%sp),%i4	# `userf' arg.
	finop			; ld8 24(%sp),%i3	# `t' arg.
	finop			; ld8 0(%c10),%c4	# `only' text location
	finop			; ld8 16(%sp),%i2	# `u' arg.
	finop			; cxnop
	finop			; jsr %c14,16(%c4)	# call `only'
#
# Pop the frame used to store the thread's initial data
#
	finop			; sadd8.ntr 0,%sp,128,%sp
	finop			; cxnop
	movi8 2,%i0		; movi8 0,%c8		# nargs brain dmg
#
# If we ever return, it's an error.
#
	finop			; jmp qt_error$TXT
	finop			; cxnop
	finop			; cxnop
	movi8 0,%i0		; movi8 0,%c8		# nargs brain dmg

#
# This stuff is broken
#
        .data
	.half 0x0, 0x0, 0x7ffff000, 0x7fff8000
qt_vstart:
	.word qt_vstart$TXT

	.text
qt_vstart$TXT:
	finop			; cxnop			# entry prefix
	finop			; cxnop			# entry prefix
	finop			; cxnop
	finop			; cxnop
	add8.ntr 11,%i31,%i31	; movi8 512,%c5
	finop			; ssub8.ntr 0,%sp,%c5,%sp	# fix stack
	finop			; ld8 8(%sp),%i2	# load `t' as arg to
	finop			; cxnop			# `startup'
	finop			; cxnop
	finop			; ld8 16(%sp),%c10	# `startup' const block
	finop			; cxnop
	finop			; cxnop
	finop			; ld8 0(%c10),%c4	# `startup' text loc.
	finop			; cxnop
	finop			; cxnop
	finop			; jsr %c14,16(%c4)	# call `startup'
	finop			; cxnop
	finop			; cxnop
	movi8 1, %i0		; movi8 0,%c8		# nargs brain dmg
#
#	finop			; sadd 0,%sp,128,%sp	# alter stack
#
	finop			; ld8 8(%sp),%i2	# load `t' as arg to
	finop			; ld8 8(%sp),%i2	# load `t' as arg to
	finop			; ld8 8(%sp),%i2	# load `t' as arg to
	finop			; ld8 8(%sp),%i2	# load `t' as arg to

	finop			; ld8 32(%sp),%c10	# `only' constant block
	finop			; ld8 8(%sp),%i2	# `u' arg.
	finop			; ld8 16(%sp),%i3	# `t' arg.
	finop			; ld8 0(%c10),%c4	# `only' text location
	finop			; ld8 24(%sp),%i4	# `userf' arg.
	finop			; cxnop
	finop			; jsr %c4,16(%c4)	# call `only'
	finop			; cxnop
	finop			; cxnop
#
# If the callee ever calls `nargs', the following instruction (pair)
# will be executed.  However, we don't know when we compile this code
# how many args are being passed.  So we give our best guess: 0.
#
	movi8 0,%i0		; movi8 0,%c8		# nargs brain dmg
#
# If we ever return, it's an error.
#
	finop			; jmp qt_error$TXT
	finop			; cxnop
	finop			; cxnop
	movi8 0,%i0		; movi8 0,%c8		# nargs brain dmg


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.file   "ksr1_b.s"
	.def	.debug;	.endef

        .globl b_call_reg$TXT
	.globl b_call_reg
	.globl b_call_imm$TXT
	.globl b_call_imm
	.globl b_add$TXT
	.globl b_add
	.globl b_load$TXT
	.globl b_load

b_call_reg:
b_call_imm:
b_add:
b_load:
	.word	b_call_reg$TXT
	.word	qt_error
	.word	qt_error$TXT

b_call_reg$TXT:
b_call_imm$TXT:
b_add$TXT:
b_load$TXT:
	finop			; cxnop
	finop			; cxnop
	finop			; ld8 16(%cp),%c4
        finop			; ld8 8(%cp),%cp
	finop			; cxnop
	finop			; cxnop
        finop			; jsr %c4,0(%c4)
	finop			; cxnop
	finop			; cxnop


code:
#
# Hosted compilers for 88k for Meerkat.
#
CC = gcc88 -Dm88k -ansi -pedantic -Wall -fno-builtin
AS = as88


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#include <stdarg.h>
#include "qt.h"

/* Varargs is harder on the m88k.  Parameters are saved on the stack as
   something like (stack grows down to low memory; low at bottom of
   picture):

	|  :
	| arg8		<-- va_list.__va_stk
	+---
	|  :
	+---
	| arg7
	|  :
	| iarg0		<-- va_list.__va_reg
	+---
	|  :
	| va_list { __va_arg, __va_stk, __va_reg }
	|  :
	+---

   Here, `va_list.__va_arg' is the number of word-size arguments
   that have already been skipped.  Doubles must be double-arligned.

   What this means for us is that the user's routine needs to be
   called with an arg list where some of the words in the `__va_stk'
   part of the parameter list have to be promoted to registers.

   BUG: doubleword register arguments must be double-aligned.  If
   something is passed as an even # arg and used as an odd # arg or
   vice-versa, the code in the called routine (in the new thread) that
   decides how to adjust the index will get it wrong, because it will
   be expect it to be, say, doubleword aligned and it will really be
   singleword aligned.

   I'm not sure you can solve this without knowing the types of all
   the arguments.  All in all, we never promised varargs would work
   reliably. */

#define QT_VADJ(sp)	(((char *)sp) - QT_VSTKBASE)

/* Always allocate at least enough space for 8 args; waste some space
   at the base of the stack to ensure the startup routine doesn't read
   off the end of the stack. */

#define QT_VARGS_MD0(sp, vabytes) \
   ((qt_t *)(((char *)(sp)) - 8*4 - QT_STKROUNDUP(vabytes)))

extern void qt_vstart(void);
#define QT_VARGS_MD1(sp)	(QT_SPUT (sp, QT_1, qt_vstart))

  struct qt_t *
qt_vargs (struct qt_t *qsp, int nbytes, void *vargs,
	  void *pt, qt_function_t *startup,
	  qt_function_t *vuserf, qt_function_t *cleanup)
{
  va_list ap;
  int i;
  int n;		/* Number of words into original arg list. */
  qt_word_t *sp;
  int *reg;		/* Where to read passed-in-reg args. */
  int *stk;		/* Where to read passed-on-stk args. */

  ap = *(va_list *)vargs;
  qsp = QT_VARGS_MD0 (qsp, nbytes);
  sp = (qt_word_t *)qsp;

  reg = (ap.__va_arg < 8)
    ? &ap.__va_reg[ap.__va_arg]
    : 0;
  stk = &ap.__va_stk[8];
  n = ap.__va_arg;
  for (i=0; i<nbytes/sizeof(qt_word_t) && n<8; ++i,++n) {
    sp[i] = *reg++;
  }
  for (; i<nbytes/sizeof(qt_word_t); ++i) {
    sp[i] = *stk++;
  }

#ifdef QT_NDEF
  for (i=0; i<nbytes/sizeof(qt_word_t); ++i) {
    sp[i] = (n < 8)
      ? *reg++
      : *stk++;
    ++n;
  }
#endif

  QT_VARGS_MD1 (QT_VADJ(sp));
  QT_SPUT (QT_VADJ(sp), QT_VARGT_INDEX, pt);
  QT_SPUT (QT_VADJ(sp), QT_VSTARTUP_INDEX, startup);
  QT_SPUT (QT_VADJ(sp), QT_VUSERF_INDEX, vuserf);
  QT_SPUT (QT_VADJ(sp), QT_VCLEANUP_INDEX, cleanup);
  return ((qt_t *)QT_VADJ(sp));
}


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_M88K_H
#define QT_M88K_H

typedef unsigned long qt_word_t;

#define QT_GROW_DOWN

/* Stack layout on the mips:

   Callee-save registers are: $16-$23, $30; $f20-$f30.
   Also save $31, return pc.

   Non-varargs:

   +---
   | r30 (fp)	on startup === 0
   | r25
   | r24
   | r23
   | r22
   | r21
   | r20
   | r19
   | r18
   | r17	on startup === `only'
   | r16	on startup === `userf'
   | r15	on startup === `pt'
   | r14	on startup === `pu'
   | r1		on startup === `qt_start'
   | 0
   | 0
   +---
   | 0
   | ... (8 regs worth === 32 bytes of homing area)
   | 0						<--- sp
   +---

   Conventions for varargs:

   |  :
   | arg8
   +---
   | r30 (fp)	arg7
   | r25	arg6
   | r24	arg5
   | r23	arg4
   | r22	arg3
   | r21	arg2
   | r20	arg1
   | r19	arg0
   | r18
   | r17	on startup === `startup'
   | r16	on startup === `vuserf'
   | r15	on startup === `pt'
   | r14	on startup === `cleanup'
   | r1		on startup === `qt_vstart'
   | 0
   | 0
   +---
   | 0
   | ... (8 regs worth === 32 bytes of homing area)
   | 0						<--- sp
   +---

   */

/* Stack must be doubleword aligned. */
#define QT_STKALIGN	(16)	/* Doubleword aligned. */

/* How much space is allocated to hold all the crud for
   initialization: saved registers plus padding to keep the stack
   aligned plus 8 words of padding to use as a `homing area' (for
   r2-r9) when calling helper functions on the stack of the (not yet
   started) thread.  The varargs save area is small because it gets
   overlapped with the top of the parameter list.  In case the
   parameter list is less than 8 args, QT_ARGS_MD0 adds some dead
   space at the top of the stack. */

#define QT_STKBASE	(16*4 + 8*4)
#define QT_VSTKBASE	(8*4 + 8*4)

/* Index of various registers. */
#define QT_1	(8+2)
#define QT_14	(8+3)
#define QT_15	(8+4)
#define QT_16	(8+5)
#define QT_17	(8+6)
#define QT_30	(8+15)

/* When a never-before-run thread is restored, the return pc points
   to a fragment of code that starts the thread running.  For
   non-vargs functions, it sets up arguments and calls the client's
   `only' function.  For varargs functions, the startup code calls the
   startup, user, and cleanup functions.

   For non-varargs functions, we set the frame pointer to 0 to
   null-terminate the call chain.

   For varargs functions, the frame pointer register is used to hold
   one of the arguments, so that all arguments can be laid out in
   memory by the conventional `qt_vargs' varargs initialization
   routine.

   The varargs startup routine always reads 8 words of arguments from
   the stack.  If there are less than 8 words of arguments, then the
   arg list could call off the top of the stack.  To prevent fall-off,
   always allocate 8 words. */

extern void qt_start(void);
#define QT_ARGS_MD(sp) \
  (QT_SPUT (sp, QT_1, qt_start), \
   QT_SPUT (sp, QT_30, 0))

/* The m88k uses a struct for `va_list', so pass a pointer to the
   struct. */

typedef void (qt_function_t)(void);

struct qt_t;
extern struct qt_t *qt_vargs (struct qt_t *sp, int nbytes,
			      void *vargs, void *pt,
			      qt_function_t *startup,
			      qt_function_t *vuserf,
			      qt_function_t *cleanup);

#define QT_VARGS(sp, nbytes, vargs, pt, startup, vuserf, cleanup) \
  (qt_vargs (sp, nbytes, &(vargs), pt, (qt_function_t *)startup, \
	     (qt_function_t *)vuserf, (qt_function_t *)cleanup))

/* The *index* (positive offset) of where to put each value. */
#define QT_ONLY_INDEX	(QT_17)
#define QT_USER_INDEX	(QT_16)
#define QT_ARGT_INDEX	(QT_15)
#define QT_ARGU_INDEX	(QT_14)

#define QT_VCLEANUP_INDEX	(QT_14)
#define QT_VUSERF_INDEX		(QT_16)
#define QT_VSTARTUP_INDEX	(QT_17)
#define QT_VARGT_INDEX		(QT_15)

#endif /* ndef QT_M88K_H */


code:
/* m88k.s -- assembly support. */

/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/* Callee-save r14..r25, r31(sp), r30(fp).  r1 === return pc.
 * Argument registers r2..r9, return value r2..r3.
 *
 * On startup, restore regs so retpc === call to a function to start.
 *
 * We're going to call a function (r2) from within the context switch
 * routine.  Call it on the new thread's stack on behalf of the old
 * thread.
 */

	.globl _qt_block
	.globl _qt_blocki
	.globl _qt_abort
	.globl _qt_start
	.globl _qt_vstart

	/*
	** r2: ptr to function to call once curr is suspended
	**	and control is on r5's stack.
	** r3: 1'th arg to *r2.
	** r4: 2'th arg to *r2.
	** r5: sp of thread to suspend.
	**
	** The helper routine returns a value that is passed on as the
	** return value from the blocking routine.  Since we don't
	** touch r2 between the helper's return and the end of
	** function, we get this behavior for free.
	**
	** Same entry for integer-only and floating-point, since there
	** are no separate integer and floating-point registers.
	**
	** Each procedure call sets aside a ``home region'' of 8 regs
	** for r2-r9 for varargs.  For context switches we don't use
	** the ``home region'' for varargs so use it to save regs.
	** Allocate 64 bytes of save space -- use 32 bytes of register
	** save area passed in to us plus 32 bytes we allcated, use
	** the other 32 bytes for save area for a save area to call
	** the helper function.
	*/
_qt_block:
_qt_blocki:
	sub r31, r31,64		/* Allocate reg save space. */
	st r1, r31,8+32		/* Save callee-save registers. */
	st r14, r31,12+32
	st.d r15, r31,16+32
	st.d r17, r31,24+32
	st.d r19, r31,32+32
	st.d r21, r31,40+32
	st.d r23, r31,48+32
	st r25, r31,56+32
	st r30, r31,60+32

_qt_abort:
	addu r14, r31,0		/* Remember old sp. */
	addu r31, r5,0		/* Set new sp. */
	jsr.n r2		/* Call helper. */
	addu r2, r14,0		/* Pass old sp as an arg0 to helper. */

	ld r1, r31,8+32		/* Restore callee-save registers. */
	ld r14, r31,12+32
	ld.d r15, r31,16+32
	ld.d r17, r31,24+32
	ld.d r19, r31,32+32
	ld.d r21, r31,40+32
	ld.d r23, r31,48+32
	ld r25, r31,56+32
	ld r30, r31,60+32

	jmp.n r1		/* Return to new thread's caller. */
	addu r31, r31,64	/* Free register save space. */

	/*
	** Non-varargs thread startup.
	** See `m88k.h' for register use conventions.
	*/
_qt_start:
	addu r2, r14,0		/* Set user arg `pu'. */
	addu r3, r15,0		/* ... user function pt. */
	jsr.n r17		/* Call `only'. */
	addu r4, r16,0		/* ... user function userf. */

	bsr _qt_error		/* `only' erroniously returned. */

	/*
	** Varargs thread startup.
	** See `m88k.h' for register use conventions.
	**
	** Call the `startup' function with just argument `pt'.
	** Then call `vuserf' with 8 register args plus any
	** stack args.
	** Then call `cleanup' with `pt' and the return value
	** from `vuserf'.
	*/
_qt_vstart:
	addu r18, r30,0		/* Remember arg7 to `vuserf'. */
	addu r30, r0,0		/* Null-terminate call chain. */

	jsr.n r17		/* Call `startup'. */
	addu r2, r15,0		/* `pt' is arg0 to `startup'. */

	addu r2, r19,0		/* Set arg0. */
	addu r3, r20,0		/* Set arg1. */
	addu r4, r21,0		/* Set arg2. */
	addu r5, r22,0		/* Set arg3. */
	addu r6, r23,0		/* Set arg4. */
	addu r7, r24,0		/* Set arg5. */
	addu r8, r25,0		/* Set arg6. */
	jsr.n r16		/* Call `vuserf'. */
	addu r9, r18,0		/* Set arg7. */

	addu r3, r2,0		/* Ret. value is arg1 to `cleanup'. */
	jsr.n r14		/* Call `cleanup'. */
	addu r2, r15,0		/* `pt' is arg0 to `cleanup'. */

	bsr _qt_error		/* `cleanup' erroniously returned. */


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.text
	.globl _b_call_reg
	.globl _b_call_imm
	.globl _b_add
	.globl _b_load

_b_null:
	jmp   r1

_b_call_reg:
	subu r31, r31,8			/* Alloc ret pc save space. */
	st r1, r31,32			/* Save ret pc. */
	or.u r3, r0,hi16(_b_null)	/* Put call addr in a reg. */
	or r3, r3,lo16(_b_null)
	jsr r3
L0:
	jsr r3
	jsr r3
	jsr r3
	jsr.n r3
	subu r2, r2,5			/* Decrement #of iter to go. */
	bcnd.n gt0,r2,L0
	jsr r3

	ld r1, r31,32
	jmp r1

_b_call_imm:
	subu r31, r31,8			/* Alloc ret pc save space. */
	st r1, r31,32			/* Save ret pc. */
	bsr _b_null
L1:
	bsr _b_null
	bsr _b_null
	bsr _b_null
	bsr.n _b_null
	subu r2, r2,5			/* Decrement #of iter to go. */
	bcnd.n gt0,r2,L1
	bsr _b_null

	ld r1, r31,32
	jmp r1

_b_add:
	add r0, r3,r4
L2:
	add r3, r4,r5
	add r4, r5,r6
	add r5, r6,r7
	add r8, r9,r0
	add r0, r3,r4
	add r3, r4,r5
	add r4, r5,r6
	add r5, r6,r7
	add r8, r9,r0

	add r0, r3,r4
	add r3, r4,r5
	add r4, r5,r6
	add r5, r6,r7
	add r8, r9,r0
	add r0, r3,r4
	add r3, r4,r5
	add r4, r5,r6
	add r5, r6,r7
	add r8, r9,r0

	subu r2, r2,20			/* Decrement #of iter to go. */
	bcnd.n gt0,r2,L2
	add r0, r3,r4

	jmp r1

_b_load:
	ld r0, r31,0
L3:
	ld r3, r31,4
	ld r4, r31,8
	ld r5, r31,12
	ld r6, r31,16
	ld r0, r31,0
	ld r3, r31,4
	ld r4, r31,8
	ld r5, r31,12
	ld r6, r31,16

	ld r0, r31,0
	ld r3, r31,4
	ld r4, r31,8
	ld r5, r31,12
	ld r6, r31,16
	ld r0, r31,0
	ld r3, r31,4
	ld r4, r31,8
	ld r5, r31,12
	ld r6, r31,16

	subu r2, r2,20			/* Decrement #of iter to go. */
	bcnd.n gt0,r2,L3
	ld r0, r31,0

	jmp   r1


code:
/* mips.s -- assembly support. */

/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/* Callee-save $16-$23, $30-$31.
 *
 * $25 is used as a procedure value pointer, used to discover constants
 * in a callee.  Thus, each caller here sets $25 before the call.
 *
 * On startup, restore regs so retpc === call to a function to start.
 * We're going to call a function ($4) from within this routine.
 * We're passing 3 args, therefore need to allocate 12 extra bytes on
 * the stack for a save area.  The start routine needs a like 16-byte
 * save area.  Must be doubleword aligned (_mips r3000 risc
 * architecture_, gerry kane, pg d-23).
 */

/*
 * Modified by Assar Westerlund <assar@sics.se> to support Irix 5.x
 * calling conventions for dynamically-linked code.
 */

	/* Make this position-independent code. */
	.option pic2

	.globl qt_block
	.globl qt_blocki
	.globl qt_abort
	.globl qt_start
	.globl qt_vstart

	/*
	** $4: ptr to function to call once curr is suspended
	**	and control is on $7's stack.
	** $5: 1'th arg to $4.
	** $6: 2'th arg to $4
	** $7: sp of thread to suspend.
	**
	** Totally gross hack: The MIPS calling convention reserves
	** 4 words on the stack for a0..a3.  This routine "ought" to
	** allocate space for callee-save registers plus 4 words for
	** the helper function, but instead we use the 4 words
	** provided by the function that called us (we don't need to
	** save our argument registers).  So what *appears* to be
	** allocating only 40 bytes is actually allocating 56, by
	** using the caller's 16 bytes.
	**
	** The helper routine returns a value that is passed on as the
	** return value from the blocking routine.  Since we don't
	** touch $2 between the helper's return and the end of
	** function, we get this behavior for free.
	*/
qt_blocki:
	sub $sp,$sp,40		/* Allocate reg save space. */
	sw $16, 0+16($sp)
	sw $17, 4+16($sp)
	sw $18, 8+16($sp)
	sw $19,12+16($sp)
	sw $20,16+16($sp)
	sw $21,20+16($sp)
	sw $22,24+16($sp)
	sw $23,28+16($sp)
	sw $30,32+16($sp)
	sw $31,36+16($sp)
	add $2, $sp,$0		/* $2 <= old sp to pass to func@$4. */
qt_abort:
	add $sp, $7,$0		/* $sp <= new sp. */
	.set noreorder
	add $25, $4,$0		/* Set helper function procedure value. */
	jal $31,$25		/* Call helper func@$4 . */
	add $4, $2,$0		/* $a0 <= pass old sp as a parameter. */
	.set reorder
	lw $31,36+16($sp)	/* Restore callee-save regs... */
	lw $30,32+16($sp)
	lw $23,28+16($sp)
	lw $22,24+16($sp)
	lw $21,20+16($sp)
	lw $20,16+16($sp)
	lw $19,12+16($sp)
	lw $18, 8+16($sp)
	lw $17, 4+16($sp)
	lw $16, 0+16($sp)	/* Restore callee-save */

	add $sp,$sp,40		/* Deallocate reg save space. */
	j $31			/* Return to caller. */

	/*
	** Non-varargs thread startup.
	** Note: originally, 56 bytes were allocated on the stack.
	** The thread restore routine (_blocki/_abort) removed 40
	** of them, which means there is still 16 bytes for the
	** argument area required by the MIPS calling convention.
	*/
qt_start:
	add $4, $16,$0		/* Load up user function pu. */
	add $5, $17,$0		/* ... user function pt. */
	add $6, $18,$0		/* ... user function userf. */
	add $25, $19,$0		/* Set `only' procedure value. */
	jal $31,$25		/* Call `only'. */
	la $25,qt_error		/* Set `qt_error' procedure value. */
	j $25

	/*
	** Save calle-save floating-point regs $f20-$f30
	** See comment in `qt_block' about calling conventinos and
	** reserved space.  Use the same trick here, but here we
	** actually have to allocate all the bytes since we have to
	** leave 4 words leftover for `qt_blocki'.
	**
	** Return value from `qt_block' is the same as the return from
	** `qt_blocki'.  We get that for free since we don't touch $2
	** between the return from `qt_blocki' and the return from
	** `qt_block'.
	*/
qt_block:
	sub $sp, $sp,56		/* 6 8-byte regs, saved ret pc, aligned. */
	swc1 $f20,  0+16($sp)
	swc1 $f22,  8+16($sp)
	swc1 $f24, 16+16($sp)
	swc1 $f26, 24+16($sp)
	swc1 $f28, 32+16($sp)
	swc1 $f30, 40+16($sp)
	sw $31, 48+16($sp)
	jal qt_blocki
	lwc1 $f20,  0+16($sp)
	lwc1 $f22,  8+16($sp)
	lwc1 $f24, 16+16($sp)
	lwc1 $f26, 24+16($sp)
	lwc1 $f28, 32+16($sp)
	lwc1 $f30, 40+16($sp)
	lw $31, 48+16($sp)
	add $sp, $sp,56
	j $31

	/*
	** First, call `startup' with the `pt' argument.
	**
	** Next, call the user's function with all arguments.
	** Note that we don't know whether args were passed in
	** integer regs, fp regs, or on the stack (See Gerry Kane
	** "MIPS R2000 RISC Architecture" pg D-22), so we reload
	** all the registers, possibly with garbage arguments.
	**
	** Finally, call `cleanup' with the `pt' argument and with
	** the return value from the user's function.  It is an error
	** for `cleanup' to return.
	*/
qt_vstart:
	add $4, $17,$0		/* `pt' is arg0 to `startup'. */
	add $25, $18,$0		/* Set `startup' procedure value. */
	jal $31, $25		/* Call `startup'. */

	add $sp, $sp,16		/* Free extra save space. */
	lw $4,  0($sp)		/* Load up args. */
	lw $5,  4($sp)
	lw $6,  8($sp)
	lw $7, 12($sp)
	lwc1 $f12, 0($sp)	/* Load up fp args. */
	lwc1 $f14, 8($sp)
	add $25, $19,$0		/* Set `userf' procedure value. */
	jal $31,$25		/* Call `userf'. */

	add $4, $17,$0		/* `pt' is arg0 to `cleanup'. */
	add $5, $2,$0		/* Ret. val is arg1 to `cleanup'. */
	add $25, $16,$0		/* Set `cleanup' procedure value. */
	jal $31, $25		/* Call `cleanup'. */

	la $25,qt_error		/* Set `qt_error' procedure value. */
	j $25


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_MIPS_H
#define QT_MIPS_H

typedef unsigned long qt_word_t;

#define QT_GROW_DOWN

/* Stack layout on the mips:

   Callee-save registers are: $16-$23, $30; $f20-$f30.
   Also save $31, return pc.

   Non-varargs:

   +---
   | $f30	The first clump is only saved if `qt_block'
   | $f28	is called, in which case it saves the fp regs
   | $f26	then calls `qt_blocki' to save the int regs.
   | $f24
   | $f22
   | $f20
   | $31 === return pc in `qt_block'
   +---
   | $31 === return pc; on startup == qt_start
   | $30
   | $23
   | $22
   | $21
   | $20
   | $19	on startup === only
   | $18	on startup === $a2 === userf
   | $17	on startup === $a1 === pt
   | $16	on startup === $a0 === pu
   | <a3>	save area req'd by MIPS calling convention
   | <a2>	save area req'd by MIPS calling convention
   | <a1>	save area req'd by MIPS calling convention
   | <a0>	save area req'd by MIPS calling convention	<--- sp
   +---

   Conventions for varargs:

   | args ...
   +---
   |  :
   |  :
   | $21
   | $20
   | $19	on startup === `userf'
   | $18	on startup === `startup'
   | $17	on startup === `pt'
   | $16	on startup === `cleanup'
   | <a3>
   | <a2>
   | <a1>
   | <a0>	<--- sp
   +---

   Note: if we wanted to, we could muck about and try to get the 4
   argument registers loaded in to, e.g., $22, $23, $30, and $31,
   and the return pc in, say, $20.  Then, the first 4 args would
   not need to be loaded from memory, they could just use
   register-to-register copies. */

/* Stack must be doubleword aligned. */
#define QT_STKALIGN	(8)	/* Doubleword aligned. */

/* How much space is allocated to hold all the crud for
   initialization: $16-$23, $30, $31.  Just do an integer restore,
   no need to restore floating-point.  Four words are needed for the
   argument save area for the helper function that will be called for
   the old thread, just before the new thread starts to run. */

#define QT_STKBASE	(14 * 4)
#define QT_VSTKBASE	QT_STKBASE

/* Offsets of various registers. */
#define QT_31	(9+4)
#define QT_19	(3+4)
#define QT_18	(2+4)
#define QT_17	(1+4)
#define QT_16	(0+4)

/* When a never-before-run thread is restored, the return pc points
   to a fragment of code that starts the thread running.  For
   non-vargs functions, it just calls the client's `only' function.
   For varargs functions, it calls the startup, user, and cleanup
   functions.

   The varargs startup routine always reads 4 words of arguments from
   the stack.  If there are less than 4 words of arguments, then the
   startup routine can read off the top of the stack.  To prevent
   errors we always allocate 4 words.  If there are more than 3 words
   of arguments, the 4 preallocated words are simply wasted. */

extern void qt_start(void);
#define QT_ARGS_MD(sp)	(QT_SPUT (sp, QT_31, qt_start))

#define QT_VARGS_MD0(sp, vabytes) \
  ((qt_t *)(((char *)(sp)) - 4*4 - QT_STKROUNDUP(vabytes)))

extern void qt_vstart(void);
#define QT_VARGS_MD1(sp)	(QT_SPUT (sp, QT_31, qt_vstart))

#define QT_VARGS_DEFAULT

/* The *index* (positive offset) of where to put each value. */
#define QT_ONLY_INDEX	(QT_19)
#define QT_USER_INDEX	(QT_18)
#define QT_ARGT_INDEX	(QT_17)
#define QT_ARGU_INDEX	(QT_16)

#define QT_VCLEANUP_INDEX	(QT_16)
#define QT_VUSERF_INDEX		(QT_19)
#define QT_VSTARTUP_INDEX	(QT_18)
#define QT_VARGT_INDEX		(QT_17)

#endif /* ndef QT_MIPS_H */


code:
/* mips.s -- assembly support. */

/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/* Callee-save $16-$23, $30-$31.
 *
 * On startup, restore regs so retpc === call to a function to start.
 * We're going to call a function ($4) from within this routine.
 * We're passing 3 args, therefore need to allocate 12 extra bytes on
 * the stack for a save area.  The start routine needs a like 16-byte
 * save area.  Must be doubleword aligned (_mips r3000 risc
 * architecture_, gerry kane, pg d-23).
 */

	.globl qt_block
	.globl qt_blocki
	.globl qt_abort
	.globl qt_start
	.globl qt_vstart

	/*
	** $4: ptr to function to call once curr is suspended
	**	and control is on $7's stack.
	** $5: 1'th arg to $4.
	** $6: 2'th arg to $4
	** $7: sp of thread to suspend.
	**
	** Totally gross hack: The MIPS calling convention reserves
	** 4 words on the stack for a0..a3.  This routine "ought" to
	** allocate space for callee-save registers plus 4 words for
	** the helper function, but instead we use the 4 words
	** provided by the function that called us (we don't need to
	** save our argument registers).  So what *appears* to be
	** allocating only 40 bytes is actually allocating 56, by
	** using the caller's 16 bytes.
	**
	** The helper routine returns a value that is passed on as the
	** return value from the blocking routine.  Since we don't
	** touch $2 between the helper's return and the end of
	** function, we get this behavior for free.
	*/
qt_blocki:
	sub $sp,$sp,40		/* Allocate reg save space. */
	sw $16, 0+16($sp)
	sw $17, 4+16($sp)
	sw $18, 8+16($sp)
	sw $19,12+16($sp)
	sw $20,16+16($sp)
	sw $21,20+16($sp)
	sw $22,24+16($sp)
	sw $23,28+16($sp)
	sw $30,32+16($sp)
	sw $31,36+16($sp)
	add $2, $sp,$0		/* $2 <= old sp to pass to func@$4. */
qt_abort:
	add $sp, $7,$0		/* $sp <= new sp. */
	.set noreorder
	jal $31,$4		/* Call helper func@$4 . */
	add $4, $2,$0		/* $a0 <= pass old sp as a parameter. */
	.set reorder
	lw $31,36+16($sp)	/* Restore callee-save regs... */
	lw $30,32+16($sp)
	lw $23,28+16($sp)
	lw $22,24+16($sp)
	lw $21,20+16($sp)
	lw $20,16+16($sp)
	lw $19,12+16($sp)
	lw $18, 8+16($sp)
	lw $17, 4+16($sp)
	lw $16, 0+16($sp)	/* Restore callee-save */

	add $sp,$sp,40		/* Deallocate reg save space. */
	j $31			/* Return to caller. */

	/*
	** Non-varargs thread startup.
	** Note: originally, 56 bytes were allocated on the stack.
	** The thread restore routine (_blocki/_abort) removed 40
	** of them, which means there is still 16 bytes for the
	** argument area required by the MIPS calling convention.
	*/
qt_start:
	add $4, $16,$0		/* Load up user function pu. */
	add $5, $17,$0		/* ... user function pt. */
	add $6, $18,$0		/* ... user function userf. */
	jal $31,$19		/* Call `only'. */
	j qt_error

	/*
	** Save calle-save floating-point regs $f20-$f30
	** See comment in `qt_block' about calling conventinos and
	** reserved space.  Use the same trick here, but here we
	** actually have to allocate all the bytes since we have to
	** leave 4 words leftover for `qt_blocki'.
	**
	** Return value from `qt_block' is the same as the return from
	** `qt_blocki'.  We get that for free since we don't touch $2
	** between the return from `qt_blocki' and the return from
	** `qt_block'.
	*/
qt_block:
	sub $sp, $sp,56		/* 6 8-byte regs, saved ret pc, aligned. */
	swc1 $f20,  0+16($sp)
	swc1 $f22,  8+16($sp)
	swc1 $f24, 16+16($sp)
	swc1 $f26, 24+16($sp)
	swc1 $f28, 32+16($sp)
	swc1 $f30, 40+16($sp)
	sw $31, 48+16($sp)
	jal qt_blocki
	lwc1 $f20,  0+16($sp)
	lwc1 $f22,  8+16($sp)
	lwc1 $f24, 16+16($sp)
	lwc1 $f26, 24+16($sp)
	lwc1 $f28, 32+16($sp)
	lwc1 $f30, 40+16($sp)
	lw $31, 48+16($sp)
	add $sp, $sp,56
	j $31

	/*
	** First, call `startup' with the `pt' argument.
	**
	** Next, call the user's function with all arguments.
	** Note that we don't know whether args were passed in
	** integer regs, fp regs, or on the stack (See Gerry Kane
	** "MIPS R2000 RISC Architecture" pg D-22), so we reload
	** all the registers, possibly with garbage arguments.
	**
	** Finally, call `cleanup' with the `pt' argument and with
	** the return value from the user's function.  It is an error
	** for `cleanup' to return.
	*/
qt_vstart:
	add $4, $17,$0		/* `pt' is arg0 to `startup'. */
	jal $31, $18		/* Call `startup'. */

	add $sp, $sp,16		/* Free extra save space. */
	lw $4,  0($sp)		/* Load up args. */
	lw $5,  4($sp)
	lw $6,  8($sp)
	lw $7, 12($sp)
	lwc1 $f12, 0($sp)	/* Load up fp args. */
	lwc1 $f14, 8($sp)
	jal $31,$19		/* Call `userf'. */

	add $4, $17,$0		/* `pt' is arg0 to `cleanup'. */
	add $5, $2,$0		/* Ret. val is arg1 to `cleanup'. */
	jal $31, $16		/* Call `cleanup'. */

	j qt_error


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.globl b_call_reg
	.globl b_call_imm
	.globl b_add
	.globl b_load

	.ent b_null
b_null:
	j $31
	.end b_null

	.ent b_call_reg
b_call_reg:
	la $5,b_null
	add $6, $31,0
$L0:
	jal $5
	jal $5
	jal $5
	jal $5
	jal $5

	sub $4, $4,5
	bgtz $4,$L0
	j $6
	.end

	.ent b_call_imm
b_call_imm:
	add $6, $31,0
$L1:
	jal b_null
	jal b_null
	jal b_null
	jal b_null
	jal b_null

	sub $4, $4,5
	bgtz $4,$L1
	j $6
	.end

	.ent b_add
b_add:
	add $5, $0,$4
	add $6, $0,$4
	add $7, $0,$4
	add $8, $0,$4
$L2:
	sub $4, $4,5
	sub $5, $5,5
	sub $6, $6,5
	sub $7, $7,5
	sub $8, $8,5

	sub $4, $4,5
	sub $5, $5,5
	sub $6, $6,5
	sub $7, $7,5
	sub $8, $8,5

	bgtz $4,$L2
	j $31
	.end

	.ent b_load
b_load:
$L3:
	ld $0, 0($sp)
	ld $0, 4($sp)
	ld $0, 8($sp)
	ld $0, 12($sp)
	ld $0, 16($sp)

	ld $0, 20($sp)
	ld $0, 24($sp)
	ld $0, 28($sp)
	ld $0, 32($sp)
	ld $0, 36($sp)

	sub $4, $4,10
	bgtz $4,$L3
	j $31
	.end


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

char const qtmd_rcsid[] = "$Header: /home/ludo/src/guile.cvs/gitification/guile-cvs/guile/guile-core/qt/md/null.c,v 1.1 1996-10-01 03:34:16 mdj Exp $";


code:
Solaris 2.x is like System V (maybe it *is* System V?) and is different
from older versions in that it uses no leading underscore for variable
and function names.  That is, the old convention was:

	foo(){}

got compiled as

		.globl	_foo
	_foo:

and is now compiled as

		.globl	foo
	foo:

The `config' script should fix up the older (leading underscore) versions
of the machine-dependent files to use the newer (no leading underscore)
calling conventions.


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_SPARC_H
#define QT_SPARC_H

typedef unsigned long qt_word_t;

/* Stack layout on the sparc:

   non-varargs:

   +---
   | <blank space for alignment>
   | %o7 == return address -> qt_start
   | %i7
   | %i6 == frame pointer -> 0 (NULL-terminated stack frame chain)
   | %i5 -> only
   | %i4 -> userf
   | %i3
   | %i2 -> pt
   | %i1 -> pu
   | %i0
   | %l7
   | %l6
   | %l5
   | %l4
   | %l3
   | %l2
   | %l1
   | %l0	<--- qt_t.sp
   +---

   varargs:

   |  :
   |  :
   | argument list
   | one-word aggregate return pointer
   +---
   | <blank space for alignment>
   | %o7 == return address -> qt_vstart
   | %i7
   | %i6 == frame pointer -> 0 (NULL-terminated stack frame chain)
   | %i5 -> startup
   | %i4 -> userf
   | %i3 -> cleanup
   | %i2 -> pt
   | %i1
   | %i0
   | %l7
   | %l6
   | %l5
   | %l4
   | %l3
   | %l2
   | %l1
   | %l0	<--- qt_t.sp
   +---

   */

/* What to do to start a thread running. */
extern void qt_start (void);
extern void qt_vstart (void);

/* Hold 17 saved registers + 1 word for alignment. */
#define QT_STKBASE	(18 * 4)
#define QT_VSTKBASE	QT_STKBASE

/* Stack must be doubleword aligned. */
#define QT_STKALIGN	(8)	/* Doubleword aligned. */

#define QT_ONLY_INDEX	(QT_I5)
#define QT_USER_INDEX	(QT_I4)
#define QT_ARGT_INDEX	(QT_I2)
#define QT_ARGU_INDEX	(QT_I1)

#define QT_VSTARTUP_INDEX	(QT_I5)
#define QT_VUSERF_INDEX		(QT_I4)
#define QT_VCLEANUP_INDEX	(QT_I3)
#define QT_VARGT_INDEX		(QT_I2)

#define QT_O7	(16)
#define QT_I6	(14)
#define QT_I5	(13)
#define QT_I4	(12)
#define QT_I3	(11)
#define QT_I2	(10)
#define QT_I1	( 9)

/* The thread will ``return'' to the `qt_start' routine to get things
   going.  The normal return sequence takes us to QT_O7+8, so we
   pre-subtract 8.  The frame pointer chain is 0-terminated to prevent
   the trap handler from chasing off in to random memory when flushing
   stack windows. */

#define QT_ARGS_MD(top) \
    (QT_SPUT ((top), QT_O7, ((void *)(((int)qt_start)-8))), \
     QT_SPUT ((top), QT_I6, 0))

/* The varargs startup routine always reads 6 words of arguments
   (6 argument registers) from the stack, offset by one word to
   allow for an aggregate return area  pointer.  If the varargs
   routine actually pushed fewer words than that, qt_vstart could read
   off the top of the stack.  To prevent errors, we always allocate 8
   words.  The space is often just wasted. */

#define QT_VARGS_MD0(sp, vabytes) \
  ((qt_t *)(((char *)(sp)) - 8*4 - QT_STKROUNDUP(vabytes)))

#define QT_VARGS_MD1(sp) \
  (QT_SPUT (sp, QT_O7, ((void *)(((int)qt_vstart)-8))))

/* The SPARC has wierdo calling conventions which stores a hidden
   parameter for returning aggregate values, so the rest of the
   parameters are shoved up the stack by one place. */
#define QT_VARGS_ADJUST(sp)	(((char *)sp)+4)

#define QT_VARGS_DEFAULT

#define QT_GROW_DOWN

#endif /* ndef QT_SPARC_H */


code:
/* sparc.s -- assembly support for the `qt' thread building kit. */

/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

/* #include <machine/trap.h> */

	.text
	.align 4
	.global qt_blocki
	.global qt_block
	.global qt_abort
	.global qt_start
	.global qt_vstart

/* Register assignment:
// %o0:	incoming `helper' function to call after cswap
//	also used as outgoing sp of old thread (qt_t *)
// %o1, %o2:
//	parameters to `helper' function called after cswap
// %o3:	sp of new thread
// %o5: tmp used to save old thread sp, while using %o0
//	to call `helper' f() after cswap.
//
//
// Aborting a thread is easy if there are no cached register window
// frames: just switch to the new stack and away we go.  If there are
// cached register window frames they must all be written back to the
// old stack before we move to the new stack.  If we fail to do the
// writeback then the old stack memory can be written with register
// window contents e.g., after the stack memory has been freed and
// reused.
//
// If you don't believe this, try setting the frame pointer to zero
// once we're on the new stack.  This will not affect correctnes
// otherwise because the frame pointer will eventually get reloaded w/
// the new thread's frame pointer.  But it will be zero briefly before
// the reload.  You will eventually (100,000 cswaps later on a small
// SPARC machine that I tried) get an illegal instruction trap  from
// the kernel trying to flush a cached window to location 0x0.
//
// Solution: flush windows before switching stacks, which invalidates
// all the other register windows.  We could do the trap
// conditionally: if we're in the lowest frame of a thread, the fp is
// zero already so we know there's nothing cached.  But we expect most
// aborts will be done from a first function that does a `save', so we
// will rarely save anything and always pay the cost of testing to see
// if we should flush.
//
// All floating-point registers are caller-save, so this routine
// doesn't need to do anything to save and restore them.
//
// `qt_block' and `qt_blocki' return the same value as the value
// returned by the helper function.  We get this ``for free''
// since we don't touch the return value register between the
// return from the helper function and return from qt_block{,i}.
*/

qt_block:
qt_blocki:
	sub %sp, 8, %sp		/* Allocate save area for return pc. */
	st %o7, [%sp+64]	/* Save return pc. */
qt_abort:
	ta 0x03			/* Save locals and ins. */
	mov %sp, %o5		/* Remember old sp w/o chng ins/locals. */
	sub %o3, 64, %sp	/* Allocate kwsa, switch stacks. */
	call %o0, 0		/* Call `helper' routine. */
	mov %o5, %o0		/* Pass old thread to qt_after_t() */
				/* .. along w/ args in %o1 & %o2. */

	/* Restore callee-save regs.  The kwsa
	// is on this stack, so offset all
	// loads by sizeof(kwsa), 64 bytes.
	*/
	ldd [%sp+ 0+64], %l0
	ldd [%sp+ 8+64], %l2
	ldd [%sp+16+64], %l4
	ldd [%sp+24+64], %l6
	ldd [%sp+32+64], %i0
	ldd [%sp+40+64], %i2
	ldd [%sp+48+64], %i4
	ldd [%sp+56+64], %i6
	ld [%sp+64+64], %o7	/* Restore return pc. */

	retl			/* Return to address in %o7. */
	add %sp, 72, %sp	/* Deallocate kwsa, ret pc area. */

/* The function calling conventions say there has to be a 1-word area
// in the caller's stack to hold a pointer to space for aggregate
// return values.  It also says there should be a 6-word area to hold
// %o0..%o5 if the callee wants to save them (why?  I don't know...)
// Round up to 8 words to maintain alignment.
//
// Parameter values were stored in callee-save regs and are moved to
// the parameter registers.
*/
qt_start:
	mov %i1, %o0		/* `pu': Set up args to `only'. */
	mov %i2, %o1		/* `pt'. */
	mov %i4, %o2		/* `userf'. */
	call %i5, 0		/* Call client function. */
	sub %sp, 32, %sp	/* Allocate 6-word callee space. */

	call qt_error, 0	/* `only' erroniously returned. */
	nop

/* Same comments as `qt_start' about allocating rounded-up 7-word
// save areas. */

qt_vstart:
	sub %sp, 32, %sp	/* Allocate 7-word callee space. */
	call %i5, 0		/* call `startup'. */
	mov %i2, %o0		/* .. with argument `pt'. */

	add %sp, 32, %sp	/* Use 7-word space in varargs. */
	ld [%sp+ 4+64], %o0	/* Load arg0 ... */
	ld [%sp+ 8+64], %o1
	ld [%sp+12+64], %o2
	ld [%sp+16+64], %o3
	ld [%sp+20+64], %o4
	call %i4, 0		/* Call `userf'. */
	ld [%sp+24+64], %o5

				/* Use 6-word space in varargs. */
	mov %o0, %o1		/* Pass return value from userf */
	call %i3, 0		/* .. when call `cleanup. */
	mov %i2, %o0		/* .. along with argument `pt'. */

	call qt_error, 0	/* `cleanup' erroniously returned. */
	nop


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.globl b_call_reg
	.globl b_call_imm
	.globl b_add
	.globl b_load

b_null:
	retl
	nop

b_call_reg:
	sethi %hi(b_null),%o4
	or %o4,%lo(b_null),%o4
	add %o7,%g0, %o3
L0:
	call %o4
	nop
	call %o4
	nop
	call %o4
	nop
	call %o4
	nop
	call %o4
	nop

	subcc %o0,1,%o0
	bg L0
	nop
	add %o3,%g0, %o7
	retl
	nop

b_call_imm:
	sethi %hi(b_null),%o4
	or %o4,%lo(b_null),%o4
	add %o7,%g0, %o3
L1:
	call b_null
	call b_null
	call b_null
	call b_null
	call b_null

	subcc %o0,1,%o0
	bg L0
	nop
	add %o3,%g0, %o7
	retl
	nop

b_add:
	add %o0,%g0,%o1
	add %o0,%g0,%o2
	add %o0,%g0,%o3
	add %o0,%g0,%o4
L2:
	sub %o0,5,%o0
	sub %o1,5,%o1
	sub %o2,5,%o2
	sub %o3,5,%o3
	sub %o4,5,%o4

	subcc %o0,5,%o0
	sub %o1,5,%o1
	sub %o2,5,%o2
	sub %o3,5,%o3
	sub %o4,5,%o4

	bg L2
	nop
	retl
	nop

b_load:
	ld [%sp+ 0], %g0
L3:
	ld [%sp+ 4],%g0
	ld [%sp+ 8],%g0
	ld [%sp+12],%g0
	ld [%sp+16],%g0
	ld [%sp+20],%g0
	ld [%sp+24],%g0
	ld [%sp+28],%g0
	ld [%sp+32],%g0
	ld [%sp+36],%g0

	subcc %o0,10,%o0
	bg L3
	ld [%sp+ 0],%g0
	retl
	nop


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

#ifndef QT_VAX_H
#define QT_VAX_H

typedef unsigned long qt_word_t;

/* Thread's initial stack layout on the VAX:

   non-varargs:

   +---
   | arg[2]	=== `userf' on startup
   | arg[1]	=== `pt' on startup
   | arg[0]	=== `pu' on startup
   | ...	=== `only' on startup.
   +---
   | ret pc	=== `qt_start' on startup
   | fp		=== 0 on startup
   | ap		=== 0 on startup
   | <mask>
   | 0 (handler)			<--- qt_t.sp
   +---

   When a non-varargs thread is started, it ``returns'' to the start
   routine, which calls the client's `only' function.

   The varargs case is clearly bad code.  The various values should be
   stored in a save area and snarfed in to callee-save registers on
   startup.  However, it's too painful to figure out the register
   mask (right now), so do it the slow way.

   +---
   | arg[n-1]
   | ..
   | arg[0]
   | nargs
   +---
   |		=== `cleanup'
   |		=== `vuserf'
   |		=== `startup'
   |		=== `pt'
   +---
   | ret pc	=== `qt_start' on startup
   | fp		=== 0 on startup
   | ap		=== 0 on startup
   | <mask>
   | 0 (handler)			<--- qt_t.sp
   +---

   When a varargs thread is started, it ``returns'' to the `qt_vstart'
   startup code.  The startup code pops all the extra arguments, then
   calls the appropriate functions. */

/* What to do to start a thread running. */
extern void qt_start (void);
extern void qt_vstart (void);

/* Initial call frame for non-varargs and varargs cases. */
#define QT_STKBASE	(10 * 4)
#define QT_VSTKBASE	(9 * 4)

/* Stack "must be" 4-byte aligned.  (Actually, no, but it's
   easiest and probably fastest to do so.) */

#define QT_STKALIGN	(4)

/* Where to place various arguments. */
#define QT_ONLY_INDEX	(5)
#define QT_USER_INDEX	(8)
#define QT_ARGT_INDEX	(7)
#define QT_ARGU_INDEX	(6)

#define QT_VSTARTUP_INDEX	(6)
#define QT_VUSERF_INDEX		(7)
#define QT_VCLEANUP_INDEX	(8)
#define QT_VARGT_INDEX		(5)

/* Stack grows down.  The top of the stack is the first thing to
   pop off (predecrement, postincrement). */
#define QT_GROW_DOWN

extern void qt_error (void);

#define QT_VAX_GMASK_NOREGS	(0)

/* Push on the error return address, null termination to call chains,
   number of arguments to `only', register save mask (save no
   registers). */

#define QT_ARGS_MD(sto) \
    (QT_SPUT (sto, 0, 0), \
     QT_SPUT (sto, 1, QT_VAX_GMASK_NOREGS), \
     QT_SPUT (sto, 2, 0), \
     QT_SPUT (sto, 3, 0), \
     QT_SPUT (sto, 4, qt_start))

#define QT_VARGS_MD0(sto, nbytes) \
    (QT_SPUT (sto, (-(nbytes)/4)-1, (nbytes)/4), \
     ((char *)(((sto)-4) - QT_STKROUNDUP(nbytes))))

#define QT_VARGS_ADJUST(sp)	((char *)sp + 4)

#define QT_VARGS_MD1(sto) \
    (QT_SPUT (sto, 0, 0), \
     QT_SPUT (sto, 1, QT_VAX_GMASK_NOREGS), \
     QT_SPUT (sto, 2, 0), \
     QT_SPUT (sto, 3, 0), \
     QT_SPUT (sto, 4, qt_vstart))

#define QT_VARGS_DEFAULT

#endif /* QT_VAX_H */


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.text

	.globl _qt_abort
	.globl _qt_block
	.globl _qt_blocki
	.globl _qt_start
	.globl _qt_vstart

/*
// Calls to these routines have the signature
//
//	void *block (func, arg1, arg2, newsp)
//
// Since the prologue saves 5 registers, nargs, pc, fp, ap, mask, and
// a condition handler (at sp+0), the first argument is 40=4*10 bytes
// offset from the stack pointer.
*/
_qt_block:
_qt_blocki:
_qt_abort:
	.word 0x7c0		/* Callee-save mask: 5 registers. */
	movl 56(sp),r1		/* Get stack pointer of new thread. */
	movl 52(sp),-(r1)	/* Push arg2 */
	movl 48(sp),-(r1)	/* Push arg1 */
	movl sp,-(r1)		/* Push arg0 */

	movl 44(sp),r0		/* Get helper to call. */
	movl r1,sp		/* Move to new thread's stack. */
	addl3 sp,$12,fp		/* .. including the frame pointer. */
	calls $3,(r0)		/* Call helper. */

	ret

_qt_start:
	movl (sp)+,r0		/* Get `only'. */
	calls $3,(r0)		/* Call `only'. */
	calls $0,_qt_error	/* `only' erroniously returned. */

_qt_vstart:
	movl (sp)+,r10		/* Get `pt'. */
	movl (sp)+,r9		/* Get `startup'. */
	movl (sp)+,r8		/* Get `vuserf'. */
	movl (sp)+,r7		/* Get `cleanup'. */

	pushl r10		/* Push `qt'. */
	calls $1,(r9)		/* Call `startup', pop `qt' on return. */

	calls (sp)+,(r8)	/* Call user's function. */

	pushl r0		/* Push `vuserf_retval'. */
	pushl r10		/* Push `qt'. */
	calls $2,(r7)		/* Call `cleanup', never return. */

	calls $0,_qt_error	/* `cleanup' erroniously returned. */


code:
/*
 * QuickThreads -- Threads-building toolkit.
 * Copyright (c) 1993 by David Keppel
 *
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice and this notice
 * appear in all copies.  This software is provided as a
 * proof-of-concept and for demonstration purposes; there is no
 * representation about the suitability of this software for any
 * purpose.
 */

	.text
	.globl _b_call_reg
	.globl _b_call_imm
	.globl _b_add
	.globl _b_load

_b_null:
	.word 0x0
	ret

_b_call_reg:
	.word 0x0
	movl 4(ap),r0
	moval _b_null,r1
L0:
	calls $0,(r1)
	calls $0,(r1)
	calls $0,(r1)
	calls $0,(r1)
	calls $0,(r1)

	subl2 $5,r0
	bgtr L0
	ret

_b_call_imm:
	.word 0x0
	movl 4(ap),r0
L1:
	calls $0,_b_null
	calls $0,_b_null
	calls $0,_b_null
	calls $0,_b_null
	calls $0,_b_null

	subl2 $5,r0
	bgtr L1
	ret

_b_add:
	.word 0x0
	movl 4(ap),r0
L2:
	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0

	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0
	subl2 $1,r0

	bgtr L2
	ret

_b_load:
	.word 0x0
	movl 4(ap),r0
L3:
	movl 0(sp),r1
	movl 4(sp),r1
	movl 8(sp),r1
	movl 12(sp),r1
	movl 16(sp),r1
	movl 20(sp),r1
	movl 24(sp),r1
	movl 28(sp),r1
	movl 32(sp),r1
	movl 36(sp),r1

	subl2 $1,r0
	bgtr L3
	ret