/*******************************************************************************
Copyright (c) 2015, The OpenBLAS Project
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. Neither the name of the OpenBLAS project nor the names of
its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBLAS PROJECT OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#define ASSEMBLER
#include "common.h"

#define	M	x0	/* Y vector length */
#define	N	x1	/* X vector length */
#define	A	x3	/* A vector address */
#define	LDA	x4	/* A stride */
#define	X	x5	/* X vector address */
#define	INC_X	x6	/* X stride */
#define	Y	x7	/* Y vector address */
#define	INC_Y	x2	/* Y stride */
#define	A_PTR	x9	/* loop A vector address */
#define	X_PTR	x10	/* loop Y vector address */
#define J	x11	/* loop variable */
#define I	x12	/* loop variable */

/*******************************************************************************
* Macro definitions
*******************************************************************************/

#if !defined(DOUBLE)
#define ALPHA_R		s0
#define ALPHA_I		s1
#define ALPHA_R_COPY	s7
#define ALPHA_I_COPY	s8
#define SHZ		3
#else
#define ALPHA_R		d0
#define ALPHA_I		d1
#define ALPHA_R_COPY	d7
#define ALPHA_I_COPY	d8
#define SHZ		4
#endif

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


.macro SAVE_REGS
	add	sp, sp, #-(11 * 16)
	stp	d8, d9, [sp, #(0 * 16)]
	stp	d10, d11, [sp, #(1 * 16)]
	stp	d12, d13, [sp, #(2 * 16)]
	stp	d14, d15, [sp, #(3 * 16)]
	stp	d16, d17, [sp, #(4 * 16)]
	stp	x18, x19, [sp, #(5 * 16)]
	stp	x20, x21, [sp, #(6 * 16)]
	stp	x22, x23, [sp, #(7 * 16)]
	stp	x24, x25, [sp, #(8 * 16)]
	stp	x26, x27, [sp, #(9 * 16)]
	str	x28, [sp, #(10 * 16)]
.endm

.macro RESTORE_REGS
	ldp	d8, d9, [sp, #(0 * 16)]
	ldp	d10, d11, [sp, #(1 * 16)]
	ldp	d12, d13, [sp, #(2 * 16)]
	ldp	d14, d15, [sp, #(3 * 16)]
	ldp	d16, d17, [sp, #(4 * 16)]
	ldp	x18, x19, [sp, #(5 * 16)]
	ldp	x20, x21, [sp, #(6 * 16)]
	ldp	x22, x23, [sp, #(7 * 16)]
	ldp	x24, x25, [sp, #(8 * 16)]
	ldp	x26, x27, [sp, #(9 * 16)]
	ldr	x28, [sp, #(10 * 16)]
	add	sp, sp, #(11*16)
.endm

.macro INIT
#if !defined(XCONJ)
#if !defined(DOUBLE)
	ins	v0.s[1], v0.s[0]		// v0 = ALPHA_R, ALPHA_R 
	eor	v2.16b, v2.16b, v2.16b
	fsub	s2, s2, ALPHA_I
	ins	v1.s[1], v2.s[0]
	ext	v1.8b, v1.8b, v1.8b, #4		// v1 = ALPHA_I, -ALPHA_I 
#else
	ins	v0.d[1], v0.d[0]		// v0 = ALPHA_R, ALPHA_R 
	eor	v2.16b, v2.16b, v2.16b
	fsub	d2, d2, ALPHA_I
	ins	v1.d[1], v2.d[0]
	ext	v1.16b, v1.16b, v1.16b, #8	// v1 = ALPHA_I, -ALPHA_I 
#endif
#else // XCONJ
#if !defined(DOUBLE)
	eor	v2.16b, v2.16b, v2.16b
	fsub	s2, s2, ALPHA_R
	ins	v0.s[1], v2.s[0]		// v0 = -ALPHA_R, ALPHA_R 
	ins	v1.s[1], v1.s[0]		// v1 = ALPHA_I, ALPHA_I 
#else
	eor	v2.16b, v2.16b, v2.16b
	fsub	d2, d2, ALPHA_R
	ins	v0.d[1], v2.d[0]		// v0 = -ALPHA_R, ALPHA_R 
	ins	v1.d[1], v1.d[0]		// v1 = ALPHA_I, ALPHA_I 
#endif
#endif
.endm

.macro INIT_LOOP
	fmov	d9, xzr				// TEMP_R = [0, 0]
	fmov	d10, xzr			// TEMP_I = [0, 0]
#if !defined(DOUBLE)
#else
	fmov	d15, xzr			// TEMP_R = [0, 0]
	fmov	d16, xzr			// TEMP_I = [0, 0]
#endif

	fmov	d2, xzr				// TEMP = [0, 0]
.endm

.macro KERNEL_F4
#if !defined(DOUBLE)

	ld2	{v11.4s, v12.4s}, [X_PTR], #32
	ld2	{v13.4s, v14.4s}, [A_PTR], #32

#if (!defined(CONJ) && !defined(XCONJ)) || (defined(CONJ) && defined(XCONJ))
	fmla	v9.4s,  v11.4s, v13.4s		// [+ R(X) * A_R]
	fmls	v9.4s,  v12.4s, v14.4s		// [- I(X) * A_I]
	fmla	v10.4s, v11.4s, v14.4s		// [+ R(X) * A_I]
	fmla	v10.4s, v12.4s, v13.4s		// [+ I(X) * A_R]
#else
	fmla	v9.4s,  v11.4s, v13.4s		// [+ R(X) * A_R]
	fmla	v9.4s,  v12.4s, v14.4s		// [+ I(X) * A_I]
	fmls	v10.4s, v11.4s, v14.4s		// [- R(X) * A_I]
	fmla	v10.4s, v12.4s, v13.4s		// [+ I(X) * A_R]
#endif

#else // DOUBLE
	ld2	{v11.2d, v12.2d}, [X_PTR], #32
	ld2	{v13.2d, v14.2d}, [A_PTR], #32
	prfm    PLDL1STRM, [X_PTR, #512]

#if (!defined(CONJ) && !defined(XCONJ)) || (defined(CONJ) && defined(XCONJ))
	fmla	v9.2d,  v11.2d, v13.2d		// [+ R(X) * A_R]
	fmls	v9.2d,  v12.2d, v14.2d		// [- I(X) * A_I]
	fmla	v10.2d, v11.2d, v14.2d		// [+ R(X) * A_I]
	fmla	v10.2d, v12.2d, v13.2d		// [+ I(X) * A_R]
#else
	fmla	v9.2d,  v11.2d, v13.2d		// [+ R(X) * A_R]
	fmla	v9.2d,  v12.2d, v14.2d		// [+ I(X) * A_I]
	fmls	v10.2d, v11.2d, v14.2d		// [- R(X) * A_I]
	fmla	v10.2d, v12.2d, v13.2d		// [+ I(X) * A_R]
#endif

	ld2	{v17.2d, v18.2d}, [X_PTR], #32
	ld2	{v19.2d, v20.2d}, [A_PTR], #32
	prfm    PLDL1STRM, [A_PTR, #512]

#if (!defined(CONJ) && !defined(XCONJ)) || (defined(CONJ) && defined(XCONJ))
	fmla	v15.2d, v17.2d, v19.2d		// [+ R(X) * A_R]
	fmls	v15.2d, v18.2d, v20.2d		// [- I(X) * A_I]
	fmla	v16.2d, v17.2d, v20.2d		// [+ R(X) * A_I]
	fmla	v16.2d, v18.2d, v19.2d		// [+ I(X) * A_R]
#else
	fmla	v15.2d, v17.2d, v19.2d		// [+ R(X) * A_R]
	fmla	v15.2d, v18.2d, v20.2d		// [- I(X) * A_I]
	fmls	v16.2d, v17.2d, v20.2d		// [+ R(X) * A_I]
	fmla	v16.2d, v18.2d, v19.2d		// [+ I(X) * A_R]
#endif

#endif //DOUBLE
.endm

.macro KERNEL_F4_FINALIZE
#if !defined(DOUBLE)
	ext	v21.16b, v9.16b, v9.16b, #8
	fadd	v9.2s, v9.2s, v21.2s
	faddp	s9, v9.2s

	ext	v21.16b, v10.16b, v10.16b, #8
	fadd	v10.2s, v10.2s, v21.2s
	faddp	s10, v10.2s

	ins	v2.s[0], v9.s[0]
	ins	v2.s[1], v10.s[0]
#else
	fadd	v9.2d, v9.2d, v15.2d
	fadd	v10.2d, v10.2d, v16.2d

	faddp	d9, v9.2d
	faddp	d10, v10.2d

	ins	v2.d[0], v9.d[0]
	ins	v2.d[1], v10.d[0]
#endif
.endm


.macro KERNEL_F1
#if !defined(DOUBLE)
	ld1r	{v4.2s}, [A_PTR], #4		// [A0, A0]
	ld1	{v5.s}[0], [A_PTR], #4		// A1
	ld1	{v6.2s}, [X_PTR], #8		// [X1, X0]
	eor	v16.16b, v16.16b, v16.16b
	fsub	s16, s16, s5
	ins	v5.s[1], v16.s[0]		// [-A1, A1]
#if ( !defined(CONJ) && !defined(XCONJ) ) || ( defined(CONJ) && defined(XCONJ) )
	ext	v5.8b, v5.8b, v5.8b, #4		// [A1, -A1]
#endif
	ext	v7.8b, v6.8b, v6.8b, #4		// [X0, X1]
	fmla	v2.2s, v4.2s, v6.2s
	fmla	v2.2s, v5.2s, v7.2s
#else // DOUBLE
	ld1r	{v4.2d}, [A_PTR], #8		// [A0, A0]
	ld1	{v5.d}[0], [A_PTR], #8		// A1
	ld1	{v6.2d}, [X_PTR], #16		// [X1, X0]
	eor	v16.16b, v16.16b, v16.16b
	fsub	d16, d16, d5
	ins	v5.d[1], v16.d[0]		// [-A1, A1]
#if ( !defined(CONJ) && !defined(XCONJ) ) || ( defined(CONJ) && defined(XCONJ) )
	ext	v5.16b, v5.16b, v5.16b, #8	// [A1, -A1]
#endif
	ext	v7.16b, v6.16b, v6.16b, #8	// [X0, X1]
	fmla	v2.2d, v4.2d, v6.2d
	fmla	v2.2d, v5.2d, v7.2d
#endif
.endm

.macro INIT_S
	lsl	INC_X, INC_X, #SHZ
.endm

.macro KERNEL_S1
#if !defined(DOUBLE)
	ld1r	{v4.2s}, [A_PTR], #4		// [A0, A0]
	ld1	{v5.s}[0], [A_PTR], #4		// A1
	ld1	{v6.2s}, [X_PTR], INC_X		// [X1, X0]
	eor	v16.16b, v16.16b, v16.16b
	fsub	s16, s16, s5
	ins	v5.s[1], v16.s[0]		// [-A1, A1]
#if ( !defined(CONJ) && !defined(XCONJ) ) || ( defined(CONJ) && defined(XCONJ) )
	ext	v5.8b, v5.8b, v5.8b, #4		// [A1, -A1]
#endif
	ext	v7.8b, v6.8b, v6.8b, #4		// [X0, X1]
	fmla	v2.2s, v4.2s, v6.2s
	fmla	v2.2s, v5.2s, v7.2s
#else // DOUBLE
	ld1r	{v4.2d}, [A_PTR], #8		// [A0, A0]
	ld1	{v5.d}[0], [A_PTR], #8		// A1
	ld1	{v6.2d}, [X_PTR], INC_X		// [X1, X0]
	eor	v16.16b, v16.16b, v16.16b
	fsub	d16, d16, d5
	ins	v5.d[1], v16.d[0]		// [-A1, A1]
#if ( !defined(CONJ) && !defined(XCONJ) ) || ( defined(CONJ) && defined(XCONJ) )
	ext	v5.16b, v5.16b, v5.16b, #8	// [A1, -A1]
#endif
	ext	v7.16b, v6.16b, v6.16b, #8	// [X0, X1]
	fmla	v2.2d, v4.2d, v6.2d
	fmla	v2.2d, v5.2d, v7.2d
#endif
.endm

/*******************************************************************************
* End of macro definitions
*******************************************************************************/

	PROLOGUE

	ldr	INC_Y, [sp]
	SAVE_REGS

	cmp	N, xzr
	ble	zgemv_t_kernel_L999
	cmp	M, xzr
	ble	zgemv_t_kernel_L999

	lsl	LDA, LDA, #SHZ
	lsl	INC_Y, INC_Y, #SHZ
	mov	J, N

	INIT

	cmp	INC_X, #1
	bne	zgemv_t_kernel_S_BEGIN

zgemv_t_kernel_F_LOOP:

	mov	A_PTR, A
	mov	X_PTR, X

	INIT_LOOP

	asr	I, M, #2
	cmp	I, xzr
	beq	zgemv_t_kernel_F1

zgemv_t_kernel_F4:

	KERNEL_F4

	subs	I, I, #1
	bne	zgemv_t_kernel_F4

	KERNEL_F4_FINALIZE

zgemv_t_kernel_F1:

	ands	I, M, #3
	ble	zgemv_t_kernel_F_END

zgemv_t_kernel_F10:

	KERNEL_F1

	subs    I, I, #1
        bne     zgemv_t_kernel_F10

zgemv_t_kernel_F_END:

#if !defined(DOUBLE)
	ld1	{v4.2s}, [Y]
	ext	v3.8b, v2.8b, v2.8b, #4		// [TEMP_R, TEMP_I]
	fmla	v4.2s, v0.2s, v2.2s
	fmla	v4.2s, v1.2s, v3.2s
	st1	{v4.2s}, [Y], INC_Y
#else // DOUBLE
	ld1	{v4.2d}, [Y]
	ext	v3.16b, v2.16b, v2.16b, #8	// [TEMP_R, TEMP_I]
	fmla	v4.2d, v0.2d, v2.2d
	fmla	v4.2d, v1.2d, v3.2d
	st1	{v4.2d}, [Y], INC_Y
#endif

	add	A, A, LDA
	subs    J, J, #1
        bne     zgemv_t_kernel_F_LOOP

	b	zgemv_t_kernel_L999

zgemv_t_kernel_S_BEGIN:

	INIT_S

zgemv_t_kernel_S_LOOP:

	mov	A_PTR, A
	mov	X_PTR, X
	INIT_LOOP

	asr	I, M, #2
	cmp	I, xzr
	ble	zgemv_t_kernel_S1

zgemv_t_kernel_S4:

	KERNEL_S1
	KERNEL_S1
	KERNEL_S1
	KERNEL_S1

	subs	I, I, #1
	bne	zgemv_t_kernel_S4

zgemv_t_kernel_S1:

	ands	I, M, #3
	ble	zgemv_t_kernel_S_END

zgemv_t_kernel_S10:

	KERNEL_S1

	subs    I, I, #1
        bne     zgemv_t_kernel_S10

zgemv_t_kernel_S_END:

#if !defined(DOUBLE)
	ld1	{v4.2s}, [Y]
	ext	v3.8b, v2.8b, v2.8b, #4		// [TEMP_R, TEMP_I]
	fmla	v4.2s, v0.2s, v2.2s
	fmla	v4.2s, v1.2s, v3.2s
	st1	{v4.2s}, [Y], INC_Y
#else // DOUBLE
	ld1	{v4.2d}, [Y]
	ext	v3.16b, v2.16b, v2.16b, #8	// [TEMP_R, TEMP_I]
	fmla	v4.2d, v0.2d, v2.2d
	fmla	v4.2d, v1.2d, v3.2d
	st1	{v4.2d}, [Y], INC_Y
#endif

	add	A, A, LDA
	subs    J, J, #1
        bne     zgemv_t_kernel_S_LOOP

zgemv_t_kernel_L999:
	RESTORE_REGS
	mov	w0, wzr
	ret

	EPILOGUE
