Buggy version - can't find the segfault right now.
[ocaml-ancient.git] / ancient_c.c
1 /* Mark objects as 'ancient' so they are taken out of the OCaml heap.
2  * $Id: ancient_c.c,v 1.5 2006-09-27 18:39:44 rich Exp $
3  */
4
5 #include <string.h>
6 #include <assert.h>
7
8 #include <caml/config.h>
9 #include <caml/memory.h>
10 #include <caml/alloc.h>
11 #include <caml/mlvalues.h>
12 #include <caml/fail.h>
13
14 #include "mmalloc/mmalloc.h"
15
16 // From byterun/misc.h:
17 typedef char * addr;
18
19 // From byterun/minor_gc.c:
20 CAMLextern char *caml_young_start;
21 CAMLextern char *caml_young_end;
22 #define Is_young(val) \
23   (assert (Is_block (val)),                                             \
24    (addr)(val) < (addr)caml_young_end && (addr)(val) > (addr)caml_young_start)
25
26 // From byterun/major_gc.h:
27 #ifdef __alpha
28 typedef int page_table_entry;
29 #else
30 typedef char page_table_entry;
31 #endif
32 CAMLextern char *caml_heap_start;
33 CAMLextern char *caml_heap_end;
34 CAMLextern page_table_entry *caml_page_table;
35 extern asize_t caml_page_low, caml_page_high;
36
37 #define In_heap 1
38 #define Not_in_heap 0
39 #define Page(p) ((uintnat) (p) >> Page_log)
40 #define Is_in_heap(p) \
41   (assert (Is_block ((value) (p))),                                     \
42    (addr)(p) >= (addr)caml_heap_start && (addr)(p) < (addr)caml_heap_end \
43    && caml_page_table [Page (p)])
44
45 // Area is an expandable buffer, allocated on the C heap.
46 typedef struct area {
47   void *ptr;                    // Start of area.
48   size_t n;                     // Current position.
49   size_t size;                  // Allocated size.
50
51   // If this area requires custom realloc function, these will be non-null.
52   void *(*realloc)(void *data, void *ptr, size_t size);
53   void (*free)(void *data, void *ptr);
54   void *data;
55 } area;
56
57 static inline void
58 area_init (area *a)
59 {
60   a->ptr = 0;
61   a->n =
62   a->size = 0;
63   a->realloc = 0;
64   a->free = 0;
65   a->data = 0;
66 }
67
68 static inline void
69 area_init_custom (area *a,
70                   void *(*realloc)(void *data, void *ptr, size_t size),
71                   void (*free)(void *data, void *ptr),
72                   void *data)
73 {
74   area_init (a);
75   a->realloc = realloc;
76   a->free = free;
77   a->data = data;
78 }
79
80 static inline int
81 area_append (area *a, const void *obj, size_t size)
82 {
83   while (a->n + size > a->size) {
84     if (a->size == 0) a->size = 256; else a->size <<= 1;
85     a->ptr =
86       a->realloc
87       ? a->realloc (a->data, a->ptr, a->size)
88       : realloc (a->ptr, a->size);
89     if (a->ptr == 0) return -1; // Out of memory.
90   }
91   memcpy (a->ptr + a->n, obj, size);
92   a->n += size;
93   return 0;
94 }
95
96 static inline void
97 area_shrink (area *a)
98 {
99   if (a->n != a->size) {
100     a->size = a->n;
101     a->ptr =
102       a->realloc
103       ? a->realloc (a->data, a->ptr, a->size)
104       : realloc (a->ptr, a->size);
105     assert (a->ptr); // Getting smaller, so shouldn't really fail.
106   }
107 }
108
109 static inline void
110 area_free (area *a)
111 {
112   if (a->free) a->free (a->data, a->ptr);
113   else free (a->ptr);
114   a->n =
115   a->size = 0;
116 }
117
118 struct restore_item {
119   char *header;
120   value field_zero;
121 };
122
123 // When a block is visited, we overwrite the header with all 1's.
124 // This is not quite an impossible value - one could imagine an
125 // enormous custom block where the header could take on this
126 // value. (XXX)
127 static header_t visited = (unsigned long) -1;
128
129 // The general plan here:
130 //
131 // 1. Starting at [obj], copy it to our out-of-heap memory area
132 // defined by [ptr].
133 // 2. Recursively visit subnodes of [obj] and do the same.
134 // 3. As we copy each object, we avoid circularity by setting that
135 // object's header to a special 'visited' value.  However since these
136 // are objects in the Caml heap we have to restore the original
137 // headers at the end, which is the purpose of the [restore] area.
138 // 4. We use realloc to allocate the memory for the copy, but because
139 // the memory can move around, we cannot store absolute pointers.
140 // Instead we store offsets and fix them up later.  This is the
141 // purpose of the [fixups] area.
142 //
143 // XXX Large, deeply recursive structures cause a stack overflow.
144 // Temporary solution: 'ulimit -s unlimited'.  This function should
145 // be replaced with something iterative.
146 static size_t
147 mark (value obj, area *ptr, area *restore, area *fixups)
148 {
149   char *header = Hp_val (obj);
150   assert (Wosize_hp (header) > 0); // Always true? (XXX)
151
152   // XXX This assertion might fail if someone tries to mark an object
153   // which is already ancient.
154   assert (Is_young (obj) || Is_in_heap (obj));
155
156   // If we've already visited this object, just return its offset
157   // in the out-of-heap memory.
158   if (memcmp (header, &visited, sizeof visited) == 0)
159     return (Long_val (Field (obj, 0)));
160
161   // Offset where we will store this object in the out-of-heap memory.
162   size_t offset = ptr->n;
163
164   // Copy the object out of the OCaml heap.
165   size_t bytes = Bhsize_hp (header);
166   if (area_append (ptr, header, bytes) == -1)
167     return -1;                  // Error out of memory.
168
169   // Scan the fields looking for pointers to blocks.
170   int can_scan = Tag_val (obj) < No_scan_tag;
171   if (can_scan) {
172     mlsize_t nr_words = Wosize_hp (header);
173     mlsize_t i;
174
175     for (i = 0; i < nr_words; ++i) {
176       value field = Field (obj, i);
177
178       if (Is_block (field) &&
179           (Is_young (field) || Is_in_heap (field))) {
180         size_t field_offset = mark (field, ptr, restore, fixups);
181         if (field_offset == -1) return -1; // Propagate out of memory errors.
182
183         // Since the recursive call to mark above can reallocate the
184         // area, we need to recompute these each time round the loop.
185         char *obj_copy_header = ptr->ptr + offset;
186         value obj_copy = Val_hp (obj_copy_header);
187
188         // Don't store absolute pointers yet because realloc will
189         // move the memory around.  Store a fake pointer instead.
190         // We'll fix up these fake pointers afterwards.
191         Field (obj_copy, i) = field_offset + sizeof (header_t);
192
193         size_t fixup = (void *)&Field(obj_copy, i) - ptr->ptr;
194         area_append (fixups, &fixup, sizeof fixup);
195       }
196     }
197   }
198
199   // Mark this object as having been "visited", but keep track of
200   // what was there before so it can be restored.  We also need to
201   // record the offset.
202   // Observations:
203   // (1) What was in the header before is kept in the out-of-heap
204   // copy, so we don't explicitly need to remember that.
205   // (2) We can keep the offset in the zeroth field, but since
206   // the code above might have modified the copy, we need to remember
207   // what was in that field before.
208   // (3) We can overwrite the header with all 1's to indicate that
209   // we've visited (but see notes on 'static header_t visited' above).
210   // (4) All objects in OCaml are at least one word long (we hope!).
211   struct restore_item restore_item;
212   restore_item.header = header;
213   restore_item.field_zero = Field (obj, 0);
214   area_append (restore, &restore_item, sizeof restore_item);
215
216   memcpy (header, (void *)&visited, sizeof visited);
217   Field (obj, 0) = Val_long (offset);
218
219   return offset;
220 }
221
222 // See comments immediately above.
223 static void
224 do_restore (area *ptr, area *restore)
225 {
226   mlsize_t i;
227   for (i = 0; i < restore->n; i += sizeof (struct restore_item))
228     {
229       struct restore_item *restore_item =
230         (struct restore_item *)(restore->ptr + i);
231       assert (memcmp (restore_item->header, &visited, sizeof visited) == 0);
232
233       value obj = Val_hp (restore_item->header);
234       size_t offset = Long_val (Field (obj, 0));
235
236       char *obj_copy_header = ptr->ptr + offset;
237       //value obj_copy = Val_hp (obj_copy_header);
238
239       // Restore the original header.
240       memcpy (restore_item->header, obj_copy_header, sizeof visited);
241
242       // Restore the original zeroth field.
243       Field (obj, 0) = restore_item->field_zero;
244     }
245 }
246
247 // Fixup fake pointers.
248 static void
249 do_fixups (area *ptr, area *fixups)
250 {
251   long i;
252
253   for (i = 0; i < fixups->n; i += sizeof (size_t))
254     {
255       size_t fixup = *(size_t *)(fixups->ptr + i);
256       size_t offset = *(size_t *)(ptr->ptr + fixup);
257       void *real_ptr = ptr->ptr + offset;
258       *(value *)(ptr->ptr + fixup) = (value) real_ptr;
259     }
260 }
261
262 static void *
263 do_mark (value obj,
264          void *(*realloc)(void *data, void *ptr, size_t size),
265          void (*free)(void *data, void *ptr),
266          void *data)
267 {
268   area ptr; // This will be the out of heap area.
269   area_init_custom (&ptr, realloc, free, data);
270   area restore; // Headers to be fixed up after.
271   area_init (&restore);
272   area fixups; // List of fake pointers to be fixed up.
273   area_init (&fixups);
274
275   if (mark (obj, &ptr, &restore, &fixups) == -1) {
276     // Ran out of memory.  Recover and throw an exception.
277     area_free (&fixups);
278     do_restore (&ptr, &restore);
279     area_free (&restore);
280     area_free (&ptr);
281     caml_failwith ("out of memory");
282   }
283   area_shrink (&ptr);
284
285   // Restore Caml heap structures.
286   do_restore (&ptr, &restore);
287   area_free (&restore);
288
289   // Update all fake pointers in the out of heap area to make them real
290   // pointers.
291   do_fixups (&ptr, &fixups);
292   area_free (&fixups);
293
294   return ptr.ptr;
295 }
296
297 static void *
298 my_realloc (void *data __attribute__((unused)), void *ptr, size_t size)
299 {
300   return realloc (ptr, size);
301 }
302
303 static void
304 my_free (void *data __attribute__((unused)), void *ptr)
305 {
306   return free (ptr);
307 }
308
309 CAMLprim value
310 ancient_mark (value obj)
311 {
312   CAMLparam1 (obj);
313   CAMLlocal1 (proxy);
314
315   void *ptr = do_mark (obj, my_realloc, my_free, 0);
316
317   // Return the proxy.
318   proxy = caml_alloc (1, Abstract_tag);
319   Field (proxy, 0) = (value) ptr;
320
321   CAMLreturn (proxy);
322 }
323
324 CAMLprim value
325 ancient_follow (value obj)
326 {
327   CAMLparam1 (obj);
328   CAMLlocal1 (v);
329
330   v = Field (obj, 0);
331   if (Is_long (v)) caml_invalid_argument ("deleted");
332   v = Val_hp (v); // v points to the header; make it point to the object.
333
334   CAMLreturn (v);
335 }
336
337 CAMLprim value
338 ancient_delete (value obj)
339 {
340   CAMLparam1 (obj);
341   CAMLlocal1 (v);
342
343   v = Field (obj, 0);
344   if (Is_long (v)) caml_invalid_argument ("deleted");
345
346   // Otherwise v is a pointer to the out of heap malloc'd object.
347   assert (!Is_young (v) && !Is_in_heap (v));
348   free ((void *) v);
349
350   // Replace the proxy (a pointer) with an int 0 so we know it's
351   // been deleted in future.
352   Field (obj, 0) = Val_long (0);
353
354   CAMLreturn (Val_unit);
355 }
356
357 CAMLprim value
358 ancient_attach (value fdv)
359 {
360   CAMLparam1 (fdv);
361   CAMLlocal1 (mdv);
362
363   int fd = Int_val (fdv);
364   void *md = mmalloc_attach (fd, 0);
365   if (md == 0) {
366     perror ("mmalloc_attach");
367     caml_failwith ("mmalloc_attach");
368   }
369
370   mdv = caml_alloc (1, Abstract_tag);
371   Field (mdv, 0) = (value) md;
372
373   CAMLreturn (mdv);
374 }
375
376 CAMLprim value
377 ancient_detach (value mdv)
378 {
379   CAMLparam1 (mdv);
380
381   void *md = (void *) Field (mdv, 0);
382
383   if (mmalloc_detach (md) != 0) {
384     perror ("mmalloc_detach");
385     caml_failwith ("mmalloc_detach");
386   }
387
388   CAMLreturn (Val_unit);
389 }
390
391 CAMLprim value
392 ancient_share (value mdv, value keyv, value obj)
393 {
394   CAMLparam3 (mdv, keyv, obj);
395   CAMLlocal1 (proxy);
396
397   void *md = (void *) Field (mdv, 0);
398   int key = Int_val (keyv);
399
400   // Existing key exists?  Free it.
401   void *old_obj = mmalloc_getkey (md, key);
402   if (old_obj != 0) mfree (md, old_obj);
403   mmalloc_setkey (md, key, 0);
404
405   void *ptr = do_mark (obj, mrealloc, mfree, md);
406
407   mmalloc_setkey (md, key, ptr);
408
409   // Return the proxy.
410   proxy = caml_alloc (1, Abstract_tag);
411   Field (proxy, 0) = (value) ptr;
412
413   CAMLreturn (proxy);
414 }
415
416 CAMLprim value
417 ancient_get (value mdv, value keyv)
418 {
419   CAMLparam2 (mdv, keyv);
420   CAMLlocal1 (proxy);
421
422   void *md = (void *) Field (mdv, 0);
423   int key = Int_val (keyv);
424
425   void *ptr = mmalloc_getkey (md, key);
426   if (!ptr) caml_raise_not_found ();
427
428   // Return the proxy.
429   proxy = caml_alloc (1, Abstract_tag);
430   Field (proxy, 0) = (value) ptr;
431
432   CAMLreturn (proxy);
433 }