/**************************************************************************/ /* */ /* OCaml */ /* */ /* Damien Doligez, projet Moscova, INRIA Rocquencourt */ /* */ /* Copyright 2000 Institut National de Recherche en Informatique et */ /* en Automatique. */ /* */ /* All rights reserved. This file is distributed under the terms of */ /* the GNU Lesser General Public License version 2.1, with the */ /* special exception on linking described in the file LICENSE. */ /* */ /**************************************************************************/ #define CAML_INTERNALS /* Handling of finalised values. */ #include "caml/callback.h" #include "caml/compact.h" #include "caml/fail.h" #include "caml/finalise.h" #include "caml/minor_gc.h" #include "caml/mlvalues.h" #include "caml/roots.h" #include "caml/signals.h" struct final { value fun; value val; int offset; }; struct finalisable { struct final *table; uintnat old; uintnat young; uintnat size; }; /* [0..old) : finalisable set, the values are in the major heap [old..young) : recent set, the values could be in the minor heap [young..size) : free space The element of the finalisable set are moved to the finalising set below when the value are unreachable (for the first or last time). */ static struct finalisable finalisable_first = {NULL,0,0,0}; static struct finalisable finalisable_last = {NULL,0,0,0}; struct to_do { struct to_do *next; int size; struct final item[1]; /* variable size */ }; static struct to_do *to_do_hd = NULL; static struct to_do *to_do_tl = NULL; /* to_do_hd: head of the list of finalisation functions that can be run. to_do_tl: tail of the list of finalisation functions that can be run. It is the finalising set. */ static int running_finalisation_function = 0; /* [size] is a number of elements for the [to_do.item] array */ static void alloc_to_do (int size) { struct to_do *result = caml_stat_alloc_noexc (sizeof (struct to_do) + size * sizeof (struct final)); if (result == NULL) caml_fatal_error ("out of memory"); result->next = NULL; result->size = size; if (to_do_tl == NULL){ to_do_hd = result; to_do_tl = result; if(!running_finalisation_function) caml_set_action_pending(); }else{ CAMLassert (to_do_tl->next == NULL); to_do_tl->next = result; to_do_tl = result; } } /* Find white finalisable values, move them to the finalising set, and darken them (if darken_value is true). */ static void generic_final_update (struct finalisable * final, int darken_value) { uintnat i, j, k; uintnat todo_count = 0; CAMLassert (final->old <= final->young); for (i = 0; i < final->old; i++){ CAMLassert (Is_block (final->table[i].val)); CAMLassert (Is_in_heap (final->table[i].val)); if (Is_white_val (final->table[i].val)){ ++ todo_count; } } /** invariant: - 0 <= j <= i /\ 0 <= k <= i /\ 0 <= k <= todo_count - i : index in final_table, before i all the values are black (alive or in the minor heap) or the finalizer have been copied in to_do_tl. - j : index in final_table, before j all the values are black (alive or in the minor heap), next available slot. - k : index in to_do_tl, next available slot. */ if (todo_count > 0){ alloc_to_do (todo_count); j = k = 0; for (i = 0; i < final->old; i++){ CAMLassert (Is_block (final->table[i].val)); CAMLassert (Is_in_heap (final->table[i].val)); CAMLassert (Tag_val (final->table[i].val) != Forward_tag); if(Is_white_val (final->table[i].val)){ /** dead */ to_do_tl->item[k] = final->table[i]; if(!darken_value){ /* The value is not darken so the finalisation function is called with unit not with the value */ to_do_tl->item[k].val = Val_unit; to_do_tl->item[k].offset = 0; }; k++; }else{ /** alive */ final->table[j++] = final->table[i]; } } CAMLassert (i == final->old); CAMLassert (k == todo_count); final->old = j; for(;i < final->young; i++){ final->table[j++] = final->table[i]; } final->young = j; to_do_tl->size = k; if(darken_value){ for (i = 0; i < k; i++){ /* Note that item may already be dark due to multiple entries in the final table. */ caml_darken (to_do_tl->item[i].val, NULL); } } } } void caml_final_update_mark_phase (){ generic_final_update(&finalisable_first, /* darken_value */ 1); } void caml_final_update_clean_phase (){ generic_final_update(&finalisable_last, /* darken_value */ 0); } /* Call the finalisation functions for the finalising set. Note that this function must be reentrant. */ value caml_final_do_calls_exn (void) { struct final f; value res; if (!running_finalisation_function && to_do_hd != NULL){ if (caml_finalise_begin_hook != NULL) (*caml_finalise_begin_hook) (); caml_gc_message (0x80, "Calling finalisation functions.\n"); while (1){ while (to_do_hd != NULL && to_do_hd->size == 0){ struct to_do *next_hd = to_do_hd->next; caml_stat_free (to_do_hd); to_do_hd = next_hd; if (to_do_hd == NULL) to_do_tl = NULL; } if (to_do_hd == NULL) break; CAMLassert (to_do_hd->size > 0); -- to_do_hd->size; f = to_do_hd->item[to_do_hd->size]; running_finalisation_function = 1; res = caml_callback_exn (f.fun, f.val + f.offset); running_finalisation_function = 0; if (Is_exception_result (res)) return res; } caml_gc_message (0x80, "Done calling finalisation functions.\n"); if (caml_finalise_end_hook != NULL) (*caml_finalise_end_hook) (); } return Val_unit; } /* Call a scanning_action [f] on [x]. */ #define Call_action(f,x) (*(f)) ((x), &(x)) /* Call [*f] on the closures of the finalisable set and the closures and values of the finalising set. This is called by the major GC [caml_darken_all_roots] and by the compactor through [caml_do_roots] */ void caml_final_do_roots (scanning_action f) { uintnat i; struct to_do *todo; CAMLassert (finalisable_first.old <= finalisable_first.young); for (i = 0; i < finalisable_first.young; i++){ Call_action (f, finalisable_first.table[i].fun); }; CAMLassert (finalisable_last.old <= finalisable_last.young); for (i = 0; i < finalisable_last.young; i++){ Call_action (f, finalisable_last.table[i].fun); }; for (todo = to_do_hd; todo != NULL; todo = todo->next){ for (i = 0; i < todo->size; i++){ Call_action (f, todo->item[i].fun); Call_action (f, todo->item[i].val); } } } /* Call caml_invert_root on the values of the finalisable set. This is called directly by the compactor. */ void caml_final_invert_finalisable_values () { uintnat i; CAMLassert (finalisable_first.old <= finalisable_first.young); for (i = 0; i < finalisable_first.young; i++){ caml_invert_root(finalisable_first.table[i].val, &finalisable_first.table[i].val); }; CAMLassert (finalisable_last.old <= finalisable_last.young); for (i = 0; i < finalisable_last.young; i++){ caml_invert_root(finalisable_last.table[i].val, &finalisable_last.table[i].val); }; } /* Call [caml_oldify_one] on the closures and values of the recent set. This is called by the minor GC through [caml_oldify_local_roots]. */ void caml_final_oldify_young_roots () { uintnat i; CAMLassert (finalisable_first.old <= finalisable_first.young); for (i = finalisable_first.old; i < finalisable_first.young; i++){ caml_oldify_one(finalisable_first.table[i].fun, &finalisable_first.table[i].fun); caml_oldify_one(finalisable_first.table[i].val, &finalisable_first.table[i].val); } CAMLassert (finalisable_last.old <= finalisable_last.young); for (i = finalisable_last.old; i < finalisable_last.young; i++){ caml_oldify_one(finalisable_last.table[i].fun, &finalisable_last.table[i].fun); } } static void generic_final_minor_update (struct finalisable * final) { uintnat i, j, k; uintnat todo_count = 0; CAMLassert (final->old <= final->young); for (i = final->old; i < final->young; i++){ CAMLassert (Is_block (final->table[i].val)); CAMLassert (Is_in_heap_or_young (final->table[i].val)); if (Is_young(final->table[i].val) && Hd_val(final->table[i].val) != 0){ ++ todo_count; } } /** invariant: - final->old <= j <= i /\ final->old <= k <= i /\ 0 <= k <= todo_count - i : index in final_table, before i all the values are alive or the finalizer have been copied in to_do_tl. - j : index in final_table, before j all the values are alive, next available slot. - k : index in to_do_tl, next available slot. */ if (todo_count > 0){ alloc_to_do (todo_count); k = 0; j = final->old; for (i = final->old; i < final->young; i++){ CAMLassert (Is_block (final->table[i].val)); CAMLassert (Is_in_heap_or_young (final->table[i].val)); CAMLassert (Tag_val (final->table[i].val) != Forward_tag); if(Is_young(final->table[i].val) && Hd_val(final->table[i].val) != 0){ /** dead */ to_do_tl->item[k] = final->table[i]; /* The finalisation function is called with unit not with the value */ to_do_tl->item[k].val = Val_unit; to_do_tl->item[k].offset = 0; k++; }else{ /** alive */ final->table[j++] = final->table[i]; } } CAMLassert (i == final->young); CAMLassert (k == todo_count); final->young = j; to_do_tl->size = todo_count; } /** update the minor value to the copied major value */ for (i = final->old; i < final->young; i++){ CAMLassert (Is_block (final->table[i].val)); CAMLassert (Is_in_heap_or_young (final->table[i].val)); if (Is_young(final->table[i].val)) { CAMLassert (Hd_val(final->table[i].val) == 0); final->table[i].val = Field(final->table[i].val,0); } } /** check invariant */ CAMLassert (final->old <= final->young); for (i = 0; i < final->young; i++){ CAMLassert( Is_in_heap(final->table[i].val) ); }; } /* At the end of minor collection update the finalise_last roots in minor heap when moved to major heap or moved them to the finalising set when dead. */ void caml_final_update_minor_roots () { generic_final_minor_update(&finalisable_last); } /* Empty the recent set into the finalisable set. This is called at the end of each minor collection. The minor heap must be empty when this is called. */ void caml_final_empty_young (void) { finalisable_first.old = finalisable_first.young; finalisable_last.old = finalisable_last.young; } /* Put (f,v) in the recent set. */ static void generic_final_register (struct finalisable *final, value f, value v) { if (!Is_block (v) || !Is_in_heap_or_young(v) || Tag_val (v) == Lazy_tag #ifdef FLAT_FLOAT_ARRAY || Tag_val (v) == Double_tag #endif || Tag_val (v) == Forward_tag) { caml_invalid_argument ("Gc.finalise"); } CAMLassert (final->old <= final->young); if (final->young >= final->size){ if (final->table == NULL){ uintnat new_size = 30; final->table = caml_stat_alloc (new_size * sizeof (struct final)); CAMLassert (final->old == 0); CAMLassert (final->young == 0); final->size = new_size; }else{ uintnat new_size = final->size * 2; final->table = caml_stat_resize (final->table, new_size * sizeof (struct final)); final->size = new_size; } } CAMLassert (final->young < final->size); final->table[final->young].fun = f; if (Tag_val (v) == Infix_tag){ final->table[final->young].offset = Infix_offset_val (v); final->table[final->young].val = v - Infix_offset_val (v); }else{ final->table[final->young].offset = 0; final->table[final->young].val = v; } ++ final->young; } CAMLprim value caml_final_register (value f, value v){ generic_final_register(&finalisable_first, f, v); return Val_unit; } CAMLprim value caml_final_register_called_without_value (value f, value v){ generic_final_register(&finalisable_last, f, v); return Val_unit; } CAMLprim value caml_final_release (value unit) { running_finalisation_function = 0; /* Some finalisers might be waiting. */ if (to_do_tl != NULL) caml_set_action_pending(); return Val_unit; } static void gen_final_invariant_check(struct finalisable *final){ uintnat i; CAMLassert (final->old <= final->young); for (i = 0; i < final->old; i++){ CAMLassert( Is_in_heap(final->table[i].val) ); }; for (i = final->old; i < final->young; i++){ CAMLassert( Is_in_heap_or_young(final->table[i].val) ); }; } void caml_final_invariant_check(void){ gen_final_invariant_check(&finalisable_first); gen_final_invariant_check(&finalisable_last); }