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.

407 lines
11 KiB

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