/* --- Copyright University of Sussex 1994. All rights reserved. ----------
 * File:        C.mips/src/aprocess.s
 * Purpose:     Assembler support for processes on MIPS R2000/R3000
 * Author:      Robert Duncan and Simon Nichols, Feb 20 1990 (see revisions)
 */


#_<

#_INCLUDE 'declare.ph'
#_INCLUDE 'process.ph'

lconstant macro (

	_ID_VALOF		= @@ID_VALOF,
	_PD_EXECUTE		= @@PD_EXECUTE,
	_PD_EXIT		= @@PD_EXIT,
	_PD_FLAGS		= @@PD_FLAGS,
	_PD_FRAME_LEN		= @@PD_FRAME_LEN,
	_PD_NLOCALS		= @@PD_NLOCALS,
	_PD_NUM_STK_VARS	= @@PD_NUM_STK_VARS,
	_PD_REGMASK		= @@PD_REGMASK,
	_PD_TABLE		= @@PD_TABLE,
	_PS_CALLSTACK_LIM	= @@PS_CALLSTACK_LIM,
	_PS_CALLSTACK_PARTIAL	= @@PS_CALLSTACK_PARTIAL,
	_PS_FLAGS		= @@PS_FLAGS,
	_PS_PARTIAL_RETURN	= @@PS_PARTIAL_RETURN,
	_PS_STATE		= @@PS_STATE,
);

>_#

#_INCLUDE 'pop_regdef.h'


/************************* wrapping structures ************************/

	.data
	.word	Ldata_size
	.word	C_LAB(Sys$-objmod_pad_key)
Ldata_start:

/**********************************************************************/


	.text

;;; === SWAPPING THE CALLSTACK ========================================

BRANCH_std = 8 	;;; Size of a standard branch instruction
		;;; (as placed by I_BRANCH_std)

;;; _SWAP_OUT_CALLSTACK:
;;;	Swap out the callstack for a process
;;;	(i.e. copy the callstack into the process record)

;;; Call:
;;;	_swap_out_callstack(PROCESS)

;;; Registers used:
;;;	cr	process record
;;;	a0	start of saved callstack
;;;	a1	limit of saved callstack
;;;	a2	callstack pointer
;;;	t0	ident table pointer
;;;	t1	(1) register mask
;;;		(2) number of dynamic locals (loop counter)
;;;		(3) number of on-stack lvars (loop counter)
;;;	t2	(1) size of current stack frame
;;;		(2) pointer to next frame
;;;	t3,t4	temporaries

DEF_C_LAB (_swap_out_callstack)

	.ent	$swap_out_callstack
$swap_out_callstack:

	lw	cr, (usp)			;;; Process record
	lw	a0, _PS_STATE(cr)		;;; Start of saved callstack
	lw	a1, _PS_CALLSTACK_LIM(cr)	;;; Limit of saved callstack
	addu	usp, 4
	b	so_test

so_loop:
	;;; Copy out the next stack frame:
	;;; Make the return address relative to the procedure base

	subu	ra, pb

	;;; Test if the procedure has dlocal expression code to run:
	;;; if so break out to run the code

	lbu	t3, _PD_FLAGS(pb)
	and	t3, _:M_PD_PROC_DLEXPR_CODE
	bnez	t3, so_brk

so_cont:
	;;; Continue here after running the dlocal expression code:
	;;; get pointer to next stack frame

	lbu	t2, _PD_FRAME_LEN(pb)	;;; frame size of this procedure
	sll	t2, 2			;;; frame size in bytes
	addu	t2, sp			;;; pointer to next frame
	subu	a2, t2, 4		;;; callstack pointer

#_IF DEF PIC
	;;; Copy context pointer from stack frame to process
	subu	a2, 4
	lw	gp, (a2)
	subu	a1, 4
	sw	gp, (a1)
#_ENDIF

	;;; Save registers in use (as determined by the register mask)
	;;; in the process record, and restore those registers from the
	;;; stack frame

	lhu	t1, _PD_REGMASK(pb)

	;;; NPOP_REG_3

	and	t3, t1, 32
	beqz	t3, 1f
	subu	a1, 4
	sw	np3, (a1)
	subu	a2, 4
	lw	np3, (a2)

1:	;;; NPOP_REG_2

	and	t3, t1, 16
	beqz	t3, 2f
	subu	a1, 4
	sw	np2, (a1)
	subu	a2, 4
	lw	np2, (a2)

2:	;;; NPOP_REG_1

	and	t3, t1, 8
	beqz	t3, 3f
	subu	a1, 4
	sw	np1, (a1)
	subu	a2, 4
	lw	np1, (a2)

3:	;;; NPOP_REG_0

	and	t3, t1, 4
	beqz	t3, 4f
	subu	a1, 4
	sw	np0, (a1)
	subu	a2, 4
	lw	np0, (a2)

4:	;;; POP_REG_1

	and	t3, t1, 2
	beqz	t3, 5f
	subu	a1, 4
	sw	p1, (a1)
	subu	a2, 4
	lw	p1, (a2)

5:	;;; POP_REG_0

	and	t3, t1, 1
	beqz	t3, 6f
	subu	a1, 4
	sw	p0, (a1)
	subu	a2, 4
	lw	p0, (a2)

6:	;;; Test if there are any dynamic locals

	lbu	t1, _PD_NLOCALS(pb)
	addu	t0, pb, _PD_TABLE	;;; ptr stepping through ident table
	beqz	t1, 8f

	;;; If so, copy the current values of the locals into the process
	;;; record, and restore their previous values from the stack

7:	lw	t3, (t0)		;;; load ident to t3
	addu	t0, 4
	lw	t4, _ID_VALOF(t3)	;;; load idval to t4
	subu	a1, 4
	sw	t4, (a1)		;;; store idval in process record
	subu	a2, 4
	lw	t4, (a2)		;;; load previous idval from stack
	sw	t4, _ID_VALOF(t3)	;;; restore idval to ident table
	subu	t1, 1			;;; decrement count of locals
	bnez	t1, 7b			;;; loop until count = 0

	;;; Copy any on-stack lvars into the process record

8:	lbu	t1, _PD_NUM_STK_VARS(pb)
	beqz	t1, 10f

9:	subu	a2, 4
	lw	t3, (a2)		;;; get stack lvar value
	subu	a1, 4
	sw	t3, (a1)		;;; save in process record
	subu	t1, 1			;;; decrement stack lvar count
	bnez	t1, 9b			;;; loop until count = 0

10:	;;; Save relative return address and owner address in process record

	subu	a1, 8
	sw	pb, 4(a1)
	sw	ra, (a1)

	;;; Set return address and procedure base from next stack frame

	lw	ra, -4(t2)
	lw	pb, (t2)

	;;; Clear the current stack frame

	move	sp, t2

so_test:
	;;; Test if there are any more stack frames to swap out

	bltu	a0, a1, so_loop

	;;; Finished swapping out -- chain procedure on stack

	sw	zero, _PS_CALLSTACK_PARTIAL(cr)
	sh	zero, _PS_FLAGS(cr)		;;; 0 flags = suspended
	lw	a0, (usp)			;;; pop procedure to a0
	addu	usp, 4
	lw	t9, _PD_EXECUTE(a0)
	j	t9				;;; chain procedure

so_brk:
	;;; Come here to run dlocal expression code, by jumping into the
	;;; appropriate part of the exit code of the procedure.

	;;; Save relative return address and callstack save pointer in the
	;;; process record

	sw	ra, _PS_PARTIAL_RETURN(cr)
	addu	ra, pb				;;; make it absolute again
	sw	a1, _PS_CALLSTACK_PARTIAL(cr)

#_IF DEF PIC
	;;; Must reset the context pointer from the current stack frame
	;;; or the suspend code may be invalid
	lbu	t2, _PD_FRAME_LEN(pb)	;;; frame size of the procedure
	sll	t2, 2			;;; frame size in bytes
	addu	t2, sp			;;; pointer to next frame
	lw	gp, -8(t2)
#_ENDIF

	;;; Jump to the procedure suspend code

	lw	t9, _PD_EXIT(pb)
	subu	t9, BRANCH_std*2
	j	t9

	.end	$swap_out_callstack

	;;; The suspend code returns here to resume the swap-out loop

DEF_C_LAB (_swap_out_continue)

	.ent	$swap_out_continue
$swap_out_continue:

	;;; Restore relative return address, callstack save limit and
	;;; callstack save pointer from the process record

	lw	ra, _PS_PARTIAL_RETURN(cr)
	lw	a0, _PS_STATE(cr)
	lw	a1, _PS_CALLSTACK_PARTIAL(cr)

	;;; Continue the swapout loop

	b	so_cont

	.end	$swap_out_continue


;;; _SWAP_IN_CALLSTACK:
;;;	Swap in the callstack for a process
;;;	(i.e. copy stack frames from the process record onto the stack)

;;; Call:
;;;	_swap_in_callstack(PROCESS)

;;; Registers used:
;;;	cr	process record
;;;	a0	limit of saved callstack
;;;	a1	start of saved callstack
;;;	a2	callstack pointer
;;;	t0	ident table pointer
;;;	t1	(1) register mask
;;;		(2) number of dynamic locals (loop counter)
;;;		(3) number of on-stack lvars (loop counter)
;;;	t2	(1) size of current stack frame
;;;		(2) pointer to old frame
;;;	t3,t4	temporaries

DEF_C_LAB (_swap_in_callstack)

	.ent	$swap_in_callstack
$swap_in_callstack:

	lw	cr, (usp)			;;; Process record
	lw	a0, _PS_CALLSTACK_LIM(cr)	;;; Limit of saved callstack
	lw	a1, _PS_STATE(cr)		;;; Start of saved callstack
	addu	usp, 4
	b	si_test

si_loop:
	;;; Restore owner address from process

	lw	pb, 4(a1)

	;;; Set callstack pointer

	lbu	t2, _PD_FRAME_LEN(pb)	;;; frame size of this procedure
	sll	t2, 2			;;; frame size in bytes
	subu	a2, sp, t2		;;; callstack pointer

	;;; Allocate new stack frame

	move	t2, sp			;;; old frame pointer
	move	sp, a2			;;; new frame

	;;; Save current return address and restore the old (relative)
	;;; one from process

	sw	ra, -4(t2)
	lw	ra, (a1)
	addu	a1, 8

	;;; Restore owner address to stack

	sw	pb, (a2)
	addu	a2, 4

	;;; Restore on-stack lvars from the process record to the stack

	lbu	t1, _PD_NUM_STK_VARS(pb)
	beqz	t1, 2f

1:	lw	t3, (a1)		;;; get saved stack lvar value
	addu	a1, 4
	sw	t3, (a2)		;;; save on stack
	addu	a2, 4
	subu	t1, 1			;;; decrement stack lvar count
	bnez	t1, 1b			;;; loop until count = 0

2:	;;; Test if there are any dynamic locals

	lbu	t1, _PD_NLOCALS(pb)
	addu	t0, pb, _PD_TABLE	;;; ptr stepping through ident table
	beqz	t1, 4f

	;;; If so, copy the current values of the locals into the process
	;;; record, and restore their previous values from the stack

	sll	t3, t1, 2		;;; offset in bytes
	addu	t0, t3			;;; point to end of ident table

3:	subu	t0, 4
	lw	t3, (t0)		;;; load ident to t3
	lw	t4, _ID_VALOF(t3)	;;; load idval to t4
	sw	t4, (a2)		;;; push current idval on stack
	addu	a2, 4
	lw	t4, (a1)		;;; new idval from process to t4
	addu	a1, 4
	sw	t4, _ID_VALOF(t3)	;;; store new idval in ident table
	subu	t1, 1			;;; decrement count of locals
	bnez	t1, 3b			;;; loop until count = 0

4:	;;; Save registers in use (as determined by the register mask) in the
	;;; stack frame, and restore those registers from the process record

	lhu	t1, _PD_REGMASK(pb)

	;;; POP_REG_0

	and	t3, t1, 1
	beqz	t3, 5f
	sw	p0, (a2)
	addu	a2, 4
	lw	p0, (a1)
	addu	a1, 4

5:	;;; POP_REG_1

	and	t3, t1, 2
	beqz	t3, 6f
	sw	p1, (a2)
	addu	a2, 4
	lw	p1, (a1)
	addu	a1, 4

6:	;;; NPOP_REG_0

	and	t3, t1, 4
	beqz	t3, 7f
	sw	np0, (a2)
	addu	a2, 4
	lw	np0, (a1)
	addu	a1, 4

7:	;;; NPOP_REG_1

	and	t3, t1, 8
	beqz	t3, 8f
	sw	np1, (a2)
	addu	a2, 4
	lw	np1, (a1)
	addu	a1, 4

8:	;;; NPOP_REG_2

	and	t3, t1, 16
	beqz	t3, 9f
	sw	np2, (a2)
	addu	a2, 4
	lw	np2, (a1)
	addu	a1, 4


9:	;;; NPOP_REG_3

	and	t3, t1, 32
	beqz	t3, 10f
	sw	np3, (a2)
	addu	a2, 4
	lw	np3, (a1)
	addu	a1, 4

10:

#_IF DEF PIC
	;;; Restore the context pointer from the process record and
	;;; re-save to stack
	lw	gp, (a1)
	addu	a1, 4
	sw	gp, (a2)
	addu	a2, 4
#_ENDIF

	;;; Test if the procedure has dlocal expression code to run:
	;;; if so break out to run the code

	lbu	t3, _PD_FLAGS(pb)
	and	t3, _:M_PD_PROC_DLEXPR_CODE
	bnez	t3, si_brk

si_cont:
	;;; Continue here after running the dlocal expression code:
	;;; make return address absolute

	addu	ra, pb

si_test:
	;;; Test if there are any more stack frames to swap out

	bgtu	a0, a1, si_loop

	;;; Finished swapping in -- chain procedure from stack

	sw	zero, _PS_CALLSTACK_PARTIAL(cr)
	lw	a0, (usp)			;;; pop procedure to a0
	addu	usp, 4
	lw	t9, _PD_EXECUTE(a0)
	j	t9				;;; chain procedure

si_brk:
	;;; Come here to run dlocal expression code, by jumping into the
	;;; appropriate part of the exit code of the procedure.

	;;; Save relative return address and callstack save pointer in the
	;;; process record

	sw	ra, _PS_PARTIAL_RETURN(cr)
	addu	ra, pb				;;; make it absolute again
	sw	a1, _PS_CALLSTACK_PARTIAL(cr)

	;;; Jump to the procedure resume code

	lw	t9, _PD_EXIT(pb)
	subu	t9, BRANCH_std
	j	t9

	.end	$swap_in_callstack

	;;; The resume code returns here to continue the swap-in loop

DEF_C_LAB (_swap_in_continue)

	.ent	$swap_in_continue
$swap_in_continue:

	;;; Restore relative return address, callstack save limit and
	;;; callstack save pointer from the process record

	lw	ra, _PS_PARTIAL_RETURN(cr)
	lw	a0, _PS_CALLSTACK_LIM(cr)
	lw	a1, _PS_CALLSTACK_PARTIAL(cr)

	;;; Continue the swapout loop

	b	si_cont

	.end	$swap_in_continue


;;; === SWAPPING THE USERSTACK ========================================

;;; _USSAVE:
;;;	Save and erase a number of bytes from the end of the userstack

;;; Call:
;;;	_ussave(_BYTE_LENGTH, _DST_ADDR);

;;; Registers used:
;;;	a0	byte length (1st argument to -movwords-)
;;;	a1	source address of move (2nd argument to -movwords-)
;;;	a2	destination address of move (3rd argument to -movwords-)
;;;	t0	value of _userhi
;;;	t1	saved source address of first move

;;; Uses:
;;;	-movwords- (defined in amove.s)

DEF_C_LAB (_ussave)

	.ent	$ussave
$ussave:

	;;; Save return address

	.frame	sp, 8, ra
	.mask	0x80000000, -4
	subu	sp, 8
	sw	ra, 4(sp)

	;;; Save the bytes from the end of the userstack

	lw	a0, 4(usp)		;;; number of bytes to save
	lw	a2, (usp)		;;; destination of saved part
	addu	usp, 8
	lw	t0, _SVB_OFFS(_userhi)(svb)
	subu	a1, t0, a0		;;; source of move (_userhi - length)
	move	t1, a1			;;; save source address for later
	jal	movwords		;;; do the move

	;;; Reposition the rest above

	move	a1, usp			;;; source of move
	subu	a0, t1, a1		;;; size of rest to move
	subu	usp, t0, a0		;;; new stack top after move ...
	move	a2, usp			;;; ... also destination of move
	lw	ra, 4(sp)		;;; restore return address
	addu	sp, 8
	la	t9, movwords		;;; do the move, returning to caller
	j	t9

	.end	$ussave


;;; _USRESTORE:
;;;	Restore a number of bytes at the end of the user stack

;;; Call:
;;;	_usrestore(_BYTE_LENGTH, _SRC_ADDR)

;;; Registers used:
;;;	a0	byte length (1st argument to -movwords-)
;;;	a1	source address of move (2nd argument to -movwords-)
;;;	a2	destination address of move (3rd argument to -movwords-)
;;;	t0	value of _userhi
;;;	t1	number of bytes to restore

;;; Uses:
;;;	-movwords- (defined in amove.s)

DEF_C_LAB (_usrestore)

	.ent	$usrestore
$usrestore:

	;;; Save return address

	.frame	sp, 8, ra
	.mask	0x80000000, -4
	subu	sp, 8
	sw	ra, 4(sp)

	;;; Move existing stack down, leaving room for restored bytes

	lw	t0, _SVB_OFFS(_userhi)(svb)
	lw	t1, 4(usp)		;;; number of bytes to restore
	subu	a0, t0, usp		;;; size of current stack to move
	move	a1, usp			;;; stack top is source of move
	subu	usp, t1			;;; new stack top after move ...
	move	a2, usp			;;; ... also destination of move
	jal	movwords		;;; do the move

	;;; Move in the new part

	lw	a1, (usp)		;;; address of part to restore
	addu	usp, 8			;;; remove 2 arguments
	move	a0, t1			;;; number of bytes to restore
	subu	a2, t0, a0		;;; destination of move
	lw	ra, 4(sp)		;;; restore return address
	addu	sp, 8
	la	t9, movwords		;;; do the move, returning to caller
	j	t9

	.end	$usrestore


;;; _USERASUND:
;;;	Erase a number of bytes from the end of the user stack

;;; Call:
;;;	_userasund(_BYTE_LENGTH)

;;; Registers used:
;;;	a0	byte length (1st argument to -movwords-)
;;;	a1	source address of move (2nd argument to -movwords-)
;;;	a2	destination address of move (3rd argument to -movwords-)
;;;	t0	value of _userhi
;;;	t1	number of bytes to erase

;;; Uses:
;;;	-movwords- (defined in amove.s)

DEF_C_LAB (_userasund)

	.ent	$userasund
$userasund:

	lw	t0, _SVB_OFFS(_userhi)(svb)
	lw	t1, (usp)		;;; Number of bytes to erase
	addu	a1, usp, 4		;;; source of move
	addu	usp, a1, t1		;;; new stack top after move ...
	move	a2, usp			;;; ... also destination of move
	subu	a0, t0, usp		;;; length of move
	la	t9, movwords		;;; do the move, returning to caller
	j	t9

	.end	$userasund


/***************** end labels for wrapping structures *****************/

	.data
Ldata_end:
Ldata_size = 0 ##PATCH## Ldata_size Ldata_end Ldata_start

/**********************************************************************/


/* --- Revision History ---------------------------------------------------
--- Robert John Duncan, Mar 29 1994
	Fixed treatment of the context pointer
--- Robert John Duncan, Mar 25 1994
	Changed stack frames to be 8-byte aligned.
--- Robert John Duncan, Mar 22 1994
	Changed external jumps to go off t9.
--- Robert John Duncan, Mar 15 1994
	Removed the wrapping structure from the text section
--- Robert John Duncan, Mar 10 1994
	Added cases for position-independent code. Changed to use special
	var block register (t8) requiring some register renumbering.
--- Robert John Duncan, Mar  8 1994
	Added .ent/.end directives
--- Robert John Duncan, Mar  7 1994
	Changed to use register $t9 for function calls only.
--- Robert John Duncan, Jan 13 1994
	Fixed a typo in _swap_in_callstack which was screwing up restoration
	of the non-pop registers
--- Robert John Duncan, Jun 19 1991
	Made _swap_in_callstack chain a procedure off the stack when
	finished
--- Robert John Duncan, Oct  1 1990
	Fixed assignments to SP in _swap_in/_swap_out_callstack to ensure
	that there's never any needed data below the stack pointer (in case
	of interrupt).
	Restored absolute return address for duration of dlocal suspend and
	resume code.
--- Robert John Duncan, Jul  4 1990
	Added extern declarations for special vars
 */
