| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | Copyright 2024, 2025 Joel Svensson svenssonjoel@yahoo.se | ||
| 3 | |||
| 4 | This program is free software: you can redistribute it and/or modify | ||
| 5 | it under the terms of the GNU General Public License as published by | ||
| 6 | the Free Software Foundation, either version 3 of the License, or | ||
| 7 | (at your option) any later version. | ||
| 8 | |||
| 9 | This program is distributed in the hope that it will be useful, | ||
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | GNU General Public License for more details. | ||
| 13 | |||
| 14 | You should have received a copy of the GNU General Public License | ||
| 15 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <lbm_defrag_mem.h> | ||
| 19 | #include <heap.h> | ||
| 20 | #include <lbm_memory.h> | ||
| 21 | |||
| 22 | 339446 | static inline lbm_uint bs2ws(lbm_uint bs) { | |
| 23 |
2/2✓ Branch 0 taken 326117 times.
✓ Branch 1 taken 13329 times.
|
339446 | return bs % sizeof(lbm_uint) == 0 ? bs / sizeof(lbm_uint) : (bs / sizeof(lbm_uint)) + 1; |
| 24 | } | ||
| 25 | |||
| 26 | #define DEFRAG_MEM_HEADER_SIZE 2 | ||
| 27 | #define DEFRAG_MEM_HEADER_BYTES (DEFRAG_MEM_HEADER_SIZE*sizeof(lbm_uint)) | ||
| 28 | #define DEFRAG_MEM_DATA(X) &(X)[DEFRAG_MEM_HEADER_SIZE]; | ||
| 29 | // length and flags. | ||
| 30 | // Currently only one flag that tells if we should do a compaction before allocation. | ||
| 31 | #define DEFRAG_MEM_SIZE(X) X[0] | ||
| 32 | #define DEFRAG_MEM_FLAGS(X) X[1] | ||
| 33 | |||
| 34 | // TODO: We can move the GC index to the end (or elsewhere) of an array to save space in these | ||
| 35 | // headers in the case of ByteArrays. | ||
| 36 | #define DEFRAG_ALLOC_SIZE(X) X[0] | ||
| 37 | #define DEFRAG_ALLOC_DATA(X) X[1] | ||
| 38 | //#define DEFRAG_ALLOC_INDEX(X) X[2] // GC index for traversal of high-level arrays | ||
| 39 | #define DEFRAG_ALLOC_CELLPTR(X) X[2] | ||
| 40 | #define DEFRAG_ALLOC_ARRAY_HEADER_SIZE 3 | ||
| 41 | |||
| 42 | 169 | lbm_value lbm_defrag_mem_create(lbm_uint nbytes) { | |
| 43 | 169 | lbm_value res = ENC_SYM_TERROR; | |
| 44 | 169 | lbm_uint nwords = bs2ws(nbytes); // multiple of 4. | |
| 45 |
1/2✓ Branch 0 taken 169 times.
✗ Branch 1 not taken.
|
169 | if (nwords > 0) { |
| 46 | 169 | res = ENC_SYM_MERROR; | |
| 47 | 169 | lbm_uint *data = (lbm_uint*)lbm_malloc(DEFRAG_MEM_HEADER_BYTES + nwords * sizeof(lbm_uint)); | |
| 48 |
1/2✓ Branch 0 taken 169 times.
✗ Branch 1 not taken.
|
169 | if (data) { |
| 49 | 169 | memset((uint8_t*)data , 0, DEFRAG_MEM_HEADER_BYTES + nwords*sizeof(lbm_uint)); | |
| 50 | 169 | data[0] = nwords; | |
| 51 | 169 | data[1] = 0; //flags | |
| 52 | 169 | lbm_value cell = lbm_heap_allocate_cell(LBM_TYPE_DEFRAG_MEM, (lbm_uint)data, ENC_SYM_DEFRAG_MEM_TYPE); | |
| 53 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 169 times.
|
169 | if (cell == ENC_SYM_MERROR) { |
| 54 | ✗ | lbm_free(data); | |
| 55 | } else { | ||
| 56 | 169 | res = cell; | |
| 57 | } | ||
| 58 | } | ||
| 59 | } | ||
| 60 | 169 | return res; | |
| 61 | } | ||
| 62 | |||
| 63 | 84 | static void free_defrag_allocation(lbm_uint *allocation) { | |
| 64 | 84 | lbm_uint size = DEFRAG_ALLOC_SIZE(allocation); // array allocation is size in bytes | |
| 65 | // a defrag-mem allocation is always bigger than 0 | ||
| 66 | 84 | lbm_uint nwords = bs2ws(size) + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 67 | 84 | lbm_value cell_back_ptr = DEFRAG_ALLOC_CELLPTR(allocation); | |
| 68 | |||
| 69 | // I have a feeling that it should be impossible for the | ||
| 70 | // cell to be recovered if we end up here. | ||
| 71 | // if the cell is recovered, then the data should also have been | ||
| 72 | // cleared in the defrag_mem. | ||
| 73 | |||
| 74 | 84 | cell_back_ptr = lbm_set_ptr_type(cell_back_ptr, LBM_TYPE_CONS); | |
| 75 | 84 | bool marked = lbm_cdr(cell_back_ptr) & LBM_GC_MASK; | |
| 76 |
1/2✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
|
84 | lbm_value new_cdr = marked ? (ENC_SYM_NIL | LBM_GC_MARKED) : ENC_SYM_NIL; |
| 77 | 84 | lbm_set_car_and_cdr(cell_back_ptr, ENC_SYM_NIL, new_cdr); | |
| 78 | // possible optimize, if not marked. dont bother setting anything. | ||
| 79 | |||
| 80 |
2/2✓ Branch 0 taken 504 times.
✓ Branch 1 taken 84 times.
|
588 | for (lbm_uint i = 0; i < nwords; i ++) { |
| 81 | 504 | allocation[i] = 0; | |
| 82 | } | ||
| 83 | 84 | } | |
| 84 | |||
| 85 | // Called by GC. | ||
| 86 | // As it is called by GC, gc bits may be set and needs to be | ||
| 87 | // honored! | ||
| 88 | 28 | void lbm_defrag_mem_destroy(lbm_uint *defrag_mem) { | |
| 89 | 28 | lbm_uint nwords = DEFRAG_MEM_SIZE(defrag_mem); | |
| 90 | 28 | lbm_uint *defrag_data = DEFRAG_MEM_DATA(defrag_mem); | |
| 91 |
2/2✓ Branch 0 taken 280 times.
✓ Branch 1 taken 28 times.
|
308 | for (lbm_uint i = 0; i < nwords;) { |
| 92 | 280 | lbm_uint a = defrag_data[i]; | |
| 93 |
2/2✓ Branch 0 taken 84 times.
✓ Branch 1 taken 196 times.
|
280 | if (a != 0) { |
| 94 | 84 | lbm_uint *allocation = &defrag_data[i]; | |
| 95 | 84 | lbm_uint alloc_words = | |
| 96 | DEFRAG_ALLOC_ARRAY_HEADER_SIZE + | ||
| 97 | 84 | bs2ws(DEFRAG_ALLOC_SIZE(allocation)); | |
| 98 | 84 | free_defrag_allocation(allocation); | |
| 99 | 84 | i += alloc_words; | |
| 100 | } | ||
| 101 | 196 | else i ++; | |
| 102 | } | ||
| 103 | 28 | lbm_free(defrag_mem); | |
| 104 | 28 | } | |
| 105 | |||
| 106 | |||
| 107 | 28588 | static void lbm_defrag_mem_defrag(lbm_uint *defrag_mem, lbm_uint bytes) { | |
| 108 | 28588 | lbm_uint mem_size = ((lbm_uint*)defrag_mem)[0]; // mem size words | |
| 109 | 28588 | lbm_uint *mem_data = DEFRAG_MEM_DATA(defrag_mem); | |
| 110 | 28588 | lbm_uint hole_start = 0; | |
| 111 | |||
| 112 | 28588 | lbm_uint until_size = DEFRAG_ALLOC_ARRAY_HEADER_SIZE + bs2ws(bytes); // defrag until hole is this size or complete defrag. | |
| 113 | |||
| 114 |
2/2✓ Branch 0 taken 230692 times.
✓ Branch 1 taken 28560 times.
|
259252 | for (lbm_uint i = 0; i < mem_size; ) { |
| 115 | // check if there is an allocation here | ||
| 116 |
2/2✓ Branch 0 taken 58772 times.
✓ Branch 1 taken 171920 times.
|
230692 | if (mem_data[i] != 0) { |
| 117 | 58772 | lbm_uint *source = &mem_data[i]; | |
| 118 | 58772 | lbm_uint *target = &mem_data[hole_start]; | |
| 119 | 58772 | lbm_uint alloc_bytes = DEFRAG_ALLOC_SIZE(source); | |
| 120 | 58772 | lbm_uint alloc_words = bs2ws(alloc_bytes); | |
| 121 | // move allocation into hole | ||
| 122 |
2/2✓ Branch 0 taken 58548 times.
✓ Branch 1 taken 224 times.
|
58772 | if (hole_start == i) { |
| 123 | 58548 | i += alloc_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 124 | 58548 | hole_start = i; | |
| 125 | } else { | ||
| 126 | 224 | lbm_uint move_dist = i - hole_start; | |
| 127 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 196 times.
|
224 | if (move_dist >= until_size) break; |
| 128 | 196 | lbm_uint clear_ix = (hole_start + alloc_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE); | |
| 129 | 196 | memmove(target, source, (alloc_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE) * sizeof(lbm_uint)); | |
| 130 | 196 | memset(&mem_data[clear_ix],0, move_dist* sizeof(lbm_uint)); | |
| 131 | 196 | DEFRAG_ALLOC_DATA(target) = (lbm_uint)&target[DEFRAG_ALLOC_ARRAY_HEADER_SIZE]; | |
| 132 | 196 | lbm_value cell = DEFRAG_ALLOC_CELLPTR(target); | |
| 133 | |||
| 134 | 196 | lbm_set_car(cell,(lbm_uint)target); | |
| 135 | // move home and i forwards. | ||
| 136 | // i can move to the original end of allocation. | ||
| 137 | 196 | hole_start += alloc_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 138 | 196 | i += alloc_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 139 | } | ||
| 140 | } else { | ||
| 141 | // no allocation hole remains but i increments. | ||
| 142 | 171920 | i ++; | |
| 143 | } | ||
| 144 | } | ||
| 145 | 28588 | } | |
| 146 | |||
| 147 | // Allocate an array from the defragable pool | ||
| 148 | // these arrays must be recognizable by GC so that | ||
| 149 | // gc can free them by performing a call into the defrag_mem api. | ||
| 150 | // At the same time they need to be just bytearrays.. | ||
| 151 | // Allocation must always scan from start. | ||
| 152 | // | ||
| 153 | |||
| 154 | #define INIT 0 | ||
| 155 | #define FREE_LEN 1 | ||
| 156 | |||
| 157 | // An array allocated in defragmem has the following layout inside of the defrag mem | ||
| 158 | // | ||
| 159 | // [header (size, data-ptr) cell_back_ptr | data | padding ] | ||
| 160 | |||
| 161 | 86773 | lbm_value lbm_defrag_mem_alloc_internal(lbm_uint *defrag_mem, lbm_uint bytes, lbm_uint type) { | |
| 162 | |||
| 163 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 86773 times.
|
86773 | if (bytes == 0) return ENC_SYM_EERROR; |
| 164 | 86773 | lbm_value cell = lbm_heap_allocate_cell(LBM_TYPE_CONS, ENC_SYM_NIL, type); | |
| 165 | |||
| 166 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 86773 times.
|
86773 | if (cell == ENC_SYM_MERROR) { |
| 167 | ✗ | return cell; | |
| 168 | } | ||
| 169 | |||
| 170 | 86773 | lbm_uint mem_size = DEFRAG_MEM_SIZE(defrag_mem); | |
| 171 | 86773 | lbm_uint *mem_data = DEFRAG_MEM_DATA(defrag_mem); | |
| 172 | |||
| 173 | 86773 | lbm_uint num_words = bs2ws(bytes); | |
| 174 | 86773 | lbm_uint alloc_words = num_words + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 175 | |||
| 176 | 86773 | uint8_t state = INIT; | |
| 177 | 86773 | lbm_uint free_words = 0; | |
| 178 | 86773 | lbm_uint free_start = 0; | |
| 179 | 86773 | bool alloc_found = false; | |
| 180 | 86773 | lbm_value res = ENC_SYM_MERROR; | |
| 181 | |||
| 182 |
2/2✓ Branch 0 taken 856491 times.
✓ Branch 1 taken 57120 times.
|
913611 | for (lbm_uint i = 0; i < mem_size;) { |
| 183 |
2/3✓ Branch 0 taken 222741 times.
✓ Branch 1 taken 633750 times.
✗ Branch 2 not taken.
|
856491 | switch(state) { |
| 184 | 222741 | case INIT: | |
| 185 |
2/2✓ Branch 0 taken 86773 times.
✓ Branch 1 taken 135968 times.
|
222741 | if (mem_data[i] == 0) { |
| 186 | 86773 | free_start = i; | |
| 187 | 86773 | free_words = 1; | |
| 188 | 86773 | state = FREE_LEN; | |
| 189 | 86773 | i++; | |
| 190 | } else { | ||
| 191 | // jump to next spot | ||
| 192 | 135968 | i += bs2ws(mem_data[i]) + DEFRAG_ALLOC_ARRAY_HEADER_SIZE; | |
| 193 | } | ||
| 194 | 222741 | break; | |
| 195 | 633750 | case FREE_LEN: | |
| 196 |
2/2✓ Branch 0 taken 633666 times.
✓ Branch 1 taken 84 times.
|
633750 | if (mem_data[i] == 0) { |
| 197 | 633666 | free_words ++; | |
| 198 |
2/2✓ Branch 0 taken 29653 times.
✓ Branch 1 taken 604013 times.
|
633666 | if (free_words >= alloc_words) { |
| 199 | 29653 | alloc_found = true; | |
| 200 | } else { | ||
| 201 | 604013 | i ++; | |
| 202 | } | ||
| 203 | } else { | ||
| 204 | 84 | state = INIT; | |
| 205 | 84 | i++; | |
| 206 | } | ||
| 207 | 633750 | break; | |
| 208 | } | ||
| 209 |
2/2✓ Branch 0 taken 29653 times.
✓ Branch 1 taken 826838 times.
|
856491 | if (alloc_found) break; |
| 210 | } | ||
| 211 |
2/2✓ Branch 0 taken 29653 times.
✓ Branch 1 taken 57120 times.
|
86773 | if (alloc_found) { |
| 212 | 29653 | lbm_uint *allocation = (lbm_uint*)&mem_data[free_start]; | |
| 213 | 29653 | DEFRAG_ALLOC_SIZE(allocation) = bytes; | |
| 214 | 29653 | DEFRAG_ALLOC_DATA(allocation) = (lbm_uint)&allocation[DEFRAG_ALLOC_ARRAY_HEADER_SIZE]; //data starts after back_ptr | |
| 215 | 29653 | DEFRAG_ALLOC_CELLPTR(allocation) = cell; | |
| 216 | 29653 | lbm_set_car(cell, (lbm_uint)allocation); | |
| 217 | 29653 | res = cell; | |
| 218 | } else { | ||
| 219 | 57120 | DEFRAG_MEM_FLAGS(defrag_mem) = 1; | |
| 220 | 57120 | lbm_set_car_and_cdr(cell, ENC_SYM_NIL, ENC_SYM_NIL); | |
| 221 | } | ||
| 222 | 86773 | return res; | |
| 223 | } | ||
| 224 | |||
| 225 | 58185 | lbm_value lbm_defrag_mem_alloc(lbm_uint *defrag_mem, lbm_uint bytes) { | |
| 226 | |||
| 227 | 58185 | lbm_value res = lbm_defrag_mem_alloc_internal(defrag_mem, bytes, ENC_SYM_DEFRAG_ARRAY_TYPE); // Try to allocate | |
| 228 |
2/2✓ Branch 0 taken 28588 times.
✓ Branch 1 taken 29597 times.
|
58185 | if (lbm_is_symbol_merror(res)) { |
| 229 |
1/2✓ Branch 0 taken 28588 times.
✗ Branch 1 not taken.
|
28588 | if (DEFRAG_MEM_FLAGS(defrag_mem)) { //if we already performed GC, then also defrag |
| 230 | 28588 | lbm_defrag_mem_defrag(defrag_mem, bytes); | |
| 231 | 28588 | res = lbm_defrag_mem_alloc_internal(defrag_mem, bytes, ENC_SYM_DEFRAG_ARRAY_TYPE); // then try again | |
| 232 | 28588 | DEFRAG_MEM_FLAGS(defrag_mem) = 0; | |
| 233 | } | ||
| 234 | } | ||
| 235 |
2/2✓ Branch 0 taken 29653 times.
✓ Branch 1 taken 28532 times.
|
58185 | if (!lbm_is_symbol_merror(res)) { |
| 236 | 29653 | res = lbm_set_ptr_type(res, LBM_TYPE_ARRAY); | |
| 237 | } | ||
| 238 | 58185 | return res; | |
| 239 | } | ||
| 240 | |||
| 241 | // Allocate a high-level array from DM area | ||
| 242 | /* lbm_value lbm_defrag_mem_alloc_lisparray(lbm_uint *defrag_mem, lbm_uint elts) { */ | ||
| 243 | |||
| 244 | /* size_t bytes = elts * sizeof(lbm_uint); */ | ||
| 245 | /* lbm_value res = lbm_defrag_mem_alloc_internal(defrag_mem, bytes, ENC_SYM_DEFRAG_LISPARRAY_TYPE); // Try to allocate */ | ||
| 246 | /* if (lbm_is_symbol_merror(res)) { */ | ||
| 247 | /* if (DEFRAG_MEM_FLAGS(defrag_mem)) { //if we already performed GC, then also defrag */ | ||
| 248 | /* lbm_defrag_mem_defrag(defrag_mem, bytes); */ | ||
| 249 | /* res = lbm_defrag_mem_alloc_internal(defrag_mem, bytes, ENC_SYM_DEFRAG_LISPARRAY_TYPE); // then try again */ | ||
| 250 | /* DEFRAG_MEM_FLAGS(defrag_mem) = 0; */ | ||
| 251 | /* } */ | ||
| 252 | /* } */ | ||
| 253 | /* if (!lbm_is_symbol_merror(res)) { */ | ||
| 254 | /* res = lbm_set_ptr_type(res, LBM_TYPE_LISPARRAY); */ | ||
| 255 | /* } */ | ||
| 256 | /* return res; */ | ||
| 257 | /* } */ | ||
| 258 | |||
| 259 | |||
| 260 | |||
| 261 | // IF GC frees a defrag allocation, the cell pointing to it will also be destroyed by GC. | ||
| 262 | |||
| 263 | // is it guaranteed there are no copies of a heap-cell representing a defrag allocation. | ||
| 264 | // Same guarantee must hold for arrays in general or it would not be possible to explicitly free | ||
| 265 | // any array. | ||
| 266 | |||
| 267 | // At the time of free from GC all we have is the pointer. | ||
| 268 | 29008 | void lbm_defrag_mem_free(lbm_uint* data) { | |
| 269 | 29008 | lbm_uint nbytes = data[0]; | |
| 270 | 29008 | lbm_uint words_to_wipe = DEFRAG_ALLOC_ARRAY_HEADER_SIZE + bs2ws(nbytes); | |
| 271 |
2/2✓ Branch 0 taken 370076 times.
✓ Branch 1 taken 29008 times.
|
399084 | for (lbm_uint i = 0; i < words_to_wipe; i ++) { |
| 272 | 370076 | data[i] = 0; | |
| 273 | } | ||
| 274 | 29008 | } | |
| 275 | |||
| 276 |