/* --- Copyright University of Sussex 1995. All rights reserved. ----------
 * File:        C.mips/src/amove.s
 * Purpose:     Memory moves and compares for MIPS R2000/R3000
 * Author:      Robert Duncan and Simon Nichols, Jan 16 1990 (see revisions)
 */


#_<

#_INCLUDE 'declare.ph'

lconstant macro (
	;;; offset of true from false on the assumption that poplink
	;;; generates booleans in the order: false, true
	_TRUE_OFFS	= @@(struct BOOLEAN)++,
);

>_#

#_INCLUDE 'pop_regdef.h'


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

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

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


	.text

;;; === COMPARISONS ===================================================

;;; _BCMP:
;;;	Compare two byte regions of the same length

;;; Call:
;;;	_bcmp(_BYTE_LENGTH, _SRC1, _SRC2) -> BOOL

;;; Registers used:
;;;	a0 	length of the comparison in bytes
;;;	a1 	pointer to region 1
;;;	a2 	pointer to region 2
;;;	t0	(1) current byte of region 1
;;;		(2) if successful, result of <true>
;;;	t1	current byte of region 2

DEF_C_LAB (_bcmp)

	.ent	$bcmp
$bcmp:

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 8

	;;; If length (a0) is zero, then return <true> immediately

	beqz	a0, 2f

1:	;;; Repeat

	;;; Load byte from region 1 to t0 and byte from region 2 to t1

	lbu	t0, (a1)
	addu	a1, 1
	lbu	t1, (a2)
	addu	a2, 1

	;;; If the bytes are different, return <false>

	bne	t0, t1, 3f

	;;; Decrement length (in a0)

	subu	a0, 1

	;;; Until a0 = 0

	bnez	a0, 1b

2:	;;; Same: return <true>

	la	t0, _TRUE_OFFS(false)
	sw	t0, (usp)
	j	ra

3:	;;; Different: return <false>

	sw	false, (usp)
	j	ra

	.end	$bcmp


;;; _SCMP:
;;;	Compare two short (halfword) regions of the same length

;;; Call:
;;;	_scmp(_BYTE_LENGTH, _SRC1, _SRC2) -> BOOL

;;; Registers used:
;;;	a0 	length of the comparison in bytes
;;;	a1 	pointer to region 1
;;;	a2 	pointer to region 2
;;;	t0	(1) current halfword of region 1
;;;		(2) if successful, result of <true>
;;;	t1	current halfword of region 2

DEF_C_LAB (_scmp)

	.ent	$scmp
$scmp:

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 8

	;;; If length (a0) is zero, then return <true> immediately

	beqz	a0, 2f

1:	;;; Repeat

	;;; Load halfword from region 1 to t0 and halfword from region 2 to t1

	lh	t0, (a1)
	addu	a1, 2
	lh	t1, (a2)
	addu	a2, 2

	;;; If the halfwords are different, return <false>

	bne	t0, t1, 3f

	;;; Decrement length (in a0)

	subu	a0, 2

	;;; Until a0 = 0

	bnez	a0, 1b

2:	;;; Same: return <true>

	la	t0, _TRUE_OFFS(false)
	sw	t0, (usp)
	j	ra

3:	;;; Different: return <false>

	sw	false, (usp)
	j	ra

	.end	$scmp


;;; _ICMP:
;;; _CMP:
;;;	Compare two int/word regions of the same length

;;; Call:
;;;	_cmp(_BYTE_LENGTH, _SRC1, _SRC2) -> BOOL

;;; Registers used:
;;;	a0 	length of the comparison in bytes
;;;	a1 	pointer to region 1
;;;	a2 	pointer to region 2
;;;	t0	(1) current nword of region 1
;;;		(2) if successful, result of <true>
;;;	t1	current word of region 2

DEF_C_LAB (_icmp)
DEF_C_LAB (_cmp)

	.ent	$cmp
$cmp:

	lw	a2, (usp)
	lw	a1, 4(usp)
	lw	a0, 8(usp)
	addu	usp, 8

	;;; If length (a0) is zero, then return <true> immediately

	beqz	a0, 2f

1:	;;; Repeat

	;;; Load word from region 1 to t0 and word from region 2 to t1

	lw	t0, (a1)
	addu	a1, 4
	lw	t1, (a2)
	addu	a2, 4

	;;; If the words are different, return <false>

	bne	t0, t1, 3f

	;;; Decrement length (in a0)

	subu	a0, 4

	;;; Until a0 = 0

	bnez	a0, 1b

2:	;;; Same: return <true>

	la	t0, _TRUE_OFFS(false)
	sw	t0, (usp)
	j	ra

3:	;;; Different: return <false>

	sw	false, (usp)
	j	ra

	.end	$cmp


;;; === MOVES =========================================================

;;; _MOVEQ:
;;;	Quick word move. The move is done from the lowest word first, so if
;;;	the source and destination regions overlap, the direction of the move
;;;	must be downwards to preserve correctness.
;;;	Returns a pointer to the next destination word.

;;; Call:
;;;	_moveq(_BYTE_LENGTH, _SRCADDR, _DSTADDR) -> _NEXT_DSTADDR;

;;; Registers used:
;;;	a0	length of the move
;;;	a1	source pointer
;;;	a2	destination pointer
;;;	t0	current word

DEF_C_LAB (_moveq)

	.ent	$moveq
$moveq:

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 8

	;;; Quit if the source and destination addresses are the same

	beq	a1, a2, 3f

	;;; Quit if the byte length is zero

	beqz	a0, 2f

1:	;;; Repeat

	;;; Move word from source (a1) to destination (a2)

	lw	t0, (a1)
	addu	a1, 4
	sw	t0, (a2)
	addu	a2, 4

	;;; Decrement length (in a0)

	subu	a0, 4

	;;; Until a0 = 0

	bnez	a0, 1b

2:	;;; Return the next destination address

	sw	a2, (usp)
	j	ra

3:	;;; Source and destination the same:
	;;; compute and return the next destination address

	addu	a2, a0
	sw	a2, (usp)
	j	ra

	.end	$moveq


;;; _BMOVE:
;;; _SMOVE:
;;; _IMOVE:
;;; _MOVE:
;;;	General purpose moves for bytes, halfwords and words. Cope with any
;;;	alignment and with moves in both directions. Return address of the
;;;	next destination.

;;; Call:
;;;	_move(_BYTE_LENGTH, _SRCADDR, _DSTADDR) -> _NEXT_DSTADDR;

;;; Uses:
;;;	movbytes, movwords (defined below)

DEF_C_LAB (_bmove)
DEF_C_LAB (_smove)

	.ent	$bmove
$bmove:

	;;; Set register arguments for -movbytes-

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 8

	;;; Push the next destination address as the return value

	addu	t0, a2, a0
	sw	t0, (usp)

	;;; Return if source and destination addresses are the same,
	;;; otherwise do the move

	bne	a1, a2, movbytes
	j	ra

	.end	$bmove

DEF_C_LAB (_imove)
DEF_C_LAB (_dmove)
DEF_C_LAB (_move)

	.ent	$move
$move:

	;;; Set register arguments for -movwords-

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 8

	;;; Push the next destination address as the return value

	addu	t0, a2, a0
	sw	t0, (usp)

	;;; Return if source and destination addresses are the same,
	;;; otherwise do the move

	bne	a1, a2, movwords
	j	ra

	.end	$move


;;; MOVBYTES:
;;; MOVWORDS:
;;;     General purpose memory move. Not callable from Pop, as they take
;;;     their their arguments in registers rather than on the stack (but
;;;     cf. _bmove, _smove and _move above). MOVWORDS is for use when
;;;     the move is word-aligned.

;;; Arguments:
;;;	a0	length of move in bytes
;;;	a1	source address
;;;	a2	destination address

;;; Results:
;;;	none

;;; Other registers used:
;;;	t6	(1) result of bitwise OR of all the arguments
;;;		(2) byte/word being moved

	.ent	movbytes
movbytes:

	;;; OR together the bits of the source address, destination address
	;;; and byte count to test for word alignment

	or	t6, a1, a2
	or	t6, a0

	;;; If neither of the bottom two bits is set, the move is word
	;;; aligned so move a word at a time

	and	t6, 3
	beqz	t6, movwords

	;;; Otherwise, move a byte at a time

	;;; If length is zero there's nothing to move, so exit

	beqz	a0, 4f

	;;; Test the direction of the move:
	;;; if source (a1) > destination (a2), the move is downward;
	;;; otherwise, the move is upward, so branch

	bltu	a1, a2, 2f

	;;; Downward move

1:	;;; Repeat

	;;; Move a byte from source (a1) to destination (a2) via t6

	lbu	t6, (a1)
	addu	a1, 1
	sb	t6, (a2)
	addu	a2, 1

	;;; Decrement count (in a0)

	subu	a0, 1

	;;; Until a0 = 0

	bnez	a0, 1b

	;;; Return

	j	ra

2:	;;; Upward move:
	;;; adjust pointers to point above the last byte in each region

	addu	a1, a0
	addu	a2, a0

3:	;;; Repeat

	;;; Move a byte from source (a1) to destination (a2) via t6

	subu	a1, 1
	lbu	t6, (a1)
	subu	a2, 1
	sb	t6, (a2)

	;;; Decrement count (in a0)

	subu	a0, 1

	;;; Until a0 = 0

	bnez	a0, 3b

4:	;;; Return

	j	ra

	.end	movbytes

	.globl	movwords
	.ent	movwords
movwords:

	;;; If length is zero there's nothing to move, so exit

	beqz	a0, 4f

	;;; Test the direction of the move:
	;;; if source (a1) > destination (a2), the move is downward;
	;;; otherwise, the move is upward, so branch

	bltu	a1, a2, 2f

	;;; Downward move

1:	;;; Repeat

	;;; Move a word from source (a1) to destination (a2) via t6

	lw	t6, (a1)
	addu	a1, 4
	sw	t6, (a2)
	addu	a2, 4

	;;; Decrement count (in a0)

	subu	a0, 4

	;;; Until a0 = 0

	bnez	a0, 1b

	;;; Return

	j	ra

2:	;;; Upward move:
	;;; adjust pointers to point above the last word in each region

	addu	a1, a0
	addu	a2, a0

3:	;;; Repeat

	;;; Move a word from source (a1) to destination (a2) via t6

	subu	a1, 4
	lw	t6, (a1)
	subu	a2, 4
	sw	t6, (a2)

	;;; Decrement count (in a0)

	subu	a0, 4

	;;; Until a0 = 0

	bnez	a0, 3b

4:	;;; Return

	j	ra

	.end	movwords


;;; === FILLING =======================================================

;;; _BFILL:
;;;	Fill a region of bytes with a given byte.

;;; Call:
;;;	_bfill(_BYTE, _BYTE_LENGTH, _DSTADDR);

;;; Registers used:
;;;	a0	the byte
;;;	a1	length of the region in bytes
;;;	a2	destination pointer

DEF_C_LAB (_bfill)

	.ent	$bfill
$bfill:

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 12

	;;; If length (a1) is zero, then return immediately

	beqz	a1, 2f

1:	;;; Repeat

	sb	a0, (a2)
	addu	a2, 1

	;;; Decrement length (in a1)

	subu	a1, 1

	;;; Until a1 = 0

	bnez	a1, 1b

2:	;;; Return

	j	ra

	.end	$bfill


;;; _IFILL:
;;; _FILL:
;;;	Fill a region of ints/words with a given word.

;;; Call:
;;;	_fill(_LONG, _BYTE_LENGTH, _DSTADDR);

;;; Registers used:
;;;	a0	the word
;;;	a1	length of the region in bytes
;;;	a2	destination pointer

DEF_C_LAB (_ifill)
DEF_C_LAB (_fill)

	.ent	$fill
$fill:

	lw	a0, 8(usp)
	lw	a1, 4(usp)
	lw	a2, (usp)
	addu	usp, 12

	;;; If length (a1) is zero, then return immediately

	beqz	a1, 2f

1:	;;; Repeat

	sw	a0, (a2)
	addu	a2, 4

	;;; Decrement length (in a1)

	subu	a1, 4

	;;; Until a1 = 0

	bnez	a1, 1b

2:	;;; Return

	j	ra

	.end	$fill


;;; === RELOCATING POPLOG MEMORY REGIONS ==============================

;;; _MOVE_USERSTACK:
;;;	Move the user stack up or down. _BYTE_OFFS specifies the amount of
;;;	the shift in bytes.

;;; Call:
;;;	_move_userstack(_BYTE_OFFS)

;;; Registers used:
;;;	a0	the size of the move (stack length)
;;;	a1	source pointer
;;;	a2	destination pointer
;;;	t0	_BYTE_OFFS
;;;	t1	_USERHI (start of user stack)

DEF_C_LAB (_move_userstack)

	.ent	$move_userstack
$move_userstack:

	;;; Load the amount to shift (in bytes) into t0

	lw	t0, (usp)
	addu	usp, 4

	;;; Load value of _USERHI to t1

	lw	t1, _SVB_OFFS(_userhi)(svb)

	;;; Compute size of move in a0 (_USERHI - usp)

	subu	a0, t1, usp

	;;; Adjust the value of _USERHI

	addu	t1, t0
	sw	t1, _SVB_OFFS(_userhi)(svb)

	;;; Adjust the stack pointer and make the current value the source
	;;; pointer (in a1) and the new value the destination pointer (in a2)

	move	a1, usp
	addu	usp, t0
	move	a2, usp

	;;; Do the move:
	;;; movwords expects a0 = byte length, a1 = source, a2 = destination

	b	movwords

	.end	$move_userstack


;;; _MOVE_CALLSTACK:
;;;	Relocate the top part of the callstack between the stack pointer
;;;	sp and the _LIMIT address. The size of the relocation is _BYTE_OFFS
;;;	and may be up or down.

;;; Call:
;;;	_move_callstack(_BYTE_OFFS, _LIMIT);

;;; Registers used:
;;;	a0	the size of the move (stack length)
;;;	a1	source pointer
;;;	a2	destination pointer
;;;	t0	_BYTE_OFFS
;;;	t1	_LIMIT

DEF_C_LAB (_move_callstack)

	.ent	$move_callstack
$move_callstack:

	;;; Load the amount to shift into t0 and the limit address to t1

	lw	t0, 4(usp)
	lw	t1, (usp)
	addu	usp, 8

	;;; Compute size of move in a0 (_LIMIT - sp)

	subu	a0, t1, sp

	;;; Adjust the stack pointer and make the current value the source
	;;; pointer (in a1) and the new value the destination pointer (in a2)

	move	a1, sp
	addu	sp, t0
	move	a2, sp

	;;; Do the move:
	;;; movwords expects a0 = byte length, a1 = source, a2 = destination

	b	movwords

	.end	$move_callstack


;;; === BITFIELD OPERATIONS ===========================================

;;; getbfield:
;;;	Extract a bitfield in a structure into a word, left aligned.
;;;	Implements _BFIELD and _SBFIELD (defined below)

;;; Arguments:
;;;	a0	structure address
;;;	a1	bit offset of field within structure
;;;	a2	field width in bits (W)

;;; Results:
;;;	v0	the extracted bitfield, left-justified
;;;	v1	bit shift to right-align field
;;;		(W - 32, the number of bits to the left of the field)

;;; Registers used:
;;;	a0	address of lower word containing the bit field
;;;	a1	offset of field within the first word
;;;	t0	temporary
;;;	t1	lower word
;;;	t2	upper word
;;;	t3	constant 32
;;;	t4	(1) number of bits to the right of the field
;;;		(2) number of bits of the field in the upper word
;;;		(3) amount to shift left the field half in the upper word

	.ent	getbfield
getbfield:

	;;; Adjust a0 to point to lower word containing the bitfield
	;;; and a1 to be the offset within the word

	sra	t0, a1, 3
	or	t0, 3
	xor	t0, 3
	addu	a0, t0
	and	a1, 31

	;;; Load t1 with the lower word

	lw	t1, (a0)

	;;; Load t3 with 32

	li	t3, 32

	;;; Compute in v1 number of bits above the field (32 - W)

	subu	v1, t3, a2

#_IF DEF BIG_ENDIAN

	;;; Shift field to top of word to clear bits to the left

	sll	v0, t1, a1

	;;; Branch if the field occupies two words, i.e., if (32 - W) < offset

	bltu	v1, a1, 1f

	;;; Field is contained in one word: return

	j	ra

1:	;;; Field occupies two words:
	;;; load the upper word to t2

	lw	t2, 4(a0)

	;;; Compute number of bits in first word (32 - offset):
	;;; shift the higher word right by this amount

	subu	t4, t3, a1
	srl	t2, t4

	;;; OR the two parts of the field together and return

	or	v0, t2
	j	ra

#_ELSE

	;;; Compute in t4 number of bits to right of field (32 - W - offset):
	;;; if this number (t4) is negative, the field occupies two words

	subu	t4, v1, a1
	bltz	t4, 1f

	;;; Field is contained in one word:
	;;; shift field to top of word to clear bits to the left and return

	sll	v0, t1, t4
	j	ra

1:	;;; Field occupies two words:
	;;; load the upper word to t2

	lw	t2, 4(a0)

	;;; Negate t4 to get number of bits in upper word:
	;;; shift the lower word right by this amount

	neg	t4
	srl	t1, t4

	;;; Shift the part of the field in the upper word to the top end of
	;;; the upper word, i.e. shift left by (64 - W - offset) bits

	sub	t4, t3, t4
	sll	t2, t4

	;;; OR the two parts of the field together and return

	or	v0, t1, t2
	j	ra

#_ENDIF

	.end	getbfield

;;; _BFIELD:
;;; _SBFIELD:
;;;	Extract an (un)signed bitfield. Not called directly from POP, but
;;;	used to implement M_MOVE(s)bit and I_PUSH_FIELD instructions.

;;; Arguments:
;;;	a0	structure address
;;;	a1	bit offset of field within structure
;;;	a2	field width in bits

;;; Results:
;;;	v0	the extracted bitfield, right-justified and zero-extended or
;;;		sign-extended as appropriate

;;; Other registers used:
;;;	cr	saved return address

;;; Uses:
;;;	getbfield

DEF_C_LAB(_bfield)

	.ent	$bfield
$bfield:

	move	cr, ra
	bal	getbfield
	srl	v0, v1
	j	cr

	.end	$bfield

DEF_C_LAB(_sbfield)

	.ent	$sbfield
$sbfield:

	move	cr, ra
	bal	getbfield
	sra	v0, v1
	j	cr

	.end	$sbfield


;;; _UBFIELD:
;;;	Update a bitfield within a structure. Not called directly from POP
;;;	but used to implement M_UPDbit and I_POP_FIELD instructions.

;;; Arguments:
;;;	a0	structure address
;;;	a1	bit offset of field within structure
;;;	a2	field width in bits (W)
;;;	a3	the new value as a system integer

;;; Results:
;;;	none

;;; Other registers used:
;;;	a0	address of lower word containing the bit field
;;;	a1	offset of field within the first word
;;;	t0	temporary
;;;	t1	lower word
;;;	t2	upper word
;;;	t3	constant 32
;;;	t4	(1) number of bits to the right of the field
;;;		(2) number of bits of the field in the upper word
;;;		(3) amount to shift left the field half in the upper word
;;;	t5	number of bits to left of field (32 - W)
;;;	t6	(1) bits from new value which belong in upper word
;;;		(2) field mask

DEF_C_LAB (_ubfield)

	.ent	$ubfield
$ubfield:

	;;; Adjust a0 to point to lower word containing the bitfield
	;;; and a1 to be the offset within the word

	sra	t0, a1, 3
	or	t0, 3
	xor	t0, 3
	addu	a0, t0
	and	a1, 31

	;;; Load t1 with the lower word

	lw	t1, (a0)

	;;; Load t3 with 32

	li	t3, 32

	;;; Compute in t5 number of bits to left of field (32 - W)

	subu	t5, t3, a2

	;;; Compute in t4 number of bits to right of field (32 - W - offset):
	;;; if this number (t4) is negative, the field occupies two words

	subu	t4, t5, a1
	bgez	t4, 1f

	;;; Field occupies two words:
	;;; load the upper word to t2

	lw	t2, 4(a0)

	;;; Negate t4 to get number of bits in upper word

	neg	t4

#_IF DEF BIG_ENDIAN

	;;; Clear field bits from upper word

	sll	t2, t4
	srl	t2, t4

	;;; Get corresponding bits of new value at top of t6

	subu	t4, t3, t4
	sll	t6, a3, t4

	;;; OR them into the upper word and store it back

	or	t2, t6
	sw	t2, 4(a0)

1:	;;; Deal with lower word:
	;;; Construct field mask in t6

	li	t6, -1
	sll	t6, t5
	srl	t6, a1

	;;; Mask out field bits from lower word

	or	t1, t6
	xor	t1, t6

	;;; Isolate corresponding bits from new value

	sll	a3, t5
	srl	a3, a1

#_ELSE

	;;; Clear field bits from upper word

	srl	t2, t4
	sll	t2, t4

	;;; Get corresponding bits of new value at low end of t6

	subu	t4, t3, t4
	sll	t6, a3, t5
	srl	t6, t4

	;;; OR them into the upper word and store it back

	or	t2, t6
	sw	t2, 4(a0)

1:	;;; Deal with lower word:
	;;; construct field mask in t6

	li	t6, -1
	srl	t6, t5
	sll	t6, a1

	;;; Mask out field bits from lower word

	or	t1, t6
	xor	t1, t6

	;;; Isolate corresponding bits from new value

	sll	a3, a1
	and	a3, t6

#_ENDIF

	;;; OR them into the lower word and store it back

	or	t1, a3
	sw	t1, (a0)

	;;; Return

	j	ra

	.end	$ubfield


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

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

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

/* --- Revision History ---------------------------------------------------
--- John Gibson, Apr  6 1995
	Added _icmp, _imove, _dmove, _ifill as equivalent to word versions
--- Robert John Duncan, Mar 15 1994
	Removed the wrapping structure from the text section
--- Robert John Duncan, Mar 10 1994
	Changed to use the special var block register (svb) instead of
	the global pointer.
--- Robert John Duncan, Mar  8 1994
	Added .ent/.end directives
--- Robert John Duncan, Mar  7 1994
	Changed not to use register $t9.
--- Robert John Duncan, Jul  4 1990
	Added extern declarations for special vars
 */
