// This file is a part of Julia. License is MIT: https://julialang.org/license

/*
  Garbage Collection interface that must be implemented by third-party GCs
*/

#ifndef JL_GC_INTERFACE_H
#define JL_GC_INTERFACE_H

#include "dtypes.h"

#ifdef __cplusplus
extern "C" {
#endif

struct _jl_tls_states_t;
struct _jl_value_t;
struct _jl_weakref_t;
struct _jl_datatype_t;

// ========================================================================= //
// GC Metrics
// ========================================================================= //

// This struct must be kept in sync with the Julia type of the same name in base/timing.jl
typedef struct {
    int64_t allocd;
    int64_t deferred_alloc;
    int64_t freed;
    uint64_t malloc;
    uint64_t realloc;
    uint64_t poolalloc;
    uint64_t bigalloc;
    uint64_t freecall;
    uint64_t total_time;
    uint64_t total_allocd;
    size_t interval;
    int pause;
    int full_sweep;
    uint64_t max_pause;
    uint64_t max_memory;
    uint64_t time_to_safepoint;
    uint64_t max_time_to_safepoint;
    uint64_t total_time_to_safepoint;
    uint64_t sweep_time;
    uint64_t mark_time;
    uint64_t stack_pool_sweep_time;
    uint64_t total_sweep_time;
    uint64_t    total_sweep_page_walk_time;
    uint64_t    total_sweep_madvise_time;
    uint64_t    total_sweep_free_mallocd_memory_time;
    uint64_t total_mark_time;
    uint64_t total_stack_pool_sweep_time;
    uint64_t last_full_sweep;
    uint64_t last_incremental_sweep;
} jl_gc_num_t;

// ========================================================================= //
// System-wide Initialization
// ========================================================================= //

// System-wide initialization function. Responsible for initializing global locks as well as
// global memory parameters (e.g. target heap size) used by the collector.
void jl_gc_init(void);
// Spawns GC threads.
void jl_start_gc_threads(void);

// ========================================================================= //
// Per-thread Initialization
// ========================================================================= //

// Initializes thread-local data structures such as thread-local object pools,
// thread-local remembered sets and thread-local allocation counters.
// Should be called exactly once per Julia thread.
void jl_init_thread_heap(struct _jl_tls_states_t *ptls) JL_NOTSAFEPOINT;
// Deallocates any memory previously used for thread-local GC data structures.
// Mostly used to ensure that we perform this memory cleanup for foreign threads that are
// about to leave Julia.
void jl_free_thread_gc_state(struct _jl_tls_states_t *ptls);

// ========================================================================= //
// Controls
// ========================================================================= //

typedef enum {
    JL_GC_AUTO = 0, // use heuristics to determine the collection type
    JL_GC_FULL = 1, // force a full collection
    JL_GC_INCREMENTAL = 2, // force an incremental collection
} jl_gc_collection_t;
// Enables or disables (depending on the value of the argument) the collector. Returns
// whether GC was previously enabled.
JL_DLLEXPORT int jl_gc_enable(int on);
// Returns whether the collector is enabled.
JL_DLLEXPORT int jl_gc_is_enabled(void);
// Sets a soft limit to Julia's heap.
JL_DLLEXPORT void jl_gc_set_max_memory(uint64_t max_mem);
// Runs a GC cycle. This function's parameter determines whether we're running an
// incremental, full, or automatic (i.e. heuristic driven) collection. Returns whether we
// should run a collection cycle again (e.g. a full mark right after a full sweep to ensure
// we do a full heap traversal).
JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection);
// Returns whether the thread with `tid` is a collector thread
JL_DLLEXPORT int gc_is_collector_thread(int tid) JL_NOTSAFEPOINT;
// Enables or disables automatic full (non-generational) collections.
// When disabled (on == 0), automatic collections will only be incremental
// (young generation only). Explicit full collections via jl_gc_collect(JL_GC_FULL)
// are still honored. Returns whether automatic full collections were previously enabled.
JL_DLLEXPORT int jl_gc_enable_auto_full_collection(int on);
// Returns whether automatic full (non-generational) collections are enabled.
JL_DLLEXPORT int jl_gc_auto_full_collection_is_enabled(void);
// Returns which GC implementation is being used and possibly its version according to the list of supported GCs
// NB: it should clearly identify the GC by including e.g. 'stock' or 'mmtk' as a substring.
JL_DLLEXPORT const char* jl_gc_active_impl(void);
// Sweep Julia's stack pools and mtarray buffers. Note that this function has been added to the interface as
// each GC should implement it but it will most likely not be used by other code in the runtime.
// It still needs to be annotated with JL_DLLEXPORT since it is called from Rust by MMTk.
JL_DLLEXPORT void jl_gc_sweep_stack_pools_and_mtarraylist_buffers(jl_ptls_t ptls) JL_NOTSAFEPOINT;

// ========================================================================= //
// Metrics
// ========================================================================= //

// Retrieves Julia's `GC_Num` (structure that stores GC statistics).
JL_DLLEXPORT jl_gc_num_t jl_gc_num(void);
// Returns the difference between the current value of total live bytes now
// (live bytes at the last collection plus number of bytes allocated since then),
// compared to the value at the last time this function was called.
JL_DLLEXPORT int64_t jl_gc_diff_total_bytes(void) JL_NOTSAFEPOINT;
// Returns the difference between the current value of total live bytes now
// (live bytes at the last collection plus number of bytes allocated since then)
// compared to the value at the last time this function was called. The offset parameter
// is subtracted from this value in order to obtain the return value.
JL_DLLEXPORT int64_t jl_gc_sync_total_bytes(int64_t offset) JL_NOTSAFEPOINT;
// Returns the number of pool allocated bytes. This could always return 0 for GC
// implementations that do not use pools.
JL_DLLEXPORT int64_t jl_gc_pool_live_bytes(void);
// Returns the number of live bytes at the end of the last collection cycle
// (doesn't include the number of allocated bytes since then).
JL_DLLEXPORT int64_t jl_gc_live_bytes(void);
// Stores the number of live bytes at the end of the last collection cycle plus the number
// of bytes we allocated since then into the 64-bit integer pointer passed as an argument.
JL_DLLEXPORT void jl_gc_get_total_bytes(int64_t *bytes) JL_NOTSAFEPOINT;
// Retrieves the value of Julia's soft heap limit.
JL_DLLEXPORT uint64_t jl_gc_get_max_memory(void);
// High-resolution (nano-seconds) value of total time spent in GC.
JL_DLLEXPORT uint64_t jl_gc_total_hrtime(void);

// ========================================================================= //
// Allocation
// ========================================================================= //

// On GCC, this function is inlined when sz is constant (see julia_internal.h)
// In general, this function should implement allocation and should use the specific GC's logic
// to decide whether to allocate a small or a large object. Finally, note that this function
// **must** also set the type of the returning object to be `ty`. The type `ty` may also be used to record
// an allocation of that type in the allocation profiler.
struct _jl_value_t *jl_gc_alloc_(struct _jl_tls_states_t * ptls, size_t sz, void *ty);
// Allocates small objects and increments Julia allocation counterst. Size of the object
// header must be included in the object size. The (possibly unused in some implementations)
// offset to the arena in which we're allocating is passed in the second parameter, and the
// object size in the third parameter. If thread-local allocators are used, then this
// function should allocate in the thread-local allocator of the thread referenced by the
// jl_ptls_t argument. An additional (last) parameter containing information about the type
// of the object being allocated may be used to record an allocation of that type in the
// allocation profiler.
JL_DLLEXPORT struct _jl_value_t *jl_gc_small_alloc(struct _jl_tls_states_t *ptls,
                                                   int offset, int osize,
                                                   struct _jl_value_t *type);
// Description: Allocates large objects and increments Julia allocation counters. Size of
// the object header must be included in the object size. If thread-local allocators are
// used, then this function should allocate in the thread-local allocator of the thread
// referenced by the jl_ptls_t argument. An additional (last) parameter containing
// information about the type of the object being allocated may be used to record an
// allocation of that type in the allocation profiler.
JL_DLLEXPORT struct _jl_value_t *jl_gc_big_alloc(struct _jl_tls_states_t *ptls, size_t sz,
                                                 struct _jl_value_t *type);
// Wrapper around Libc malloc that updates Julia allocation counters.
JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz);
// Wrapper around Libc calloc that updates Julia allocation counters.
JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz);
// Wrapper around Libc free that updates Julia allocation counters.
JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz);
// Wrapper around Libc realloc that updates Julia allocation counters.
JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size_t sz);
// Wrapper around Libc malloc that's used to dynamically allocate memory for Arrays and
// Strings. It increments Julia allocation counters and should check whether we're close to
// the Julia heap target, and therefore, whether we should run a collection. Note that this
// doesn't record the size of the allocation request in a side metadata (i.e. a few words in
// front of the memory payload): this function is used for Julia object allocations, and we
// assume that there is already a field in the Julia object being allocated that we may use
// to store the size of the memory buffer.
JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz);
// Allocates a new weak-reference, assigns its value and increments Julia allocation
// counters. If thread-local allocators are used, then this function should allocate in the
// thread-local allocator of the thread referenced by the first jl_ptls_t argument.
JL_DLLEXPORT struct _jl_weakref_t *jl_gc_new_weakref_th(struct _jl_tls_states_t *ptls,
                                                        struct _jl_value_t *value);
// Permanently allocates a memory slot of the size specified by the first parameter. This
// block of memory is allocated in an immortal region that is never swept. The second
// parameter specifies whether the memory should be filled with zeros. The third and fourth
// parameters specify the alignment and an offset in bytes, respectively. Specifically, the
// pointer obtained by advancing the result of this function by the number of bytes
// specified in the fourth parameter will be aligned according to the value given by the
// third parameter in bytes.
JL_DLLEXPORT void *jl_gc_perm_alloc(size_t sz, int zero, unsigned align,
                                    unsigned offset) JL_NOTSAFEPOINT;
// Permanently allocates an object of the size specified by the first parameter. Size of the
// object header must be included in the object size. This object is allocated in an
// immortal region that is never swept. The second parameter specifies the type of the
// object being allocated and will be used to set the object header.
//
// !!! warning: Because permanently allocated objects are not swept, the GC will not
//              necessarily mark any objects that would have ordinarily been rooted by
//              the allocated object. All objects stored in fields of this object
//              must be either permanently allocated or have other roots.
struct _jl_value_t *jl_gc_permobj(size_t sz, void *ty) JL_NOTSAFEPOINT;
// This function notifies the GC about memory addresses that are set when loading the boot image.
// The GC may use that information to, for instance, determine that such objects should
// be treated as marked and belonged to the old generation in nursery collections.
void jl_gc_notify_image_load(const char* img_data, size_t len);

// ========================================================================= //
// Runtime Write-Barriers
// ========================================================================= //

// Write barrier slow-path. If a generational collector is used,
// it may enqueue an old object into the remembered set of the calling thread.
JL_DLLEXPORT void jl_gc_queue_root(const struct _jl_value_t *ptr) JL_NOTSAFEPOINT;
// In a generational collector is used, this function walks over the fields of the
// object specified by the second parameter (as defined by the data type in the third
// parameter). If a field points to a young object, the first parameter is enqueued into the
// remembered set of the calling thread.
JL_DLLEXPORT void jl_gc_queue_multiroot(const struct _jl_value_t *root, const void *stored,
                                        struct _jl_datatype_t *dt) JL_NOTSAFEPOINT;
// If a generational collector is used, checks whether the function argument points to an
// old object, and if so, calls the write barrier slow path above. In most cases, this
// function is used when its caller has verified that there is a young reference in the
// object that's being passed as an argument to this function.
STATIC_INLINE void jl_gc_wb_back(const void *ptr) JL_NOTSAFEPOINT;
// Write barrier function that must be used after pointer writes to heap-allocated objects –
// the value of the field being written must also point to a heap-allocated object.
// If a generational collector is used, it may check whether the two function arguments are
// in different GC generations (i.e. if the first argument points to an old object and the
// second argument points to a young object), and if so, call the write barrier slow-path.
STATIC_INLINE void jl_gc_wb(const void *parent, const void *ptr) JL_NOTSAFEPOINT;
// Freshly allocated objects are known to be in the young generation until the next safepoint,
// so write barriers can be omitted until the next allocation. This function is a no-op that
// can be used to annotate that a write barrier would be required were it not for this property
// (as opposed to somebody just having forgotten to think about write barriers).
STATIC_INLINE void jl_gc_wb_fresh(const void *parent, const void *ptr) JL_NOTSAFEPOINT {}
// As an optimization, the current_task is explicitly added to the remset while it is running.
// Upon deschedule, we conservatively move the write barrier into the young generation.
// This allows the omission of write barriers for all GC roots on the current task stack (JL_GC_PUSH_*),
// as well as the Task's explicit fields (but only for the current task).
// This function is a no-op that can be used to annotate that a write barrier would be required were
// it not for this property (as opposed to somebody just having forgotten to think about write barriers).
STATIC_INLINE void jl_gc_wb_current_task(const void *parent JL_UNUSED, const void *ptr JL_UNUSED) JL_NOTSAFEPOINT {}
// Used to annotate that a write barrier would be required, but may be omitted because `ptr`
// is known to be an old object.
STATIC_INLINE void jl_gc_wb_knownold(const void *parent, const void *ptr) JL_NOTSAFEPOINT {}
// Write-barrier function that must be used after copying multiple fields of an object into
// another. It should be semantically equivalent to triggering multiple write barriers – one
// per field of the object being copied, but may be special-cased for performance reasons.
STATIC_INLINE void jl_gc_multi_wb(const void *parent,
                                  const struct _jl_value_t *ptr) JL_NOTSAFEPOINT;

#ifdef __cplusplus
}
#endif

#endif
