/**************************************************************************/
/*                                                                        */
/*                                 OCaml                                  */
/*                                                                        */
/*             Damien Doligez, projet Para, INRIA Rocquencourt            */
/*                                                                        */
/*   Copyright 1997 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.          */
/*                                                                        */
/**************************************************************************/

/* Operations on weak arrays */

#ifndef CAML_WEAK_H
#define CAML_WEAK_H

#include "mlvalues.h"

#ifdef __cplusplus
extern "C" {
#endif

/** The requirements of the functions must be satisfied, it is
    unspecified what happens if they are not. The debugging runtime
    could check some of them. */

CAMLextern value caml_ephemeron_create(mlsize_t len);
/** Create an ephemeron with the given number of keys.
    This function allocates.
 */

CAMLextern mlsize_t caml_ephemeron_num_keys(value eph);
/** Return the number of key in the ephemeron. The valid key offset goes
    from [0] to the predecessor of the returned value. */

CAMLextern int caml_ephemeron_key_is_set(value eph, mlsize_t offset);
/** Return 1 if the key in the ephemeron at the given offset is set.
    Otherwise 0. The value [eph] must be an ephemeron and [offset] a
    valid key offset.
*/

CAMLextern void caml_ephemeron_set_key(value eph, mlsize_t offset, value k);
/** Set the key of the given ephemeron [eph] at the given offset
    [offset] to the given value [k]. The value [eph] must be an
    ephemeron, [offset] a valid key offset and [k] a block.
*/

CAMLextern void caml_ephemeron_unset_key(value eph, mlsize_t offset);
/** Unset the key of the given ephemeron at the given offset. The
    value [eph] must be an ephemeron and [offset] a valid key offset.
*/

CAMLextern int caml_ephemeron_get_key(value eph, mlsize_t offset, value *key);
/** Return 1 if the key in the ephemeron at the given offset is set.
    Otherwise 0. When returning 1, set [*key] to the pointed value.

    The value [eph] must be an ephemeron and [offset] a valid key
    offset.
*/

CAMLextern int caml_ephemeron_get_key_copy(value eph, mlsize_t offset,
                                           value *key);
/** Return 1 if the key in the ephemeron at the given offset is set.
    Otherwise 0. When returning 1, set [*key] to a shallow copy of the
    key. This function allocates.

    The value [eph] must be an ephemeron and [offset] a valid key
    offset.
*/

CAMLextern void caml_ephemeron_blit_key(value eph1, mlsize_t off1,
                                        value eph2, mlsize_t off2,
                                        mlsize_t len);
/** Fill the given range of keys of [eph2] with the given range of
    keys of [eph1]. Contrary to using caml_ephemeron_get_key followed
    by caml_ephemeron_set_key or caml_ephemeron_unset_key, this
    function does not prevent the incremental GC from erasing the
    value in its current cycle. The value [eph1] (resp. [eph2]) must
    be an ephemeron and the offsets between [off1] and [off1+len]
    (resp. between [off2] and [off2+offset]) must be valid keys of
    [eph1] (resp. [eph2]).
*/

CAMLextern int caml_ephemeron_data_is_set(value eph);
/** Return 1 if the data in the ephemeron is set.
    Otherwise 0. The value [eph] must be an ephemeron.
*/

CAMLextern void caml_ephemeron_set_data(value eph, value k);
/** Set the data of the given ephemeron [eph] to the given value
    [k]. The value [eph] must be an ephemeron and [k] a block.
*/

CAMLextern void caml_ephemeron_unset_data(value eph);
/** Unset the data of the given ephemeron. The value [eph] must be an
    ephemeron.
*/

CAMLextern int caml_ephemeron_get_data(value eph, value *data);
/** Return 1 if the data in the ephemeron at the given offset is set.
    Otherwise 0. When returning 1, set [*data] to the pointed value.

    The value [eph] must be an ephemeron and [offset] a valid key
    offset.
*/

CAMLextern int caml_ephemeron_get_data_copy(value eph, value *data);
/** Return 1 if the data in the ephemeron at the given offset is set.
    Otherwise 0. When returning 1, set [*data] to a shallow copy of
    the data. This function allocates.

    The value [eph] must be an ephemeron and [offset] a valid key
    offset.
*/

CAMLextern void caml_ephemeron_blit_data(value eph1, value eph2);
/** Sets the data of [eph2] to be the same as the data of [eph1].
    Contrary to using caml_ephemeron_get_data followed by
    caml_ephemeron_set_data or caml_ephemeron_unset_data, this
    function does not prevent the incremental GC from erasing the
    value in its current cycle. The values [eph1] and [eph2] must be
    ephemerons.
*/


#define caml_weak_array_length caml_ephemeron_num_keys
#define caml_weak_array_create caml_ephemeron_create
#define caml_weak_array_check caml_ephemeron_key_is_set
#define caml_weak_array_unset caml_ephemeron_unset_key
#define caml_weak_array_set caml_ephemeron_set_key
#define caml_weak_array_get caml_ephemeron_get_key
#define caml_weak_array_get_copy caml_ephemeron_get_key_copy
#define caml_weak_array_blit caml_ephemeron_blit_key

#ifdef CAML_INTERNALS

extern value caml_ephe_list_head;
extern value caml_ephe_none;


/** The first field 0:  weak list;
       second field 1:  data;
       others       2..:  keys;

    A weak pointer is an ephemeron with the data at caml_ephe_none
    If fields are added, don't forget to update weak.ml, [additional_values],
    and obj.ml, [Ephemeron.additional_values].


 */

#define CAML_EPHE_LINK_OFFSET 0
#define CAML_EPHE_DATA_OFFSET 1
#define CAML_EPHE_FIRST_KEY 2
#define CAML_EPHE_MAX_WOSIZE (Max_wosize - CAML_EPHE_FIRST_KEY)

/* In the header, in order to let major_gc.c
   and weak.c see the body of the function */
static inline void caml_ephe_clean (value v){
  value child;
  int release_data = 0;
  mlsize_t size, i;
  header_t hd;
  CAMLassert(caml_gc_phase == Phase_clean);

  hd = Hd_val (v);
  size = Wosize_hd (hd);
  for (i = 2; i < size; i++){
    child = Field (v, i);
  ephemeron_again:
    if (child != caml_ephe_none
        && Is_block (child) && Is_in_heap_or_young (child)){
      if (Tag_val (child) == Forward_tag){
        value f = Forward_val (child);
        if (Is_block (f)) {
          if (!Is_in_value_area(f) || Tag_val (f) == Forward_tag
              || Tag_val (f) == Lazy_tag || Tag_val (f) == Double_tag){
            /* Do not short-circuit the pointer. */
          }else{
            Field (v, i) = child = f;
            if (Is_block (f) && Is_young (f))
              add_to_ephe_ref_table(&caml_ephe_ref_table, v, i);
            goto ephemeron_again;
          }
        }
      }
      if (Is_white_val (child) && !Is_young (child)){
        release_data = 1;
        Field (v, i) = caml_ephe_none;
      }
    }
  }

  child = Field (v, 1);
  if(child != caml_ephe_none){
      if (release_data){
        Field (v, 1) = caml_ephe_none;
      } else {
        /* The mark phase must have marked it */
        CAMLassert( !(Is_block (child) && Is_in_heap (child)
                  && Is_white_val (child)) );
      }
  }
}

#endif /* CAML_INTERNALS */

#ifdef __cplusplus
}
#endif

#endif /* CAML_WEAK_H */
