//		    ANSI C Runtime Library
//
//	Copyright 1983-2017 Green Hills Software, Inc.
//
//    This program is the property of Green Hills Software, Inc,
//    its contents are proprietary information and no part of it
//    is to be disclosed to anyone except employees of Green Hills
//    Software, Inc., or as agreed in writing signed by the President
//    of Green Hills Software, Inc.

	.file	"ind_mset.a64"

	.text

#if defined(__ghs_asm)
// Define an argcheck __callee symbol for the function memset.
// void * memset(void *, int, size_t)
__callee.memset.p.pil::
	.size __callee.memset.p.pil,0
#endif

	.align 16
	.globl	memset
memset:
	// Parameters:
	//     x0 -- Pointer to array
	//     w1 -- Byte to be written (as an int)
	//     x2 -- Size of array in bytes
	// Return value:
	//     x0 -- The array pointer passed in
	//           Note: since x0 is already set to the correct value, this
	//           function never modifies this register.
	// Other registers used:
	//     x4 -- End of array

	// This is a leaf function, so for efficiency don't create a stack
	// frame.

	// Basic approach:
	//   - If array is large, copy most of it via an unrolled loop that
	//     writes 4 pairs of registers (totalling 64 bytes) per iteration.
	//   - If the array is small, and also to handle the odd 16-byte chunks
	//     for large arrays, write one pair of registers at a time, then
	//     handle the odd bytes at the end with a single 16-byte chunk
	//     ending at the end of the array (which may overlap with the
	//     previous chunk).
	//   - If the array is 16 bytes or less, write the whole array in 2
	//     (possibly overlapping) chunks.

	// Zero-extend the low byte of w1; we must ignore any higher bits.
	uxtb	w1, w1

	// Tile x1 with its low byte
	orr	x1, x1, x1, lsl #8
	orr	x1, x1, x1, lsl #16
	orr	x1, x1, x1, lsl #32

	// Calculate the address of the end of the array, which we use in all
	// cases.
	add	x4, x0, x2

	cmp	x2, #16
	b.ls	array_is_tiny

	// Use x3 as another pointer to the array, since we don't modify x0.
	mov	x3, x0

	// Avoid the overhead of aligning if the array is already aligned.
	// Empirically, this helps significantly on large aligned arrays.
	tst	x0, #0xf
	b.eq	array_is_aligned

	// Write the first 16 bytes, moving x3 forward to a 16-byte-aligned
	// address. Adjust x2 accordingly.
	add	x2, x2, x3
	stp	x1, x1, [x3], #16
	and	x3, x3, #0xfffffffffffffff0
	sub	x2, x2, x3

array_is_aligned:

	// We now subtract 1 from the length so that if it's an exact multiple
	// of 16, we don't copy the last chunk twice.
	//
	// To see why this works, consider the two cases:
	//
	//     Case 1: the length is a multiple of 16. Then this will cause the
	//         main loop to skip the last chunk. However, we are already
	//         writing the last chunk separately, so we will still write
	//         every byte.
	//
	//     Case 2: the length is not a multiple of 16. Since the main loop
	//         deals only in 16-byte chunks, this will not change the
	//         number of chunks written by that loop, so is definitely
	//         safe.
	//
	// Note: we can't subtract any more than 1 here because the length
	// could be exactly one more than a multiple of 16, in which case
	// subtracting more than 1 would cause us to miss the byte at the start
	// of the last whole chunk.

	// Rather than doing a subtract followed by a compare against 64 at the
	// end of each iteration of the unrolled loop, we save an instruction
	// by also decreasing x2 by 64 here, so that we can do a single 'subs'
	// on all subsequent iterations.

	// As an optimization, the above two are combined into a single
	// subtract.
	subs	x2, x2, #65
	b.lo	unrolled_word_loop_end

unrolled_word_loop:
	stp	x1, x1, [x3],#16
	stp	x1, x1, [x3],#16
	stp	x1, x1, [x3],#16
	stp	x1, x1, [x3],#16
	subs	x2, x2, #64
	b.hs	unrolled_word_loop

unrolled_word_loop_end:
	// Undo the offset we made to x2 for the duration of the loop.
	add	x2, x2, #64

	// Handle any chunks that were leftover from the unrolled loop.
	cmp	x2, #16
	b.lo	fixup_done
	stp	x1, x1, [x3]

	cmp	x2, #32
	b.lo	fixup_done
	stp	x1, x1, [x3,#16]

	cmp	x2, #48
	b.lo	fixup_done
	stp	x1, x1, [x3,#32]

fixup_done:
	// Write the last chunk, which may overlap with the previous chunk.
	stp	x1, x1, [x4,#-16]
	ret

array_is_tiny:
	// Write the whole array in two (possibly overlapping) chunks of the
	// same power-of-two size, one starting at the start of the array
	// and the other ending at the end of the array. The chunks' sizes
	// are chosen based on the size of the array.

// at_most_16_bytes:
	cmp	x2, #8
	b.ls	at_most_8_bytes

	// Write (8..16] bytes using two 8-byte writes.
	str	x1, [x0]
	str	x1, [x4,#-8]
	ret

at_most_8_bytes:
	cmp	x2, #4
	b.ls	at_most_4_bytes

	// Write (4..8] bytes using two 4-byte writes.
	str	w1, [x0]
	str	w1, [x4,#-4]
	ret

at_most_4_bytes:
	cmp	x2, #2
	b.ls	at_most_2_bytes

	// Write (2..4] bytes using two 2-byte writes.
	strh	w1, [x0]
	strh	w1, [x4,#-2]
	ret

at_most_2_bytes:
	cbz	x2, all_done

	// Write 1 or 2 bytes using two 1-byte writes.
	strb	w1, [x0]
	strb	w1, [x4,#-1]
	// Fall through

all_done:
	ret

#if !defined(__VXWORKS)
	.type     memset, @function
	.size     memset, .-memset
#if !defined(__linux)
	.maxstack memset, 0
	.scall    memset, __leaf__
#endif
#endif

	.global	__ghs_fast_memset
	.set    __ghs_fast_memset, memset

#if defined(__ghs_asm)
// Define an argcheck __callee symbol for the function memset.
// void * memset(void *, int, size_t)
__callee.__ghs_fast_memset.p.pil::
	.size __callee.__ghs_fast_memset.p.pil,0
#endif
