| /** |
| * 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 src/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 (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 (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 (i, ref v; initialEdges) |
| v = cast(int)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() { } |
| } |