/*
 * Copyright (c) 2020-2021 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#ifndef PAS_BITFIT_DIRECTORY_H
#define PAS_BITFIT_DIRECTORY_H

#include "pas_bitfit_max_free.h"
#include "pas_bitfit_page_config_kind.h"
#include "pas_bitfit_view_and_index.h"
#include "pas_compact_atomic_bitfit_size_class_ptr.h"
#include "pas_compact_atomic_bitfit_view_ptr.h"
#include "pas_heap_summary.h"
#include "pas_page_sharing_participant.h"
#include "pas_segmented_vector.h"
#include "pas_versioned_field.h"

PAS_BEGIN_EXTERN_C;

struct pas_bitfit_directory;
struct pas_bitfit_directory_bitvector_segment;
struct pas_bitfit_size_class;
struct pas_bitfit_page;
struct pas_bitfit_view;
struct pas_segregated_heap;
typedef struct pas_bitfit_directory pas_bitfit_directory;
typedef struct pas_bitfit_directory_bitvector_segment pas_bitfit_directory_bitvector_segment;
typedef struct pas_bitfit_size_class pas_bitfit_size_class;
typedef struct pas_bitfit_page pas_bitfit_page;
typedef struct pas_bitfit_view pas_bitfit_view;
typedef struct pas_segregated_heap pas_segregated_heap;

#define PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE false

PAS_DECLARE_SEGMENTED_VECTOR(pas_bitfit_directory_max_free_vector,
                             pas_bitfit_max_free,
                             128);

PAS_DECLARE_SEGMENTED_VECTOR(pas_bitfit_directory_view_vector,
                             pas_compact_atomic_bitfit_view_ptr,
                             8);

struct pas_bitfit_directory_bitvector_segment {
    unsigned empty_bits;
};

#define PAS_BITFIT_DIRECTORY_BITVECTOR_SEGMENT_INITIALIZER \
    ((pas_bitfit_directory_bitvector_segment){ \
         .empty_bits = 0, \
     })

PAS_DECLARE_SEGMENTED_VECTOR(pas_bitfit_directory_segmented_bitvectors,
                             pas_bitfit_directory_bitvector_segment,
                             4);

struct PAS_ALIGNED(sizeof(pas_versioned_field)) pas_bitfit_directory {
    pas_versioned_field first_unprocessed_free;
    pas_versioned_field first_empty;
    pas_versioned_field last_empty_plus_one; /* Zero means there aren't any. */
    pas_segregated_heap* heap;
    pas_bitfit_directory_segmented_bitvectors bitvectors;
    pas_bitfit_directory_max_free_vector max_frees;
    pas_bitfit_directory_view_vector views;
    pas_page_sharing_participant_payload physical_sharing_payload;
    pas_compact_atomic_bitfit_size_class_ptr largest_size_class;
    pas_bitfit_page_config_kind config_kind : 8;
};

PAS_API void pas_bitfit_directory_construct(pas_bitfit_directory* directory,
                                            const pas_bitfit_page_config* config,
                                            pas_segregated_heap* heap);

static inline unsigned pas_bitfit_directory_size(pas_bitfit_directory* directory)
{
    return directory->views.size;
}

static inline pas_bitfit_max_free* pas_bitfit_directory_get_max_free_ptr(
    pas_bitfit_directory* directory,
    size_t index)
{
    return pas_bitfit_directory_max_free_vector_get_ptr_checked(&directory->max_frees, index);
}

static inline pas_bitfit_max_free pas_bitfit_directory_get_max_free(pas_bitfit_directory* directory,
                                                                    size_t index)
{
    return *pas_bitfit_directory_get_max_free_ptr(directory, index);
}

static inline void pas_bitfit_directory_set_max_free_unchecked(
    pas_bitfit_directory* directory,
    size_t index,
    pas_bitfit_max_free max_free,
    const char* reason)
{
    if (PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE)
        pas_log("%p:%zu: setting max_free unchecked to %u (%s)\n", directory, index, max_free, reason);
    *pas_bitfit_directory_get_max_free_ptr(directory, index) = max_free;
}

/* We maintain the convention that you should only do this while holding the ownership lock. */
static inline void pas_bitfit_directory_set_max_free_not_empty_impl(pas_bitfit_directory* directory,
                                                                    size_t index,
                                                                    pas_bitfit_max_free max_free)
{
    pas_bitfit_max_free* ptr;
    ptr = pas_bitfit_directory_get_max_free_ptr(directory, index);
    if (*ptr == PAS_BITFIT_MAX_FREE_EMPTY) {
        pas_log("%p:%zu: found empty when setting max_free\n", directory, index);
        PAS_ASSERT(*ptr != PAS_BITFIT_MAX_FREE_EMPTY);
    }
    PAS_ASSERT(max_free != PAS_BITFIT_MAX_FREE_EMPTY);
    *ptr = max_free;
}

static inline void pas_bitfit_directory_set_processed_max_free(pas_bitfit_directory* directory,
                                                               size_t index,
                                                               pas_bitfit_max_free max_free,
                                                               const char* reason)
{
    if (PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE)
        pas_log("%p:%zu: setting max_free to processed %u (%s)\n", directory, index, max_free, reason);
    PAS_ASSERT(max_free < (size_t)PAS_BITFIT_MAX_FREE_UNPROCESSED);
    PAS_ASSERT(PAS_BITFIT_MAX_FREE_UNPROCESSED < PAS_BITFIT_MAX_FREE_EMPTY);
    pas_bitfit_directory_set_max_free_not_empty_impl(directory, index, max_free);
}

static inline void pas_bitfit_directory_set_unprocessed_max_free_unchecked(
    pas_bitfit_directory* directory,
    size_t index,
    const char* reason)
{
    if (PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE)
        pas_log("%p:%zu: setting max_free to unprocessed unchecked (%s)\n", directory, index, reason);
    *pas_bitfit_directory_get_max_free_ptr(directory, index) = PAS_BITFIT_MAX_FREE_UNPROCESSED;
}

static inline void pas_bitfit_directory_set_empty_max_free(
    pas_bitfit_directory* directory,
    size_t index,
    const char* reason)
{
    if (PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE)
        pas_log("%p:%zu: setting max_free to empty (%s)\n", directory, index, reason);
    *pas_bitfit_directory_get_max_free_ptr(directory, index) = PAS_BITFIT_MAX_FREE_EMPTY;
}

static inline void pas_bitfit_directory_set_unprocessed_max_free(pas_bitfit_directory* directory,
                                                                 size_t index,
                                                                 const char* reason)
{
    if (PAS_BITFIT_DIRECTORY_VERBOSE_MAX_FREE)
        pas_log("%p:%zu: setting max_free to unprocessed (%s)\n", directory, index, reason);
    pas_bitfit_directory_set_max_free_not_empty_impl(directory, index, PAS_BITFIT_MAX_FREE_UNPROCESSED);
}

PAS_API void pas_bitfit_directory_update_biasing_eligibility(pas_bitfit_directory* directory,
                                                             size_t index);

PAS_API void pas_bitfit_directory_max_free_did_become_unprocessed(pas_bitfit_directory* directory,
                                                                  size_t index,
                                                                  const char* reason);

PAS_API void pas_bitfit_directory_max_free_did_become_unprocessed_unchecked(
    pas_bitfit_directory* directory,
    size_t index,
    const char* reason);

PAS_API void pas_bitfit_directory_max_free_did_become_empty_without_biasing_update(
    pas_bitfit_directory* directory,
    size_t index,
    const char* reason);

PAS_API void pas_bitfit_directory_max_free_did_become_empty(pas_bitfit_directory* directory,
                                                            size_t index,
                                                            const char* reason);

static inline pas_compact_atomic_bitfit_view_ptr*
pas_bitfit_directory_get_view_ptr(pas_bitfit_directory* directory,
                                  size_t index)
{
    return pas_bitfit_directory_view_vector_get_ptr_checked(&directory->views, index);
}

static inline pas_bitfit_view* pas_bitfit_directory_get_view(pas_bitfit_directory* directory,
                                                             size_t index)
{
    return pas_compact_atomic_bitfit_view_ptr_load(
        pas_bitfit_directory_get_view_ptr(directory, index));
}

/* This does not update any indices held onto by the directory or size classes. To do that, use
   size_class's version of this function. */
PAS_API pas_bitfit_view_and_index
pas_bitfit_directory_get_first_free_view(pas_bitfit_directory* directory,
                                         unsigned start_index,
                                         unsigned size,
                                         const pas_bitfit_page_config* page_config);

PAS_API pas_heap_summary pas_bitfit_directory_compute_summary(pas_bitfit_directory* directory);

typedef bool (*pas_bitfit_directory_for_each_live_object_callback)(
    pas_bitfit_directory* directory,
    pas_bitfit_view* view,
    uintptr_t begin,
    size_t size,
    void* arg);

PAS_API bool pas_bitfit_directory_for_each_live_object(
    pas_bitfit_directory* directory,
    pas_bitfit_directory_for_each_live_object_callback callback,
    void* arg);

PAS_API bool pas_bitfit_directory_does_sharing(pas_bitfit_directory* directory);

PAS_API uint64_t pas_bitfit_directory_get_use_epoch(pas_bitfit_directory* directory);

PAS_API bool pas_bitfit_directory_get_empty_bit_at_index(
    pas_bitfit_directory* directory,
    size_t index);

/* Returns whether we changed the value. */
PAS_API bool pas_bitfit_directory_set_empty_bit_at_index(
    pas_bitfit_directory* directory,
    size_t index,
    bool value);

PAS_API void pas_bitfit_directory_view_did_become_empty_at_index(
    pas_bitfit_directory* directory,
    size_t index);
PAS_API void pas_bitfit_directory_view_did_become_empty(
    pas_bitfit_directory* directory,
    pas_bitfit_view* view);

PAS_API pas_page_sharing_pool_take_result pas_bitfit_directory_take_last_empty(
    pas_bitfit_directory* directory,
    pas_deferred_decommit_log* decommit_log,
    pas_lock_hold_mode heap_lock_hold_mode);

PAS_API void pas_bitfit_directory_dump_reference(
    pas_bitfit_directory* directory, pas_stream* stream);

PAS_API void pas_bitfit_directory_dump_for_spectrum(pas_stream* stream, void* directory);

PAS_END_EXTERN_C;

#endif /* PAS_BITFIT_DIRECTORY_H */

