/*
 * Copyright (c) 2011, 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * string/memcpy.c
 * Copy memory between non-overlapping regions.
 */

#include <scram.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#if defined(__is_sortix_libk)
#include <libk.h>
#endif

inline static
void* memcpy_slow(void* restrict dst_ptr,
                  const void* restrict src_ptr,
                  size_t size)
{
	unsigned char* restrict dst = (unsigned char* restrict) dst_ptr;
	const unsigned char* restrict src = (const unsigned char* restrict) src_ptr;
	for ( size_t i = 0; i < size; i++ )
		dst[i] = src[i];
	return dst_ptr;
}

void* memcpy(void* restrict dst_ptr,
             const void* restrict src_ptr,
             size_t size)
{
	if ( dst_ptr == src_ptr || !size )
		return dst_ptr;

	void* dst_end = (char*) dst_ptr + size;
	const void* src_end = (const const char*) src_ptr + size;
	if ( (dst_ptr < src_ptr && src_ptr < dst_end) ||
	     (src_ptr < dst_ptr && dst_ptr < src_end) )
	{
#if defined(__is_sortix_libk)
		libk_overlapping_memcpy();
#else
		struct scram_undefined_behavior info;
		info.filename = __FILE__;
		info.line = __LINE__;
		info.column = 0;
		info.violation = "overlapping memcpy";
		scram(SCRAM_UNDEFINED_BEHAVIOR, &info);
#endif
	}

#if 8 < __SIZEOF_LONG__
#warning "you should add support for your unexpectedly large unsigned long."
	return memcpy_slow(dst_ptr, src_ptr, size);
#else
	unsigned long unalign_mask = sizeof(unsigned long) - 1;
	unsigned long src_unalign = (unsigned long) src_ptr & unalign_mask;
	unsigned long dst_unalign = (unsigned long) dst_ptr & unalign_mask;
	if ( src_unalign != dst_unalign )
		return memcpy_slow(dst_ptr, src_ptr, size);

	union
	{
		unsigned long srcval;
		const unsigned char* restrict src8;
		const uint16_t* restrict src16;
		const uint32_t* restrict src32;
		const uint64_t* restrict src64;
		const unsigned long* restrict srcul;
	} su;
	su.srcval = (unsigned long) src_ptr;

	union
	{
		unsigned long dstval;
		unsigned char* restrict dst8;
		uint16_t* restrict dst16;
		uint32_t* restrict dst32;
		uint64_t* restrict dst64;
		unsigned long* restrict dstul;
	} du;
	du.dstval = (unsigned long) dst_ptr;

	if ( dst_unalign )
	{
		if ( 1 <= size && !(du.dstval & (1-1)) && (du.dstval & (2-1)) )
			*du.dst8++ = *su.src8++,
			size -= 1;

		if ( 2 <= size && !(du.dstval & (2-1)) && (du.dstval & (4-1)) )
			*du.dst16++ = *su.src16++,
			size -= 2;

	#if 8 <= __SIZEOF_LONG__
		if ( 4 <= size && !(du.dstval & (4-1)) && (du.dstval & (8-1)) )
			*du.dst32++ = *su.src32++,
			size -= 4;
	#endif
	}

	size_t num_copies = size / sizeof(unsigned long);
	for ( size_t i = 0; i < num_copies; i++ )
		*du.dstul++ = *su.srcul++;

	size -= num_copies * sizeof(unsigned long);

	if ( size )
	{
	#if 8 <= __SIZEOF_LONG__
		if ( 4 <= size )
			*du.dst32++ = *su.src32++,
			size -= 4;
	#endif

		if ( 2 <= size )
			*du.dst16++ = *su.src16++,
			size -= 2;

		if ( 1 <= size )
			*du.dst8++ = *su.src8++,
			size -= 1;
	}

	return dst_ptr;
#endif
}
