/**
 * Written in the D programming language.
 * Module initialization routines.
 *
 * Copyright: Copyright Digital Mars 2000 - 2013.
 * License: Distributed under the
 *      $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0).
 *    (See accompanying file LICENSE)
 * Authors:   Walter Bright, Sean Kelly
 * Source: $(DRUNTIMESRC rt/_minfo.d)
 */

module rt.minfo;

import core.stdc.stdlib;  // alloca
import core.stdc.string;  // memcpy
import rt.sections;

enum
{
    MIctorstart  = 0x1,   // we've started constructing it
    MIctordone   = 0x2,   // finished construction
    MIstandalone = 0x4,   // module ctor does not depend on other module
                        // ctors being done first
    MItlsctor    = 8,
    MItlsdtor    = 0x10,
    MIctor       = 0x20,
    MIdtor       = 0x40,
    MIxgetMembers = 0x80,
    MIictor      = 0x100,
    MIunitTest   = 0x200,
    MIimportedModules = 0x400,
    MIlocalClasses = 0x800,
    MIname       = 0x1000,
}

/*****
 * A ModuleGroup is an unordered collection of modules.
 * There is exactly one for:
 *  1. all statically linked in D modules, either directely or as shared libraries
 *  2. each call to rt_loadLibrary()
 */

struct ModuleGroup
{
    this(immutable(ModuleInfo*)[] modules) nothrow @nogc
    {
        _modules = modules;
    }

    @property immutable(ModuleInfo*)[] modules() const nothrow @nogc
    {
        return _modules;
    }

    // this function initializes the bookeeping necessary to create the
    // cycle path, and then creates it. It is a precondition that src and
    // target modules are involved in a cycle.
    //
    // The return value is malloc'd using C, so it must be freed after use.
    private size_t[] genCyclePath(size_t srcidx, size_t targetidx, int[][] edges)
    {
        import core.bitop : bt, btc, bts;

        // set up all the arrays.
        size_t[] cyclePath = (cast(size_t*)malloc(size_t.sizeof * _modules.length * 2))[0 .. _modules.length * 2];
        size_t totalMods;
        int[] distance = (cast(int*)malloc(int.sizeof * _modules.length))[0 .. _modules.length];
        scope(exit)
            .free(distance.ptr);

        // determine the shortest path between two modules. Uses dijkstra
        // without a priority queue. (we can be a bit slow here, in order to
        // get a better printout).
        void shortest(size_t start, size_t target)
        {
            // initial setup
            distance[] = int.max;
            int curdist = 0;
            distance[start] = 0;
            while (true)
            {
                bool done = true;
                foreach (i, x; distance)
                {
                    if (x == curdist)
                    {
                        if (i == target)
                        {
                            done = true;
                            break;
                        }
                        foreach (n; edges[i])
                        {
                            if (distance[n] == int.max)
                            {
                                distance[n] = curdist + 1;
                                done = false;
                            }
                        }
                    }
                }
                if (done)
                    break;
                ++curdist;
            }
            // it should be impossible to not get to target, this is just a
            // sanity check. Not an assert, because druntime is compiled in
            // release mode.
            if (distance[target] != curdist)
            {
                throw new Error("internal error printing module cycle");
            }

            // determine the path. This is tricky, because we have to
            // follow the edges in reverse to get back to the original. We
            // don't have a reverse mapping, so it takes a bit of looping.
            totalMods += curdist;
            auto subpath = cyclePath[totalMods - curdist .. totalMods];
            while (true)
            {
                --curdist;
                subpath[curdist] = target;
                if (curdist == 0)
                    break;
            distloop:
                // search for next (previous) module in cycle.
                foreach (int m, d; distance)
                {
                    if (d == curdist)
                    {
                        // determine if m can reach target
                        foreach (e; edges[m])
                        {
                            if (e == target)
                            {
                                // recurse
                                target = m;
                                break distloop;
                            }
                        }
                    }
                }
            }
        }

        // first get to the target
        shortest(srcidx, targetidx);
        // now get back.
        shortest(targetidx, srcidx);

        return cyclePath[0 .. totalMods];
    }

    /******************************
     * Allocate and fill in _ctors[] and _tlsctors[].
     * Modules are inserted into the arrays in the order in which the constructors
     * need to be run.
     *
     * Params:
     *  cycleHandling - string indicating option for cycle handling
     * Throws:
     *  Exception if it fails.
     */
    void sortCtors(string cycleHandling)
    {
        import core.bitop : bts, btr, bt, BitRange;
        import rt.util.container.hashtab;

        enum OnCycle
        {
            deprecate,
            abort,
            print,
            ignore
        }

        auto onCycle = OnCycle.abort;

        switch(cycleHandling) with(OnCycle)
        {
        case "deprecate":
            onCycle = deprecate;
            break;
        case "abort":
            onCycle = abort;
            break;
        case "print":
            onCycle = print;
            break;
        case "ignore":
            onCycle = ignore;
            break;
        case "":
            // no option passed
            break;
        default:
            // invalid cycle handling option.
            throw new Error("DRT invalid cycle handling option: " ~ cycleHandling);
        }

        debug (printModuleDependencies)
        {
            import core.stdc.stdio : printf;

            foreach (_m; _modules)
            {
                printf("%s%s%s:", _m.name.ptr, (_m.flags & MIstandalone)
                        ? "+".ptr : "".ptr, (_m.flags & (MIctor | MIdtor)) ? "*".ptr : "".ptr);
                foreach (_i; _m.importedModules)
                    printf(" %s", _i.name.ptr);
                printf("\n");
            }
        }

        immutable uint len = cast(uint) _modules.length;
        if (!len)
            return; // nothing to do.

        // allocate some stack arrays that will be used throughout the process.
        immutable nwords = (len + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof);
        immutable flagbytes = nwords * size_t.sizeof;
        auto ctorstart = cast(size_t*) malloc(flagbytes); // ctor/dtor seen
        auto ctordone = cast(size_t*) malloc(flagbytes); // ctor/dtor processed
        auto relevant = cast(size_t*) malloc(flagbytes); // has ctors/dtors
        scope (exit)
        {
            .free(ctorstart);
            .free(ctordone);
            .free(relevant);
        }

        void clearFlags(size_t* flags)
        {
            memset(flags, 0, flagbytes);
        }


        // build the edges between each module. We may need this for printing,
        // and also allows avoiding keeping a hash around for module lookups.
        int[][] edges = (cast(int[]*)malloc((int[]).sizeof * _modules.length))[0 .. _modules.length];
        {
            HashTab!(immutable(ModuleInfo)*, int) modIndexes;
            foreach (i, m; _modules)
                modIndexes[m] = cast(int) i;

            auto reachable = cast(size_t*) malloc(flagbytes);
            scope(exit)
                .free(reachable);

            foreach (i, m; _modules)
            {
                // use bit array to prevent duplicates
                // https://issues.dlang.org/show_bug.cgi?id=16208
                clearFlags(reachable);
                // preallocate enough space to store all the indexes
                int *edge = cast(int*)malloc(int.sizeof * _modules.length);
                size_t nEdges = 0;
                foreach (imp; m.importedModules)
                {
                    if (imp is m) // self-import
                        continue;
                    if (auto impidx = imp in modIndexes)
                    {
                        if (!bts(reachable, *impidx))
                            edge[nEdges++] = *impidx;
                    }
                }
                // trim space to what is needed.
                edges[i] = (cast(int*)realloc(edge, int.sizeof * nEdges))[0 .. nEdges];
            }
        }

        // free all the edges after we are done
        scope(exit)
        {
            foreach(e; edges)
                if(e.ptr)
                    .free(e.ptr);
            .free(edges.ptr);
        }

        void buildCycleMessage(size_t sourceIdx, size_t cycleIdx, scope void delegate(string) sink)
        {
            version (Windows)
                enum EOL = "\r\n";
            else
                enum EOL = "\n";

            sink("Cyclic dependency between module ");
            sink(_modules[sourceIdx].name);
            sink(" and ");
            sink(_modules[cycleIdx].name);
            sink(EOL);
            auto cyclePath = genCyclePath(sourceIdx, cycleIdx, edges);
            scope(exit) .free(cyclePath.ptr);

            sink(_modules[sourceIdx].name);
            sink("* ->" ~ EOL);
            foreach (x; cyclePath[0 .. $ - 1])
            {
                sink(_modules[x].name);
                sink(bt(relevant, x) ? "* ->" ~ EOL : " ->" ~ EOL);
            }
            sink(_modules[sourceIdx].name);
            sink("*" ~ EOL);
        }

        // find all the non-trivial dependencies (that is, dependencies that have a
        // ctor or dtor) of a given module.  Doing this, we can 'skip over' the
        // trivial modules to get at the non-trivial ones.
        //
        // If a cycle is detected, returns the index of the module that completes the cycle.
        // Returns: true for success, false for a deprecated cycle error
        bool findDeps(size_t idx, size_t* reachable)
        {
            static struct stackFrame
            {
                size_t curMod;
                size_t curDep;
            }

            // initialize "stack"
            auto stack = cast(stackFrame*) malloc(stackFrame.sizeof * len);
            scope (exit)
                .free(stack);
            auto stacktop = stack + len;
            auto sp = stack;
            sp.curMod = cast(int) idx;
            sp.curDep = 0;

            // initialize reachable by flagging source module
            clearFlags(reachable);
            bts(reachable, idx);

            for (;;)
            {
                auto m = _modules[sp.curMod];
                if (sp.curDep >= edges[sp.curMod].length)
                {
                    // return
                    if (sp == stack) // finished the algorithm
                        break;
                    --sp;
                }
                else
                {
                    auto midx = edges[sp.curMod][sp.curDep];
                    if (!bts(reachable, midx))
                    {
                        if (bt(relevant, midx))
                        {
                            // need to process this node, don't recurse.
                            if (bt(ctorstart, midx))
                            {
                                // was already started, this is a cycle.
                                final switch(onCycle) with(OnCycle)
                                {
                                case deprecate:
                                    // check with old algorithm
                                    if(sortCtorsOld(edges))
                                    {
                                        // unwind to print deprecation message.
                                        return false;   // deprecated cycle error
                                    }
                                    goto case abort; // fall through
                                case abort:

                                    string errmsg = "";
                                    buildCycleMessage(idx, midx, (string x) {errmsg ~= x;});
                                    throw new Error(errmsg, __FILE__, __LINE__);
                                case ignore:
                                    break;
                                case print:
                                    // print the message
                                    buildCycleMessage(idx, midx, (string x) {
                                                      import core.stdc.stdio : fprintf, stderr;
                                                      fprintf(stderr, "%.*s", cast(int) x.length, x.ptr);
                                                      });
                                    // continue on as if this is correct.
                                    break;
                                }
                            }
                        }
                        else if (!bt(ctordone, midx))
                        {
                            // non-relevant, and hasn't been exhaustively processed, recurse.
                            if (++sp >= stacktop)
                            {
                                // stack overflow, this shouldn't happen.
                                import core.internal.abort : abort;

                                abort("stack overflow on dependency search");
                            }
                            sp.curMod = midx;
                            sp.curDep = 0;
                            continue;
                        }
                    }
                }

                // next dependency
                ++sp.curDep;
            }
            return true; // success
        }

        // The list of constructors that will be returned by the sorting.
        immutable(ModuleInfo)** ctors;
        // current element being inserted into ctors list.
        size_t ctoridx = 0;

        // This function will determine the order of construction/destruction and
        // check for cycles. If a cycle is found, the cycle path is transformed
        // into a string and thrown as an error.
        //
        // Each call into this function is given a module that has static
        // ctor/dtors that must be dealt with. It recurses only when it finds
        // dependencies that also have static ctor/dtors.
        // Returns: true for success, false for a deprecated cycle error
        bool processMod(size_t curidx)
        {
            immutable ModuleInfo* current = _modules[curidx];

            // First, determine what modules are reachable.
            auto reachable = cast(size_t*) malloc(flagbytes);
            scope (exit)
                .free(reachable);
            if (!findDeps(curidx, reachable))
                return false;   // deprecated cycle error

            // process the dependencies. First, we process all relevant ones
            bts(ctorstart, curidx);
            auto brange = BitRange(reachable, len);
            foreach (i; brange)
            {
                // note, don't check for cycles here, because the config could have been set to ignore cycles.
                // however, don't recurse if there is one, so still check for started ctor.
                if (i != curidx && bt(relevant, i) && !bt(ctordone, i) && !bt(ctorstart, i))
                {
                    if (!processMod(i))
                        return false; // deprecated cycle error
                }
            }

            // now mark this node, and all nodes reachable from this module as done.
            bts(ctordone, curidx);
            btr(ctorstart, curidx);
            foreach (i; brange)
            {
                // Since relevant dependencies are already marked as done
                // from recursion above (or are going to be handled up the call
                // stack), no reason to check for relevance, that is a wasted
                // op.
                bts(ctordone, i);
            }

            // add this module to the construction order list
            ctors[ctoridx++] = current;
            return true;
        }

        // returns `false` if deprecated cycle error otherwise set `result`.
        bool doSort(size_t relevantFlags, ref immutable(ModuleInfo)*[] result)
        {
            clearFlags(relevant);
            clearFlags(ctorstart);
            clearFlags(ctordone);

            // pre-allocate enough space to hold all modules.
            ctors = (cast(immutable(ModuleInfo)**).malloc(len * (void*).sizeof));
            ctoridx = 0;
            foreach (int idx, m; _modules)
            {
                if (m.flags & relevantFlags)
                {
                    if (m.flags & MIstandalone)
                    {
                        // can run at any time. Just run it first.
                        ctors[ctoridx++] = m;
                    }
                    else
                    {
                        bts(relevant, idx);
                    }
                }
            }

            // now run the algorithm in the relevant ones
            foreach (idx; BitRange(relevant, len))
            {
                if (!bt(ctordone, idx))
                {
                    if (!processMod(idx))
                        return false;
                }
            }

            if (ctoridx == 0)
            {
                // no ctors in the list.
                .free(ctors);
            }
            else
            {
                ctors = cast(immutable(ModuleInfo)**).realloc(ctors, ctoridx * (void*).sizeof);
                if (ctors is null)
                    assert(0);
                result = ctors[0 .. ctoridx];
            }
            return true;
        }

        // finally, do the sorting for both shared and tls ctors. If either returns false,
        // print the deprecation warning.
        if (!doSort(MIctor | MIdtor, _ctors) ||
            !doSort(MItlsctor | MItlsdtor, _tlsctors))
        {
            // print a warning
            import core.stdc.stdio : fprintf, stderr;
            fprintf(stderr, "Deprecation 16211 warning:\n"
                ~ "A cycle has been detected in your program that was undetected prior to DMD\n"
                ~ "2.072. This program will continue, but will not operate when using DMD 2.074\n"
                ~ "to compile. Use runtime option --DRT-oncycle=print to see the cycle details.\n");

        }
    }

    /// ditto
    void sortCtors()
    {
        import rt.config : rt_configOption;
        sortCtors(rt_configOption("oncycle"));
    }

    /******************************
     * This is the old ctor sorting algorithm that does not find all cycles.
     *
     * It is here to allow the deprecated behavior from the original algorithm
     * until people have fixed their code.
     *
     * If no cycles are found, the _ctors and _tlsctors are replaced with the
     * ones generated by this algorithm to preserve the old incorrect ordering
     * behavior.
     *
     * Params:
     *   edges = The module edges as found in the `importedModules` member of
     *          each ModuleInfo. Generated in sortCtors.
     * Returns:
     *   true if no cycle is found, false if one was.
     */
    bool sortCtorsOld(int[][] edges)
    {
        immutable len = edges.length;
        assert(len == _modules.length);

        static struct StackRec
        {
            @property int mod()
            {
                return _mods[_idx];
            }

            int[] _mods;
            size_t         _idx;
        }

        auto stack = (cast(StackRec*).calloc(len, StackRec.sizeof))[0 .. len];
        // TODO: reuse GCBits by moving it to rt.util.container or core.internal
        immutable nwords = (len + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof);
        auto ctorstart = cast(size_t*).malloc(nwords * size_t.sizeof);
        auto ctordone = cast(size_t*).malloc(nwords * size_t.sizeof);
        int[] initialEdges = (cast(int *)malloc(int.sizeof * len))[0 .. len];
        if (!stack.ptr || ctorstart is null || ctordone is null || !initialEdges.ptr)
            assert(0);
        scope (exit)
        {
            .free(stack.ptr);
            .free(ctorstart);
            .free(ctordone);
            .free(initialEdges.ptr);
        }

        // initialize the initial edges
        foreach (int i, ref v; initialEdges)
            v = i;

        bool sort(ref immutable(ModuleInfo)*[] ctors, uint mask)
        {
            import core.bitop;

            ctors = (cast(immutable(ModuleInfo)**).malloc(len * size_t.sizeof))[0 .. len];
            if (!ctors.ptr)
                assert(0);

            // clean flags
            memset(ctorstart, 0, nwords * size_t.sizeof);
            memset(ctordone, 0, nwords * size_t.sizeof);
            size_t stackidx = 0;
            size_t cidx;

            int[] mods = initialEdges;

            size_t idx;
            while (true)
            {
                while (idx < mods.length)
                {
                    auto m = mods[idx];

                    if (bt(ctordone, m))
                    {
                        // this module has already been processed, skip
                        ++idx;
                        continue;
                    }
                    else if (bt(ctorstart, m))
                    {
                        /* Trace back to the begin of the cycle.
                         */
                        bool ctorInCycle;
                        size_t start = stackidx;
                        while (start--)
                        {
                            auto sm = stack[start].mod;
                            if (sm == m)
                                break;
                            assert(sm >= 0);
                            if (bt(ctorstart, sm))
                                ctorInCycle = true;
                        }
                        assert(stack[start].mod == m);
                        if (ctorInCycle)
                        {
                            return false;
                        }
                        else
                        {
                            /* This is also a cycle, but the import chain does not constrain
                             * the order of initialization, either because the imported
                             * modules have no ctors or the ctors are standalone.
                             */
                            ++idx;
                        }
                    }
                    else
                    {
                        auto curmod = _modules[m];
                        if (curmod.flags & mask)
                        {
                            if (curmod.flags & MIstandalone || !edges[m].length)
                            {   // trivial ctor => sort in
                                ctors[cidx++] = curmod;
                                bts(ctordone, m);
                            }
                            else
                            {   // non-trivial ctor => defer
                                bts(ctorstart, m);
                            }
                        }
                        else    // no ctor => mark as visited
                        {
                            bts(ctordone, m);
                        }

                        if (edges[m].length)
                        {
                            /* Internal runtime error, recursion exceeds number of modules.
                             */
                            (stackidx < len) || assert(0);

                            // recurse
                            stack[stackidx++] = StackRec(mods, idx);
                            idx  = 0;
                            mods = edges[m];
                        }
                    }
                }

                if (stackidx)
                {   // pop old value from stack
                    --stackidx;
                    mods    = stack[stackidx]._mods;
                    idx     = stack[stackidx]._idx;
                    auto m  = mods[idx++];
                    if (bt(ctorstart, m) && !bts(ctordone, m))
                        ctors[cidx++] = _modules[m];
                }
                else // done
                    break;
            }
            // store final number and shrink array
            ctors = (cast(immutable(ModuleInfo)**).realloc(ctors.ptr, cidx * size_t.sizeof))[0 .. cidx];
            return true;
        }

        /* Do two passes: ctor/dtor, tlsctor/tlsdtor
         */
        immutable(ModuleInfo)*[] _ctors2;
        immutable(ModuleInfo)*[] _tlsctors2;
        auto result = sort(_ctors2, MIctor | MIdtor) && sort(_tlsctors2, MItlsctor | MItlsdtor);
        if (result) // no cycle
        {
            // fall back to original ordering as part of the deprecation.
            if(_ctors.ptr)
                .free(_ctors.ptr);
            _ctors = _ctors2;
            if(_tlsctors.ptr)
                .free(_tlsctors.ptr);
            _tlsctors = _tlsctors2;
        }
        else
        {
            // free any allocated memory that will be forgotten
            if (_ctors2.ptr)
                .free(_ctors2.ptr);
            if (_tlsctors2.ptr)
                .free(_tlsctors2.ptr);
        }
        return result;
    }

    void runCtors()
    {
        // run independent ctors
        runModuleFuncs!(m => m.ictor)(_modules);
        // sorted module ctors
        runModuleFuncs!(m => m.ctor)(_ctors);
    }

    void runTlsCtors()
    {
        runModuleFuncs!(m => m.tlsctor)(_tlsctors);
    }

    void runTlsDtors()
    {
        runModuleFuncsRev!(m => m.tlsdtor)(_tlsctors);
    }

    void runDtors()
    {
        runModuleFuncsRev!(m => m.dtor)(_ctors);
    }

    void free()
    {
        if (_ctors.ptr)
            .free(_ctors.ptr);
        _ctors = null;
        if (_tlsctors.ptr)
            .free(_tlsctors.ptr);
        _tlsctors = null;
        // _modules = null; // let the owner free it
    }

private:
    immutable(ModuleInfo*)[]  _modules;
    immutable(ModuleInfo)*[]    _ctors;
    immutable(ModuleInfo)*[] _tlsctors;
}


/********************************************
 * Iterate over all module infos.
 */

int moduleinfos_apply(scope int delegate(immutable(ModuleInfo*)) dg)
{
    foreach (ref sg; SectionGroup)
    {
        foreach (m; sg.modules)
        {
            // TODO: Should null ModuleInfo be allowed?
            if (m !is null)
            {
                if (auto res = dg(m))
                    return res;
            }
        }
    }
    return 0;
}

/********************************************
 * Module constructor and destructor routines.
 */

extern (C)
{
void rt_moduleCtor()
{
    foreach (ref sg; SectionGroup)
    {
        sg.moduleGroup.sortCtors();
        sg.moduleGroup.runCtors();
    }
}

void rt_moduleTlsCtor()
{
    foreach (ref sg; SectionGroup)
    {
        sg.moduleGroup.runTlsCtors();
    }
}

void rt_moduleTlsDtor()
{
    foreach_reverse (ref sg; SectionGroup)
    {
        sg.moduleGroup.runTlsDtors();
    }
}

void rt_moduleDtor()
{
    foreach_reverse (ref sg; SectionGroup)
    {
        sg.moduleGroup.runDtors();
        sg.moduleGroup.free();
    }
}

version (Win32)
{
    // Alternate names for backwards compatibility with older DLL code
    void _moduleCtor()
    {
        rt_moduleCtor();
    }

    void _moduleDtor()
    {
        rt_moduleDtor();
    }

    void _moduleTlsCtor()
    {
        rt_moduleTlsCtor();
    }

    void _moduleTlsDtor()
    {
        rt_moduleTlsDtor();
    }
}
}

/********************************************
 */

void runModuleFuncs(alias getfp)(const(immutable(ModuleInfo)*)[] modules)
{
    foreach (m; modules)
    {
        if (auto fp = getfp(m))
            (*fp)();
    }
}

void runModuleFuncsRev(alias getfp)(const(immutable(ModuleInfo)*)[] modules)
{
    foreach_reverse (m; modules)
    {
        if (auto fp = getfp(m))
            (*fp)();
    }
}

unittest
{
    static void assertThrown(T : Throwable, E)(lazy E expr, string msg)
    {
        try
            expr;
        catch (T)
            return;
        assert(0, msg);
    }

    static void stub()
    {
    }

    static struct UTModuleInfo
    {
        this(uint flags)
        {
            mi._flags = flags;
        }

        void setImports(immutable(ModuleInfo)*[] imports...)
        {
            import core.bitop;
            assert(flags & MIimportedModules);

            immutable nfuncs = popcnt(flags & (MItlsctor|MItlsdtor|MIctor|MIdtor|MIictor));
            immutable size = nfuncs * (void function()).sizeof +
                size_t.sizeof + imports.length * (ModuleInfo*).sizeof;
            assert(size <= pad.sizeof);

            pad[nfuncs] = imports.length;
            .memcpy(&pad[nfuncs+1], imports.ptr, imports.length * imports[0].sizeof);
        }

        immutable ModuleInfo mi;
        size_t[8] pad;
        alias mi this;
    }

    static UTModuleInfo mockMI(uint flags)
    {
        auto mi = UTModuleInfo(flags | MIimportedModules);
        auto p = cast(void function()*)&mi.pad;
        if (flags & MItlsctor) *p++ = &stub;
        if (flags & MItlsdtor) *p++ = &stub;
        if (flags & MIctor) *p++ = &stub;
        if (flags & MIdtor) *p++ = &stub;
        if (flags & MIictor) *p++ = &stub;
        *cast(size_t*)p++ = 0; // number of imported modules
        assert(cast(void*)p <= &mi + 1);
        return mi;
    }

    static void checkExp2(string testname, bool shouldThrow, string oncycle,
        immutable(ModuleInfo*)[] modules,
        immutable(ModuleInfo*)[] dtors=null,
        immutable(ModuleInfo*)[] tlsdtors=null)
    {
        auto mgroup = ModuleGroup(modules);
        mgroup.sortCtors(oncycle);

        // if we are expecting sort to throw, don't throw because of unexpected
        // success!
        if (!shouldThrow)
        {
            foreach (m; mgroup._modules)
                assert(!(m.flags & (MIctorstart | MIctordone)), testname);
            assert(mgroup._ctors    == dtors, testname);
            assert(mgroup._tlsctors == tlsdtors, testname);
        }
    }

    static void checkExp(string testname, bool shouldThrow,
        immutable(ModuleInfo*)[] modules,
        immutable(ModuleInfo*)[] dtors=null,
        immutable(ModuleInfo*)[] tlsdtors=null)
    {
        checkExp2(testname, shouldThrow, "abort", modules, dtors, tlsdtors);
    }


    {
        auto m0 = mockMI(0);
        auto m1 = mockMI(0);
        auto m2 = mockMI(0);
        checkExp("no ctors", false, [&m0.mi, &m1.mi, &m2.mi]);
    }

    {
        auto m0 = mockMI(MIictor);
        auto m1 = mockMI(0);
        auto m2 = mockMI(MIictor);
        auto mgroup = ModuleGroup([&m0.mi, &m1.mi, &m2.mi]);
        checkExp("independent ctors", false, [&m0.mi, &m1.mi, &m2.mi]);
    }

    {
        auto m0 = mockMI(MIstandalone | MIctor);
        auto m1 = mockMI(0);
        auto m2 = mockMI(0);
        auto mgroup = ModuleGroup([&m0.mi, &m1.mi, &m2.mi]);
        checkExp("standalone ctor", false, [&m0.mi, &m1.mi, &m2.mi], [&m0.mi]);
    }

    {
        auto m0 = mockMI(MIstandalone | MIctor);
        auto m1 = mockMI(MIstandalone | MIctor);
        auto m2 = mockMI(0);
        m1.setImports(&m0.mi);
        checkExp("imported standalone => no dependency", false,
                 [&m0.mi, &m1.mi, &m2.mi], [&m0.mi, &m1.mi]);
    }

    {
        auto m0 = mockMI(MIstandalone | MIctor);
        auto m1 = mockMI(MIstandalone | MIctor);
        auto m2 = mockMI(0);
        m0.setImports(&m1.mi);
        checkExp("imported standalone => no dependency (2)", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m0.mi, &m1.mi]);
    }

    {
        auto m0 = mockMI(MIstandalone | MIctor);
        auto m1 = mockMI(MIstandalone | MIctor);
        auto m2 = mockMI(0);
        m0.setImports(&m1.mi);
        m1.setImports(&m0.mi);
        checkExp("standalone may have cycle", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m0.mi, &m1.mi]);
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(0);
        m1.setImports(&m0.mi);
        checkExp("imported ctor => ordered ctors", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m0.mi, &m1.mi], []);
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(0);
        m0.setImports(&m1.mi);
        checkExp("imported ctor => ordered ctors (2)", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m1.mi, &m0.mi], []);
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(0);
        m0.setImports(&m1.mi);
        m1.setImports(&m0.mi);
        assertThrown!Throwable(checkExp("", true, [&m0.mi, &m1.mi, &m2.mi]),
                "detects ctors cycles");
        assertThrown!Throwable(checkExp2("", true, "deprecate",
                                        [&m0.mi, &m1.mi, &m2.mi]),
                "detects ctors cycles (dep)");
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(0);
        m0.setImports(&m2.mi);
        m1.setImports(&m2.mi);
        m2.setImports(&m0.mi, &m1.mi);
        assertThrown!Throwable(checkExp("", true, [&m0.mi, &m1.mi, &m2.mi]),
                "detects cycle with repeats");
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(MItlsctor);
        m0.setImports(&m1.mi, &m2.mi);
        checkExp("imported ctor/tlsctor => ordered ctors/tlsctors", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m1.mi, &m0.mi], [&m2.mi]);
    }

    {
        auto m0 = mockMI(MIctor | MItlsctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(MItlsctor);
        m0.setImports(&m1.mi, &m2.mi);
        checkExp("imported ctor/tlsctor => ordered ctors/tlsctors (2)", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m1.mi, &m0.mi], [&m2.mi, &m0.mi]);
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(MItlsctor);
        m0.setImports(&m1.mi, &m2.mi);
        m2.setImports(&m0.mi);
        checkExp("no cycle between ctors/tlsctors", false,
                [&m0.mi, &m1.mi, &m2.mi], [&m1.mi, &m0.mi], [&m2.mi]);
    }

    {
        auto m0 = mockMI(MItlsctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(MItlsctor);
        m0.setImports(&m2.mi);
        m2.setImports(&m0.mi);
        assertThrown!Throwable(checkExp("", true, [&m0.mi, &m1.mi, &m2.mi]),
                "detects tlsctors cycle");
        assertThrown!Throwable(checkExp2("", true, "deprecate",
                                         [&m0.mi, &m1.mi, &m2.mi]),
                "detects tlsctors cycle (dep)");
    }

    {
        auto m0 = mockMI(MItlsctor);
        auto m1 = mockMI(MIctor);
        auto m2 = mockMI(MItlsctor);
        m0.setImports(&m1.mi);
        m1.setImports(&m0.mi, &m2.mi);
        m2.setImports(&m1.mi);
        assertThrown!Throwable(checkExp("", true, [&m0.mi, &m1.mi, &m2.mi]),
                "detects tlsctors cycle with repeats");
    }

    {
        auto m0 = mockMI(MIctor);
        auto m1 = mockMI(MIstandalone | MIctor);
        auto m2 = mockMI(MIstandalone | MIctor);
        m0.setImports(&m1.mi);
        m1.setImports(&m2.mi);
        m2.setImports(&m0.mi);
        // NOTE: this is implementation dependent, sorted order shouldn't be tested.
        checkExp("closed ctors cycle", false, [&m0.mi, &m1.mi, &m2.mi],
                [&m1.mi, &m2.mi, &m0.mi]);
        //checkExp("closed ctors cycle", false, [&m0.mi, &m1.mi, &m2.mi], [&m0.mi, &m1.mi, &m2.mi]);
    }
}

version (CRuntime_Microsoft)
{
    // Dummy so Win32 code can still call it
    extern(C) void _minit() { }
}
