From 04efd105d4885c4bfbef4fd8e718716f41da54d0 Mon Sep 17 00:00:00 2001 From: Walter Bright Date: Fri, 23 Aug 2013 01:40:47 -0700 Subject: [PATCH] add support for linux dynamic dll loading --- src/core/thread.d | 16 ++++-- src/gc/proxy.d | 2 + src/rt/deh_win64_posix.d | 2 +- src/rt/dmain2.d | 73 ++++++++++++++++--------- src/rt/minfo.d | 39 ++++++++++++- src/rt/sections_linux.d | 115 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 206 insertions(+), 41 deletions(-) diff --git a/src/core/thread.d b/src/core/thread.d index 156ac6ea5e..e4633971ab 100644 --- a/src/core/thread.d +++ b/src/core/thread.d @@ -1393,13 +1393,12 @@ private: lock[] = Mutex.classinfo.init[]; (cast(Mutex)lock.ptr).__ctor(); } + } - extern(C) void destroy() - { - foreach (ref lock; _locks) - (cast(Mutex)lock.ptr).__dtor(); - } - atexit(&destroy); + static void termLocks() + { + foreach (ref lock; _locks) + (cast(Mutex)lock.ptr).__dtor(); } __gshared Context* sm_cbeg; @@ -1725,6 +1724,11 @@ extern (C) void thread_init() Thread.sm_main = thread_attachThis(); } +extern (C) void thread_term() +{ + Thread.termLocks(); +} + /** * diff --git a/src/gc/proxy.d b/src/gc/proxy.d index 6fa11755f0..f0eba7711e 100644 --- a/src/gc/proxy.d +++ b/src/gc/proxy.d @@ -29,6 +29,7 @@ private __gshared gc_t _gc; extern (C) void thread_init(); + extern (C) void thread_term(); struct Proxy { @@ -138,6 +139,7 @@ extern (C) // static data area, roots, and ranges. _gc.Dtor(); + thread_term(); free(cast(void*)_gc); _gc = null; } diff --git a/src/rt/deh_win64_posix.d b/src/rt/deh_win64_posix.d index 8772373a03..844e3756e9 100644 --- a/src/rt/deh_win64_posix.d +++ b/src/rt/deh_win64_posix.d @@ -115,7 +115,7 @@ immutable(FuncTable)* __eh_finddata(void *address) return null; } -immutable(FuncTable)* __eh_finddata(void *address, immutable(FuncTable)* pstart, immutable(FuncTable)* pend) +nothrow immutable(FuncTable)* __eh_finddata(void *address, immutable(FuncTable)* pstart, immutable(FuncTable)* pend) { debug(PRINTF) printf("FuncTable.sizeof = %p\n", FuncTable.sizeof); debug(PRINTF) printf("__eh_finddata(address = %p)\n", address); diff --git a/src/rt/dmain2.d b/src/rt/dmain2.d index db2d7e8f3e..8b2f6fe283 100644 --- a/src/rt/dmain2.d +++ b/src/rt/dmain2.d @@ -243,7 +243,6 @@ extern (C) bool rt_init(ExceptionHandler dg = null) initStaticDataGC(); rt_moduleCtor(); rt_moduleTlsCtor(); - runModuleUnitTests(); return true; } catch (Throwable e) @@ -251,13 +250,12 @@ extern (C) bool rt_init(ExceptionHandler dg = null) /* Note that if we get here, the runtime is in an unknown state. * I'm not sure what the point of calling dg is. */ - if (dg) - dg(e); - else + if (!dg) throw e; // rethrow, don't silently ignore error - /* Rethrow, and the two STD functions aren't called? - * This needs rethinking. - */ + /* Rethrow, and the two STD functions aren't called? + * This needs rethinking. + */ + dg(e); } _STD_critical_term(); _STD_monitor_staticdtor(); @@ -284,8 +282,9 @@ extern (C) bool rt_term(ExceptionHandler dg = null) } catch (Throwable e) { - if (dg) - dg(e); + if (!dg) + throw e; // rethrow, don't silently ignore error + dg(e); } finally { @@ -375,9 +374,6 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) } } - _STI_monitor_staticctor(); - _STI_critical_init(); - // Allocate args[] on the stack char[][] args = (cast(char[]*) alloca(argc * (char[]).sizeof))[0 .. argc]; @@ -560,27 +556,27 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) void runAll() { - initSections(); - gc_init(); - initStaticDataGC(); - rt_moduleCtor(); - rt_moduleTlsCtor(); + version (linux) + { } + else + rt_init(); + if (runModuleUnitTests()) tryExec(&runMain); else result = EXIT_FAILURE; - rt_moduleTlsDtor(); - thread_joinAll(); - rt_moduleDtor(); - gc_term(); - finiSections(); + + version (linux) + { } + else + { + if (!rt_term() && result == EXIT_SUCCESS) + result = EXIT_FAILURE; + } } tryExec(&runAll); - _STD_critical_term(); - _STD_monitor_staticdtor(); - // Issue 10344: flush stdout and return nonzero on failure if (.fflush(.stdout) != 0) { @@ -593,3 +589,30 @@ extern (C) int _d_run_main(int argc, char **argv, MainFunc mainFunc) return result; } + +version (linux) +{ +/* Startup and shutdown is done with static construction/destruction + */ + +//import core.stdc.stdio; +shared static this() +{ + //printf("dmain2\n"); + _STI_monitor_staticctor(); + _STI_critical_init(); + initSections(); + gc_init(); + initStaticDataGC(); +} + +shared static ~this() +{ + //printf("~dmain2\n"); + //thread_joinAll(); + gc_term(); + finiSections(); + _STD_critical_term(); + _STD_monitor_staticdtor(); +} +} diff --git a/src/rt/minfo.d b/src/rt/minfo.d index 2aa7c368dc..1ffc431b2d 100644 --- a/src/rt/minfo.d +++ b/src/rt/minfo.d @@ -12,8 +12,9 @@ module rt.minfo; -import core.stdc.stdlib; // alloca -import core.stdc.string; // memcpy +import core.stdc.stdlib; +import core.stdc.string; +import core.stdc.stdio : printf; import rt.sections; enum @@ -46,6 +47,7 @@ struct ModuleGroup this(ModuleInfo*[] modules) { _modules = modules; + first = null; } @property inout(ModuleInfo*)[] modules() inout @@ -92,6 +94,7 @@ struct ModuleGroup size_t cidx; ModuleInfo*[] mods = _modules; + size_t idx; while (true) { @@ -169,6 +172,8 @@ struct ModuleGroup stack[stackidx++] = StackRec(mods, idx); idx = 0; mods = m.importedModules; + //printf("m %.*s imports:\n", m.name.length, m.name.ptr); + //foreach (m2; mods) printf("\t%.*s\n", m2.name.length, m2.name.ptr); } } } @@ -195,6 +200,18 @@ struct ModuleGroup m.flags = m.flags & ~(MIctorstart | MIctordone); } + /* Look for module rt.dmain2, and put that as first ctor to run + */ + foreach (m; _modules) + { + if (m.name == "rt.dmain2" && m.ctor) + { + first = m; + m.flags = m.flags | MIctordone; + break; + } + } + /* Do two passes: ctor/dtor, tlsctor/tlsdtor */ sort(_ctors, MIctor | MIdtor); @@ -203,6 +220,19 @@ struct ModuleGroup void runCtors() { + version (none) + { + foreach (m; _modules) + printf("module %.*s\n", m.name.length, m.name.ptr); + foreach (m; _ctors) + printf("ctor %.*s\n", m.name.length, m.name.ptr); + } + + if (first) + { + (*first.ctor)(); + } + // run independent ctors runModuleFuncs!(m => m.ictor)(_modules); // sorted module ctors @@ -225,6 +255,9 @@ struct ModuleGroup void runDtors() { runModuleFuncsRev!(m => m.dtor)(_ctors); + if (first && first.dtor) + (*first.dtor)(); + // clean all initialized flags foreach (m; _modules) m.flags = m.flags & ~MIctordone; @@ -245,6 +278,8 @@ private: ModuleInfo*[] _modules; ModuleInfo*[] _ctors; ModuleInfo*[] _tlsctors; +public: + ModuleInfo* first; } diff --git a/src/rt/sections_linux.d b/src/rt/sections_linux.d index f1a707c79b..c36fe2f822 100644 --- a/src/rt/sections_linux.d +++ b/src/rt/sections_linux.d @@ -18,30 +18,44 @@ import core.stdc.stdlib : calloc, malloc, free; import core.stdc.string : strlen; import core.sys.linux.elf; import core.sys.linux.link; +import core.sys.posix.pthread; +import core.memory; import rt.minfo; -import rt.deh; +import rt.deh_win64_posix; import rt.util.container; +extern (C) void thread_joinAll(); + alias DSO SectionGroup; struct DSO { static int opApply(scope int delegate(ref DSO) dg) { + DsoMutex.lock(); foreach(dso; _static_dsos) { if (auto res = dg(*dso)) + { + DsoMutex.unlock(); return res; + } } + DsoMutex.unlock(); return 0; } static int opApplyReverse(scope int delegate(ref DSO) dg) { + DsoMutex.lock(); foreach_reverse(dso; _static_dsos) { if (auto res = dg(*dso)) + { + DsoMutex.unlock(); return res; + } } + DsoMutex.unlock(); return 0; } @@ -100,9 +114,11 @@ void finiSections() */ Array!(void[])* initTLSRanges() { + DsoMutex.lock(); _tlsRanges.length = _static_dsos.length; foreach (i, ref dso; _static_dsos) _tlsRanges[i] = getTLSRange(dso._tlsMod, dso._tlsSize); + DsoMutex.unlock(); return &_tlsRanges; } @@ -122,11 +138,40 @@ private: /* * Static DSOs loaded by the runtime linker. This includes the * executable. These can't be unloaded. + * Should access to this be wrapped in a mutex? After all, what if one + * thread is unloading a DLL while another is scanning exception tables? */ __gshared Array!(DSO*) _static_dsos; Array!(void[]) _tlsRanges; +/* Mutex so we can load/unload DSO's from multiple threads + */ +struct DsoMutex +{ + __gshared pthread_mutex_t mutex; + + static void init() + { + pthread_mutex_init(&mutex, null); + } + + static void term() + { + pthread_mutex_destroy(&mutex); + } + + static void lock() + { + pthread_mutex_lock(&mutex); + } + + static void unlock() + { + pthread_mutex_unlock(&mutex); + } +} + /////////////////////////////////////////////////////////////////////////////// // Compiler to runtime interface. @@ -142,18 +187,20 @@ struct CompilerDSOData size_t _version; // currently 1 void** _slot; // can be used to store runtime data object.ModuleInfo** _minfo_beg, _minfo_end; // array of modules in this object file - immutable(rt.deh.FuncTable)* _deh_beg, _deh_end; // array of exception handling data + immutable(rt.deh_win64_posix.FuncTable)* _deh_beg, _deh_end; // array of exception handling data } T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } -/* For each shared library and executable, the compiler generates code that - * sets up CompilerDSOData and calls _d_dso_registry(). - * A pointer to that code is inserted into both the .ctors and .dtors +/* For each shared library and executable, the compiler generates code that sets + * up CompilerDSOData and then calls _d_dso_registry(). + * A pointer to that code is then inserted into both the .ctors and .dtors * segment so it gets called by the loader on startup and shutdown. */ extern(C) void _d_dso_registry(CompilerDSOData* data) { + //printf("_d_dso_registry(%p)\n", data); + // only one supported currently data._version >= 1 || assert(0, "corrupt DSO data version"); @@ -174,17 +221,71 @@ extern(C) void _d_dso_registry(CompilerDSOData* data) checkModuleCollisions(info, pdso._moduleGroup.modules); + /* If this is the first one, then it is the initialization of druntime and there's + * only one thread, so no need to sync yet. + */ + if (_static_dsos.length == 0) + DsoMutex.init(); + + DsoMutex.lock(); _static_dsos.insertBack(pdso); + DsoMutex.unlock(); + + // Run module constructors for DSO + pdso._moduleGroup.sortCtors(); + pdso._moduleGroup.runCtors(); + pdso._moduleGroup.runTlsCtors(); } // has backlink => unregister else { DSO* pdso = cast(DSO*)*data._slot; - assert(pdso == _static_dsos.back); // DSOs are unloaded in reverse order - _static_dsos.popBack(); + + // Run module destructors for DSO + pdso._moduleGroup.runTlsDtors(); + if (pdso._moduleGroup.first) + thread_joinAll(); + pdso._moduleGroup.runDtors(); + + /* If DSOs come from dynamically loaded DLLs, they can be unloaded + * in any order. Most of the time, however, they'll be unloaded in + * reverse order. So search backwards for pdso in _static_dsos[]. + * Once we find it, ripple the trailing entries over it, and shorten + * _static_dsos[] by one. + */ + DsoMutex.lock(); + for (size_t i = _static_dsos.length; ; ) + { + assert(i); // it must be there + --i; + if (pdso == _static_dsos[i]) + { // Found it. Now ripple + while (i + 1 < _static_dsos.length) + { + _static_dsos[i] = _static_dsos[i + 1]; + } + break; + } + } + + _static_dsos.popBack(); // shorten _static_dsos[] + DsoMutex.unlock(); *data._slot = null; + if (pdso._moduleGroup.first) + DsoMutex.term(); // last one, don't need mutex no more + else + { + /* Tell the GC that it doesn't need to scan the (non-TLS) data + * sections from this DSO anymore. + */ + foreach (rng; pdso._gcRanges) + { + GC.removeRange(rng.ptr); + } + } + pdso._gcRanges.reset(); .free(pdso); }