/* Alloc.c -- Memory allocation functions
2024-02-18 : Igor Pavlov : Public domain
2024-05-31 : Peter Hyman
Remove Win32 cruft */

#include "Precomp.h"

#include <stdlib.h>

#include "Alloc.h"

// for debug:
#if 0
#if defined(__CHERI__) && defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 16)
// #pragma message("=== Z7_ALLOC_NO_OFFSET_ALLOCATOR === ")
#define Z7_ALLOC_NO_OFFSET_ALLOCATOR
#endif
#endif

// #define SZ_ALLOC_DEBUG
/* #define SZ_ALLOC_DEBUG */

/* use SZ_ALLOC_DEBUG to debug alloc/free operations */
#ifdef SZ_ALLOC_DEBUG

#include <string.h>
#include <stdio.h>
static int g_allocCount = 0;

#define CONVERT_INT_TO_STR(charType, tempSize) \
	char temp[tempSize]; unsigned i = 0; \
while (val >= 10) { temp[i++] = (char)('0' + (unsigned)(val % 10)); val /= 10; } \
*s++ = (charType)('0' + (unsigned)val); \
while (i != 0) { i--; *s++ = temp[i]; } \
*s = 0;

static void ConvertUInt64ToString(UInt64 val, char *s)
{
	CONVERT_INT_TO_STR(char, 24)
}

#define GET_HEX_CHAR(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))))

static void ConvertUInt64ToHex(UInt64 val, char *s)
{
	UInt64 v = val;
	unsigned i;
	for (i = 1;; i++)
	{
		v >>= 4;
		if (v == 0)
			break;
	}
	s[i] = 0;
	do
	{
		unsigned t = (unsigned)(val & 0xF);
		val >>= 4;
		s[--i] = GET_HEX_CHAR(t);
	}
	while (i);
}

#define DEBUG_OUT_STREAM stderr

static void Print(const char *s)
{
	fputs(s, DEBUG_OUT_STREAM);
}

static void PrintAligned(const char *s, size_t align)
{
	size_t len = strlen(s);
	for(;;)
	{
		fputc(' ', DEBUG_OUT_STREAM);
		if (len >= align)
			break;
		++len;
	}
	Print(s);
}

static void PrintLn(void)
{
	Print("\n");
}

static void PrintHex(UInt64 v, size_t align)
{
	char s[32];
	ConvertUInt64ToHex(v, s);
	PrintAligned(s, align);
}

static void PrintDec(int v, size_t align)
{
	char s[32];
	ConvertUInt64ToString((unsigned)v, s);
	PrintAligned(s, align);
}

static void PrintAddr(void *p)
{
	PrintHex((UInt64)(size_t)(ptrdiff_t)p, 12);
}

#define PRINT_REALLOC(name, cnt, size, ptr) { \
	Print(name " "); \
	if (!ptr) PrintDec(cnt++, 10); \
	PrintHex(size, 10); \
	PrintAddr(ptr); \
	PrintLn(); }

#define PRINT_ALLOC(name, cnt, size, ptr) { \
	Print(name " "); \
	PrintDec(cnt++, 10); \
	PrintHex(size, 10); \
	PrintAddr(ptr); \
	PrintLn(); }

#define PRINT_FREE(name, cnt, ptr) if (ptr) { \
	Print(name " "); \
	PrintDec(--cnt, 10); \
	PrintAddr(ptr); \
	PrintLn(); }

#else

#define PRINT_FREE(name, cnt, ptr)
#define Print(s)
#define PrintLn()
#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR
#define PrintHex(v, align)
#endif
#define PrintAddr(p)

#endif

/*
   by specification:
   malloc(non_NULL, 0)   : returns NULL or a unique pointer value that can later be successfully passed to free()
   realloc(NULL, size)   : the call is equivalent to malloc(size)
   realloc(non_NULL, 0)  : the call is equivalent to free(ptr)

   in main compilers:
   malloc(0)             : returns non_NULL
   realloc(NULL,     0)  : returns non_NULL
   realloc(non_NULL, 0)  : returns NULL
*/

void *MyAlloc(size_t size)
{
	if (size == 0)
		return NULL;
	// PRINT_ALLOC("Alloc    ", g_allocCount, size, NULL)
#ifdef SZ_ALLOC_DEBUG
	{
		void *p = malloc(size);
		if (p)
		{
			PRINT_ALLOC("Alloc    ", g_allocCount, size, p)
		}
		return p;
	}
#else
	return malloc(size);
#endif
}

void MyFree(void *address)
{
	PRINT_FREE("Free    ", g_allocCount, address)
		free(address);
}

void *MyRealloc(void *address, size_t size)
{
	if (size == 0)
	{
		MyFree(address);
		return NULL;
	}
	// PRINT_REALLOC("Realloc  ", g_allocCount, size, address)
#ifdef SZ_ALLOC_DEBUG
	{
		void *p = realloc(address, size);
		if (p)
		{
			PRINT_REALLOC("Realloc    ", g_allocCount, size, address)
		}
		return p;
	}
#else
	return realloc(address, size);
#endif
}

static void *SzAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p)  return MyAlloc(size); }
static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p)  MyFree(address); }
const ISzAlloc g_Alloc = { SzAlloc, SzFree };

#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR

#define ADJUST_ALLOC_SIZE 0
/*
#define ADJUST_ALLOC_SIZE (sizeof(void *) - 1)
*/
/*
   Use (ADJUST_ALLOC_SIZE = (sizeof(void *) - 1)), if
   MyAlloc() can return address that is NOT multiple of sizeof(void *).
   */

/*
uintptr_t : <stdint.h> C99 (optional)
: unsupported in VS6
*/
typedef
#if 1
	uintptr_t
#else
	ptrdiff_t
#endif
	MY_uintptr_t;

#if 0 \
	|| (defined(__CHERI__) \
	|| defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ > 8))
	// for 128-bit pointers (cheri):
#define MY_ALIGN_PTR_DOWN(p, align)  \
	((void *)((char *)(p) - ((size_t)(MY_uintptr_t)(p) & ((align) - 1))))
#else
#define MY_ALIGN_PTR_DOWN(p, align) \
	((void *)((((MY_uintptr_t)(p)) & ~((MY_uintptr_t)(align) - 1))))
#endif

#endif

#ifndef USE_posix_memalign
#define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align)
#endif

/*
   This posix_memalign() is for test purposes only.
   We also need special Free() function instead of free(),
   if this posix_memalign() is used.
   */

/*
   static int posix_memalign(void **ptr, size_t align, size_t size)
   {
   size_t newSize = size + align;
   void *p;
   void *pAligned;
 *ptr = NULL;
 if (newSize < size)
 return 12; // ENOMEM
 p = MyAlloc(newSize);
 if (!p)
 return 12; // ENOMEM
 pAligned = MY_ALIGN_PTR_UP_PLUS(p, align);
 ((void **)pAligned)[-1] = p;
 *ptr = pAligned;
 return 0;
 }
 */

/*
ALLOC_ALIGN_SIZE >= sizeof(void *)
ALLOC_ALIGN_SIZE >= cache_line_size
*/

#define ALLOC_ALIGN_SIZE ((size_t)1 << 7)

void *z7_AlignedAlloc(size_t size)
{
#ifndef USE_posix_memalign

	void *p;
	void *pAligned;
	size_t newSize;

	/* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned
	   block to prevent cache line sharing with another allocated blocks */

	newSize = size + ALLOC_ALIGN_SIZE * 1 + ADJUST_ALLOC_SIZE;
	if (newSize < size)
		return NULL;

	p = MyAlloc(newSize);

	if (!p)
		return NULL;
	pAligned = MY_ALIGN_PTR_UP_PLUS(p, ALLOC_ALIGN_SIZE);

	Print(" size="); PrintHex(size, 8);
	Print(" a_size="); PrintHex(newSize, 8);
	Print(" ptr="); PrintAddr(p);
	Print(" a_ptr="); PrintAddr(pAligned);
	PrintLn();

	((void **)pAligned)[-1] = p;

	return pAligned;

#else

	void *p;
	if (posix_memalign(&p, ALLOC_ALIGN_SIZE, size))
		return NULL;

	Print(" posix_memalign="); PrintAddr(p);
	PrintLn();

	return p;

#endif
}

void z7_AlignedFree(void *address)
{
#ifndef USE_posix_memalign
	if (address)
		MyFree(((void **)address)[-1]);
#else
	free(address);
#endif
}

static void *SzAlignedAlloc(ISzAllocPtr pp, size_t size)
{
	UNUSED_VAR(pp)
		return z7_AlignedAlloc(size);
}

static void SzAlignedFree(ISzAllocPtr pp, void *address)
{
	UNUSED_VAR(pp)
#ifndef USE_posix_memalign
		if (address)
			MyFree(((void **)address)[-1]);
#else
	free(address);
#endif
}

const ISzAlloc g_AlignedAlloc = { SzAlignedAlloc, SzAlignedFree };

/* we align ptr to support cases where CAlignOffsetAlloc::offset is not multiply of sizeof(void *) */
#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR
#if 1
#define MY_ALIGN_PTR_DOWN_1(p)  MY_ALIGN_PTR_DOWN(p, sizeof(void *))
#define REAL_BLOCK_PTR_VAR(p)  ((void **)MY_ALIGN_PTR_DOWN_1(p))[-1]
#else
// we can use this simplified code,
// if (CAlignOffsetAlloc::offset == (k * sizeof(void *))
#define REAL_BLOCK_PTR_VAR(p)  (((void **)(p))[-1])
#endif
#endif

#if 0
#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR
#include <stdio.h>
static void PrintPtr(const char *s, const void *p)
{
	const Byte *p2 = (const Byte *)&p;
	unsigned i;
	printf("%s %p ", s, p);
	for (i = sizeof(p); i != 0;)
	{
		i--;
		printf("%02x", p2[i]);
	}
	printf("\n");
}
#endif
#endif

static void *AlignOffsetAlloc_Alloc(ISzAllocPtr pp, size_t size)
{
#if defined(Z7_ALLOC_NO_OFFSET_ALLOCATOR)
	UNUSED_VAR(pp)
		return z7_AlignedAlloc(size);
#else
	const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt);
	void *adr;
	void *pAligned;
	size_t newSize;
	size_t extra;
	size_t alignSize = (size_t)1 << p->numAlignBits;

	if (alignSize < sizeof(void *))
		alignSize = sizeof(void *);

	if (p->offset >= alignSize)
		return NULL;

	/* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned
	   block to prevent cache line sharing with another allocated blocks */
	extra = p->offset & (sizeof(void *) - 1);
	newSize = size + alignSize + extra + ADJUST_ALLOC_SIZE;
	if (newSize < size)
		return NULL;

	adr = ISzAlloc_Alloc(p->baseAlloc, newSize);

	if (!adr)
		return NULL;

	pAligned = (char *)MY_ALIGN_PTR_DOWN((char *)adr +
			alignSize - p->offset + extra + ADJUST_ALLOC_SIZE, alignSize) + p->offset;

#if 0
	printf("\nalignSize = %6x, offset=%6x, size=%8x \n", (unsigned)alignSize, (unsigned)p->offset, (unsigned)size);
	PrintPtr("base", adr);
	PrintPtr("alig", pAligned);
#endif

	PrintLn();
	Print("- Aligned: ");
	Print(" size="); PrintHex(size, 8);
	Print(" a_size="); PrintHex(newSize, 8);
	Print(" ptr="); PrintAddr(adr);
	Print(" a_ptr="); PrintAddr(pAligned);
	PrintLn();

	REAL_BLOCK_PTR_VAR(pAligned) = adr;

	return pAligned;
#endif
}

static void AlignOffsetAlloc_Free(ISzAllocPtr pp, void *address)
{
#if defined(Z7_ALLOC_NO_OFFSET_ALLOCATOR)
	UNUSED_VAR(pp)
		z7_AlignedFree(address);
#else
	if (address)
	{
		const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt);
		PrintLn();
		Print("- Aligned Free: ");
		PrintLn();
		ISzAlloc_Free(p->baseAlloc, REAL_BLOCK_PTR_VAR(address));
	}
#endif
}

void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p)
{
	p->vt.Alloc = AlignOffsetAlloc_Alloc;
	p->vt.Free = AlignOffsetAlloc_Free;
}
