/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

	.file	"memmove.s"

#include <sys/asm_linkage.h>

	ANSI_PRAGMA_WEAK(memmove,function)

/*
 * memmove(s1, s2, len)
 * Copy s2 to s1, always copy n bytes.
 * For overlapped copies it does the right thing.
 */
	ENTRY(memmove)
	save	%sp, -SA(MINFRAME), %sp		! not a leaf routine any more
	mov	%i0, %l6	! Save pointer to destination
	cmp	%i1, %i0	! if from address is >= to use forward copy
	bgeu,a	2f		! else use backward if ...
	cmp	%i2, 17		! delay slot, for small counts copy bytes

	sub	%i0, %i1, %i4	! get difference of two addresses
	cmp	%i2, %i4	! compare size and difference of addresses
	bgu	ovbc		! if size is bigger, have do overlapped copy
	cmp	%i2, 17		! delay slot, for small counts copy bytes
	!
	! normal, copy forwards
	!
2:	ble	dbytecp
	andcc	%i1, 3, %i5		! is src word aligned
	bz	aldst
	cmp	%i5, 2			! is src half-word aligned
	be	s2algn
	cmp	%i5, 3			! src is byte aligned
s1algn:	ldub	[%i1], %i3		! move 1 or 3 bytes to align it
	inc	1, %i1
	stb	%i3, [%i0]		! move a byte to align src
	inc	1, %i0
	bne	s2algn
	dec	%i2
	b	ald			! now go align dest
	andcc	%i0, 3, %i5

s2algn:	lduh	[%i1], %i3		! know src is 2 byte alinged
	inc	2, %i1
	srl	%i3, 8, %i4
	stb	%i4, [%i0]		! have to do bytes,
	stb	%i3, [%i0 + 1]		! don't know dst alingment
	inc	2, %i0
	dec	2, %i2

aldst:	andcc	%i0, 3, %i5		! align the destination address
ald:	bz	w4cp
	cmp	%i5, 2
	bz	w2cp
	cmp	%i5, 3
w3cp:	ld	[%i1], %i4
	inc	4, %i1
	srl	%i4, 24, %i5
	stb	%i5, [%i0]
	bne	w1cp
	inc	%i0
	dec	1, %i2
	andn	%i2, 3, %i3		! i3 is aligned word count
	dec	4, %i3			! avoid reading beyond tail of src
	sub	%i1, %i0, %i1		! i1 gets the difference

1:	sll	%i4, 8, %g1		! save residual bytes
	ld	[%i1+%i0], %i4
	deccc	4, %i3
	srl	%i4, 24, %i5		! merge with residual
	or	%i5, %g1, %g1
	st	%g1, [%i0]
	bnz	1b
	inc	4, %i0
	sub	%i1, 3, %i1		! used one byte of last word read
	and	%i2, 3, %i2
	b	7f
	inc	4, %i2

w1cp:	srl	%i4, 8, %i5
	sth	%i5, [%i0]
	inc	2, %i0
	dec	3, %i2
	andn	%i2, 3, %i3
	dec	4, %i3			! avoid reading beyond tail of src
	sub	%i1, %i0, %i1		! i1 gets the difference

2:	sll	%i4, 24, %g1		! save residual bytes
	ld	[%i1+%i0], %i4
	deccc	4, %i3
	srl	%i4, 8, %i5		! merge with residual
	or	%i5, %g1, %g1
	st	%g1, [%i0]
	bnz	2b
	inc	4, %i0
	sub	%i1, 1, %i1		! used three bytes of last word read
	and	%i2, 3, %i2
	b	7f
	inc	4, %i2

w2cp:	ld	[%i1], %i4
	inc	4, %i1
	srl	%i4, 16, %i5
	sth	%i5, [%i0]
	inc	2, %i0
	dec	2, %i2
	andn	%i2, 3, %i3		! i3 is aligned word count
	dec	4, %i3			! avoid reading beyond tail of src
	sub	%i1, %i0, %i1		! i1 gets the difference

3:	sll	%i4, 16, %g1		! save residual bytes
	ld	[%i1+%i0], %i4
	deccc	4, %i3
	srl	%i4, 16, %i5		! merge with residual
	or	%i5, %g1, %g1
	st	%g1, [%i0]
	bnz	3b
	inc	4, %i0
	sub	%i1, 2, %i1		! used two bytes of last word read
	and	%i2, 3, %i2
	b	7f
	inc	4, %i2

w4cp:	andn	%i2, 3, %i3		! i3 is aligned word count
	sub	%i1, %i0, %i1		! i1 gets the difference

1:	ld	[%i1+%i0], %i4		! read from address
	deccc	4, %i3			! decrement count
	st	%i4, [%i0]		! write at destination address
	bg	1b
	inc	4, %i0			! increment to address
	b	7f
	and	%i2, 3, %i2		! number of leftover bytes, if any

	!
	! differenced byte copy, works with any alignment
	!
dbytecp:
	b	7f
	sub	%i1, %i0, %i1		! i1 gets the difference

4:	stb	%i4, [%i0]		! write to address
	inc	%i0			! inc to address
7:	deccc	%i2			! decrement count
	bge,a	4b			! loop till done
	ldub	[%i1+%i0], %i4		! read from address
	ret
	restore %l6, %g0, %o0		! return pointer to destination

	!
	! an overlapped copy that must be done "backwards"
	!
ovbc:	add	%i1, %i2, %i1		! get to end of source space
	add	%i0, %i2, %i0		! get to end of destination space
	sub	%i1, %i0, %i1		! i1 gets the difference

5:	dec	%i0			! decrement to address
	ldub	[%i1+%i0], %i3		! read a byte
	deccc	%i2			! decrement count
	bg	5b			! loop until done
	stb	%i3, [%i0]		! write byte
	ret
	restore %l6, %g0, %o0		! return pointer to destination

	SET_SIZE(memmove)
