gc.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // MIT License
  2. // Copyright (c) 2019 Marc Kirchner
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. // The above copyright notice and this permission notice shall be included in all
  10. // copies or substantial portions of the Software.
  11. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  17. // SOFTWARE.
  18. // https://github.com/mkirchner/gc
  19. #include "gc.h"
  20. #include <setjmp.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. // #include <iron_system.h>
  24. #define PTRSIZE sizeof(char *)
  25. #define GC_TAG_NONE 0x0
  26. #define GC_TAG_LEAF 0x1
  27. #define GC_TAG_MARK 0x2
  28. #define GC_TAG_ROOT 0x4
  29. #define GC_TAG_CUT 0x8
  30. #if defined(_MSC_VER) && !defined(__clang__)
  31. #define __builtin_frame_address(x) ((void)(x), _AddressOfReturnAddress())
  32. #endif
  33. typedef struct gc_allocation {
  34. void *ptr; // mem pointer
  35. size_t size; // allocated size in bytes
  36. int *array_length; // if this alloc is an array
  37. char tag; // the tag for mark-and-sweep
  38. char roots; // number of root users
  39. struct gc_allocation *next; // separate chaining
  40. struct gc_allocation *cut;
  41. } gc_allocation_t;
  42. typedef struct gc_allocation_map {
  43. size_t capacity;
  44. size_t min_capacity;
  45. double downsize_factor;
  46. double upsize_factor;
  47. double sweep_factor;
  48. size_t sweep_limit;
  49. size_t size;
  50. gc_allocation_t **allocs;
  51. } gc_allocation_map_t;
  52. typedef struct garbage_collector {
  53. struct gc_allocation_map *allocs; // allocation map
  54. // struct gc_allocation_map *roots; // root map
  55. bool paused; // (temporarily) switch gc on/off
  56. void *bos; // bottom of stack
  57. } garbage_collector_t;
  58. garbage_collector_t _gc;
  59. garbage_collector_t *gc = &_gc;
  60. static gc_allocation_t *gc_allocation_new(void *ptr, size_t size) {
  61. gc_allocation_t *a = (gc_allocation_t *)malloc(sizeof(gc_allocation_t));
  62. a->ptr = ptr;
  63. a->size = size;
  64. a->array_length = NULL;
  65. a->tag = GC_TAG_NONE;
  66. a->roots = 0;
  67. a->next = NULL;
  68. a->cut = NULL;
  69. return a;
  70. }
  71. static void gc_allocation_delete(gc_allocation_t *a) {
  72. free(a);
  73. }
  74. static gc_allocation_map_t *gc_allocation_map_new(size_t min_capacity, size_t capacity,
  75. double sweep_factor, double downsize_factor, double upsize_factor) {
  76. gc_allocation_map_t *am = (gc_allocation_map_t *)malloc(sizeof(gc_allocation_map_t));
  77. am->min_capacity = min_capacity;
  78. am->capacity = capacity;
  79. am->sweep_factor = sweep_factor;
  80. am->sweep_limit = (int)(sweep_factor * am->capacity);
  81. am->downsize_factor = downsize_factor;
  82. am->upsize_factor = upsize_factor;
  83. am->allocs = (gc_allocation_t **)calloc(am->capacity, sizeof(gc_allocation_t *));
  84. am->size = 0;
  85. return am;
  86. }
  87. static void gc_allocation_map_delete(gc_allocation_map_t *am) {
  88. // Iterate over the map
  89. gc_allocation_t *alloc;
  90. gc_allocation_t *tmp;
  91. for (size_t i = 0; i < am->capacity; ++i) {
  92. if ((alloc = am->allocs[i])) {
  93. // Make sure to follow the chain inside a bucket
  94. while (alloc) {
  95. tmp = alloc;
  96. alloc = alloc->next;
  97. // free the management structure
  98. gc_allocation_delete(tmp);
  99. }
  100. }
  101. }
  102. free(am->allocs);
  103. free(am);
  104. }
  105. static size_t gc_hash(void *ptr) {
  106. return ((uintptr_t)ptr) >> 3;
  107. }
  108. static void gc_allocation_map_resize(gc_allocation_map_t *am, size_t new_capacity) {
  109. if (new_capacity <= am->min_capacity) {
  110. return;
  111. }
  112. // Replaces the existing items array in the hash table
  113. // with a resized one and pushes items into the new, correct buckets
  114. gc_allocation_t **resized_allocs = calloc(new_capacity, sizeof(gc_allocation_t *));
  115. for (size_t i = 0; i < am->capacity; ++i) {
  116. gc_allocation_t *alloc = am->allocs[i];
  117. while (alloc) {
  118. gc_allocation_t *next_alloc = alloc->next;
  119. size_t new_index = gc_hash(alloc->ptr) & (new_capacity - 1);
  120. alloc->next = resized_allocs[new_index];
  121. resized_allocs[new_index] = alloc;
  122. alloc = next_alloc;
  123. }
  124. }
  125. free(am->allocs);
  126. am->capacity = new_capacity;
  127. am->allocs = resized_allocs;
  128. am->sweep_limit = am->size + am->sweep_factor * (am->capacity - am->size);
  129. }
  130. static bool gc_allocation_map_resize_to_fit(gc_allocation_map_t *am) {
  131. double load_factor = (double)am->size / (double)am->capacity;
  132. if (load_factor > am->upsize_factor) {
  133. gc_allocation_map_resize(am, am->capacity * 2);
  134. return true;
  135. }
  136. if (load_factor < am->downsize_factor) {
  137. gc_allocation_map_resize(am, am->capacity / 2);
  138. return true;
  139. }
  140. return false;
  141. }
  142. static gc_allocation_t *gc_allocation_map_get(gc_allocation_map_t *am, void *ptr) {
  143. size_t index = gc_hash(ptr) & (am->capacity - 1); // % am->capacity
  144. gc_allocation_t *cur = am->allocs[index];
  145. while(cur) {
  146. if (cur->ptr == ptr) {
  147. return cur;
  148. }
  149. cur = cur->next;
  150. }
  151. return NULL;
  152. }
  153. static gc_allocation_t *gc_allocation_map_put(gc_allocation_map_t *am, gc_allocation_t *alloc) {
  154. size_t index = gc_hash(alloc->ptr) & (am->capacity - 1);
  155. gc_allocation_t *cur = am->allocs[index];
  156. gc_allocation_t *prev = NULL;
  157. /* Upsert if ptr is already known */
  158. while (cur != NULL) {
  159. if (cur->ptr == alloc->ptr) {
  160. // found it
  161. alloc->next = cur->next;
  162. if (!prev) {
  163. // position 0
  164. am->allocs[index] = alloc;
  165. }
  166. else {
  167. // in the list
  168. prev->next = alloc;
  169. }
  170. gc_allocation_delete(cur);
  171. return alloc;
  172. }
  173. prev = cur;
  174. cur = cur->next;
  175. }
  176. /* Insert at the front of the separate chaining list */
  177. cur = am->allocs[index];
  178. alloc->next = cur;
  179. am->allocs[index] = alloc;
  180. am->size++;
  181. void *p = alloc->ptr;
  182. if (gc_allocation_map_resize_to_fit(am)) {
  183. alloc = gc_allocation_map_get(am, p);
  184. }
  185. return alloc;
  186. }
  187. static void gc_allocation_map_remove(gc_allocation_map_t *am, void *ptr, bool allow_resize) {
  188. // ignores unknown keys
  189. size_t index = gc_hash(ptr) & (am->capacity - 1);
  190. gc_allocation_t *cur = am->allocs[index];
  191. gc_allocation_t *prev = NULL;
  192. gc_allocation_t *next;
  193. while (cur != NULL) {
  194. next = cur->next;
  195. if (cur->ptr == ptr) {
  196. // found it
  197. if (!prev) {
  198. // first item in list
  199. am->allocs[index] = cur->next;
  200. }
  201. else {
  202. // not the first item in the list
  203. prev->next = cur->next;
  204. }
  205. gc_allocation_delete(cur);
  206. am->size--;
  207. }
  208. else {
  209. // move on
  210. prev = cur;
  211. }
  212. cur = next;
  213. }
  214. if (allow_resize) {
  215. gc_allocation_map_resize_to_fit(am);
  216. }
  217. }
  218. static void* gc_mcalloc(size_t count, size_t size) {
  219. if (!count) {
  220. return malloc(size);
  221. }
  222. return calloc(count, size);
  223. }
  224. static void *gc_allocate(size_t count, size_t size) {
  225. /* Allocation logic that generalizes over malloc/calloc */
  226. /* Check if we reached the high-water mark and need to clean up */
  227. if (gc->allocs->size > gc->allocs->sweep_limit && !gc->paused) {
  228. size_t freed_mem = _gc_run();
  229. }
  230. /* With cleanup out of the way, attempt to allocate memory */
  231. void *ptr = gc_mcalloc(count, size);
  232. size_t alloc_size = count ? count * size : size;
  233. /* If allocation fails, force an out-of-policy run to free some memory and try again. */
  234. if (!ptr && !gc->paused) {
  235. _gc_run();
  236. ptr = gc_mcalloc(count, size);
  237. }
  238. /* Start managing the memory we received from the system */
  239. if (ptr) {
  240. gc_allocation_t *alloc = gc_allocation_map_put(gc->allocs, gc_allocation_new(ptr, alloc_size));
  241. /* Deal with metadata allocation failure */
  242. if (alloc) {
  243. ptr = alloc->ptr;
  244. }
  245. else {
  246. /* We failed to allocate the metadata, fail cleanly. */
  247. free(ptr);
  248. ptr = NULL;
  249. }
  250. }
  251. return ptr;
  252. }
  253. void _gc_array(void *ptr, int *length) {
  254. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  255. if (alloc) {
  256. // alloc->array_length = length;
  257. }
  258. }
  259. void _gc_leaf(void *ptr) {
  260. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  261. if (alloc) {
  262. alloc->tag |= GC_TAG_LEAF;
  263. }
  264. }
  265. void _gc_root(void *ptr) {
  266. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  267. if (alloc) {
  268. alloc->roots++;
  269. alloc->tag |= GC_TAG_ROOT;
  270. }
  271. }
  272. void _gc_unroot(void *ptr) {
  273. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  274. if (alloc) {
  275. alloc->roots--;
  276. if (alloc->roots == 0) {
  277. alloc->tag &= ~GC_TAG_ROOT;
  278. }
  279. }
  280. }
  281. static inline uint64_t pad(int di, int n) {
  282. return (n - (di % n)) % n;
  283. }
  284. // Cut out a leaf out of an existing allocation
  285. void *_gc_cut(void *ptr, size_t pos, size_t size) {
  286. size += pad(size, 8);
  287. // Start
  288. gc_allocation_t *start = gc_allocation_map_get(gc->allocs, ptr);
  289. while (start->cut) {
  290. start = start->cut;
  291. }
  292. size_t start_size = start->size;
  293. pos -= (char *)start->ptr - (char *)ptr;
  294. start->size = pos;
  295. // Middle
  296. gc_allocation_t *middle = gc_allocation_new((char *)start->ptr + pos, size);
  297. middle->tag |= GC_TAG_CUT;
  298. middle->tag |= GC_TAG_LEAF;
  299. start->cut = middle;
  300. gc_allocation_map_put(gc->allocs, middle);
  301. // End
  302. gc_allocation_t *end = gc_allocation_new((char *)start->ptr + pos + size, start_size - size - pos);
  303. end->tag |= GC_TAG_CUT;
  304. middle->cut = end;
  305. gc_allocation_map_put(gc->allocs, end);
  306. return middle->ptr;
  307. }
  308. static void gc_mark_alloc(void *ptr) {
  309. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  310. /* Mark if alloc exists and is not tagged already, otherwise skip */
  311. if (alloc && !(alloc->tag & GC_TAG_MARK)) {
  312. alloc->tag |= GC_TAG_MARK;
  313. if (!(alloc->tag & GC_TAG_LEAF)) { // Skip contents
  314. /* Iterate over allocation contents and mark them as well */
  315. int size = alloc->array_length ? (*alloc->array_length) * PTRSIZE : alloc->size;
  316. for (char *p = (char *)alloc->ptr; p <= (char *)alloc->ptr + size - PTRSIZE; ++p) {
  317. gc_mark_alloc(*(void **)p);
  318. }
  319. }
  320. }
  321. }
  322. static void gc_mark_stack() {
  323. void *tos = __builtin_frame_address(0);
  324. void *bos = gc->bos;
  325. /* The stack grows towards smaller memory addresses, hence we scan tos->bos.
  326. * Stop scanning once the distance between tos & bos is too small to hold a valid pointer */
  327. for (char *p = (char *)tos; p <= (char *)bos - PTRSIZE; ++p) {
  328. gc_mark_alloc(*(void **)p);
  329. }
  330. }
  331. static void gc_mark_roots() {
  332. for (size_t i = 0; i < gc->allocs->capacity; ++i) {
  333. gc_allocation_t *chunk = gc->allocs->allocs[i];
  334. while (chunk) {
  335. if (chunk->tag & GC_TAG_ROOT) {
  336. gc_mark_alloc(chunk->ptr);
  337. }
  338. chunk = chunk->next;
  339. }
  340. }
  341. }
  342. static void gc_mark() {
  343. /* Note: We only look at the stack and the heap, and ignore BSS. */
  344. /* Scan the heap for roots */
  345. gc_mark_roots();
  346. /* Dump registers onto stack and scan the stack */
  347. void (*volatile _mark_stack)(void) = gc_mark_stack;
  348. jmp_buf ctx;
  349. memset(&ctx, 0, sizeof(jmp_buf));
  350. setjmp(ctx);
  351. _mark_stack();
  352. }
  353. static size_t gc_sweep() {
  354. size_t total = 0;
  355. for (size_t i = 0; i < gc->allocs->capacity; ++i) {
  356. gc_allocation_t *chunk = gc->allocs->allocs[i];
  357. gc_allocation_t *next = NULL;
  358. /* Iterate over separate chaining */
  359. while (chunk) {
  360. if (chunk->tag & GC_TAG_MARK) {
  361. /* unmark */
  362. chunk->tag &= ~GC_TAG_MARK;
  363. chunk = chunk->next;
  364. }
  365. else if (chunk->tag & GC_TAG_CUT) {
  366. chunk = chunk->next;
  367. }
  368. else {
  369. /* no reference to this chunk, hence delete it */
  370. total += chunk->size;
  371. free(chunk->ptr);
  372. /* and remove it from the bookkeeping */
  373. next = chunk->next;
  374. gc_allocation_t *cut = chunk->cut;
  375. gc_allocation_map_remove(gc->allocs, chunk->ptr, false);
  376. while (cut) {
  377. gc_allocation_t *next = cut->cut;
  378. gc_allocation_map_remove(gc->allocs, cut->ptr, false);
  379. cut = next;
  380. }
  381. chunk = next;
  382. }
  383. }
  384. }
  385. gc_allocation_map_resize_to_fit(gc->allocs);
  386. return total;
  387. }
  388. void *_gc_calloc(size_t count, size_t size) {
  389. return gc_allocate(count, size);
  390. }
  391. void *_gc_realloc(void *p, size_t size) {
  392. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, p);
  393. if (p && !alloc) {
  394. // the user passed an unknown pointer
  395. return NULL;
  396. }
  397. void *q = realloc(p, size);
  398. if (!q) {
  399. // realloc failed but p is still valid
  400. return NULL;
  401. }
  402. if (!p) {
  403. // allocation, not reallocation
  404. gc_allocation_t *alloc = gc_allocation_map_put(gc->allocs, gc_allocation_new(q, size));
  405. return alloc->ptr;
  406. }
  407. if (p == q) {
  408. // successful reallocation w/o copy
  409. alloc->size = size;
  410. }
  411. else {
  412. // successful reallocation w/ copy
  413. gc_allocation_map_remove(gc->allocs, p, true);
  414. gc_allocation_map_put(gc->allocs, gc_allocation_new(q, size));
  415. }
  416. return q;
  417. }
  418. void _gc_free(void *ptr) {
  419. gc_allocation_t *alloc = gc_allocation_map_get(gc->allocs, ptr);
  420. if (alloc) {
  421. free(ptr);
  422. gc_allocation_map_remove(gc->allocs, ptr, true);
  423. }
  424. }
  425. void _gc_start(void *bos) {
  426. gc->paused = false;
  427. gc->bos = bos;
  428. // Create allocation map with no downsizing
  429. // Capacity must be a power of two
  430. gc->allocs = gc_allocation_map_new(1024 * 1024, 1024 * 1024, 0.5, 0.0, 0.8);
  431. // gc->roots = gc_allocation_map_new(1024, 1024, 0.5, 0.0, 0.8);
  432. }
  433. void _gc_pause() {
  434. gc->paused = true;
  435. }
  436. void _gc_resume() {
  437. gc->paused = false;
  438. }
  439. size_t _gc_stop() {
  440. size_t collected = gc_sweep();
  441. gc_allocation_map_delete(gc->allocs);
  442. return collected;
  443. }
  444. size_t _gc_run() {
  445. // double t = iron_time();
  446. gc_mark();
  447. size_t collected = gc_sweep();
  448. // iron_log("gc took %fms, freed %db.\n", (iron_time() - t) * 1000, collected);
  449. return collected;
  450. }