431 lines
13 KiB
C
431 lines
13 KiB
C
/**************************************************************************/
|
|
/* */
|
|
/* 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);
|
|
}
|