/**
 This module contains utility functions to help the implementation of the runtime hook

  Copyright: Copyright Digital Mars 2000 - 2019.
  License: Distributed under the
       $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
     (See accompanying file LICENSE)
  Source: $(DRUNTIMESRC core/internal/_array/_utils.d)
*/
module core.internal.array.utils;

import core.internal.traits : Parameters;

private auto gcStatsPure() nothrow pure
{
    import core.memory : GC;

    auto impureBypass = cast(GC.Stats function() pure nothrow)&GC.stats;
    return impureBypass();
}

private ulong accumulatePure(string file, int line, string funcname, string name, ulong size) nothrow pure
{
    static ulong impureBypass(string file, int line, string funcname, string name, ulong size) @nogc nothrow
    {
        import core.internal.traits : externDFunc;

        alias accumulate = externDFunc!("rt.profilegc.accumulate", void function(string file, uint line, string funcname, string type, ulong sz) @nogc nothrow);
        accumulate(file, line, funcname, name, size);
        return size;
    }

    auto func = cast(ulong function(string file, int line, string funcname, string name, ulong size) @nogc nothrow pure)&impureBypass;
    return func(file, line, funcname, name, size);
}

/**
 * TraceGC wrapper around runtime hook `Hook`.
 * Params:
 *  T = Type of hook to report to accumulate
 *  Hook = The hook to wrap
 *  errorMessage = The error message incase `version != D_TypeInfo`
 *  file = File that called `_d_HookTraceImpl`
 *  line = Line inside of `file` that called `_d_HookTraceImpl`
 *  funcname = Function that called `_d_HookTraceImpl`
 *  parameters = Parameters that will be used to call `Hook`
 * Bugs:
 *  This function template needs be between the compiler and a much older runtime hook that bypassed safety,
 *  purity, and throwabilty checks. To prevent breaking existing code, this function template
 *  is temporarily declared `@trusted pure` until the implementation can be brought up to modern D expectations.
*/
auto _d_HookTraceImpl(T, alias Hook, string errorMessage)(string file, int line, string funcname, Parameters!Hook parameters) @trusted pure
{
    version (D_TypeInfo)
    {
        pragma(inline, false);
        string name = T.stringof;

        // FIXME: use rt.tracegc.accumulator when it is accessable in the future.
        version (tracegc)
        {
            import core.stdc.stdio;

            printf("%sTrace file = '%.*s' line = %d function = '%.*s' type = %.*s\n",
                Hook.stringof.ptr,
                file.length, file.ptr,
                line,
                funcname.length, funcname.ptr,
                name.length, name.ptr
            );
        }

        ulong currentlyAllocated = gcStatsPure().allocatedInCurrentThread;

        scope(exit)
        {
            ulong size = gcStatsPure().allocatedInCurrentThread - currentlyAllocated;
            if (size > 0)
                if (!accumulatePure(file, line, funcname, name, size)) {
                    // This 'if' and 'assert' is needed to force the compiler to not remove the call to
                    // `accumulatePure`. It really want to do that while optimizing as the function is
                    // `pure` and it does not influence the result of this hook.

                    // `accumulatePure` returns the value of `size`, which can never be zero due to the
                    // previous 'if'. So this assert will never be triggered.
                    assert(0);
                }
        }
        return Hook(parameters);
    }
    else
        assert(0, errorMessage);
}

/**
 * Check if the function `F` is calleable in a `nothrow` scope.
 * Params:
 *  F = Function that does not take any parameters
 * Returns:
 *  if the function is callable in a `nothrow` scope.
 */
enum isNoThrow(alias F) = is(typeof(() nothrow { F(); }));

/**
 * Check if the type `T`'s postblit is called in nothrow, if it exist
 * Params:
 *  T = Type to check
 * Returns:
 *  if the postblit is callable in a `nothrow` scope, if it exist.
 *  if it does not exist, return true.
 */
template isPostblitNoThrow(T) {
    static if (__traits(isStaticArray, T))
        enum isPostblitNoThrow = isPostblitNoThrow!(typeof(T.init[0]));
    else static if (__traits(hasMember, T, "__xpostblit") &&
        // Bugzilla 14746: Check that it's the exact member of S.
        __traits(isSame, T, __traits(parent, T.init.__xpostblit)))
        enum isPostblitNoThrow = isNoThrow!(T.init.__xpostblit);
    else
        enum isPostblitNoThrow = true;
}
