//=============================================================================
//
//   File : kvi_memmove.cpp
//   Creation date : Sun Jun 18 2000 18:27:50 CEST by Szymon Stefanek
//
//   This file is part of the KVirc irc client distribution
//   Copyright (C) 1999-2006 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your opinion) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#define __KVILIB__

#define _KVI_DEBUG_CHECK_RANGE_
#include "kvi_debug.h"

#define _KVI_MEMMOVE_CPP_
#include "kvi_memmove.h"

// FIXME: #warning "With system memmove could be guessed by configure"

#ifndef COMPILE_WITH_SYSTEM_MEMMOVE
	
	#ifdef COMPILE_ix86_ASM


		KVILIB_API void *kvi_memmove(void * dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			// Save pointer registers
			asm("	pushl %esi");                     // save %esi
			asm("	pushl %edi");                     // save %edi
			// Load arguments
			asm("	movl 16(%ebp),%ecx");             // %ecx = len
			asm("	movl 12(%ebp),%esi");             // %esi = src
			asm("	movl 8(%ebp),%edi");              // %edi = dst
			// Compare src and dest
			asm("	cmpl %esi,%edi");                 // %edi - %esi
			asm("	jbe move_from_bottom_to_top");    // if(%edi < %esi) jump to move_from_bottom_to_top
			// dst_ptr > src_ptr
			asm("	addl %ecx,%esi");                 // %esi += %ecx (src_ptr += len);
			asm("	addl %ecx,%edi");                 // %edi += %ecx (dst_ptr += len);
			asm("	decl %esi");                      // %esi--; (src_ptr--);
			asm("	decl %edi");                      // %edi--; (dst_ptr--);
			asm("	std");                            // set direction flag (decrement esi and edi in movsb)
			// Optimization : check for non-odd len (1,3,5,7...)
			asm("	shr $1,%ecx");                    // %ecx >> 1 , shifted bit -> CF
			asm("	jnc move_two_bytes_top_to_bottom_directly");  // if !carry (CF == 0) skip this move
			// Move the first byte (non-odd)
			asm("	movsb %ds:(%esi),%es:(%edi)");    // *dst-- = *src-- if DF  else *dst++ = *src++
			asm("move_two_bytes_top_to_bottom_directly:");
			asm("	decl %esi");                      // %esi--; (src_ptr--);
			asm("	decl %edi");                      // %edi--; (dst_ptr--);
			asm("move_two_bytes_top_to_bottom:");
			asm("	shr $1,%ecx");                    // %ecx >> 1 , shifted bit -> CF
			asm("	jnc move_the_rest_top_to_bottom_directly"); // if !carry (CF == 0) skip this move
			// Move the next two bytes
			asm("	movsw %ds:(%esi),%es:(%edi)");    // *((word *)dst)-- = *((word)src)-- if DF else *((word *)dst)++ = *((word)src)++
			asm("move_the_rest_top_to_bottom_directly:");
			asm("	subl $2,%esi");                   // %esi-=2; (src-=2);
			asm("   subl $2,%edi");                   // %edi-=2; (dst-=2);
			asm("   jmp move_the_rest");              // call last repnz movsl
			// dst_ptr <= src_ptr
			asm("move_from_bottom_to_top:");
			asm("	cld");                            // clear direction flag (increment esi and edi in movsb)
			// Optimization : check for non-odd len (1,3,5,7...)
			asm("	shr $1,%ecx");                    // %ecx >> 1 , shifted bit -> CF
			asm("	jnc move_two_bytes");             // if !carry (CF == 0) skip this move
			// Move the first byte (non-odd)
			asm("	movsb %ds:(%esi),%es:(%edi)");    // *dst-- = *src-- if DF  else *dst++ = *src++
			// Optimization : pass 2 , check for %2 and %3
			asm("move_two_bytes:");
			asm("	shr $1,%ecx");                    // %ecx >> 1 , shifted bit -> CF
			asm("	jnc move_the_rest");              // if !carry (CF == 0) skip this move
			// Move the next two bytes
			asm("	movsw %ds:(%esi),%es:(%edi)");    // *((word *)dst)-- = *((word)src)-- if DF else *((word *)dst)++ = *((word)src)++
			// Main move remaining part
			asm("move_the_rest:");
			asm("	repnz; movsl %ds:(%esi),%es:(%edi)"); // loop moving 4 bytes at once (increment or decrement as above)
			// Restore pointer registers
			asm("	popl %edi");                      // restore %edi
			asm("	popl %esi");                      // restore %esi
			return dst_ptr; //asm("   movl 8(%ebp),%eax"); <-- gcc will put that (AFTER THE OPTIMISATION PASS!)
		}
	
		KVILIB_API void *kvi_memmoveodd(void * dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			// Save pointer registers
			asm("	pushl %esi");                     // save %esi
			asm("	pushl %edi");                     // save %edi
			// Load arguments
			asm("	movl 16(%ebp),%ecx");             // %ecx = len
			asm("	movl 12(%ebp),%esi");             // %esi = src
			asm("	movl 8(%ebp),%edi");              // %edi = dst
			// Compare src and dest
			asm("	cmpl %esi,%edi");                 // %edi - %esi
			asm("	jbe xmove_from_bottom_to_top");    // if(%edi < %esi) jump to move_from_bottom_to_top
			// dst_ptr > src_ptr
			asm("	addl %ecx,%esi");                 // %esi += %ecx (src_ptr += len);
			asm("	addl %ecx,%edi");                 // %edi += %ecx (dst_ptr += len);
			asm("	std");                            // set direction flag (decrement esi and edi in movsb)
			// start moving
			asm("	shr $2,%ecx");                    // %ecx >> 2 , last shifted bit -> CF
			asm("	jnc xmove_the_rest_top_to_bottom_directly"); // if !carry (CF == 0) skip this move
			// Move the next two bytes
			asm("	subl $2,%esi");                   // %esi-=2; (src_ptr-=2);
			asm("	subl $2,%edi");                   // %edi-=2; (dst_ptr-=2);
			asm("	movsw %ds:(%esi),%es:(%edi)");    // *((word *)dst)-- = *((word)src)-- if DF else *((word *)dst)++ = *((word)src)++
			asm("	subl $2,%esi");                   // %esi-=2; (src_ptr-=2);
			asm("	subl $2,%edi");                   // %edi-=2; (dst_ptr-=2);
			asm("   jmp xmove_the_rest");
			asm("xmove_the_rest_top_to_bottom_directly:");
			asm("	subl $4,%esi");                   // %esi-=4; (src-=4);
			asm("   subl $4,%edi");                   // %edi-=4; (dst-=4);
			asm("   jmp xmove_the_rest");              // call last repnz movsl
			// dst_ptr <= src_ptr
			asm("xmove_from_bottom_to_top:");
			asm("	cld");                            // clear direction flag (increment esi and edi in movsb)
			// move it
			asm("	shr $2,%ecx");                    // %ecx >> 2 , last shifted bit -> CF
			asm("	jnc xmove_the_rest");              // if !carry (CF == 0) skip this move
			// Move the next two bytes
			asm("	movsw %ds:(%esi),%es:(%edi)");    // *((word *)dst)-- = *((word)src)-- if DF else *((word *)dst)++ = *((word)src)++
			// Main move remaining part
			asm("xmove_the_rest:");
			asm("	repnz; movsl %ds:(%esi),%es:(%edi)"); // loop moving 4 bytes at once (increment or decrement as above)
			// Restore pointer registers
			asm("	popl %edi");                      // restore %edi
			asm("	popl %esi");                      // restore %esi
			return dst_ptr; //asm("   movl 8(%ebp),%eax"); <-- gcc will put that (AFTER THE OPTIMISATION PASS!)
		}

	#else // ndef COMPILE_ix86_ASM
	


		// The next 4 functions could be optimized with the & and shift technique
		// used in the assembly implementations but the compilers usually
		// will not translate the carry bit trick producing code
		// that works slower on short block of memory (really near the average case)
	
		// The trick would be:
		//
		//    if(len & 1) // the length is even
		//       *dst-- = *src--; // move one byte
		//    len >> 1; // drop the last bit (thus divide by 2)
		//    if(len & 1) // the length is still even
		//       *((short *)dst)-- = *((short *)src)--; // move two bytes
		//    len >> 1; // again drop the last bit (thus divide by 2)
		//    while(len--)*((int *)dst)-- = *((int *)src)--; // move four bytes at a time
		//
		//
	
		KVILIB_API void *kvi_memmove(void *dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			char *dst;
			const char *src;
			if(dst_ptr > src_ptr){
				dst = (char *)dst_ptr + len - 1;
				src = (const char *)src_ptr + len - 1;
				while(len--)*dst-- = *src--;
		    } else { //it is valid even if dst_ptr == src_ptr 
				dst = (char *)dst_ptr;
				src = (const char *)src_ptr;
				while(len--)*dst++ = *src++;
		    }
		    return dst_ptr;
		}
	
		KVILIB_API void *kvi_memmoveodd(void *dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			__range_valid((len & 1) == 0);
			short *dst;
			const short *src;
			if(dst_ptr > src_ptr){
				dst = (short *) (((char *)dst_ptr) + len - 2);
				src = (const short *) (((const char *)src_ptr) + len - 2);
				while(len > 0)
				{
					*dst-- = *src--;
					len -= 2;
				}
		    } else { //it is valid even if dst_ptr == src_ptr 
				dst = (short *)dst_ptr;
				src = (const short *)src_ptr;
				while(len > 0)
				{
					*dst++ = *src++;
					len -= 2;
				}
		    }
		    return dst_ptr;
		}
		
		KVILIB_API void kvi_fastmove(void *dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			const char *src = (const char *)src_ptr;
			char *dst = (char *)dst_ptr;
			while(len--)*dst++ = *src++;
		}
	
		KVILIB_API void kvi_fastmoveodd(void *dst_ptr,const void *src_ptr,int len)
		{
			__range_valid(dst_ptr);
			__range_valid(src_ptr);
			__range_valid(len >= 0);
			__range_valid((len & 1) == 0);
			const short *src = (const short *)src_ptr;
			short *dst = (short *)dst_ptr;
			while(len > 0){
				*dst++ = *src++;
				len -= 2;
			}
		}

	#endif // !COMPILE_ix86_ASM
	
	KVILIB_API void kvi_memset(void *dst_ptr,char c,int len)
	{
		__range_valid(dst_ptr);
		__range_valid(len >= 0);
		char *dst = (char *)dst_ptr;
		while(len--)*dst++ = c;
	}

#endif // !COMPILE_WITH_SYSTEM_MEMMOVE
