// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * Wrapper for Boehm GC.
 */
/* Authors:
 *   MenTaLguY <mental@rydia.net>
 *
 * Copyright (C) 2004 MenTaLguY
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "inkgc/gc-core.h"
#include <stdexcept>
#include <cstring>
#include <string>
#include <glib.h>
#include <sigc++/functors/ptr_fun.h>
#include <glibmm/main.h>
#include <cstddef>

namespace Inkscape {
namespace GC {

namespace {

#if GC_VERSION_MAJOR >= 8 and GC_VERSION_MINOR >=3
void display_warning(const char *msg, GC_word arg) {
    g_warning(msg, arg);
}
#else
void display_warning(char *msg, GC_word arg) {
    g_warning(msg, arg);
}
#endif

void do_init() {
    GC_set_no_dls(1);
    GC_set_all_interior_pointers(1);
    GC_set_finalize_on_demand(0);

    GC_INIT();

    GC_set_warn_proc(&display_warning);
}

void *debug_malloc(std::size_t size) {
    return GC_debug_malloc(size, GC_EXTRAS);
}

void *debug_malloc_atomic(std::size_t size) {
    return GC_debug_malloc_atomic(size, GC_EXTRAS);
}

void *debug_malloc_uncollectable(std::size_t size) {
    return GC_debug_malloc_uncollectable(size, GC_EXTRAS);
}

void *debug_malloc_atomic_uncollectable(std::size_t size) {
    return GC_debug_malloc_uncollectable(size, GC_EXTRAS);
}

std::ptrdiff_t compute_debug_base_fixup() {
    char *base=reinterpret_cast<char *>(GC_debug_malloc(1, GC_EXTRAS));
    char *real_base=reinterpret_cast<char *>(GC_base(base));
    GC_debug_free(base);
    return base - real_base;
}

inline std::ptrdiff_t const &debug_base_fixup() {
    static std::ptrdiff_t fixup=compute_debug_base_fixup();
    return fixup;
}

void *debug_base(void *ptr) {
    char *base=reinterpret_cast<char *>(GC_base(ptr));
    return base + debug_base_fixup();
}

int debug_general_register_disappearing_link(void **p_ptr, void const *base) {
    char const *real_base = reinterpret_cast<char const *>(base) - debug_base_fixup();
#if (GC_MAJOR_VERSION >= 7 && GC_MINOR_VERSION >= 4)
    return GC_general_register_disappearing_link(p_ptr, real_base);
#else // compatibility with older Boehm GC versions
    return GC_general_register_disappearing_link(p_ptr, const_cast<char *>(real_base));
#endif
}

void dummy_do_init() {}

void *dummy_base(void *) { return nullptr; }

void dummy_register_finalizer(void *, CleanupFunc, void *,
                                      CleanupFunc *old_func, void **old_data)
{
    if (old_func) {
        *old_func = nullptr;
    }
    if (old_data) {
        *old_data = nullptr;
    }
}

int dummy_general_register_disappearing_link(void **, void const *) { return false; }

int dummy_unregister_disappearing_link(void **/*link*/) { return false; }

std::size_t dummy_get_heap_size() { return 0; }

std::size_t dummy_get_free_bytes() { return 0; }

void dummy_gcollect() {}

void dummy_enable() {}

void dummy_disable() {}

Ops enabled_ops = {
    &do_init,
    &GC_malloc,
    &GC_malloc_atomic,
    &GC_malloc_uncollectable,
    &GC_malloc_atomic_uncollectable,
    &GC_base,
    &GC_register_finalizer_ignore_self,
#if (GC_MAJOR_VERSION >= 7 && GC_MINOR_VERSION >= 4)
     &GC_general_register_disappearing_link,
#else // compatibility with older Boehm GC versions
    (int (*)(void**, const void*))(&GC_general_register_disappearing_link),
#endif
    &GC_unregister_disappearing_link,
    &GC_get_heap_size,
    &GC_get_free_bytes,
    &GC_gcollect,
    &GC_enable,
    &GC_disable,
    &GC_free
};

Ops debug_ops = {
    &do_init,
    &debug_malloc,
    &debug_malloc_atomic,
    &debug_malloc_uncollectable,
    &debug_malloc_atomic_uncollectable,
    &debug_base,
    &GC_debug_register_finalizer_ignore_self,
    &debug_general_register_disappearing_link,
    &GC_unregister_disappearing_link,
    &GC_get_heap_size,
    &GC_get_free_bytes,
    &GC_gcollect,
    &GC_enable,
    &GC_disable,
    &GC_debug_free
};

Ops disabled_ops = {
    &dummy_do_init,
    &std::malloc,
    &std::malloc,
    &std::malloc,
    &std::malloc,
    &dummy_base,
    &dummy_register_finalizer,
    &dummy_general_register_disappearing_link,
    &dummy_unregister_disappearing_link,
    &dummy_get_heap_size,
    &dummy_get_free_bytes,
    &dummy_gcollect,
    &dummy_enable,
    &dummy_disable,
    &std::free
};

class InvalidGCModeError : public std::runtime_error {
public:
    InvalidGCModeError(const char *mode)
    : runtime_error(std::string("Unknown GC mode \"") + mode + "\"")
    {}
};

Ops const &get_ops() {
    char *mode_string=std::getenv("_INKSCAPE_GC");
    if (mode_string) {
        if (!std::strcmp(mode_string, "enable")) {
            return enabled_ops;
        } else if (!std::strcmp(mode_string, "debug")) {
            return debug_ops;
        } else if (!std::strcmp(mode_string, "disable")) {
            return disabled_ops;
        } else {
            throw InvalidGCModeError(mode_string);
        }
    } else {
        return enabled_ops;
    }
}

void die_because_not_initialized() {
    g_error("Attempt to use GC allocator before call to Inkscape::GC::init()");
}

void *stub_malloc(std::size_t) {
    die_because_not_initialized();
    return nullptr;
}

void *stub_base(void *) {
    die_because_not_initialized();
    return nullptr;
}

void stub_register_finalizer_ignore_self(void *, CleanupFunc, void *,
                                                 CleanupFunc *, void **)
{
    die_because_not_initialized();
}

int stub_general_register_disappearing_link(void **, void const *) {
    die_because_not_initialized();
    return 0;
}

int stub_unregister_disappearing_link(void **) {
    die_because_not_initialized();
    return 0;
}

std::size_t stub_get_heap_size() {
    die_because_not_initialized();
    return 0;
}

std::size_t stub_get_free_bytes() {
    die_because_not_initialized();
    return 0;
}

void stub_gcollect() {
    die_because_not_initialized();
}

void stub_enable() {
    die_because_not_initialized();
}

void stub_disable() {
    die_because_not_initialized();
}

void stub_free(void *) {
    die_because_not_initialized();
}

}

Ops Core::_ops = {
    nullptr,
    &stub_malloc,
    &stub_malloc,
    &stub_malloc,
    &stub_malloc,
    &stub_base,
    &stub_register_finalizer_ignore_self,
    &stub_general_register_disappearing_link,
    &stub_unregister_disappearing_link,
    &stub_get_heap_size,
    &stub_get_free_bytes,
    &stub_gcollect,
    &stub_enable,
    &stub_disable,
    &stub_free
};

void Core::init() {
    try {
        _ops = get_ops();
    } catch (InvalidGCModeError &e) {
        g_warning("%s; enabling normal collection", e.what());
        _ops = enabled_ops;
    }

    _ops.do_init();
}


namespace {

bool collection_requested=false;
bool collection_task() {
    Core::gcollect();
    Core::gcollect();
    collection_requested=false;
    return false;
}

}

void request_early_collection() {
    if (!collection_requested) {
        collection_requested=true;
        Glib::signal_idle().connect(sigc::ptr_fun(&collection_task));
    }
}

}
}

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
