1 /* Copyright (c) 2002 Geoffrey Keating <geoffk@geoffk.org> */
2 /* A replacement malloc with:
3    - Much reduced code size;
4    - Smaller RAM footprint;
5    - The ability to handle downward-growing heaps;
6    but
7    - Slower;
8    - Probably higher memory fragmentation;
9    - Doesn't support threads (but, if it did support threads,
10      it wouldn't need a global lock, only a compare-and-swap instruction);
11    - Assumes the maximum alignment required is the alignment of a pointer;
12    - Assumes that memory is already there and doesn't need to be allocated.
13 
14 * Synopsis of public routines
15 
16   malloc(size_t n);
17      Return a pointer to a newly allocated chunk of at least n bytes, or null
18      if no space is available.
19   free(void* p);
20      Release the chunk of memory pointed to by p, or no effect if p is null.
21   realloc(void* p, size_t n);
22      Return a pointer to a chunk of size n that contains the same data
23      as does chunk p up to the minimum of (n, p's size) bytes, or null
24      if no space is available. The returned pointer may or may not be
25      the same as p. If p is null, equivalent to malloc.  Unless the
26      #define REALLOC_ZERO_BYTES_FREES below is set, realloc with a
27      size argument of zero (re)allocates a minimum-sized chunk.
28   memalign(size_t alignment, size_t n);
29      Return a pointer to a newly allocated chunk of n bytes, aligned
30      in accord with the alignment argument, which must be a power of
31      two.  Will fail if 'alignment' is too large.
32   calloc(size_t unit, size_t quantity);
33      Returns a pointer to quantity * unit bytes, with all locations
34      set to zero.
35   cfree(void* p);
36      Equivalent to free(p).
37   malloc_trim(size_t pad);
38      Release all but pad bytes of freed top-most memory back
39      to the system. Return 1 if successful, else 0.
40   malloc_usable_size(void* p);
41      Report the number usable allocated bytes associated with allocated
42      chunk p. This may or may not report more bytes than were requested,
43      due to alignment and minimum size constraints.
44   malloc_stats();
45      Prints brief summary statistics on stderr.
46   mallinfo()
47      Returns (by copy) a struct containing various summary statistics.
48   mallopt(int parameter_number, int parameter_value)
49      Changes one of the tunable parameters described below. Returns
50      1 if successful in changing the parameter, else 0.  Actually, returns 0
51      always, as no parameter can be changed.
52 */
53 
54 #ifdef __xstormy16__
55 #define MALLOC_DIRECTION -1
56 #endif
57 
58 #ifndef MALLOC_DIRECTION
59 #define MALLOC_DIRECTION 1
60 #endif
61 
62 #include <stddef.h>
63 
64 void* malloc(size_t);
65 void    free(void*);
66 void* realloc(void*, size_t);
67 void* memalign(size_t, size_t);
68 void* valloc(size_t);
69 void* pvalloc(size_t);
70 void* calloc(size_t, size_t);
71 void    cfree(void*);
72 int     malloc_trim(size_t);
73 size_t  malloc_usable_size(void*);
74 void    malloc_stats(void);
75 int     mallopt(int, int);
76 struct mallinfo mallinfo(void);
77 
78 typedef struct freelist_entry {
79   size_t size;
80   struct freelist_entry *next;
81 } *fle;
82 
83 extern void * __malloc_end;
84 extern fle __malloc_freelist;
85 
86 /* Return the number of bytes that need to be added to X to make it
87    aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
88 #define M_ALIGN(x, align) (-(size_t)(x) & ((align) - 1))
89 
90 /* Return the number of bytes that need to be subtracted from X to make it
91    aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
92 #define M_ALIGN_SUB(x, align) ((size_t)(x) & ((align) - 1))
93 
94 extern void __malloc_start;
95 
96 /* This is the minimum gap allowed between __malloc_end and the top of
97    the stack.  This is only checked for when __malloc_end is
98    decreased; if instead the stack grows into the heap, silent data
99    corruption will result.  */
100 #define MALLOC_MINIMUM_GAP 32
101 
102 #ifdef __xstormy16__
103 register void * stack_pointer __asm__("r15");
104 #define MALLOC_LIMIT stack_pointer
105 #else
106 #define MALLOC_LIMIT __builtin_frame_address (0)
107 #endif
108 
109 #if MALLOC_DIRECTION < 0
110 #define CAN_ALLOC_P(required)				\
111   (((size_t) __malloc_end - (size_t)MALLOC_LIMIT	\
112     - MALLOC_MINIMUM_GAP) >= (required))
113 #else
114 #define CAN_ALLOC_P(required)				\
115   (((size_t)MALLOC_LIMIT - (size_t) __malloc_end	\
116     - MALLOC_MINIMUM_GAP) >= (required))
117 #endif
118 
119 /* real_size is the size we actually have to allocate, allowing for
120    overhead and alignment.  */
121 #define REAL_SIZE(sz)						\
122   ((sz) < sizeof (struct freelist_entry) - sizeof (size_t)	\
123    ? sizeof (struct freelist_entry)				\
124    : sz + sizeof (size_t) + M_ALIGN(sz, sizeof (size_t)))
125 
126 #ifdef DEFINE_MALLOC
127 
128 void * __malloc_end = &__malloc_start;
129 fle __malloc_freelist;
130 
131 void *
malloc(size_t sz)132 malloc (size_t sz)
133 {
134   fle *nextfree;
135   fle block;
136 
137   /* real_size is the size we actually have to allocate, allowing for
138      overhead and alignment.  */
139   size_t real_size = REAL_SIZE (sz);
140 
141   /* Look for the first block on the freelist that is large enough.  */
142   for (nextfree = &__malloc_freelist;
143        *nextfree;
144        nextfree = &(*nextfree)->next)
145     {
146       block = *nextfree;
147 
148       if (block->size >= real_size)
149 	{
150 	  /* If the block found is just the right size, remove it from
151 	     the free list.  Otherwise, split it.  */
152 	  if (block->size < real_size + sizeof (struct freelist_entry))
153 	    {
154 	      *nextfree = block->next;
155 	      return (void *)&block->next;
156 	    }
157 	  else
158 	    {
159 	      size_t newsize = block->size - real_size;
160 	      fle newnext = block->next;
161 	      *nextfree = (fle)((size_t)block + real_size);
162 	      (*nextfree)->size = newsize;
163 	      (*nextfree)->next = newnext;
164 	      goto done;
165 	    }
166 	}
167 
168       /* If this is the last block on the freelist, and it was too small,
169 	 enlarge it.  */
170       if (! block->next
171 	  && __malloc_end == (void *)((size_t)block + block->size))
172 	{
173 	  size_t moresize = real_size - block->size;
174 	  if (! CAN_ALLOC_P (moresize))
175 	    return NULL;
176 
177 	  *nextfree = NULL;
178 	  if (MALLOC_DIRECTION < 0)
179 	    {
180 	      block = __malloc_end = (void *)((size_t)block - moresize);
181 	    }
182 	  else
183 	    {
184 	      __malloc_end = (void *)((size_t)block + real_size);
185 	    }
186 
187 	  goto done;
188 	}
189     }
190 
191   /* No free space at the end of the free list.  Allocate new space
192      and use that.  */
193 
194   if (! CAN_ALLOC_P (real_size))
195     return NULL;
196 
197   if (MALLOC_DIRECTION > 0)
198     {
199       block = __malloc_end;
200       __malloc_end = (void *)((size_t)__malloc_end + real_size);
201     }
202   else
203     {
204       block = __malloc_end = (void *)((size_t)__malloc_end - real_size);
205     }
206  done:
207   block->size = real_size;
208   return (void *)&block->next;
209 }
210 
211 #endif
212 
213 #ifdef DEFINE_FREE
214 
215 void
free(void * block_p)216 free (void *block_p)
217 {
218   fle *nextfree;
219   fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
220 
221   if (block_p == NULL)
222     return;
223 
224   /* Look on the freelist to see if there's a free block just before
225      or just after this block.  */
226   for (nextfree = &__malloc_freelist;
227        *nextfree;
228        nextfree = &(*nextfree)->next)
229     {
230       fle thisblock = *nextfree;
231       if ((size_t)thisblock + thisblock->size == (size_t) block)
232 	{
233 	  thisblock->size += block->size;
234 	  if (MALLOC_DIRECTION > 0
235 	      && thisblock->next
236 	      && (size_t) block + block->size == (size_t) thisblock->next)
237 	    {
238 	      thisblock->size += thisblock->next->size;
239 	      thisblock->next = thisblock->next->next;
240 	    }
241 	  return;
242 	}
243       else if ((size_t) thisblock == (size_t) block + block->size)
244 	{
245 	  if (MALLOC_DIRECTION < 0
246 	      && thisblock->next
247 	      && (size_t) block == ((size_t) thisblock->next
248 				    + thisblock->next->size))
249 	    {
250 	      *nextfree = thisblock->next;
251 	      thisblock->next->size += block->size + thisblock->size;
252 	    }
253 	  else
254 	    {
255 	      block->size += thisblock->size;
256 	      block->next = thisblock->next;
257 	      *nextfree = block;
258 	    }
259 	  return;
260 	}
261       else if ((MALLOC_DIRECTION > 0
262 		&& (size_t) thisblock > (size_t) block)
263 	       || (MALLOC_DIRECTION < 0
264 		   && (size_t) thisblock < (size_t) block))
265 	break;
266     }
267 
268   block->next = *nextfree;
269   *nextfree = block;
270   return;
271 }
272 #endif
273 
274 #ifdef DEFINE_REALLOC
275 #include <string.h>
276 
277 void *
realloc(void * block_p,size_t sz)278 realloc (void *block_p, size_t sz)
279 {
280   fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
281   size_t real_size = REAL_SIZE (sz);
282   size_t old_real_size;
283 
284   if (block_p == NULL)
285     return malloc (sz);
286 
287   old_real_size = block->size;
288 
289   /* Perhaps we need to allocate more space.  */
290   if (old_real_size < real_size)
291     {
292       void *result;
293       size_t old_size = old_real_size - sizeof (size_t);
294 
295       /* Need to allocate, copy, and free.  */
296       result = malloc (sz);
297       if (result == NULL)
298 	return NULL;
299       memcpy (result, block_p, old_size < sz ? old_size : sz);
300       free (block_p);
301       return result;
302     }
303   /* Perhaps we can free some space.  */
304   if (old_real_size - real_size >= sizeof (struct freelist_entry))
305     {
306       fle newblock = (fle)((size_t)block + real_size);
307       block->size = real_size;
308       newblock->size = old_real_size - real_size;
309       free (&newblock->next);
310     }
311   return block_p;
312 }
313 #endif
314 
315 #ifdef DEFINE_CALLOC
316 #include <string.h>
317 
318 void *
calloc(size_t n,size_t elem_size)319 calloc (size_t n, size_t elem_size)
320 {
321   void *result;
322   size_t sz = n * elem_size;
323   result = malloc (sz);
324   if (result != NULL)
325     memset (result, 0, sz);
326   return result;
327 }
328 #endif
329 
330 #ifdef DEFINE_CFREE
331 void
cfree(void * p)332 cfree (void *p)
333 {
334   free (p);
335 }
336 #endif
337 
338 #ifdef DEFINE_MEMALIGN
339 void *
memalign(size_t align,size_t sz)340 memalign (size_t align, size_t sz)
341 {
342   fle *nextfree;
343   fle block;
344 
345   /* real_size is the size we actually have to allocate, allowing for
346      overhead and alignment.  */
347   size_t real_size = REAL_SIZE (sz);
348 
349   /* Some sanity checking on 'align'. */
350   if ((align & (align - 1)) != 0
351       || align <= 0)
352     return NULL;
353 
354   /* Look for the first block on the freelist that is large enough.  */
355   /* One tricky part is this: We want the result to be a valid pointer
356      to free.  That means that there has to be room for a size_t
357      before the block.  If there's additional space before the block,
358      it should go on the freelist, or it'll be lost---we could add it
359      to the size of the block before it in memory, but finding the
360      previous block is expensive.  */
361   for (nextfree = &__malloc_freelist;
362        ;
363        nextfree = &(*nextfree)->next)
364     {
365       size_t before_size;
366       size_t old_size;
367 
368       /* If we've run out of free blocks, allocate more space.  */
369       if (! *nextfree)
370 	{
371 	  old_size = real_size;
372 	  if (MALLOC_DIRECTION < 0)
373 	    {
374 	      old_size += M_ALIGN_SUB (((size_t)__malloc_end
375 					- old_size + sizeof (size_t)),
376 				       align);
377 	      if (! CAN_ALLOC_P (old_size))
378 		return NULL;
379 	      block = __malloc_end = (void *)((size_t)__malloc_end - old_size);
380 	    }
381 	  else
382 	    {
383 	      block = __malloc_end;
384 	      old_size += M_ALIGN ((size_t)__malloc_end + sizeof (size_t),
385 				   align);
386 	      if (! CAN_ALLOC_P (old_size))
387 		return NULL;
388 	      __malloc_end = (void *)((size_t)__malloc_end + old_size);
389 	    }
390 	  *nextfree = block;
391 	  block->size = old_size;
392 	  block->next = NULL;
393 	}
394       else
395 	{
396 	  block = *nextfree;
397 	  old_size = block->size;
398 	}
399 
400 
401       before_size = M_ALIGN (&block->next, align);
402       if (before_size != 0)
403 	before_size = sizeof (*block) + M_ALIGN (&(block+1)->next, align);
404 
405       /* If this is the last block on the freelist, and it is too small,
406 	 enlarge it.  */
407       if (! block->next
408 	  && old_size < real_size + before_size
409 	  && __malloc_end == (void *)((size_t)block + block->size))
410 	{
411 	  if (MALLOC_DIRECTION < 0)
412 	    {
413 	      size_t moresize = real_size - block->size;
414 	      moresize += M_ALIGN_SUB ((size_t)&block->next - moresize, align);
415 	      if (! CAN_ALLOC_P (moresize))
416 		return NULL;
417 	      block = __malloc_end = (void *)((size_t)block - moresize);
418 	      block->next = NULL;
419 	      block->size = old_size = old_size + moresize;
420 	      before_size = 0;
421 	    }
422 	  else
423 	    {
424 	      if (! CAN_ALLOC_P (before_size + real_size - block->size))
425 		return NULL;
426 	      __malloc_end = (void *)((size_t)block + before_size + real_size);
427 	      block->size = old_size = before_size + real_size;
428 	    }
429 
430 	  /* Two out of the four cases below will now be possible; which
431 	     two depends on MALLOC_DIRECTION.  */
432 	}
433 
434       if (old_size >= real_size + before_size)
435 	{
436 	  /* This block will do.  If there needs to be space before it,
437 	     split the block.  */
438 	  if (before_size != 0)
439 	    {
440 	      fle old_block = block;
441 
442 	      old_block->size = before_size;
443 	      block = (fle)((size_t)block + before_size);
444 
445 	      /* If there's no space after the block, we're now nearly
446                  done; just make a note of the size required.
447 	         Otherwise, we need to create a new free space block.  */
448 	      if (old_size - before_size
449 		  <= real_size + sizeof (struct freelist_entry))
450 		{
451 		  block->size = old_size - before_size;
452 		  return (void *)&block->next;
453 		}
454 	      else
455 		{
456 		  fle new_block;
457 		  new_block = (fle)((size_t)block + real_size);
458 		  new_block->size = old_size - before_size - real_size;
459 		  if (MALLOC_DIRECTION > 0)
460 		    {
461 		      new_block->next = old_block->next;
462 		      old_block->next = new_block;
463 		    }
464 		  else
465 		    {
466 		      new_block->next = old_block;
467 		      *nextfree = new_block;
468 		    }
469 		  goto done;
470 		}
471 	    }
472 	  else
473 	    {
474 	      /* If the block found is just the right size, remove it from
475 		 the free list.  Otherwise, split it.  */
476 	      if (old_size <= real_size + sizeof (struct freelist_entry))
477 		{
478 		  *nextfree = block->next;
479 		  return (void *)&block->next;
480 		}
481 	      else
482 		{
483 		  size_t newsize = old_size - real_size;
484 		  fle newnext = block->next;
485 		  *nextfree = (fle)((size_t)block + real_size);
486 		  (*nextfree)->size = newsize;
487 		  (*nextfree)->next = newnext;
488 		  goto done;
489 		}
490 	    }
491 	}
492     }
493 
494  done:
495   block->size = real_size;
496   return (void *)&block->next;
497 }
498 #endif
499 
500 #ifdef DEFINE_VALLOC
501 void *
valloc(size_t sz)502 valloc (size_t sz)
503 {
504   return memalign (128, sz);
505 }
506 #endif
507 #ifdef DEFINE_PVALLOC
508 void *
pvalloc(size_t sz)509 pvalloc (size_t sz)
510 {
511   return memalign (128, sz + M_ALIGN (sz, 128));
512 }
513 #endif
514 
515 #ifdef DEFINE_MALLINFO
516 #include "malloc.h"
517 #include <string.h>
518 
519 struct mallinfo
mallinfo(void)520 mallinfo (void)
521 {
522   struct mallinfo r;
523   fle fr;
524   size_t free_size;
525   size_t total_size;
526   size_t free_blocks;
527 
528   memset (&r, 0, sizeof (r));
529 
530   free_size = 0;
531   free_blocks = 0;
532   for (fr = __malloc_freelist; fr; fr = fr->next)
533     {
534       free_size += fr->size;
535       free_blocks++;
536       if (! fr->next)
537 	{
538 	  int atend;
539 	  if (MALLOC_DIRECTION > 0)
540 	    atend = (void *)((size_t)fr + fr->size) == __malloc_end;
541 	  else
542 	    atend = (void *)fr == __malloc_end;
543 	  if (atend)
544 	    r.keepcost = fr->size;
545 	}
546     }
547 
548   if (MALLOC_DIRECTION > 0)
549     total_size = (char *)__malloc_end - (char *)&__malloc_start;
550   else
551     total_size = (char *)&__malloc_start - (char *)__malloc_end;
552 
553 #ifdef DEBUG
554   /* Fixme: should walk through all the in-use blocks and see if
555      they're valid.  */
556 #endif
557 
558   r.arena = total_size;
559   r.fordblks = free_size;
560   r.uordblks = total_size - free_size;
561   r.ordblks = free_blocks;
562   return r;
563 }
564 #endif
565 
566 #ifdef DEFINE_MALLOC_STATS
567 #include "malloc.h"
568 #include <stdio.h>
569 
570 void
malloc_stats(void)571 malloc_stats(void)
572 {
573   struct mallinfo i;
574   FILE *fp;
575 
576   fp = stderr;
577   i = mallinfo();
578   fprintf (fp, "malloc has reserved %u bytes between %p and %p\n",
579 	   i.arena, &__malloc_start, __malloc_end);
580   fprintf (fp, "there are %u bytes free in %u chunks\n",
581 	   i.fordblks, i.ordblks);
582   fprintf (fp, "of which %u bytes are at the end of the reserved space\n",
583 	   i.keepcost);
584   fprintf (fp, "and %u bytes are in use.\n", i.uordblks);
585 }
586 #endif
587 
588 #ifdef DEFINE_MALLOC_USABLE_SIZE
589 size_t
malloc_usable_size(void * block_p)590 malloc_usable_size (void *block_p)
591 {
592   fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
593   return block->size - sizeof (size_t);
594 }
595 #endif
596 
597 #ifdef DEFINE_MALLOPT
598 int
mallopt(int n,int v)599 mallopt (int n, int v)
600 {
601   (void)n; (void)v;
602   return 0;
603 }
604 #endif
605