Memory Layout & Management ========================== In order to allow garbage collection all variables, global, local or arguments on stack, must be discoverable. Therefore all strings, arrays and objects must be stored in special blocks. The interpreter memory may be allocated as one block at the end of the system heap (if possible). All addressing is relocatable. The dictionary and names[] can be purged to free up all available memory for the program. After that compiling new words is no longer possible. stacks & vars: | ptr stack_end; | ↑ | ptr ssp; // string stack pointer | ↑ // local variables and arguments | int num_gsvars; // num global string variables | ↑ // global string variables | ptr gvars; // base for gvar addressing | ↓ // global normal variables | int num_gvars; // num global normal variables | ↓ // local variables and arguments | ptr sp; // stack pointer | ↓ | ptr stack_base; WRONG: | ptr heap_end; | ↓ // used and formerly used chunks | ptr heap_top; // allocation pointer | ↓ | ptr heap_base; | ptr code_end; | ↑ | ptr code_here; // during compilation | ptr code_top; // end of compiled words | ↑ // executable words | ptr code_base; If a block is resized it is most times enough to move the data and update the pointers, except for pointers to allocated data which must be fully walked and updated. Entries on the heap: All data on the heap is aligned to 'stack_alignment'. Strings: are stored as char[], no mandatory terminating 0-byte. Arrays: one chunk for the top dimension additional dimensions are stored as nested array ptr to subarray may be nullptr for "all zero" - ati, atiget and atiset must test for nullptr, ati and atiset must allocate the subarray arrays are expected to contain only data of one type, though objects might be subclassed Objects: one object per chunk subtype byte = enum for the object type to find the layout description needed by compact() Chunk header: byte type byte subtype uint16 count byte data[count * element_size] byte padding[0|1] on 64bit systems (or optionally 32bit too) the header may be double size: uint16 type uint16 subtype uint32 count byte data[count * element_size] byte padding[0…3] Type: bit7 = g = the gc_bit, used by compact() to find unused chunks. bit6 = ? = bit5 = a = set for array bit4:2 = t = the type, based on the size bit1:0 = s = log2 of the element size ((TODO: maybe entries may be larger)) %g?atttss %00000000 void (size bits invalid) %000001xx int 1,2,4,8 bytes, int, enum and char %0000101x float 4,8 %00010010 object 4/8 byte ptr %00100100 string array of char Garbage collection: compact() must find all used objects in the heap. for that it must reach all variables, global and local. when compact() is run no heap variable must be held in a register or obscure position. --> use of 2 stacks: - 'normal' stack extends from 'gvars' down to 'sp' - 'string' stack extends from 'gvars' up to 'ssp' - all allocated data must be stored on the string stack - entries on the string stack must point to the start of the array/object - entries on the string stack must not point to arbitrary positions inside these objects/arrays! - entries on the string stack may point to arrays/objects outside the heap. objects/array elements in arrays are found recursively objects/array data members of objects are found by use of the subtype byte. pass 1: set bit7 'g' in the type bytes of all chunks in the heap to '1' to indicate "unused". pass 2: walk all variables and reset bit7. pass 3: find the first 8 (16? 32?) consecutive gaps with unused chunks and construct a table pass 4: walk all variables and adjust the pointers acc. to table for the forthcoming move pass 5: move the first 8 (16? 32?) consecutive blocks of used chunks up to close the gaps return a flag whether there are more gaps which may be closed or whether this is final.