aboutsummaryrefslogblamecommitdiffstats
path: root/vmemalloc.c
blob: 8d92035dbf90f0f5955413d793fb67dd7f8afcdc (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                                                         
                     



                       


                         






                                                              








                                                                                         
                             


                                                 
                                                                                                                  


                                               







                                                 
                                                                                                                  


                                               



























































                                                                                    









                                                                   

















































                                                                              














                                                                      



                                                                               
                                                            

                         
                     
 


                                



                                 

                                







                                                                     
                                                                                        


                                       












                                                            
                                                      
                                 
                                                          

                                                

                           
      











                                                                     
                                                                                                     


                                               








                                                                         




































                                                                         
/* See LICENSE file for copyright and license details. */
#include "libsimple.h"
#include <stdalign.h>
#ifndef TEST


struct memalloc_state {
	size_t alignment;
	size_t elem_size;
	size_t size_prod;
	char zero_init;
	char if_zero;
	char round_up_size;
	char have_size;
	char cache_align;
	char cache_split;
	char pad__[(4 * sizeof(size_t) - 6) % sizeof(size_t)];
};

static int
vmemalloc_parse_size_prod(struct memalloc_state *state, size_t n, size_t arg, va_list ap)
{
	if (state->have_size++)
		goto inval;
	state->elem_size = arg;
	if (n) {
		while (--n) {
			arg = va_arg(ap, size_t);
			if (!state->elem_size)
				continue;
			if (LIBSIMPLE_UMUL_OVERFLOW_NONZERO(arg, state->elem_size, &state->elem_size, SIZE_MAX)) {
				errno = ENOMEM;
				return -1;
			}
		}
	} else {
		if (!arg)
			goto inval;
		for (;;) {
			arg = va_arg(ap, size_t);
			if (!arg)
				break;
			if (LIBSIMPLE_UMUL_OVERFLOW_NONZERO(arg, state->elem_size, &state->elem_size, SIZE_MAX)) {
				errno = ENOMEM;
				return -1;
			}
		}
	}

	return 0;
inval:
	errno = EINVAL;
	return -1;
}

static int
vmemalloc_parse_args(struct memalloc_state *state, size_t n, va_list ap)
{
	enum libsimple_memalloc_option opt;
	long int page_size;
	va_list *subapp;
	size_t arg;

	for (;;) {
		opt = va_arg(ap, enum libsimple_memalloc_option);
		switch (opt) {
		case LIBSIMPLE_MEMALLOC_END:
			return 0;

		case LIBSIMPLE_MEMALLOC_ZERO_INIT:
			if (state->zero_init >= 0)
				goto inval;
			state->zero_init = 1;
			break;

		case LIBSIMPLE_MEMALLOC_CONDITIONAL_ZERO_INIT:
			if (state->zero_init >= 0)
				goto inval;
			state->zero_init = va_arg(ap, int);
			state->zero_init = !!state->zero_init;
			break;

		case LIBSIMPLE_MEMALLOC_UNIQUE_IF_ZERO:
		case LIBSIMPLE_MEMALLOC_NULL_IF_ZERO:
			if (state->if_zero >= 0)
				goto inval;
			state->if_zero = (opt == LIBSIMPLE_MEMALLOC_UNIQUE_IF_ZERO);
			break;

		case LIBSIMPLE_MEMALLOC_ALIGNMENT:
			if (state->alignment)
				goto inval;
			state->alignment = va_arg(ap, size_t);
			if (!state->alignment)
				goto inval;
			break;

		case LIBSIMPLE_MEMALLOC_PAGE_ALIGNMENT:
			if (state->alignment)
				goto inval;
			page_size = sysconf(_SC_PAGESIZE);
			if (page_size <= 0)
				return -1;
			state->alignment = (size_t)page_size;
			break;

		case LIBSIMPLE_MEMALLOC_ALIGNMENT_TO_CACHE_LINE:
			if (state->cache_align++)
				goto inval;
			break;

		case LIBSIMPLE_MEMALLOC_ALLOW_CACHE_LINE_SPLITTING:
			if (state->cache_split++)
				goto inval;
			break;

		case LIBSIMPLE_MEMALLOC_ROUND_UP_SIZE_TO_ALIGNMENT:
			if (state->round_up_size++)
				goto inval;
			break;

		case LIBSIMPLE_MEMALLOC_ELEMENT_SIZE:
			if (state->elem_size)
				goto inval;
			state->elem_size = va_arg(ap, size_t);
			if (!state->elem_size)
				goto inval;
			break;

		case LIBSIMPLE_MEMALLOC_PRODUCT_SIZE:
			arg = va_arg(ap, size_t);
			if (vmemalloc_parse_size_prod(state, n, arg, ap))
				return -1;
			break;

		case LIBSIMPLE_MEMALLOC_VA_PRODUCT_SIZE:
			subapp = va_arg(ap, va_list *);
			arg = va_arg(*subapp, size_t);
			if (vmemalloc_parse_size_prod(state, n, arg, *subapp))
				return -1;
			break;

		case LIBSIMPLE_MEMALLOC_1_VA_PRODUCT_SIZE:
			arg = va_arg(ap, size_t);
			subapp = va_arg(ap, va_list *);
			if (vmemalloc_parse_size_prod(state, n, arg, *subapp))
				return -1;
			break;

		case LIBSIMPLE_MEMALLOC_VA_LIST:
			subapp = va_arg(ap, va_list *);
			if (vmemalloc_parse_args(state, n, *subapp))
				return -1;
			break;

		default:
			goto inval;
		}
	}

	return 0;
inval:
	errno = EINVAL;
	return -1;
}

static size_t
gcd(size_t u, size_t v)
{
	size_t t;
	int shift = 0;
	/* Not needed because u>0, v>0: if (!(u | v)) return u + v; */
	while (!((u | v) & 1)) u >>= 1, v >>= 1, shift++;
	while (!(u & 1))       u >>= 1;
	do {
		while (!(v & 1)) v >>= 1;
		if (u > v)       t = u, u = v, v = t;
	} while (v -= u);
	return u << shift;
}

void *
libsimple_vmemalloc(size_t n, va_list ap) /* TODO test ([v]{mem,array}alloc) */
{
	struct memalloc_state state;
	size_t misalignment, size, cacheline = 64, min, max;
	void *ptr = NULL;
	int saved_errno;
	long int tmp;

	state.alignment     = 0;
	state.elem_size     = 0;
	state.size_prod     = 1;
	state.zero_init     = -1;
	state.if_zero       = -1;
	state.round_up_size = 0;
	state.have_size     = 0;
	state.cache_align   = 0;
	state.cache_split   = 0;

	if (vmemalloc_parse_args(&state, n, ap))
		return NULL;

	state.elem_size = state.elem_size ? state.elem_size : 1;
	state.zero_init = state.zero_init >= 0 ? state.zero_init : 0;
	n = state.have_size ? state.size_prod : n;
	if (state.elem_size > 1) {
		if (LIBSIMPLE_UMUL_OVERFLOW_NONZERO(n, state.elem_size, &n, SIZE_MAX)) {
			errno = ENOMEM;
			return NULL;
		}
	}
	if (state.round_up_size) {
		if (!state.alignment) {
			errno = EINVAL;
			return NULL;
		}
		if ((misalignment = n % state.alignment))
			n += state.alignment - misalignment;
	}
	if (!n && state.if_zero == 0)
		return NULL;
	n = n ? n : (state.if_zero > 0);

	if (state.cache_align || !state.cache_split) {
#ifdef _SC_LEVEL1_DCACHE_LINESIZE
		tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
		if (tmp >= 1)
			cacheline = (size_t)tmp;
#else
		(void) tmp;
#endif
	}

	if (state.cache_align) {
		if (!state.alignment)
			state.alignment = alignof(max_align_t);
	align_to_cacheline:
		if (!(cacheline % state.alignment)) {
			state.alignment = cacheline;
		} else if (state.alignment % cacheline) {
			min = MIN(state.alignment, cacheline);
			max = MAX(state.alignment, cacheline);
			size = max / gcd(state.alignment, cacheline);
			if (LIBSIMPLE_UMUL_OVERFLOW_NONZERO(size, min, &state.alignment, SIZE_MAX)) {
				errno = ENOMEM;
				return NULL;
			}
		}
	} else if (!state.cache_split) {
		if (!state.alignment)
			state.alignment = alignof(max_align_t);
		misalignment = cacheline - state.alignment % cacheline;
		if (n % cacheline + misalignment % cacheline > cacheline)
			goto align_to_cacheline;
	}

	saved_errno = errno;
	errno = 0;
	if (state.alignment) {
		if (state.alignment % sizeof(void *)) {
			size = n;
			if ((misalignment = size % state.alignment))
				size += state.alignment - misalignment;
			ptr = aligned_alloc(state.alignment, size);
		} else {
			errno = posix_memalign(&ptr, state.alignment, n);
		}
		if (ptr && state.zero_init)
			memset(ptr, 0, n);
	} else {
		ptr = state.zero_init ? calloc(n, 1) : malloc(n);
	}
	if (!ptr && n) {
		if (!errno)
			errno = ENOMEM;
		return NULL;
	}
	errno = errno ? errno : saved_errno;

	return ptr;
}


#else
#include "test.h"

int
main(void)
{
	return 0;
}

#endif