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

#ifndef JL_GC_COMMON_H
#define JL_GC_COMMON_H

#include "julia.h"
#include "julia_internal.h"
#ifndef _OS_WINDOWS_
#include <sys/mman.h>
#ifndef MADV_HUGEPAGE
#define MADV_HUGEPAGE 14 //  Compatibility for kernels < 2.6.38 (as in numpy implementation)
#endif
#if defined(_OS_DARWIN_) && !defined(MAP_ANONYMOUS)
#define MAP_ANONYMOUS MAP_ANON
#endif
#endif

#include <stdlib.h>

#if defined(_OS_DARWIN_)
#include <malloc/malloc.h>
#else
#include <malloc.h> // for malloc_trim
#endif

#ifdef __cplusplus
extern "C" {
#endif

// =========================================================================== //
// GC Big objects
// =========================================================================== //

// layout for big (>2k) objects
JL_EXTENSION typedef struct _bigval_t {
    struct _bigval_t *next;
    struct _bigval_t *prev;
    size_t sz;
#ifdef _P64 // Add padding so that the value is 64-byte aligned
    // (8 pointers of 8 bytes each) - (4 other pointers in struct)
    void *_padding[8 - 4];
#else
    // (16 pointers of 4 bytes each) - (4 other pointers in struct)
    void *_padding[16 - 4];
#endif
    //struct jl_taggedvalue_t <>;
    union {
        uintptr_t header;
        struct {
            uintptr_t gc:2;
        } bits;
    };
    // must be 64-byte aligned here, in 32 & 64 bit modes
} bigval_t;

// =========================================================================== //
// GC Callbacks
// =========================================================================== //

typedef void (*jl_gc_cb_func_t)(void);

typedef struct _jl_gc_callback_list_t {
    struct _jl_gc_callback_list_t *next;
    jl_gc_cb_func_t func;
} jl_gc_callback_list_t;

extern jl_gc_callback_list_t *gc_cblist_root_scanner;
extern jl_gc_callback_list_t *gc_cblist_task_scanner;
extern jl_gc_callback_list_t *gc_cblist_pre_gc;
extern jl_gc_callback_list_t *gc_cblist_post_gc;
extern jl_gc_callback_list_t *gc_cblist_notify_external_alloc;
extern jl_gc_callback_list_t *gc_cblist_notify_external_free;
extern jl_gc_callback_list_t *gc_cblist_notify_gc_pressure;

#define gc_invoke_callbacks(ty, list, args) \
    do { \
        for (jl_gc_callback_list_t *cb = list; \
                cb != NULL; \
                cb = cb->next) \
        { \
            ((ty)(cb->func)) args; \
        } \
    } while (0)

// =========================================================================== //
// malloc wrappers, aligned allocation
// =========================================================================== //

#if defined(_OS_WINDOWS_)
STATIC_INLINE void *jl_malloc_aligned(size_t sz, size_t align)
{
    return _aligned_malloc(sz ? sz : 1, align);
}
STATIC_INLINE void *jl_realloc_aligned(void *p, size_t sz, size_t oldsz,
                                       size_t align)
{
    (void)oldsz;
    return _aligned_realloc(p, sz ? sz : 1, align);
}
STATIC_INLINE void jl_free_aligned(void *p) JL_NOTSAFEPOINT
{
    _aligned_free(p);
}
#else
STATIC_INLINE void *jl_malloc_aligned(size_t sz, size_t align)
{
#if defined(_P64) || defined(__APPLE__)
    if (align <= 16)
        return malloc(sz);
#endif
    void *ptr;
    if (posix_memalign(&ptr, align, sz))
        return NULL;
    return ptr;
}
STATIC_INLINE void *jl_realloc_aligned(void *d, size_t sz, size_t oldsz,
                                       size_t align)
{
#if defined(_P64) || defined(__APPLE__)
    if (align <= 16)
        return realloc(d, sz);
#endif
    void *b = jl_malloc_aligned(sz, align);
    if (b != NULL) {
        memcpy(b, d, oldsz > sz ? sz : oldsz);
        free(d);
    }
    return b;
}
STATIC_INLINE void jl_free_aligned(void *p) JL_NOTSAFEPOINT
{
    free(p);
}
#endif
#define malloc_cache_align(sz) jl_malloc_aligned(sz, JL_CACHE_BYTE_ALIGNMENT)
#define realloc_cache_align(p, sz, oldsz) jl_realloc_aligned(p, sz, oldsz, JL_CACHE_BYTE_ALIGNMENT)
#define malloc_page_align(sz) jl_malloc_aligned(sz, jl_getpagesize())

// =========================================================================== //
// Pointer tagging
// =========================================================================== //

STATIC_INLINE int gc_marked(uintptr_t bits) JL_NOTSAFEPOINT
{
    return (bits & GC_MARKED) != 0;
}

STATIC_INLINE int gc_old(uintptr_t bits) JL_NOTSAFEPOINT
{
    return (bits & GC_OLD) != 0;
}

STATIC_INLINE uintptr_t gc_set_bits(uintptr_t tag, int bits) JL_NOTSAFEPOINT
{
    return (tag & ~(uintptr_t)3) | bits;
}

STATIC_INLINE uintptr_t gc_ptr_tag(void *v, uintptr_t mask) JL_NOTSAFEPOINT
{
    return ((uintptr_t)v) & mask;
}

STATIC_INLINE void *gc_ptr_clear_tag(void *v, uintptr_t mask) JL_NOTSAFEPOINT
{
    return (void*)(((uintptr_t)v) & ~mask);
}

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

extern jl_gc_num_t gc_num;

// =========================================================================== //
// Stop-the-world for GC
// =========================================================================== //
void jl_gc_wait_for_the_world(jl_ptls_t* gc_all_tls_states, int gc_n_threads);

// =========================================================================== //
// Finalization
// =========================================================================== //

// Protect all access to `finalizer_list_marked` and `to_finalize`.
// For accessing `ptls->finalizers`, the lock is needed if a thread
// is going to realloc the buffer (of its own list) or accessing the
// list of another thread
extern jl_mutex_t finalizers_lock;
// `ptls->finalizers` and `finalizer_list_marked` might have tagged pointers.
// If an object pointer has `GC_FIN_CFUNC_TAG` set, the next pointer is an unboxed c function pointer.
// If an object pointer has `GC_FIN_COBJ_TAG` set, the current pointer is a c object pointer.
//   It must be aligned at least 4, and it finalized immediately (at "quiescence").
// `to_finalize` should not have tagged pointers.
#define GC_FIN_CFUNC_TAG 1
#define GC_FIN_COBJ_TAG  2
#define GC_FIN_TAG_MASK  3
extern arraylist_t finalizer_list_marked;
extern arraylist_t to_finalize;

void schedule_finalization(void *o, void *f) JL_NOTSAFEPOINT;
void run_finalizer(jl_task_t *ct, void *o, void *ff) JL_CANSAFEPOINT;
void run_finalizers(jl_task_t *ct, int finalizers_thread) JL_CANSAFEPOINT;
JL_DLLEXPORT void jl_gc_add_finalizer_th(jl_ptls_t ptls, jl_value_t *v, jl_value_t *f) JL_NOTSAFEPOINT;
JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o) JL_CANSAFEPOINT;


// =========================================================================== //
// Threading
// =========================================================================== //

extern int gc_n_threads;
extern jl_ptls_t* gc_all_tls_states;

// =========================================================================== //
// Logging
// =========================================================================== //

extern int gc_logging_enabled;

// =========================================================================== //
// MISC
// =========================================================================== //

// number of stacks to always keep available per pool
#define MIN_STACK_MAPPINGS_PER_POOL 5

void _jl_free_stack(jl_ptls_t ptls, void *stkbuf, size_t bufsz) JL_NOTSAFEPOINT;
void sweep_mtarraylist_buffers(void) JL_NOTSAFEPOINT;

int gc_slot_to_fieldidx(void *_obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT;
int gc_slot_to_arrayidx(void *_obj, void *begin) JL_NOTSAFEPOINT;

#ifdef __cplusplus
}
#endif

#endif // JL_GC_COMMON_H
