To allocate memory suitable for the dynamic creation of objects of different sizes, placing some larger objects automatically into doubly linked lists and assigning each a unique allocation ID number.
- §1. Memory manager
- §6. Architecture
- §7. Level 1: memory blocks
- §13. Level 2: memory frames and integrity checking
- §17. Level 3: managing linked lists of allocated objects
- §19. Allocator functions created by macros
- §21. Expanding many macros
- §22. Simple memory allocations
- §28. Text storage
- §32. Memory usage report
- §36. Run-time pointer type checking
§1. Memory manager. This allocates memory as needed to store the numerous "objects" of different sizes, all C structures. There's no garbage collection because nothing is ever destroyed. Each type has its own doubly-linked list, and in each type the objects created are given unique IDs (within that type) counting upwards from 0. These IDs will be unique across all threads.
§2. Before going much further, we will need to anticipate what the memory
manager wants. An "object" is a copy in memory of a C struct
; thus,
a plain int
is not an object. The memory manager can only deal with
a given type of struct
if it contains three special elements, and we
define those using a macro. Thus, if the user wants to allocate larger
structures of type thingummy
, then it needs to be defined like so:
typedef struct thingummy { int whatsit; struct text_stream *doobrey; ... MEMORY_MANAGEMENT }
The caveat about "larger structures" is that smaller objects can instead be stored in arrays, to reduce memory and speed overheads. Their structure declarations do not include the following macro; they do not have unique IDs; and they cannot be iterated over.
define MEMORY_MANAGEMENT int allocation_id; Numbered from 0 upwards in creation order void *next_structure; Next object in double-linked list void *prev_structure; Previous object in double-linked list
§3. It is also necessary to define a constant in the following enumeration
family: for thingummy
, it would be thingummy_MT
. Had it been a smaller
object, it would have been thingummy_array_MT
instead.
There is no significance to the order in which structures are registered with the memory system. The ones here are those needed by Foundation.
enum filename_MT from 0 enum pathname_MT enum string_storage_area_MT enum scan_directory_MT enum ebook_MT enum ebook_datum_MT enum ebook_volume_MT enum ebook_chapter_MT enum ebook_page_MT enum ebook_image_MT enum HTML_file_state_MT enum HTML_tag_array_MT enum text_stream_array_MT enum command_line_switch_MT enum dictionary_MT enum dict_entry_array_MT enum debugging_aspect_MT enum linked_list_MT enum linked_list_item_array_MT enum match_avinue_array_MT enum match_trie_array_MT enum method_MT enum method_set_MT enum ebook_mark_MT enum semantic_version_number_holder_MT enum semver_range_MT enum web_md_MT enum chapter_md_MT enum section_md_MT enum web_bibliographic_datum_MT enum module_MT enum module_search_MT
§4. For each type of object to be allocated, a single structure of the
following design is maintained. Types which are allocated individually,
like world objects, have no_allocated_together
set to 1, and the doubly
linked list is of the objects themselves. For types allocated in small
arrays (typically of 100 objects at a time), no_allocated_together
is set
to the number of objects in each completed array (so, typically 100) and
the doubly linked list is of the arrays.
typedef struct allocation_status_structure {
actually needed for allocation purposes:
int objects_allocated; total number of objects (or arrays) ever allocated
void *first_in_memory; head of doubly linked list
void *last_in_memory; tail of doubly linked list
used only to provide statistics for the debugging log:
char *name_of_type; e.g., "lexicon_entry_MT"
int bytes_allocated; total allocation for this type of object, not counting overhead
int objects_count; total number currently in existence (i.e., undeleted)
int no_allocated_together; number of objects in each array of this type of object
} allocation_status_structure;
The structure allocation_status_structure is private to this section.
§5. The memory allocator itself needs some memory, but only a fixed-size and fairly small array of the structures defined above. The allocator can safely begin as soon as this is initialised.
allocation_status_structure alloc_status[NO_DEFINED_MT_VALUES]; void Memory::start(void) { for (int i=0; i<NO_DEFINED_MT_VALUES; i++) { alloc_status[i].first_in_memory = NULL; alloc_status[i].last_in_memory = NULL; alloc_status[i].objects_allocated = 0; alloc_status[i].objects_count = 0; alloc_status[i].bytes_allocated = 0; alloc_status[i].no_allocated_together = 1; alloc_status[i].name_of_type = "unused"; } Memory::name_fundamental_reasons(); }
The function Memory::start is used in 1/fm (§8).
§6. Architecture. The memory manager is built in three levels, with its interface to the user being entirely at level 3 (except that when it shuts down it calls a level 1 routine to free everything). Each level uses the one below it.
- (3) Managing linked lists of large objects, within which objects can be created at any point, and from which objects can be deleted; and providing a way to create new small objects of any given type.
- (2) Allocating some thousands of memory frames, each holding one large object or an array of small objects.
- (1) Allocating and freeing a few dozen large blocks of contiguous memory.
§7. Level 1: memory blocks. Memory is allocated in blocks within which objects are allocated as needed. The "safety margin" is the number of spare bytes left blank at the end of each object: this is done because we want to be paranoid about compilers on different architectures aligning structures to different boundaries (multiples of 4, 8, 16, etc.). Each block also ends with a firebreak of zeroes, which ought never to be touched: we want to minimise the chance of a mistake causing a memory exception which crashes the compiler, because if that happens it will be difficult to recover the circumstances from the debugging log.
define SAFETY_MARGIN 128 define BLANK_END_SIZE 256
§8. At present MEMORY_GRANULARITY
is 800K. This is the quantity of memory
allocated by each individual malloc
call.
After MAX_BLOCKS_ALLOWED
blocks, we throw in the towel: we must have
fallen into an endless loop which creates endless new objects somewhere.
(If this ever happens, it would be a bug: the point of this mechanism is to
be able to recover. Without this safety measure, OS X in particular would
grind slowly to a halt, never refusing a malloc
, until the user was
unable to get the GUI responsive enough to kill the process.)
define MAX_BLOCKS_ALLOWED 15000 define MEMORY_GRANULARITY 100*1024*8 which must be divisible by 1024
int no_blocks_allocated = 0; int total_objects_allocated = 0; a potentially larger number, used only for the debugging log
§9. Memory blocks are stored in a linked list, and we keep track of the
size of the current block: that is, the block at the tail of the list.
Each memory block consists of a header structure, followed by SAFETY_MARGIN
null bytes, followed by actual data.
typedef struct memblock_header { int block_number; struct memblock_header *next; char *the_memory; } memblock_header;
The structure memblock_header is accessed in 4/taa and here.
memblock_header *first_memblock_header = NULL; head of list of memory blocks memblock_header *current_memblock_header = NULL; tail of list of memory blocks int used_in_current_memblock = 0; number of bytes so far used in the tail memory block
§11. The actual allocation and deallocation is performed by the following pair of routines.
void Memory::allocate_another_block(void) { unsigned char *cp; memblock_header *mh; <Allocate and zero out a block of memory, making cp point to it 11.1>; mh = (memblock_header *) cp; used_in_current_memblock = sizeof(memblock_header) + SAFETY_MARGIN; mh->the_memory = (void *) (cp + used_in_current_memblock); <Add new block to the tail of the list of memory blocks 11.2>; }
The function Memory::allocate_another_block is used in §16.1.
§11.1. Note that cp
and mh
are set to the same value: they merely have different
pointer types as far as the C compiler is concerned.
<Allocate and zero out a block of memory, making cp point to it 11.1> =
int i; if (no_blocks_allocated++ >= MAX_BLOCKS_ALLOWED) Errors::fatal( "the memory manager has halted inweb, which seems to be generating " "endless structures. Presumably it is trapped in a loop"); Memory::check_memory_integrity(); cp = (unsigned char *) (Memory::paranoid_calloc(MEMORY_GRANULARITY, 1)); if (cp == NULL) Errors::fatal("Run out of memory: malloc failed"); for (i=0; i<MEMORY_GRANULARITY; i++) cp[i] = 0;
This code is used in §11.
§11.2. As can be seen, memory block numbers count upwards from 0 in order of their allocation.
<Add new block to the tail of the list of memory blocks 11.2> =
if (current_memblock_header == NULL) { mh->block_number = 0; first_memblock_header = mh; } else { mh->block_number = current_memblock_header->block_number + 1; current_memblock_header->next = mh; } current_memblock_header = mh;
This code is used in §11.
§12. Freeing all this memory again is just a matter of freeing each block in turn, but of course being careful to avoid following links in a just-freed block.
void Memory::free(void) { Memory::free_ssas(); memblock_header *mh = first_memblock_header; while (mh != NULL) { memblock_header *next_mh = mh->next; void *p = (void *) mh; free(p); mh = next_mh; } }
The function Memory::free is used in 1/fm (§9).
§13. Level 2: memory frames and integrity checking. Within these extensive blocks of contiguous memory, we place the actual objects in between "memory frames", which are only used at present to police the integrity of memory: again, finding obscure and irritating memory-corruption bugs is more important to us than saving bytes. Each memory frame wraps either a single large object, or a single array of small objects.
define INTEGRITY_NUMBER 0x12345678 a value unlikely to be in memory just by chance
typedef struct memory_frame {
int integrity_check; this should always contain the INTEGRITY_NUMBER
struct memory_frame *next_frame; next frame in the list of memory frames
int mem_type; type of object stored in this frame
int allocation_id; allocation ID number of object stored in this frame
} memory_frame;
The structure memory_frame is private to this section.
§14. There is a single linked list of all the memory frames, perhaps of about 10000 entries in length, beginning here. (These frames live in different memory blocks, but we don't need to worry about that.)
memory_frame *first_memory_frame = NULL; earliest memory frame ever allocated memory_frame *last_memory_frame = NULL; most recent memory frame allocated
§15. If the integrity numbers of every frame are still intact, then it is pretty
unlikely that any bug has caused memory to overwrite one frame into another.
Memory::check_memory_integrity
might on very large runs be run often, if we didn't
prevent this: since the number of calls would be roughly proportional to
memory usage, we would implicitly have an O(n^2) running time in the
amount of storage n allocated.
int calls_to_cmi = 0; void Memory::check_memory_integrity(void) { int c; memory_frame *mf; c = calls_to_cmi++; if (!((c<10) || (c == 100) || (c == 1000) || (c == 10000))) return; for (c = 0, mf = first_memory_frame; mf; c++, mf = mf->next_frame) if (mf->integrity_check != INTEGRITY_NUMBER) Errors::fatal("Memory manager failed integrity check"); } void Memory::debug_memory_frames(int from, int to) { int c; memory_frame *mf; for (c = 0, mf = first_memory_frame; (mf) && (c <= to); c++, mf = mf->next_frame) if (c >= from) { char *desc = "corrupt"; if (mf->integrity_check == INTEGRITY_NUMBER) desc = alloc_status[mf->mem_type].name_of_type; } }
The function Memory::check_memory_integrity is used in §11.1.
The function Memory::debug_memory_frames appears nowhere else.
§16. We have seen how memory is allocated in large blocks, and that a linked list of memory frames will live inside those blocks; we have seen how the list is checked for integrity; but we not seen how it is built. Every memory frame is created by the following function:
void *Memory::allocate(int mem_type, int extent) {
CREATE_MUTEX(mutex);
LOCK_MUTEX(mutex);
unsigned char *cp;
memory_frame *mf;
int bytes_free_in_current_memblock, extent_without_overheads = extent;
extent += sizeof(memory_frame); each allocation is preceded by a memory frame
extent += SAFETY_MARGIN; each allocation is followed by SAFETY_MARGIN
null bytes
<Ensure that the current memory block has room for this many bytes 16.1>;
cp = ((unsigned char *) (current_memblock_header->the_memory)) + used_in_current_memblock;
used_in_current_memblock += extent;
mf = (memory_frame *) cp; the new memory frame,
cp = cp + sizeof(memory_frame); following which is the actual allocated data
mf->integrity_check = INTEGRITY_NUMBER;
mf->allocation_id = alloc_status[mem_type].objects_allocated;
mf->mem_type = mem_type;
<Add the new memory frame to the big linked list of all frames 16.2>;
<Update the allocation status for this type of object 16.3>;
total_objects_allocated++;
UNLOCK_MUTEX(mutex);
return (void *) cp;
}
The function Memory::allocate is used in §19.
§16.1. The granularity error below will be triggered the first time a particular object type is allocated. So this is not a potential time-bomb just waiting for a user with a particularly long and involved source text to discover.
<Ensure that the current memory block has room for this many bytes 16.1> =
if (current_memblock_header == NULL) Memory::allocate_another_block(); bytes_free_in_current_memblock = MEMORY_GRANULARITY - (used_in_current_memblock + extent); if (bytes_free_in_current_memblock < BLANK_END_SIZE) { Memory::allocate_another_block(); if (extent+BLANK_END_SIZE >= MEMORY_GRANULARITY) Errors::fatal("Memory manager failed because granularity too low"); }
This code is used in §16.
§16.2. New memory frames are added to the tail of the list:
<Add the new memory frame to the big linked list of all frames 16.2> =
mf->next_frame = NULL; if (first_memory_frame == NULL) first_memory_frame = mf; else last_memory_frame->next_frame = mf; last_memory_frame = mf;
This code is used in §16.
§16.3. See the definition of alloc_status
above.
<Update the allocation status for this type of object 16.3> =
if (alloc_status[mem_type].first_in_memory == NULL) alloc_status[mem_type].first_in_memory = (void *) cp; alloc_status[mem_type].last_in_memory = (void *) cp; alloc_status[mem_type].objects_allocated++; alloc_status[mem_type].bytes_allocated += extent_without_overheads;
This code is used in §16.
§17. Level 3: managing linked lists of allocated objects. We define macros which look as if they are functions, but for which one argument is the name of a type: expanding these macros provides suitable C functions to handle each possible type. These macros provide the interface through which all other sections allocate and leaf through memory.
Note that Inweb allows multi-line macro definitions without backslashes
to continue them, unlike ordinary C. Otherwise these are "standard"
macros, though this was my first brush with the ##
concatenation
operator: basically CREATE(thing)
expands into (allocate_thing())
because of the ##
. (See Kernighan and Ritchie, section 4.11.2.)
define CREATE(type_name) (allocate_##type_name()) define COPY(to, from, type_name) (copy_##type_name(to, from)) define CREATE_BEFORE(existing, type_name) (allocate_##type_name##_before(existing)) define DESTROY(this, type_name) (deallocate_##type_name(this)) define FIRST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_MT].first_in_memory) define LAST_OBJECT(type_name) ((type_name *) alloc_status[type_name##_MT].last_in_memory) define NEXT_OBJECT(this, type_name) ((type_name *) (this->next_structure)) define PREV_OBJECT(this, type_name) ((type_name *) (this->prev_structure)) define NUMBER_CREATED(type_name) (alloc_status[type_name##_MT].objects_count)
§18. The following macros are widely used (well, the first one is, anyway) for looking through the double linked list of existing objects of a given type.
define LOOP_OVER(var, type_name) for (var=FIRST_OBJECT(type_name); var != NULL; var = NEXT_OBJECT(var, type_name)) define LOOP_BACKWARDS_OVER(var, type_name) for (var=LAST_OBJECT(type_name); var != NULL; var = PREV_OBJECT(var, type_name))
§19. Allocator functions created by macros. The following macros generate a family of systematically named functions.
For instance, we shall shortly expand ALLOCATE_INDIVIDUALLY(parse_node)
,
which will expand to three functions: allocate_parse_node
,
deallocate_parse_node
and allocate_parse_node_before
.
Quaintly, #type_name
expands into the value of type_name
put within
double-quotes.
define NEW_OBJECT(type_name) ((type_name *) Memory::allocate(type_name##_MT, sizeof(type_name))) define ALLOCATE_INDIVIDUALLY(type_name) MAKE_REFERENCE_ROUTINES(type_name, type_name##_MT) type_name *allocate_##type_name(void) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); alloc_status[type_name##_MT].name_of_type = #type_name; type_name *prev_obj = LAST_OBJECT(type_name); type_name *new_obj = NEW_OBJECT(type_name); new_obj->allocation_id = alloc_status[type_name##_MT].objects_allocated-1; new_obj->next_structure = NULL; if (prev_obj != NULL) prev_obj->next_structure = (void *) new_obj; new_obj->prev_structure = prev_obj; alloc_status[type_name##_MT].objects_count++; UNLOCK_MUTEX(mutex); return new_obj; } void deallocate_##type_name(type_name *kill_me) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); type_name *prev_obj = PREV_OBJECT(kill_me, type_name); type_name *next_obj = NEXT_OBJECT(kill_me, type_name); if (prev_obj == NULL) { alloc_status[type_name##_MT].first_in_memory = next_obj; } else { prev_obj->next_structure = next_obj; } if (next_obj == NULL) { alloc_status[type_name##_MT].last_in_memory = prev_obj; } else { next_obj->prev_structure = prev_obj; } alloc_status[type_name##_MT].objects_count--; UNLOCK_MUTEX(mutex); } type_name *allocate_##type_name##_before(type_name *existing) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); type_name *new_obj = allocate_##type_name(); deallocate_##type_name(new_obj); new_obj->prev_structure = existing->prev_structure; if (existing->prev_structure != NULL) ((type_name *) existing->prev_structure)->next_structure = new_obj; else alloc_status[type_name##_MT].first_in_memory = (void *) new_obj; new_obj->next_structure = existing; existing->prev_structure = new_obj; alloc_status[type_name##_MT].objects_count++; UNLOCK_MUTEX(mutex); return new_obj; } void copy_##type_name(type_name *to, type_name *from) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); type_name *prev_obj = to->prev_structure; type_name *next_obj = to->next_structure; int aid = to->allocation_id; *to = *from; to->allocation_id = aid; to->next_structure = next_obj; to->prev_structure = prev_obj; UNLOCK_MUTEX(mutex); }
§20. ALLOCATE_IN_ARRAYS
is still more obfuscated. When we
ALLOCATE_IN_ARRAYS(X, 100)
, the result will be definitions of a new type
X_block
and functions allocate_X
, allocate_X_block
,
deallocate_X_block
and allocate_X_block_before
(though the last is not
destined ever to be used). Note that we are not provided with the means to
deallocate individual objects this time: that's the trade-off for
allocating in blocks.
define ALLOCATE_IN_ARRAYS(type_name, NO_TO_ALLOCATE_TOGETHER) MAKE_REFERENCE_ROUTINES(type_name, type_name##_array_MT) typedef struct type_name##_array { int used; struct type_name array[NO_TO_ALLOCATE_TOGETHER]; MEMORY_MANAGEMENT } type_name##_array; ALLOCATE_INDIVIDUALLY(type_name##_array) type_name##_array *next_##type_name##_array = NULL; struct type_name *allocate_##type_name(void) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); if ((next_##type_name##_array == NULL) || (next_##type_name##_array->used >= NO_TO_ALLOCATE_TOGETHER)) { alloc_status[type_name##_array_MT].no_allocated_together = NO_TO_ALLOCATE_TOGETHER; next_##type_name##_array = allocate_##type_name##_array(); next_##type_name##_array->used = 0; } UNLOCK_MUTEX(mutex); return &(next_##type_name##_array->array[ next_##type_name##_array->used++]); }
§21. Expanding many macros. Each given structure must have a typedef name, say marvel
, and can be
used in one of two ways. Either way, we can obtain a new one with the macro
CREATE(marvel)
.
Either (a) it will be individually allocated. In this case marvel_MT
should be defined with a new MT (memory type) number, and the macro
ALLOCATE_INDIVIDUALLY(marvel)
should be expanded. The first and last
objects created will be FIRST_OBJECT(marvel)
and LAST_OBJECT(marvel)
,
and we can proceed either way through a double linked list of them with
PREV_OBJECT(mv, marvel)
and NEXT_OBJECT(mv, marvel)
. For convenience,
we can loop through marvels, in creation order, using LOOP_OVER(var,
marvel), which expands to a
for loop in which the variable
var runs
through each created marvel in turn; or equally we can run backwards
through using LOOP_BACKWARDS_OVER(var, marvel)
. In addition, there are
corruption checks to protect the memory from overrunning accidents, and the
structure can be used as a value in the symbols table. Good for large
structures with significant semantic content.
Or (b) it will be allocated in arrays. Once again we can obtain new marvels
with CREATE(marvel)
. This is more efficient both in speed and memory
usage, but we lose the ability to loop through the objects. For this
arrangement, define marvel_array_MT
with a new MT number and expand the
macro ALLOCATE_IN_ARRAYS(marvel, 100)
, where 100 (or what may you) is the
number of objects allocated jointly as a block. Good for small structures
used in the lower levels.
Here goes, then.
ALLOCATE_INDIVIDUALLY(filename) ALLOCATE_INDIVIDUALLY(pathname) ALLOCATE_INDIVIDUALLY(string_storage_area) ALLOCATE_INDIVIDUALLY(scan_directory) ALLOCATE_INDIVIDUALLY(ebook) ALLOCATE_INDIVIDUALLY(ebook_datum) ALLOCATE_INDIVIDUALLY(ebook_volume) ALLOCATE_INDIVIDUALLY(ebook_chapter) ALLOCATE_INDIVIDUALLY(ebook_page) ALLOCATE_INDIVIDUALLY(ebook_image) ALLOCATE_INDIVIDUALLY(HTML_file_state) ALLOCATE_INDIVIDUALLY(command_line_switch) ALLOCATE_INDIVIDUALLY(dictionary) ALLOCATE_INDIVIDUALLY(debugging_aspect) ALLOCATE_INDIVIDUALLY(linked_list) ALLOCATE_INDIVIDUALLY(method) ALLOCATE_INDIVIDUALLY(method_set) ALLOCATE_INDIVIDUALLY(ebook_mark) ALLOCATE_INDIVIDUALLY(semantic_version_number_holder) ALLOCATE_INDIVIDUALLY(semver_range) ALLOCATE_INDIVIDUALLY(web_bibliographic_datum) ALLOCATE_INDIVIDUALLY(web_md) ALLOCATE_INDIVIDUALLY(chapter_md) ALLOCATE_INDIVIDUALLY(section_md) ALLOCATE_INDIVIDUALLY(module) ALLOCATE_INDIVIDUALLY(module_search) ALLOCATE_IN_ARRAYS(dict_entry, 100) ALLOCATE_IN_ARRAYS(HTML_tag, 1000) ALLOCATE_IN_ARRAYS(linked_list_item, 1000) ALLOCATE_IN_ARRAYS(match_avinue, 1000) ALLOCATE_IN_ARRAYS(match_trie, 1000) ALLOCATE_IN_ARRAYS(text_stream, 100)
§22. Simple memory allocations. Not all of our memory will be claimed in the form of structures: now and then
we need to use the equivalent of traditional malloc
and calloc
routines.
enum STREAM_MREASON from 0 enum FILENAME_STORAGE_MREASON enum STRING_STORAGE_MREASON enum DICTIONARY_MREASON enum CLS_SORTING_MREASON
void Memory::name_fundamental_reasons(void) { Memory::reason_name(STREAM_MREASON, "text stream storage"); Memory::reason_name(FILENAME_STORAGE_MREASON, "filename/pathname storage"); Memory::reason_name(STRING_STORAGE_MREASON, "string storage"); Memory::reason_name(DICTIONARY_MREASON, "dictionary storage"); Memory::reason_name(CLS_SORTING_MREASON, "sorting"); }
The function Memory::name_fundamental_reasons is used in §5.
§23. And here is the (very simple) implementation.
char *memory_needs[NO_DEFINED_MREASON_VALUES]; void Memory::reason_name(int r, char *reason) { if ((r < 0) || (r >= NO_DEFINED_MREASON_VALUES)) internal_error("MR out of range"); memory_needs[r] = reason; } char *Memory::description_of_reason(int r) { if ((r < 0) || (r >= NO_DEFINED_MREASON_VALUES)) internal_error("MR out of range"); return memory_needs[r]; }
The function Memory::reason_name is used in §22.
The function Memory::description_of_reason is used in §26.1, §31.
§24. We keep some statistics on this. The value for "memory claimed" is the net amount of memory currently owned, which is increased when we allocate it and decreased when we free it. Whether the host OS is able to make efficient use of the memory we free, we can't know, but it probably is, and therefore the best estimate of how well we're doing is the "maximum memory claimed" — the highest recorded net usage count over the run.
int max_memory_at_once_for_each_need[NO_DEFINED_MREASON_VALUES], memory_claimed_for_each_need[NO_DEFINED_MREASON_VALUES], number_of_claims_for_each_need[NO_DEFINED_MREASON_VALUES]; int total_claimed_simply = 0;
§25. Our allocation routines behave just like the standard C library's malloc
and calloc
, but where a third argument supplies a reason why the memory is
needed, and where any failure to allocate memory is tidily dealt with. We will
exit on any such failure, so that the caller can be certain that the return
values of these functions are always non-NULL
pointers.
void *Memory::I7_calloc(int how_many, int size_in_bytes, int reason) { return Memory::I7_alloc(how_many, size_in_bytes, reason); } void *Memory::I7_malloc(int size_in_bytes, int reason) { return Memory::I7_alloc(-1, size_in_bytes, reason); }
The function Memory::I7_calloc is used in 2/str (§26), 2/dct (§2), 3/cla (§14).
The function Memory::I7_malloc is used in §29, 2/str (§35.3).
§26. And this, then, is the joint routine implementing both.
void *Memory::I7_alloc(int N, int S, int R) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); void *pointer; int bytes_needed; if ((R < 0) || (R >= NO_DEFINED_MREASON_VALUES)) internal_error("no such memory reason"); if (total_claimed_simply == 0) <Zero out the statistics on simple memory allocations 26.2>; <Claim the memory using malloc or calloc as appropriate 26.1>; <Update the statistics on simple memory allocations 26.3>; UNLOCK_MUTEX(mutex); return pointer; }
The function Memory::I7_alloc is used in §25.
§26.1. I am nervous about assuming that calloc(0, X)
returns a non-NULL
pointer
in all implementations of the standard C library, so the case when N
is zero
allocates a tiny but positive amount of memory, just to be safe.
<Claim the memory using malloc or calloc as appropriate 26.1> =
if (N > 0) { pointer = Memory::paranoid_calloc((size_t) N, (size_t) S); bytes_needed = N*S; } else { pointer = Memory::paranoid_calloc(1, (size_t) S); bytes_needed = S; } if (pointer == NULL) { Errors::fatal_with_C_string("Out of memory for %s", Memory::description_of_reason(R)); }
This code is used in §26.
§26.2. These statistics have no function except to improve the diagnostics in the
debugging log, but they are very cheap to keep, since Memory::I7_alloc
is called only
rarely and to allocate large blocks of memory.
<Zero out the statistics on simple memory allocations 26.2> =
int i; for (i=0; i<NO_DEFINED_MREASON_VALUES; i++) { max_memory_at_once_for_each_need[i] = 0; memory_claimed_for_each_need[i] = 0; number_of_claims_for_each_need[i] = 0; }
This code is used in §26.
§26.3.
<Update the statistics on simple memory allocations 26.3> =
memory_claimed_for_each_need[R] += bytes_needed; total_claimed_simply += bytes_needed; number_of_claims_for_each_need[R]++; if (memory_claimed_for_each_need[R] > max_memory_at_once_for_each_need[R]) max_memory_at_once_for_each_need[R] = memory_claimed_for_each_need[R];
This code is used in §26.
§27. We also provide our own wrapper for free
:
void Memory::I7_free(void *pointer, int R, int bytes_freed) { if ((R < 0) || (R >= NO_DEFINED_MREASON_VALUES)) internal_error("no such memory reason"); if (pointer == NULL) internal_error("can't free NULL memory"); CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); memory_claimed_for_each_need[R] -= bytes_freed; free(pointer); UNLOCK_MUTEX(mutex); } void Memory::I7_array_free(void *pointer, int R, int num_cells, size_t cell_size) { Memory::I7_free(pointer, R, num_cells*((int) cell_size)); }
The function Memory::I7_free is used in §30, 2/str (§34.2), 2/dct (§7.2, §11), 3/cla (§14).
The function Memory::I7_array_free appears nowhere else.
§28. Text storage. We will also use much simpler memory areas for text, in 64K chunks:
define SSA_CAPACITY 64*1024
typedef struct string_storage_area { char *storage_at; int first_free_byte; MEMORY_MANAGEMENT } string_storage_area; string_storage_area *current_ssa = NULL;
The structure string_storage_area is private to this section.
§29. The following is ideal for parking a read-only string of text somewhere safe in memory. Since the length can't be extended, it's usually unsafe to write to the result.
char *Memory::new_string(char *from) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); int length_needed = (int) strlen(from) + 1; if (!((current_ssa) && (current_ssa->first_free_byte + length_needed < SSA_CAPACITY))) { current_ssa = CREATE(string_storage_area); current_ssa->storage_at = Memory::I7_malloc(SSA_CAPACITY, STRING_STORAGE_MREASON); current_ssa->first_free_byte = 0; } char *rp = current_ssa->storage_at + current_ssa->first_free_byte; current_ssa->first_free_byte += length_needed; strcpy(rp, from); UNLOCK_MUTEX(mutex); return rp; }
The function Memory::new_string appears nowhere else.
§30. And here we free any SSAs needed in the course of the run.
void Memory::free_ssas(void) { string_storage_area *ssa; LOOP_OVER(ssa, string_storage_area) Memory::I7_free(ssa->storage_at, STRING_STORAGE_MREASON, SSA_CAPACITY); }
The function Memory::free_ssas is used in §12.
§31. And the following provides statistics, and a mini-report, for the memory report in the debugging log (for which, see below).
int Memory::log_usage(int total) { if (total_claimed_simply == 0) return 0; int i, t = 0; for (i=0; i<NO_DEFINED_MREASON_VALUES; i++) { t += max_memory_at_once_for_each_need[i]; if (total > 0) LOG("0.%03d: %s - %d bytes in %d claim(s)\n", Memory::proportion(max_memory_at_once_for_each_need[i], total), Memory::description_of_reason(i), max_memory_at_once_for_each_need[i], number_of_claims_for_each_need[i]); } return t; }
The function Memory::log_usage is used in §32, §32.3.
§32. Memory usage report. A small utility routine to help keep track of our unquestioned profligacy.
void Memory::log_statistics(void) {
int total_for_objects = MEMORY_GRANULARITY*no_blocks_allocated; usage in bytes
int total_for_SMAs = Memory::log_usage(0); usage in bytes
int sorted_usage[NO_DEFINED_MT_VALUES]; memory type numbers, in usage order
int total = (total_for_objects + total_for_SMAs)/1024; total memory usage in KB
<Sort the table of memory type usages into decreasing size order 32.2>;
int total_for_objects_used = 0; out of the total_for_objects
, the bytes used
int total_objects = 0;
<Calculate the memory usage for objects 32.1>;
int overhead_for_objects = total_for_objects - total_for_objects_used; bytes wasted
<Print the report to the debugging log 32.3>;
}
The function Memory::log_statistics is used in 1/fm (§9).
§32.1.
<Calculate the memory usage for objects 32.1> =
int i, j; for (j=0; j<NO_DEFINED_MT_VALUES; j++) { i = sorted_usage[j]; if (alloc_status[i].objects_allocated != 0) { if (alloc_status[i].no_allocated_together == 1) total_objects += alloc_status[i].objects_allocated; else total_objects += alloc_status[i].objects_allocated* alloc_status[i].no_allocated_together; total_for_objects_used += alloc_status[i].bytes_allocated; } }
This code is used in §32.
§32.2. This is the criterion for sorting memory types in the report: descending order of total number of bytes allocated.
<Sort the table of memory type usages into decreasing size order 32.2> =
int i; for (i=0; i<NO_DEFINED_MT_VALUES; i++) sorted_usage[i] = i; qsort(sorted_usage, (size_t) NO_DEFINED_MT_VALUES, sizeof(int), Memory::compare_usage);
This code is used in §32.
§32.3. And here is the actual report:
<Print the report to the debugging log 32.3> =
LOG("\nReport by memory manager:\n\n"); LOG("Total consumption was %dK = %dMB, divided up in the following proportions:\n", total, (total+512)/1024); LOG("0.%03d: %d objects in %d frames in %d memory blocks (of %dK each):\n", Memory::proportion(total_for_objects, total), total_objects, total_objects_allocated, no_blocks_allocated, MEMORY_GRANULARITY/1024); LOG(" 0.%03d: memory manager overhead - %d bytes\n", Memory::proportion(overhead_for_objects, total), overhead_for_objects); int i, j; for (j=0; j<NO_DEFINED_MT_VALUES; j++) { i = sorted_usage[j]; if (alloc_status[i].objects_allocated != 0) { LOG(" 0.%03d: %s - ", Memory::proportion(alloc_status[i].bytes_allocated, total), alloc_status[i].name_of_type); if (alloc_status[i].no_allocated_together == 1) { LOG("%d ", alloc_status[i].objects_count); if (alloc_status[i].objects_count != alloc_status[i].objects_allocated) LOG("(+%d deleted) ", alloc_status[i].objects_allocated - alloc_status[i].objects_count); } else LOG("%d blocks of %d = %d ", alloc_status[i].objects_allocated, alloc_status[i].no_allocated_together, alloc_status[i].objects_allocated*alloc_status[i].no_allocated_together); LOG("objects, %d bytes\n", alloc_status[i].bytes_allocated); } } Memory::log_usage(total);
This code is used in §32.
int Memory::compare_usage(const void *ent1, const void *ent2) { int ix1 = *((const int *) ent1); int ix2 = *((const int *) ent2); return alloc_status[ix2].bytes_allocated - alloc_status[ix1].bytes_allocated; }
The function Memory::compare_usage is used in §32.2.
§34. Finally, a little routine to compute the proportions of memory for each
usage. Recall that bytes
is measured in bytes, but total
in kilobytes.
int Memory::proportion(int bytes, int total) { float B = (float) bytes, T = (float) total; float P = (1000*B)/(1024*T); return (int) P; }
The function Memory::proportion is used in §31, §32.3.
void *Memory::paranoid_calloc(size_t N, size_t S) { CREATE_MUTEX(mutex); LOCK_MUTEX(mutex); void *P = calloc(N, S); UNLOCK_MUTEX(mutex); return P; }
The function Memory::paranoid_calloc is used in §11.1, §26.1.
§36. Run-time pointer type checking. In several places Inform needs to store pointers of type void *
, that is,
pointers which have no indication of what type of data they point to.
This is not type-safe and therefore offers plenty of opportunity for
blunders. The following provides run-time type checking to ensure that
each time we dereference a typeless pointer, it does indeed point to
a structure of the type we think it should.
The structure general_pointer
holds a void *
pointer to any one of the
following:
- (a)
NULL
, to which we assign ID number -1; - (b)
char
, to which we assign ID number 1000; - (c) any individually allocated structure of the types listed above, to
which we assign the ID numbers used above: for instance,
blorb_figure_MT
is the ID number for ageneral_pointer
which points to ablorb_figure
structure.
define NULL_GENERAL_POINTER (Memory::store_gp_null()) define GENERAL_POINTER_IS_NULL(gp) (Memory::test_gp_null(gp))
typedef struct general_pointer {
void *pointer_to_data;
int run_time_type_code;
} general_pointer;
general_pointer Memory::store_gp_null(void) {
general_pointer gp;
gp.pointer_to_data = NULL;
gp.run_time_type_code = -1; guaranteed to differ from all _MT
values
return gp;
}
int Memory::test_gp_null(general_pointer gp) {
if (gp.run_time_type_code == -1) return TRUE;
return FALSE;
}
The function Memory::store_gp_null appears nowhere else.
The function Memory::test_gp_null appears nowhere else.
The structure general_pointer is private to this section.
§37. The symbols tables need to look at pointer values directly without knowing their types, but only to test equality, so we abstract that thus. And the debugging log also shows actual hexadecimal addresses to distinguish nameless objects and to help with interpreting output from GDB, so we abstract that too.
define COMPARE_GENERAL_POINTERS(gp1, gp2) (gp1.pointer_to_data == gp2.pointer_to_data) define GENERAL_POINTER_AS_INT(gp) ((pointer_sized_int) gp.pointer_to_data)
§38. If we have a pointer to circus
(say) then g=STORE_POINTER_circus(p)
returns a general_pointer
with p
as the actual pointer, but will not
compile unless p
is indeed of type circus *
. When we later
RETRIEVE_POINTER_circus(g)
, an internal error is thrown if g
contains a pointer
which is other than void *
, or which has never been referenced.
define MAKE_REFERENCE_ROUTINES(type_name, id_code) general_pointer STORE_POINTER_##type_name(type_name *data) { general_pointer gp; gp.pointer_to_data = (void *) data; gp.run_time_type_code = id_code; return gp; } type_name *RETRIEVE_POINTER_##type_name(general_pointer gp) { if (gp.run_time_type_code != id_code) { LOG("Wanted ID code %d, found %d\n", id_code, gp.run_time_type_code); internal_error("attempt to retrieve wrong pointer type as " #type_name); } return (type_name *) gp.pointer_to_data; } general_pointer PASS_POINTER_##type_name(general_pointer gp) { if (gp.run_time_type_code != id_code) { LOG("Wanted ID code %d, found %d\n", id_code, gp.run_time_type_code); internal_error("attempt to pass wrong pointer type as " #type_name); } return gp; } int VALID_POINTER_##type_name(general_pointer gp) { if (gp.run_time_type_code == id_code) return TRUE; return FALSE; }
§39. Suitable MAKE_REFERENCE_ROUTINES
were expanded for all of the memory
allocated objects above; so that leaves only humble char *
pointers:
MAKE_REFERENCE_ROUTINES(char, 1000)