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