/* Copyright 1997 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /***************************************************/ /* File : Memory.c */ /* */ /* Purpose: Granularity-controlled memory handling */ /* functions for the browser, designed as */ /* transparent replacements for malloc, */ /* calloc, realloc and free (amongst */ /* other memory handlers). */ /* */ /* Author : A.D.Hodgkinson */ /* */ /* History: 29-Nov-96: Created. */ /***************************************************/ #include <stdlib.h> #include <string.h> #include "kernel.h" #include "flex.h" #include "wimp.h" #include "event.h" #include "svcprint.h" #include "Global.h" #include "Utils.h" #include "Memory.h" /* Local definitions */ #define MallocGranularity 256 #define FlexGranularityShift 12 #define Flex_256_Point 8192 #define Flex_4K_Point 16384 /*************************************************/ /* memory_malloc() */ /* */ /* Works as malloc, but only allocates data in */ /* chunks of MallocGranularity (see Memory.h) */ /* bytes. It always adds 4 bytes to the amount */ /* requested after rounding to the block size, */ /* and stores the actual exact requested size */ /* in these bytes after mallocing the block. The */ /* pointer that is returned is 4 bytes on from */ /* the actual size store so that the caller */ /* can treat the routines exactly like malloc. */ /* This does mean that a mixture of these */ /* routines and malloc etc. must *NEVER BE USED* */ /* on the same blocks of data! */ /* */ /* Parameters: As for malloc. */ /* */ /* Returns: As for malloc, in effect. */ /*************************************************/ void * memory_malloc(size_t size) { size_t s; void * r; /* Get the rounded up size of the block to allocate, plus */ /* the four bytes for the requested size store. */ s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4; /* Allocate the block */ #ifdef TRACE if (tl & (1u<<12)) Printf("memory_malloc: %d bytes asked for, giving %d\n",size,s); #endif r = malloc(s); #ifdef TRACE if (tl & (1u<<12)) { if (r) Printf("memory_malloc: Success, block is %p\n",r); else Printf("memory_malloc: Failure\n"); } #endif /* Assuming the allocation was successful, return the */ /* pointer to the block plus 4 to skip the size store, */ /* after storing the exact requested size in those */ /* four bytes. Else return NULL. */ if (r) { #ifdef TRACE malloccount += s; if (tl & (1u<<13)) Printf("** malloccount (memory_malloc): \0211%d\0217\n",malloccount); #endif *((int *) r) = size; return ((void *) (((int) r) + 4)); } else return NULL; } /*************************************************/ /* memory_calloc() */ /* */ /* Works as calloc, but with the added design */ /* rationale of memory_malloc. */ /* */ /* Parameters: As for calloc. */ /* */ /* Returns: As for calloc, in effect. */ /*************************************************/ void * memory_calloc(size_t n, size_t size) { size_t s; void * r; /* On entry, 'n' holds a number of objects, and 'size' */ /* holds the size of each object. Set 'size' to now */ /* hold the total amount required for all the objects. */ size = n * size; /* Get the rounded up size of the block to allocate, plus */ /* the four bytes for the requested size store. */ s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4; /* Allocate the block */ #ifdef TRACE if (tl & (1u<<12)) Printf("memory_calloc: %d bytes asked for, giving %d\n",size,s); #endif r = malloc(s); #ifdef TRACE if (tl & (1u<<12)) { if (r) Printf("memory_calloc: Success, block is %p\n",r); else Printf("memory_calloc: Failure\n"); } #endif /* Assuming the allocation was successful, return the */ /* pointer to the block plus 4 to skip the size store, */ /* after storing the exact requested size in those */ /* four bytes and zeroing the rest of the block. Else */ /* return NULL. */ if (r) { #ifdef TRACE malloccount += s; if (tl & (1u<<13)) Printf("** malloccount (memory_calloc): \0211%d\0217\n",malloccount); #endif *((int *) r) = size; memset((void *) (((int) r) + 4), 0, s - 4); return ((void *) (((int) r) + 4)); } else return NULL; } /*************************************************/ /* memory_realloc() */ /* */ /* Works as realloc, but with the added design */ /* rationale of memory_malloc. In this case, */ /* should the rounding up of the requested sizes */ /* mean that the requested size this time fits */ /* within the actual amount allocated, no */ /* reallocation will be done, thus speeding */ /* things up a bit. It also helps cut down on */ /* fragmentation. This applies both for growing */ /* and shrinking the size of a block. */ /* */ /* Parameters: As for realloc. */ /* */ /* Returns: As for realloc, in effect. */ /*************************************************/ void * memory_realloc(void * ptr, size_t size) { size_t s, c; void * r; /* Work out what the current size must be, based on the */ /* first four bytes of the block; these contain the exact */ /* size last passed to memory_alloc or memory_calloc. */ r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */ s = *((int *) r); /* s now holds the last requested size */ c = (((s / MallocGranularity) + 1) * MallocGranularity) + 4; /* Get the rounded up new size plus 4 bytes of size store */ s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4; /* If these two sizes are different, we need to do a realloc. */ /* If the realloc fails we return NULL anyway, so there's no */ /* problem with doing 'pointer = realloc (same pointer, ...)' */ #ifdef TRACE if (tl & (1u<<12)) Printf("memory_realloc: Block is %p.\n" " Will proceed if old size %d <> new size %d\n",r,c,s); #endif if (s != c) { #ifdef TRACE if (tl & (1u<<12)) Printf("memory_realloc: %d bytes asked for, giving %d\n",size,s); #endif r = realloc(r,s); #ifdef TRACE if (tl & (1u<<12)) { if (r) Printf("memory_realloc: Success\n"); else Printf("memory_realloc: Failure\n"); } #endif /* If the realloc succeeded write the new exact requested size */ /* into the first word and return the pointer plus 4, as in */ /* memory_malloc; else return NULL. */ if (r) { #ifdef TRACE malloccount += (s - c); if (tl & (1u<<13)) Printf("** malloccount (memory_realloc): \0211%d\0217\n",malloccount); #endif * ((int *) r) = size; return ((void *) (((int) r) + 4)); } else return NULL; } /* If the two sizes were the same, no realloc is needed so */ /* just give the original pointer back again. */ else return ptr; } /*************************************************/ /* memory_alloc_and_set() */ /* */ /* mallocs an area of memory and sets it to */ /* contain a specific character using memset. */ /* Does *NOT* given an error if the malloc fails */ /* - this is left to the caller to handle. */ /* */ /* Parameters: The amount of memory to allocate, */ /* in bytes; */ /* */ /* The value to fill the block with */ /* (from 0 to 255). */ /* */ /* Returns: Pointer to the claimed memory, or */ /* NULL if the claim failed */ /*************************************************/ void * memory_alloc_and_set(size_t s, int f) { void * p; #ifdef TRACE if (tl & (1u<<12)) Printf("memory_alloc_and_set: malloc %d, initialise to %d\n",s,f); #endif p = malloc(s); if (p) memset(p,f,s); #ifdef TRACE if (tl & (1u<<12)) { if (p) Printf("memory_alloc_and_set: Success, block is %p\n",p); else Printf("memory_alloc_and_set: Failure\n"); } #endif return p; } /*************************************************/ /* memory_free() */ /* */ /* Works as free() but subtracts four from the */ /* passed pointer first, assuming that the block */ /* was originally allocated with memory_alloc. */ /* The contents of the pointer *ARE NOT ZEROED* */ /* (unlike with, say, flex_free). */ /* */ /* Parameters: As for free. */ /*************************************************/ void memory_free(void * ptr) { #ifdef TRACE if (tl & (1u<<12)) Printf("memory_free: Freeing block %p\n",(void *) (((int) ptr) - 4)); malloccount -= memory_granular_size(ptr); if (tl & (1u<<13)) Printf("** malloccount (memory_free): \0212%d\0217\n",malloccount); #endif free ((void *) (((int) ptr) - 4)); } /*************************************************/ /* memory_size() */ /* */ /* Returns the size of a memory_malloced block */ /* of memory. There's no alloc-style equivalent. */ /* The amount is the originally requested size, */ /* and not the actual 'granular' size. */ /* */ /* Parameters: The pointer to the block, as */ /* returned by memory_malloc and */ /* memory_realloc (for example). */ /* */ /* Returns: The size in bytes as an int. The */ /* function has no way of knowing if */ /* the pointer was sensible, but */ /* will return zero if it's null. */ /*************************************************/ int memory_size(void * ptr) { void * r; r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */ return *((int *) r); /* Return the size */ } /*************************************************/ /* memory_granular_size() */ /* */ /* As memory_size, but returns the granular size */ /* (i.e. the real amount allocated). */ /* */ /* Parameters: As memory_size. */ /* */ /* Returns: As memory_size, but the real size */ /* rather than the requested size. */ /*************************************************/ int memory_granular_size(void * ptr) { void * r; int s; r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */ s = *((int *) r); /* s now holds the last requested size */ /* Get the rounded up new size plus 4 bytes of size store */ return (((s / MallocGranularity) + 1) * MallocGranularity) + 4; /* Return the granular size */ } /*************************************************/ /* memory_set_chunk_size() */ /* */ /* Sets the size of a particular block of memory */ /* identified by int chunk, where chunk is: */ /* */ /* 1: CK_FURL (fetching URL) */ /* 2: CK_DURL (display URL) */ /* 3: CK_CHIL (frames/children array) */ /* 4: CK_NAME (window name for targetted links) */ /* 5: CK_LINE (line structures array) */ /* 6: CK_LDAT (chunk structures array) */ /* 7: CK_FWID (frame widths array) */ /* 8: CK_FHEI (frame heights array) */ /* 9: CK_LINV (lines, variable granularity) */ /* 10: CK_LDAV (chunks, variable granularity) */ /* 11: CK_STAT (status_content array) */ /* 12: CK_OBJB (OBJECT, APPLET, EMBED structs) */ /* */ /* Parameters: Pointer to a browser_data struct */ /* for which the memory is to be */ /* allocated; */ /* */ /* Pointer to a reformat_cell struct */ /* in which some allocations will be */ /* made (e.g. CK_LINE, CK_LINV); */ /* */ /* The chunk identifier (see above); */ /* */ /* The amount of memory to allocate, */ /* in bytes. */ /* */ /* Assumes: The reformat_cell pointer may be */ /* NULL, in which case the 'cell' */ /* field of the browser_data struct */ /* will be used. */ /*************************************************/ _kernel_oserror * memory_set_chunk_size(browser_data * b, reformat_cell * cell, int chunk, int size) { int oldsize = 0; int success = 1; void * pointer = NULL; if (!cell) cell = b->cell; #ifdef TRACE if (tl & (1u<<7)) Printf("memory_set_chunk_size: Data * %p, cell * %p, chunk %d, size %d\n",b,cell,chunk,size); #endif /* Go through all the possible chunks, dealing with each case individually. */ /* Though some of the code will be duplicated, this individual handling */ /* allows greater flexibility in future - various blocks could have the way */ /* they are dealt with changed radically without too much trouble at this */ /* end of things. */ switch (chunk) { case CK_OBJB: { /* Set oldsize to the block size of the requested bit of memory, */ /* giving an immediate exit error if the chunk ID isn't recognised. */ /* Note that oldsize will be zero if the block hasn't been */ /* allocated already. */ if (b->odata) oldsize = flex_size((flex_ptr) &b->odata); else if (size == 0) return NULL; /* If the requested size is different from the current size... */ if (oldsize != size) { if (!size) { #ifdef TRACE flexcount -= oldsize; if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); #endif flex_free((flex_ptr) &b->odata); /* If size = 0, free the block */ } else { /* If the block exists, resize it. Otherwise allocate it anew. */ if (oldsize) success = flex_extend((flex_ptr) &b->odata, size); else success = flex_alloc((flex_ptr) &(b->odata), size); #ifdef TRACE if (success) { flexcount += size - oldsize; if (tl & (1u<<14)) Printf("** flexcount: %d\n",flexcount); } #endif } } break; } case CK_FURL: { if (!size) { /* If setting the size to zero, want to free the block */ if (b->urlfdata) { memory_free((void *) b->urlfdata); b->urlfdata = NULL; } } else { /* If there is a non-null pointer, reallocate the block. */ /* Store the new pointer in 'pointer', knowing this may */ /* be NULL if the realloc failed. Otherwise, malloc a */ /* new block again storing the pointer in 'pointer'. */ if (b->urlfdata) pointer = memory_realloc((void *) b->urlfdata, size); else pointer = memory_malloc(size); /* If 'pointer' isn't null, set b->urlfdata to point to */ /* the new block. If the pointer *is* null, then b->urlfdata */ /* is left alone. This is what we want - if it pointed to */ /* an old block that was realloced, we still want that */ /* pointer as the old size block is still malloced; else, it */ /* will have the default value of NULL. */ if (pointer) b->urlfdata = (char *) pointer; /* Failure is flagged through success = 0 (it's default value is 1). */ else success = 0; } break; } /* The rest of the code is basically the same as the stuff above at present. */ case CK_DURL: { if (!size) { if (b->urlddata) { memory_free((void *) b->urlddata); b->urlddata = NULL; } } else { if (b->urlddata) pointer = memory_realloc((void *) b->urlddata, size); else pointer = memory_malloc(size); if (pointer) b->urlddata = (char *) pointer; else success = 0; } break; } case CK_NAME: { if (!size) { if (b->window_name) { free((void *) b->window_name); b->window_name = NULL; } } else { if (b->window_name) pointer = realloc((void *) b->window_name, size); else pointer = malloc(size); if (pointer) b->window_name = (char *) pointer; else success = 0; } break; } /* Small arrays - not allocated in any granular fashion, and other */ /* generally necessary flags/counters allow the array size to be */ /* calculated easily. Hence use malloc etc. directly. */ case CK_CHIL: { if (!size) { if (b->children) { free((void *) b->children); b->children = NULL; } } else { if (b->children) pointer = realloc((void *) b->children, size); else pointer = malloc(size); if (pointer) b->children = (browser_data **) pointer; else success = 0; } break; } case CK_FWID: { if (!size) { if (b->frame_widths) { free((void *) b->frame_widths); b->frame_widths = NULL; } } else { if (b->frame_widths) pointer = realloc((void *) b->frame_widths, size); else pointer = malloc(size); if (pointer) b->frame_widths = (int *) pointer; else success = 0; } break; } case CK_FHEI: { if (!size) { if (b->frame_heights) { free((void *) b->frame_heights); b->frame_heights = NULL; } } else { if (b->frame_heights) pointer = realloc((void *) b->frame_heights, size); else pointer = malloc(size); if (pointer) b->frame_heights = (int *) pointer; else success = 0; } break; } /* Small block, likely to change size fairly often - so use */ /* memory_malloc etc. for the granularity of allocation. */ case CK_STAT: { if (!size) { if (b->status_contents) { memory_free((void *) b->status_contents); b->status_contents = NULL; } } else { if (b->status_contents) pointer = memory_realloc((void *) b->status_contents, size); else pointer = memory_malloc(size); if (pointer) b->status_contents = (browser_data **) pointer; else success = 0; } break; } /* The line and line data code only allocates in blocks of 4096 bytes - */ /* the shifting by FlexGranularityShift (see Memory.h) bits handles */ /* effectively fast division / multiplication by this size to see if */ /* the requested allocation exceeds the current block size. */ case CK_LINE: { if (cell->ldata) oldsize = flex_size((flex_ptr) &cell->ldata); else if (size == 0) return NULL; if (((oldsize >> FlexGranularityShift) != ((size >> FlexGranularityShift) + 1)) || !size) { if (!size) { #ifdef TRACE flexcount -= oldsize; if (tl & (1u<<14)) Printf("** line flexcount: %d\n",flexcount); #endif flex_free((flex_ptr) &cell->ldata); } else { if (oldsize) success = flex_extend((flex_ptr) &cell->ldata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift); else success = flex_alloc((flex_ptr) &cell->ldata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift); #ifdef TRACE if (success) { flexcount += (flex_size((flex_ptr) &cell->ldata) - oldsize); if (tl & (1u<<14)) Printf("** line flexcount: %d\n",flexcount); } #endif } } break; } case CK_LDAT: { if (cell->cdata) oldsize = flex_size((flex_ptr) &cell->cdata); else if (size == 0) return NULL; if (((oldsize >> FlexGranularityShift) != ((size >> FlexGranularityShift) + 1)) || !size) { if (!size) { #ifdef TRACE flexcount -= oldsize; if (tl & (1u<<14)) Printf("** ldat flexcount: %d\n",flexcount); #endif flex_free((flex_ptr) &cell->cdata); } else { if (oldsize) success = flex_extend((flex_ptr) &cell->cdata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift); else success = flex_alloc((flex_ptr) &cell->cdata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift); #ifdef TRACE if (success) { flexcount += (flex_size((flex_ptr) &cell->cdata) - oldsize); if (tl & (1u<<14)) Printf("** ldat flexcount: %d\n",flexcount); } #endif } } break; } /* Variable granularity versions of the above static granularity routines */ case CK_LINV: { int gshift = 0; if (cell->ldata) oldsize = flex_size((flex_ptr) &cell->ldata); else if (size == 0) return NULL; if (oldsize > Flex_256_Point) gshift = 8; /* 256 byte granularity */ if (oldsize > Flex_4K_Point) gshift = 12; /* 4K granularity */ if (((oldsize >> gshift) != ((size >> gshift) + 1)) || !size) { if (!size) { #ifdef TRACE flexcount -= oldsize; if (tl & (1u<<14)) Printf("** line flexcount: %d\n",flexcount); #endif flex_free((flex_ptr) &cell->ldata); } else { if (oldsize) success = flex_extend((flex_ptr) &cell->ldata, ((size >> gshift) + 1) << gshift); else success = flex_alloc((flex_ptr) &cell->ldata, ((size >> gshift) + 1) << gshift); #ifdef TRACE if (success) { flexcount += (flex_size((flex_ptr) &cell->ldata) - oldsize); if (tl & (1u<<14)) Printf("** line flexcount: %d\n",flexcount); } #endif } } break; } case CK_LDAV: { int gshift = 0; if (cell->cdata) oldsize = flex_size((flex_ptr) &cell->cdata); else if (size == 0) return NULL; if (oldsize > Flex_256_Point) gshift = 8; /* 256 byte granularity */ if (oldsize > Flex_4K_Point) gshift = 12; /* 4K granularity */ if (((oldsize >> gshift) != ((size >> gshift) + 1)) || !size) { if (!size) { #ifdef TRACE flexcount -= oldsize; if (tl & (1u<<14)) Printf("** ldat flexcount: %d\n",flexcount); #endif flex_free((flex_ptr) &cell->cdata); } else { if (oldsize) success = flex_extend((flex_ptr) &cell->cdata, ((size >> gshift) + 1) << gshift); else success = flex_alloc((flex_ptr) &cell->cdata, ((size >> gshift) + 1) << gshift); #ifdef TRACE if (success) { flexcount += (flex_size((flex_ptr) &cell->cdata) - oldsize); if (tl & (1u<<14)) Printf("** ldat flexcount: %d\n",flexcount); } #endif } } break; } /* If we reach this stage, something is very wrong - the chunk ID */ /* hasn't been recognised. This is a fairly good reason to panic */ /* and Get Out Now. */ default: { erb.errnum = 0; StrNCpy0(erb.errmess, lookup_token("STCUnkwn:Serious internal error - Unknown chunk ID in memory_set_chunk_size; must exit immediately.", 0, 0)); show_error(&erb); break; } } /* If success is 1 at this stage, exit with no error. This will */ /* be the case if no (re)allocation was needed as the old and */ /* requested sizes were the same, as success was initialised to */ /* a value of 1. */ #ifdef TRACE if (tl & (1u<<7)) { if (success) Printf("memory_set_chunk_size: Successful\n"); else Printf("memory_set_chunk_size: Exitting with an error\n"); } #endif if (success) return NULL; /* The allocation failed, so must have run out of memory or something... */ return make_no_cont_memory_error(10); }