libinterp/heap.c
, and the structures in include/interp.h
.
n
bytes in heap, and then free them, you need to do the following:
Heap *h; uchar *data; h = nheap(256); data = H2D(uchar*, h); ... // data 256 destroy(data);
Heap
structure, plus there are two macros for converting a pointer to a heap header into a pointer to data (moreover, for convenience, immediately with a type H2D()
) H2D()
and vice versa D2H()
. The destroy()
function uses D2H()
to get a heap header instead of a pointer to the beginning of user data of unknown length and find out how many bytes need to be freed.
struct Heap { int color; /* Allocation color */ ulong ref; Type* t; ulong hprof; /* heap profiling */ }; #define H2D(t, x) ((t)(((uchar*)(x))+sizeof(Heap))) #define D2H(x) ((Heap*)(((uchar*)(x))-sizeof(Heap)))
hprof
, I didn't understand heap profiling, but the rest of the fields are extremely important.
ref
is the reference counter, and color
used for tri-color marking. When nheap()
or another similar function allocates a new object to the heap, the ref
value is set to 1 in its heap header. When destroy()
is called, it decreases the ref
value by 1, and only if ref
became equal to 0, it frees up the object occupied memory.
nheap()
(or any other similar function) in one variable, you have exactly one reference to this object, and its ref
is 1. As soon as you copy this reference to another variable, you need to increase the reference count plus notify the tri-color algorithm. It is done this way ( Setmark()
is also a macro, but in order to deal with it you need to understand the work of the tri-color algorithm, which is not required from you right now):
Heap *h; uchar *data, *data2; data = H2D(uchar*, nheap(256)); data2 = data; h = D2H(data2); h->ref++; Setmark(h); // h->color destroy(data); // destroy(data2); //
*f->ret
, then nothing else needs to be done with ref
and color
- the link will be removed from the local variables of your function when the function ends, and only one will be left again the reference to this object is for the user, in the variable where he saved the value returned by your function, i.e. There was a move, not a copy of the link.
Setmark()
(this is also related to the peculiarities of this algorithm and will be described below):
dst->data = src->data; src->data = H; Setmark(D2H(dst->data));
nheap()
described above is almost never used in real code, since Limbo does not have this data type: n bytes. Therefore, neither get from Limbo nor return to Limbo the object selected through nheap()
will fail. And to allocate memory for the internal needs of your C-module, as a rule, there are usually enough malloc()
with free()
.
Type
structure. This allows you to solve the problem of automatic detection of all links within any object - what needs to be done when:
H
aka nil
);destroy()
(so that when deleting an object from memory with it, delete or reduce the ref
of all objects it referred to); struct Type { int ref; void (*free)(Heap*, int); void (*mark)(Type*, void*); int size; int np; void* destroy; void* initialize; uchar map[STRUCTALIGN]; };
destroy()
- like String*
and Array*
), the np
and map
fields are used. The map
field contains a bit mask, one bit each (starting from the high bit of the first byte) for every 4 bytes of adt / tuple / structure, where the set bit means that the corresponding 4 bytes are a pointer to a heap object. (The np
field contains the length of the map
field in bytes.)
String
and Array
- the first contains inside the char*
field, which must be freed via free()
; the second can be a slice and contain within itself a link to the "parent" array. The Type
structure allows you to specify your own non-standard handler functions for these types, which will be called when allocating / initializing memory from destroy()
and from the garbage collector: free
, mark
, destroy
and initialize
.
size
field contains the size of a structure of this type (since we still have to specify its type when allocating memory for this structure, preserving the size of the structure inside the type allows us to limit ourselves to specifying only the type, without adding the size of the structure to it each time).
ref
field is used to store the current number of objects in this type of heap. The fact is that the list of types is not limited to standard string
, array of
, etc. - any tuple described on the fly is a separate type for which you need your description by the Type
structure. It turns out that some types are created by Limbo on the fly, stored in the same heap, and must be removed from memory as soon as all objects of this type are deleted. Therefore, when creating a new object of a certain type, you need to increase the ref
this type, and when you delete this object, destroy()
will automatically reduce the ref
also for the type of this object (and remove it from memory if ref
equals 0).
Type
values ​​for standard types are declared statically, with ref
set to 1 (so their ref
will never be less than 1 and they will never be removed from memory), and are described at the beginning of libinterp/head.c
:
Type Tbyte = { 1, 0, 0, 1 }; Type Tword = { 1, 0, 0, sizeof(WORD) }; Type Tptr = { 1, 0, markheap, sizeof(WORD*), 1, 0, 0, { 0x80 } }; Type Tarray = { 1, freearray, markarray, sizeof(Array) }; Type Tstring = { 1, freestring, noptrs, sizeof(String) }; ...
libinterp/runt.h
(by calculating the size of your structure for Type.size
and the bit mask of pointer fields for Type.map
and Type.np
), in others you will have to define them independently (for example, to create and return a tuple from the C function). They are usually created during initialization of your module (returning to the example with the module Example) using the dtype()
function. And memory is allocated and initialized for them through heap(&Tsometype)
, and not nheap(n_bytes)
.
module/example.m
Example: module { ... MyData: adt{ i: int; s: string; new: fn(i: int): ref MyData; }; };
libinterp/runt.h
(automatically generated from module/example.m
)
typedef struct Example_MyData Example_MyData; struct Example_MyData { WORD i; String* s; }; #define Example_MyData_size 8 #define Example_MyData_map {0x40,} void MyData_new(void*); typedef struct F_MyData_new F_MyData_new; struct F_MyData_new { WORD regs[NREG-1]; Example_MyData** ret; uchar temps[12]; WORD i; };
libinterp/example.c
static Type* TMyData; static uchar MyData_map[] = Example_MyData_map; void examplemodinit(void) { ... TMyData = dtype(freeheap, Example_MyData_size, MyData_map, sizeof(MyData_map)); } void MyData_new(void *fp) { F_MyData_new *f; int i; Example_MyData* mydata; f = fp; i = f->i; mydata = H2D(Example_MyData*, heap(TMyData)); mydata->i = i; destroy(*f->ret); *f->ret = mydata; }
testexample.b
... example: Example; MyData: import example; ... init(nil: ref Draw->Context, nil: list of string) { ... mydata := MyData.new(5); sys->print("i = %d, s = %q\n", mydata.i, mydata.s); } ; testexample ... i = 5, s = '' ;
H
) and later to correctly remove from memory, you need to know the type of these elements (then there is no need to specify the size, it is already specified inside the type).
struct Array { WORD len; Type* t; Array* root; uchar* data; };
len
is the size of the array, t
is the type of its elements, root
pointer to the parent array (if this array is its slice), and data
pointer to the first element of the array (this is the next byte after the Array
structure if the array is independent or the address of the first element of our slice is among the elements parent array).
Array
structure itself (and, accordingly, as indicated in Tarray.size
). Therefore, we will not be able to allocate memory for the array through the heap()
function used previously. Fortunately, for this there is a convenient heaparray()
function. An example of selecting array[16] of byte
:
Array *arr; arr = H2D(Array*, heaparray(&Tbyte, 16));
a := array[10] of MyData; b := array[10] of ref MyData; for(i := 0; i < len b; i++) b[i] = ref MyData; a[0].i = b[0].i;
Array *a, *b; int i; Example_MyData* adata; Example_MyData** bdata; a = H2D(Array*, heaparray(TMyData, 10)); adata = (Example_MyData*)a->data; b = H2D(Array*, heaparray(&Tptr, 10)); bdata = (Example_MyData**)b->data; for(i = 0; i < b->len; i++) bdata[i] = H2D(Example_MyData*, heap(TMyData)); adata[0].i = bdata[0]->i;
mutator
, marker
and sweeper
.
h->color=mutator
.
mutator
, marker
and sweeper
variables change in a circle, thus changing the values ​​of h->color
all objects in heap:
mutator -> marker marker -> sweeper sweeper -> mutator // .. sweeper
h->color==sweeper
, then h
is removed from memory.
Setmark()
and why it is needed.
#define Setmark(h) if((h)->color!=mutator) { (h)->color = propagator; nprop=1; }
Setmark()
. The nprop
flag nprop
it, if necessary, indicates to the GC that after the current crawl cycle of objects in the heap cannot be deleted, unused objects must be repeated from the beginning.
h->color==propagator
means that the next time gc is run, it is necessary to view this object. In the process of viewing the object, h->color
set in mutator
. (The same propagator
value is set to “root” objects at the beginning of a new GC cycle, but in this context it is not important.)
ref
, the question arises: how to store a link to the heap object outside Dis threads, for example in the global variables of your C-module, so that it does not kill the GC? To do this, you must inform the GC that it should not monitor this heap object using poolimmutable()
(to connect an object back to the GC there is a poolmutable()
, all these functions are in emu/port/alloc.c
):
libinterp/example.c
... static Array* EmptyArray; void examplemodinit(void) { ... EmptyArray = H2D(Array*, heaparray(&Tbyte, 0)); poolimmutable(D2H(EmptyArray)); }
Source: https://habr.com/ru/post/118696/