aboutsummaryrefslogtreecommitdiff
path: root/lib/libmalloc/memalign.c
blob: 270aa4e626766fa07913856318761bd143061dbb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*  Author: Mark Moraes <moraes@csri.toronto.edu> */

/*LINTLIBRARY*/

#include "defs.h"
#include "globals.h"

RCSID("$Id: memalign.c,v 1.1 1994/03/06 22:59:49 nate Exp $")

/* 
 * !! memalign may leave small (< _malloc_minchunk) blocks as garbage.
 * Not worth fixing now -- I've only seen two applications call valloc()
 * or memalign(), and they do it only once in their life.
 */
/* 
 * This is needed to be compatible with Sun mallocs - Dunno how many
 * programs need it - the X server sure does... Returns a block 'size'
 * bytes long, such that the address is a multiple of 'alignment'.
 * (alignment MUST be a power of 2). This routine is possibly more
 * convoluted than free() - certainly uglier. Since it is rarely called
 * - possibly once in a program, it should be ok.  Since this is called
 * from valloc() which is usually needed in conjunction with
 * mmap()/munmap(), note the comment in the Sun manual page about
 * freeing segments of size 128K and greater. Ugh.
 */
univptr_t
memalign(alignment, size)
size_t alignment, size;
{
	univptr_t cp;
	univptr_t addr;
	REGISTER Word *p0, *p1;
	REGISTER size_t before, after;
	size_t blksize;
#ifdef DEBUG
	int tmp_debugging = _malloc_debugging;
#endif /* DEBUG */

	if (alignment < sizeof(int) || !is_power_of_2(alignment) ||size == 0) {
		errno = EINVAL;
		return(NULL);
	}
	if (alignment < sizeof(Word))
		return(malloc(size)); /* We guarantee this alignment anyway */
	/* 
	 *  Life starts to get complicated - need to get a block large
	 *  enough to hold a block 'size' long, starting on an 'alignment'
	 *  boundary
	 */
	if ((cp = malloc((size_t) (size + alignment - 1))) == NULL)
		return(NULL);
	addr = SIMPLEALIGN(cp, alignment);
	/* 
	 *  This is all we really need - can go back now, except that we
	 *  might be wasting 'alignment - 1' bytes, which can be large since
	 *  this junk is usually called to align with things like pagesize.
	 *  So we try to push any free space before 'addr' and after 'addr +
	 *  size' back on the free list by making the memaligned chunk
	 *  ('addr' to 'addr + size') a block, and then doing stuff with the
	 *  space left over - either making them free blocks or coelescing
	 *  them whichever way is simplest. This usually involves making
	 *  them look like allocated blocks and calling free() which has all
	 *  the code to deal with this, and should do it reasonably fast.
	 */
	p0 = (Word *) cp;
	p0 -= HEADERWORDS;
	/*
	 *  p0 now points to the word tag starting the block which we got
	 *  from malloc. This remains invariant from now on - p1 is our
	 *  temporary pointer
	 */
	p1 = (Word *) addr;
	p1 -= HEADERWORDS;
	blksize = (size + sizeof(Word) - 1) / sizeof(Word);
	before = p1 - p0;
	after = SIZE(p0) - ALLOC_OVERHEAD - blksize - before;
	/*
	 *  p1 now points to the word before addr - this is going to be the
	 *  start of the memaligned block
	 */
	if (after < _malloc_minchunk) {
		/*
		 * We merge the extra space after the memaligned block into it
		 * since that space isn't enough for a separate block. Note
		 * that if the block after the one malloc returned is free, we
		 * might be able to merge the space into that block even if it
		 * is too small - unfortunately, free() won't accept a block of
		 * this size, and I don't want to do that code here, so we'll
		 * just let it go to waste in the memaligned block. !! fix later, maybe
		 */
		blksize += after;
		after = 0;
	}
	/*
	 *  We mark the newly carved memaligned block p1 as alloced. addr is
	 *  (p1 + 1) which is the address we'll return
	 */
	SIZEFIELD(p1) = ALLOCMASK(blksize + ALLOC_OVERHEAD);
	SIZEFIELD(p1 + blksize + ALLOC_OVERHEAD - 1) = SIZEFIELD(p1);
	SET_REALSIZE(p1, size);
	if (after > 0) {
		/* We can now free the block after the memaligned block. */
		p1 += blksize + ALLOC_OVERHEAD; /* SIZE(p1) */
		/*
 		 * p1 now points to the space after the memaligned block. we
		 * fix the size, mark it alloced, and call free - the block
		 * after this may be free, which isn't simple to coalesce - let
		 * free() do it.
		 */
		SIZEFIELD(p1) = ALLOCMASK(after);
		SIZEFIELD(p1 + after - 1) = SIZEFIELD(p1);
		SET_REALSIZE(p1, (after - ALLOC_OVERHEAD) * sizeof(Word));
#ifdef DEBUG
		/* Full heap checking will break till we finish memalign */
		_malloc_debugging = 0;
#endif /* DEBUG */
		free((univptr_t) (p1 + HEADERWORDS));
	}
	if (addr != cp) {
		/*
		 *  If what's 'before' is large enough to be freed, add p0 to
		 *  free list after changing its size to just consist of the
		 *  space before the memaligned block, also setting the
		 *  alloced flag. Then call free() -- may merge with preceding
		 *  block. (block after it is the memaligned block)
		 */
		/* 
		 *  Else the space before the block is too small to form a
		 *  free block, and the preceding block isn't free, so we
		 *  aren't touching it. Theoretically, we could put it in
		 *  the preceding alloc'ed block, but there are painful
		 *  complications if this is the start of the arena. We
		 *  pass, but MUST mark it as allocated. This sort of garbage
		 *  can split up the arena -- fix later with special case maybe?!!
		 */
		p1 = p0;
		SIZEFIELD(p1) = ALLOCMASK(before);
		SIZEFIELD(p1 + before - 1) = SIZEFIELD(p1);
		SET_REALSIZE(p1, (before - ALLOC_OVERHEAD) * sizeof(Word));
		if (before >= _malloc_minchunk) {
			free(cp);
		}
	}
#ifdef DEBUG
	_malloc_debugging = tmp_debugging;
#endif /* DEBUG */
	return(addr);
}

/* Just following the Sun manual page here */
univptr_t
valloc(size)
size_t size;
{
	static size_t pagesz = 0;

	if (pagesz == 0)
		pagesz = (size_t) getpagesize();
	return(memalign(pagesz, size));
}