You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
11 KiB

10 years ago
10 years ago
10 years ago
10 years ago
  1. /* -*- coding: utf-8 -*-
  2. * ----------------------------------------------------------------------
  3. * Copyright © 2011-2014, RedJack, LLC.
  4. * All rights reserved.
  5. *
  6. * Please see the COPYING file in this distribution for license details.
  7. * ----------------------------------------------------------------------
  8. */
  9. #include <stdlib.h>
  10. #include "libcork/config/config.h"
  11. #include "libcork/core/allocator.h"
  12. #include "libcork/core/gc.h"
  13. #include "libcork/core/types.h"
  14. #include "libcork/ds/dllist.h"
  15. #include "libcork/threads/basics.h"
  16. #if !defined(CORK_DEBUG_GC)
  17. #define CORK_DEBUG_GC 0
  18. #endif
  19. #if CORK_DEBUG_GC
  20. #include <stdio.h>
  21. #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
  22. #else
  23. #define DEBUG(...) /* no debug messages */
  24. #endif
  25. /*-----------------------------------------------------------------------
  26. * GC context life cycle
  27. */
  28. #define ROOTS_SIZE 1024
  29. /* An internal structure allocated with every garbage-collected object. */
  30. struct cork_gc_header;
  31. /* A garbage collector context. */
  32. struct cork_gc {
  33. /* The number of used entries in roots. */
  34. size_t root_count;
  35. /* The possible roots of garbage cycles */
  36. struct cork_gc_header *roots[ROOTS_SIZE];
  37. };
  38. cork_tls(struct cork_gc, cork_gc);
  39. static void
  40. cork_gc_collect_cycles(struct cork_gc *gc);
  41. /*-----------------------------------------------------------------------
  42. * Garbage collection functions
  43. */
  44. struct cork_gc_header {
  45. /* The current reference count for this object, along with its color
  46. * during the mark/sweep process. */
  47. volatile int ref_count_color;
  48. /* The allocated size of this garbage-collected object (including
  49. * the header). */
  50. size_t allocated_size;
  51. /* The garbage collection interface for this object. */
  52. struct cork_gc_obj_iface *iface;
  53. };
  54. /*
  55. * Structure of ref_count_color:
  56. *
  57. * +-----+---+---+---+---+---+
  58. * | ... | 4 | 3 | 2 | 1 | 0 |
  59. * +-----+---+---+---+---+---+
  60. * ref_count | color
  61. * |
  62. * buffered --/
  63. */
  64. #define cork_gc_ref_count_color(count, buffered, color) \
  65. (((count) << 3) | ((buffered) << 2) | (color))
  66. #define cork_gc_get_ref_count(hdr) \
  67. ((hdr)->ref_count_color >> 3)
  68. #define cork_gc_inc_ref_count(hdr) \
  69. do { \
  70. (hdr)->ref_count_color += (1 << 3); \
  71. } while (0)
  72. #define cork_gc_dec_ref_count(hdr) \
  73. do { \
  74. (hdr)->ref_count_color -= (1 << 3); \
  75. } while (0)
  76. #define cork_gc_get_color(hdr) \
  77. ((hdr)->ref_count_color & 0x3)
  78. #define cork_gc_set_color(hdr, color) \
  79. do { \
  80. (hdr)->ref_count_color = \
  81. ((hdr)->ref_count_color & ~0x3) | (color & 0x3); \
  82. } while (0)
  83. #define cork_gc_get_buffered(hdr) \
  84. (((hdr)->ref_count_color & 0x4) != 0)
  85. #define cork_gc_set_buffered(hdr, buffered) \
  86. do { \
  87. (hdr)->ref_count_color = \
  88. ((hdr)->ref_count_color & ~0x4) | (((buffered) & 1) << 2); \
  89. } while (0)
  90. #define cork_gc_free(hdr) \
  91. do { \
  92. if ((hdr)->iface->free != NULL) { \
  93. (hdr)->iface->free(cork_gc_get_object((hdr))); \
  94. } \
  95. cork_free((hdr), (hdr)->allocated_size); \
  96. } while (0)
  97. #define cork_gc_recurse(gc, hdr, recurser) \
  98. do { \
  99. if ((hdr)->iface->recurse != NULL) { \
  100. (hdr)->iface->recurse \
  101. ((gc), cork_gc_get_object((hdr)), (recurser), NULL); \
  102. } \
  103. } while (0)
  104. enum cork_gc_color {
  105. /* In use or free */
  106. GC_BLACK = 0,
  107. /* Possible member of garbage cycle */
  108. GC_GRAY = 1,
  109. /* Member of garbage cycle */
  110. GC_WHITE = 2,
  111. /* Possible root of garbage cycle */
  112. GC_PURPLE = 3
  113. };
  114. #define cork_gc_get_header(obj) \
  115. (((struct cork_gc_header *) (obj)) - 1)
  116. #define cork_gc_get_object(hdr) \
  117. ((void *) (((struct cork_gc_header *) (hdr)) + 1))
  118. void
  119. cork_gc_init(void)
  120. {
  121. cork_gc_get();
  122. }
  123. void
  124. cork_gc_done(void)
  125. {
  126. cork_gc_collect_cycles(cork_gc_get());
  127. }
  128. void *
  129. cork_gc_alloc(size_t instance_size, struct cork_gc_obj_iface *iface)
  130. {
  131. size_t full_size = instance_size + sizeof(struct cork_gc_header);
  132. DEBUG("Allocating %zu (%zu) bytes\n", instance_size, full_size);
  133. struct cork_gc_header *header = cork_malloc(full_size);
  134. DEBUG(" Result is %p[%p]\n", cork_gc_get_object(header), header);
  135. header->ref_count_color = cork_gc_ref_count_color(1, false, GC_BLACK);
  136. header->allocated_size = full_size;
  137. header->iface = iface;
  138. return cork_gc_get_object(header);
  139. }
  140. void *
  141. cork_gc_incref(void *obj)
  142. {
  143. if (obj != NULL) {
  144. struct cork_gc_header *header = cork_gc_get_header(obj);
  145. cork_gc_inc_ref_count(header);
  146. DEBUG("Incrementing %p -> %d\n",
  147. obj, cork_gc_get_ref_count(header));
  148. cork_gc_set_color(header, GC_BLACK);
  149. }
  150. return obj;
  151. }
  152. static void
  153. cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud);
  154. static void
  155. cork_gc_release(struct cork_gc *gc, struct cork_gc_header *header)
  156. {
  157. cork_gc_recurse(gc, header, cork_gc_decref_step);
  158. cork_gc_set_color(header, GC_BLACK);
  159. if (!cork_gc_get_buffered(header)) {
  160. cork_gc_free(header);
  161. }
  162. }
  163. static void
  164. cork_gc_possible_root(struct cork_gc *gc, struct cork_gc_header *header)
  165. {
  166. if (cork_gc_get_color(header) != GC_PURPLE) {
  167. DEBUG(" Possible garbage cycle root\n");
  168. cork_gc_set_color(header, GC_PURPLE);
  169. if (!cork_gc_get_buffered(header)) {
  170. cork_gc_set_buffered(header, true);
  171. if (gc->root_count >= ROOTS_SIZE) {
  172. cork_gc_collect_cycles(gc);
  173. }
  174. gc->roots[gc->root_count++] = header;
  175. }
  176. } else {
  177. DEBUG(" Already marked as possible garbage cycle root\n");
  178. }
  179. }
  180. static void
  181. cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud)
  182. {
  183. if (obj != NULL) {
  184. struct cork_gc_header *header = cork_gc_get_header(obj);
  185. cork_gc_dec_ref_count(header);
  186. DEBUG("Decrementing %p -> %d\n",
  187. obj, cork_gc_get_ref_count(header));
  188. if (cork_gc_get_ref_count(header) == 0) {
  189. DEBUG(" Releasing %p\n", header);
  190. cork_gc_release(gc, header);
  191. } else {
  192. cork_gc_possible_root(gc, header);
  193. }
  194. }
  195. }
  196. void
  197. cork_gc_decref(void *obj)
  198. {
  199. if (obj != NULL) {
  200. struct cork_gc *gc = cork_gc_get();
  201. struct cork_gc_header *header = cork_gc_get_header(obj);
  202. cork_gc_dec_ref_count(header);
  203. DEBUG("Decrementing %p -> %d\n",
  204. obj, cork_gc_get_ref_count(header));
  205. if (cork_gc_get_ref_count(header) == 0) {
  206. DEBUG(" Releasing %p\n", header);
  207. cork_gc_release(gc, header);
  208. } else {
  209. cork_gc_possible_root(gc, header);
  210. }
  211. }
  212. }
  213. static void
  214. cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud);
  215. static void
  216. cork_gc_mark_gray(struct cork_gc *gc, struct cork_gc_header *header)
  217. {
  218. if (cork_gc_get_color(header) != GC_GRAY) {
  219. DEBUG(" Setting color to gray\n");
  220. cork_gc_set_color(header, GC_GRAY);
  221. cork_gc_recurse(gc, header, cork_gc_mark_gray_step);
  222. }
  223. }
  224. static void
  225. cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud)
  226. {
  227. if (obj != NULL) {
  228. DEBUG(" cork_gc_mark_gray(%p)\n", obj);
  229. struct cork_gc_header *header = cork_gc_get_header(obj);
  230. cork_gc_dec_ref_count(header);
  231. DEBUG(" Reference count now %d\n", cork_gc_get_ref_count(header));
  232. cork_gc_mark_gray(gc, header);
  233. }
  234. }
  235. static void
  236. cork_gc_mark_roots(struct cork_gc *gc)
  237. {
  238. size_t i;
  239. for (i = 0; i < gc->root_count; i++) {
  240. struct cork_gc_header *header = gc->roots[i];
  241. if (cork_gc_get_color(header) == GC_PURPLE) {
  242. DEBUG(" Checking possible garbage cycle root %p\n",
  243. cork_gc_get_object(header));
  244. DEBUG(" cork_gc_mark_gray(%p)\n",
  245. cork_gc_get_object(header));
  246. cork_gc_mark_gray(gc, header);
  247. } else {
  248. DEBUG(" Possible garbage cycle root %p already checked\n",
  249. cork_gc_get_object(header));
  250. cork_gc_set_buffered(header, false);
  251. gc->roots[i] = NULL;
  252. if (cork_gc_get_color(header) == GC_BLACK &&
  253. cork_gc_get_ref_count(header) == 0) {
  254. DEBUG(" Freeing %p\n", header);
  255. cork_gc_free(header);
  256. }
  257. }
  258. }
  259. }
  260. static void
  261. cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud);
  262. static void
  263. cork_gc_scan_black(struct cork_gc *gc, struct cork_gc_header *header)
  264. {
  265. DEBUG(" Setting color of %p to BLACK\n",
  266. cork_gc_get_object(header));
  267. cork_gc_set_color(header, GC_BLACK);
  268. cork_gc_recurse(gc, header, cork_gc_scan_black_step);
  269. }
  270. static void
  271. cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud)
  272. {
  273. if (obj != NULL) {
  274. struct cork_gc_header *header = cork_gc_get_header(obj);
  275. cork_gc_inc_ref_count(header);
  276. DEBUG(" Increasing reference count %p -> %d\n",
  277. obj, cork_gc_get_ref_count(header));
  278. if (cork_gc_get_color(header) != GC_BLACK) {
  279. cork_gc_scan_black(gc, header);
  280. }
  281. }
  282. }
  283. static void
  284. cork_gc_scan(struct cork_gc *gc, void *obj, void *ud)
  285. {
  286. if (obj != NULL) {
  287. DEBUG(" Scanning possible garbage cycle entry %p\n", obj);
  288. struct cork_gc_header *header = cork_gc_get_header(obj);
  289. if (cork_gc_get_color(header) == GC_GRAY) {
  290. if (cork_gc_get_ref_count(header) > 0) {
  291. DEBUG(" Remaining references; can't be a cycle\n");
  292. cork_gc_scan_black(gc, header);
  293. } else {
  294. DEBUG(" Definitely a garbage cycle\n");
  295. cork_gc_set_color(header, GC_WHITE);
  296. cork_gc_recurse(gc, header, cork_gc_scan);
  297. }
  298. } else {
  299. DEBUG(" Already checked\n");
  300. }
  301. }
  302. }
  303. static void
  304. cork_gc_scan_roots(struct cork_gc *gc)
  305. {
  306. size_t i;
  307. for (i = 0; i < gc->root_count; i++) {
  308. if (gc->roots[i] != NULL) {
  309. void *obj = cork_gc_get_object(gc->roots[i]);
  310. cork_gc_scan(gc, obj, NULL);
  311. }
  312. }
  313. }
  314. static void
  315. cork_gc_collect_white(struct cork_gc *gc, void *obj, void *ud)
  316. {
  317. if (obj != NULL) {
  318. struct cork_gc_header *header = cork_gc_get_header(obj);
  319. if (cork_gc_get_color(header) == GC_WHITE &&
  320. !cork_gc_get_buffered(header)) {
  321. DEBUG(" Releasing %p\n", obj);
  322. cork_gc_set_color(header, GC_BLACK);
  323. cork_gc_recurse(gc, header, cork_gc_collect_white);
  324. DEBUG(" Freeing %p\n", header);
  325. cork_gc_free(header);
  326. }
  327. }
  328. }
  329. static void
  330. cork_gc_collect_roots(struct cork_gc *gc)
  331. {
  332. size_t i;
  333. for (i = 0; i < gc->root_count; i++) {
  334. if (gc->roots[i] != NULL) {
  335. struct cork_gc_header *header = gc->roots[i];
  336. void *obj = cork_gc_get_object(header);
  337. cork_gc_set_buffered(header, false);
  338. DEBUG("Collecting cycles from garbage root %p\n", obj);
  339. cork_gc_collect_white(gc, obj, NULL);
  340. gc->roots[i] = NULL;
  341. }
  342. }
  343. gc->root_count = 0;
  344. }
  345. static void
  346. cork_gc_collect_cycles(struct cork_gc *gc)
  347. {
  348. DEBUG("Collecting garbage cycles\n");
  349. cork_gc_mark_roots(gc);
  350. cork_gc_scan_roots(gc);
  351. cork_gc_collect_roots(gc);
  352. }