/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: April 2020 */ /* Store Management Routines */ #include "pmwhdr.h" /* If this is defined, every staff item is in a separate block, testing the code for jumping between blocks. */ // #define DEBUGJUMP 1 /* If this is defined, memory sanity checks are done. */ // #define sanity /* Store is taken from the system in blocks whose minimum size is parameterized here. Then it is carved up into smaller blocks, as needed. It may be that Unix-like systems could do this efficiently enough with malloc/free these days, but this approach dates back to times when that was a performance hit. */ #define store_allocation_unit 32*1024 /* Musical data is packed into blocks whose size is parameterized here. */ #define store_item_unit 8*1000 /* Free queue entries contain a pointer to the next entry and their length, in a storestr at the start. Allocated blocks also have a storestr at their start, but only the length field is set. We used just to have an int at the start, but this doesn't work on 64-bit systems, because we need to return correctly aligned blocks. */ typedef struct storestr { struct storestr *next; usint length; } storestr; /************************************************* * Static data * *************************************************/ #ifdef trace static FILE *tracefile = NULL; #endif /* An anchoring free queue block - the queue is searched from the next block on the chain. */ static storestr store_freequeue = { NULL, sizeof(storestr) }; /* An anchoring item buffer, just large enough to hold a b_Jump, which will be inserted when the first real item is required. */ static b_Jumpstr store_bstrbase; static bstr *store_itemptr = (bstr *)(&store_bstrbase); /* Next item */ static int store_itemspace = ROUND(sizeof(b_Jumpstr)); /* Space left */ /************************************************* * Trace logging * *************************************************/ /* This function is used for debugging memory handling by tracing gets and frees etc. Arguments: s a string to print a an address to print b a number to print Returns: nothing */ #ifdef trace static void do_trace(char *s, void *a, int b) { if (tracefile == NULL) tracefile = fopen("storetrace", "w"); fprintf(tracefile, "%s %8p %8d\n", s, a, b); } #endif /************************************************* * Free queue sanity check * *************************************************/ /* This function is used for debugging memory handling by checking the sanity of the free queue. The check is just that the chaining is valid. Arguments: none Returns: nothing */ #ifdef sanity static void store_freequeuecheck(void) { storestr *p = store_freequeue.next; while (p != NULL) p = p->next; } #endif /************************************************* * Get block * *************************************************/ /* This function gets a new block of memory, searching the free queue first for something that is big enough. If not, it gets a new large block, and starts to carve it up. If it can't get any more store, it returns NULL. Argument: the size required, in bytes Returns: pointer to usable store (after an initial storestr), or NULL */ void * store_get(usint bytesize) { int newlength; usint strsize = (usint)sizeof(storestr); storestr *newblock; storestr *previous = &store_freequeue; storestr *p = store_freequeue.next; /* Add space for one storestr to hold the length (and a pointer when the block is freed), then round up to a multiple of storestrs. Use (~strsize + 1) rather than -strsize because strsize is unsigned. */ bytesize = (bytesize + 2*strsize - 1) & (~strsize + 1); #ifdef sanity store_freequeuecheck(); #endif /* Keep statistics */ main_storetotal += bytesize; /* Search free queue for a block that is big enough */ while(p != NULL) { if (bytesize <= p->length) { /* found suitable block */ int leftover = p->length - bytesize; if (leftover == 0) { /* block used completely */ previous->next = p->next; } else { /* use bottom of block */ storestr *remains = p + bytesize/sizeof(storestr); remains->length = leftover; previous->next = remains; remains->next = p->next; p->length = bytesize; } #ifdef sanity store_freequeuecheck(); #endif #ifdef trace do_trace("get ", p, bytesize); #endif return (void *)(p + 1); /* leave leading stavestr "hidden" */ } else { /* try next block */ previous = p; p = p->next; } } /* No block long enough has been found. Get a new big block and recurse. */ main_storetotal -= bytesize; /* correction */ newlength = (bytesize > store_allocation_unit)? bytesize : store_allocation_unit; newblock = malloc(newlength); if (newblock == NULL) return NULL; #ifdef trace do_trace("GET ", newblock, newlength); #endif newblock->length = newlength; /* Set block length */ main_storetotal += newlength; /* Pretend it's allocated */ store_free(newblock + 1); /* Add to free queue */ return store_get(bytesize-sizeof(storestr)); /* Try again */ } /************************************************* * Get store, failing if none available * *************************************************/ /* This function gives a hard error if no store is available. In these days of gigabyte memories, failure is a remote possibility. Argument: size of block required Returns: pointer to useable portion of the block */ void * store_Xget(int bytesize) { void *yield = store_get(bytesize); if (yield == NULL) error_moan(ERR1, bytesize); /* Hard */ return yield; } /************************************************* * Copy store, failing if no store * *************************************************/ /* This function makes a copy of a block of store. Argument: the block to copy ) in both cases, the usable pointers Returns: the new block ) */ void * store_copy(void *p) { storestr *pp = (storestr *)p - 1; usint length = pp->length - sizeof(storestr); void *yield = store_Xget(length); memcpy(yield, p, length); return yield; } /************************************************* * Copy string, failing if no store * *************************************************/ /* This function copies a C string into a new block of store. Argument: the string Returns: pointer to the copy */ uschar * store_copystring(uschar *s) { uschar *yield = store_Xget(Ustrlen(s)+1); Ustrcpy(yield, s); return yield; } /************************************************* * Yield address of next staff item * *************************************************/ /* This is used to initialize the bar index and on other occasions when we want to know what the next item address will be without actually setting the item up. There always is a next item available. Arguments: none Returns: pointer to the next item */ void * store_nextitem(void) { return store_itemptr; } /************************************************* * Get a staff item * *************************************************/ /* Staff items come from the staff item block, packed in together. When the block is used up, we put in an adjustment item to the next one. There is always one next item available. The size depends on the type. Argument: the item type Returns: pointer to the next item, with type initialized */ void *store_getitem(int type) { void *yield; int size = length_table[type]; #ifndef DEBUGJUMP if (store_itemspace < size + length_table[b_Jump]) #endif { b_Jumpstr *j = (b_Jumpstr *)store_itemptr; bstr *newbuff = store_Xget(store_item_unit); j->type = b_Jump; j->next = (bstr *)((uschar *)newbuff - length_table[b_Jump]); store_itemptr = newbuff; store_itemspace = store_item_unit; main_storestaves += length_table[b_Jump]; } store_itemptr->type = type; yield = (void *)store_itemptr; store_itemptr = (bstr *)((uschar *)store_itemptr + size); store_itemspace -= size; main_storestaves += size; return yield; } /************************************************* * Free a chunk of store * *************************************************/ /* The length is in the storestr precedes the address that the client was given. The freed block is put onto the free chain, possibly amalgamated with a block that is already there. Argument: the client (usable) address Returns: nothing */ void store_free(void *address) { storestr *previous = &store_freequeue; storestr *this = previous->next; storestr *start = ((storestr *)address) - 1; storestr *end = start + start->length/sizeof(storestr); main_storetotal -= start->length; #ifdef sanity store_freequeuecheck(); #endif #ifdef trace do_trace("free", start, start->length); #endif /* Find where to insert */ while (this != NULL) { if (start < this) break; previous = this; this = previous->next; } /* Insert */ previous->next = start; start->next = this; /* Check for overlap with next */ if (end > this && this != NULL) error_moan(ERR2, start, start->length, this); /* Hard */ /* Check for contiguity with next */ if (end == this) { start->next = this->next; start->length += this->length; } /* Check for overlap/contiguity with previous */ if (previous != &store_freequeue) { storestr *prevend = previous + previous->length/sizeof(storestr); if (prevend > start) error_moan(ERR3, previous, previous->length, start); /* Hard */ if (prevend == start) { previous->next = start->next; previous->length += start->length; } } #ifdef sanity store_freequeuecheck(); #endif } /* End of store.c */