diff --git a/changelog/gc_registry.dd b/changelog/gc_registry.dd new file mode 100644 index 0000000000..46c500a60e --- /dev/null +++ b/changelog/gc_registry.dd @@ -0,0 +1,7 @@ +User supplied garbage collectors can now be linked with the runtime + +A GC registry has been implemented that allows to add +garbage collector implementations by just linking them into +the binary. See the +$(LINK2 $(ROOT_DIR)spec/garbage.html#gc_registry, documentation) +for details. diff --git a/mak/SRCS b/mak/SRCS index 2c7e6e15bb..4fa5f11f16 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -390,10 +390,11 @@ SRCS=\ src\gc\bits.d \ src\gc\config.d \ src\gc\gcinterface.d \ + src\gc\impl\conservative\gc.d \ src\gc\os.d \ src\gc\pooltable.d \ src\gc\proxy.d \ - src\gc\impl\conservative\gc.d \ + src\gc\registry.d \ src\gc\impl\manual\gc.d \ src\gc\impl\proto\gc.d \ \ diff --git a/src/gc/config.d b/src/gc/config.d index e431846d54..cdedfc5ee6 100644 --- a/src/gc/config.d +++ b/src/gc/config.d @@ -34,10 +34,18 @@ struct Config void help() @nogc nothrow { - string s = "GC options are specified as whitespace separated assignments: + import gc.registry : registeredGCFactories; + + printf("GC options are specified as white space separated assignments: disable:0|1 - start disabled (%d) profile:0|1|2 - enable profiling with summary when terminating program (%d) - gc:conservative|precise|manual - select gc implementation (default = conservative) + gc:".ptr, disable, profile); + foreach (i, entry; registeredGCFactories) + { + if (i) printf("|"); + printf("%.*s", cast(int) entry.name.length, entry.name.ptr); + } + printf(" - select gc implementation (default = conservative) initReserve:N - initial memory to reserve in MB (%lld) minPoolSize:N - initial and minimum pool size in MB (%lld) @@ -45,8 +53,8 @@ struct Config incPoolSize:N - pool size increment MB (%lld) heapSizeFactor:N - targeted heap size to used memory ratio (%g) cleanup:none|collect|finalize - how to treat live objects when terminating (collect) -"; - printf(s.ptr, disable, profile, cast(long)initReserve, cast(long)minPoolSize, +".ptr, + cast(long)initReserve, cast(long)minPoolSize, cast(long)maxPoolSize, cast(long)incPoolSize, heapSizeFactor); } diff --git a/src/gc/gcinterface.d b/src/gc/gcinterface.d index b659498201..bdcae8b9e9 100644 --- a/src/gc/gcinterface.d +++ b/src/gc/gcinterface.d @@ -38,12 +38,6 @@ struct Range interface GC { - - /* - * - */ - void Dtor(); - /** * */ diff --git a/src/gc/impl/conservative/gc.d b/src/gc/impl/conservative/gc.d index 3b87fdd2ba..0d8137acfb 100644 --- a/src/gc/impl/conservative/gc.d +++ b/src/gc/impl/conservative/gc.d @@ -103,6 +103,42 @@ alias GC gc_t; /* ============================ GC =============================== */ +// register GC in C constructor (_STI_) +extern(C) pragma(crt_constructor) void _d_register_conservative_gc() +{ + import gc.registry; + registerGCFactory("conservative", &initialize); +} + +extern(C) pragma(crt_constructor) void _d_register_precise_gc() +{ + import gc.registry; + registerGCFactory("precise", &initialize_precise); +} + +private GC initialize() +{ + import core.stdc.string: memcpy; + + auto p = cstdlib.malloc(__traits(classInstanceSize, ConservativeGC)); + + if (!p) + onOutOfMemoryErrorNoGC(); + + auto init = typeid(ConservativeGC).initializer(); + assert(init.length == __traits(classInstanceSize, ConservativeGC)); + auto instance = cast(ConservativeGC) memcpy(p, init.ptr, init.length); + instance.__ctor(); + + return instance; +} + +private GC initialize_precise() +{ + ConservativeGC.isPrecise = true; + return initialize(); +} + class ConservativeGC : GC { // For passing to debug code (not thread safe) @@ -124,42 +160,6 @@ class ConservativeGC : GC gcLock.lock(); } - - static void initialize(ref GC gc) - { - import core.stdc.string: memcpy; - - if ((config.gc != "precise") && (config.gc != "conservative")) - return; - - if (config.gc == "precise") - isPrecise = true; - - auto p = cstdlib.malloc(__traits(classInstanceSize,ConservativeGC)); - - if (!p) - onOutOfMemoryErrorNoGC(); - - auto init = typeid(ConservativeGC).initializer(); - assert(init.length == __traits(classInstanceSize, ConservativeGC)); - auto instance = cast(ConservativeGC) memcpy(p, init.ptr, init.length); - instance.__ctor(); - - gc = instance; - } - - - static void finalize(ref GC gc) - { - if ((config.gc != "precise") && (config.gc != "conservative")) - return; - - auto instance = cast(ConservativeGC) gc; - instance.Dtor(); - cstdlib.free(cast(void*)instance); - } - - this() { //config is assumed to have already been initialized @@ -176,7 +176,7 @@ class ConservativeGC : GC } - void Dtor() + ~this() { version (linux) { @@ -190,6 +190,9 @@ class ConservativeGC : GC cstdlib.free(gcx); gcx = null; } + // TODO: cannot free as memory is overwritten and + // the monitor is still read in rt_finalize (called by destroy) + // cstdlib.free(cast(void*) this); } diff --git a/src/gc/impl/manual/gc.d b/src/gc/impl/manual/gc.d index 61902ba88e..c07e5aa09f 100644 --- a/src/gc/impl/manual/gc.d +++ b/src/gc/impl/manual/gc.d @@ -35,46 +35,43 @@ static import core.memory; extern (C) void onOutOfMemoryError(void* pretend_sideffect = null) @trusted pure nothrow @nogc; /* dmd @@@BUG11461@@@ */ -class ManualGC : GC +// register GC in C constructor (_STI_) +extern(C) pragma(crt_constructor) void _d_register_manual_gc() { - __gshared Array!Root roots; - __gshared Array!Range ranges; - - static void initialize(ref GC gc) - { - import core.stdc.string; + import gc.registry; + registerGCFactory("manual", &initialize); +} - if (config.gc != "manual") - return; +private GC initialize() +{ + import core.stdc.string: memcpy; - auto p = cstdlib.malloc(__traits(classInstanceSize, ManualGC)); - if (!p) - onOutOfMemoryError(); + auto p = cstdlib.malloc(__traits(classInstanceSize, ManualGC)); + if (!p) + onOutOfMemoryError(); - auto init = typeid(ManualGC).initializer(); - assert(init.length == __traits(classInstanceSize, ManualGC)); - auto instance = cast(ManualGC) memcpy(p, init.ptr, init.length); - instance.__ctor(); + auto init = typeid(ManualGC).initializer(); + assert(init.length == __traits(classInstanceSize, ManualGC)); + auto instance = cast(ManualGC) memcpy(p, init.ptr, init.length); + instance.__ctor(); - gc = instance; - } - - static void finalize(ref GC gc) - { - if (config.gc != "manual") - return; + return instance; +} - auto instance = cast(ManualGC) gc; - instance.Dtor(); - cstdlib.free(cast(void*) instance); - } +class ManualGC : GC +{ + Array!Root roots; + Array!Range ranges; this() { } - void Dtor() + ~this() { + // TODO: cannot free as memory is overwritten and + // the monitor is still read in rt_finalize (called by destroy) + // cstdlib.free(cast(void*) this); } void enable() diff --git a/src/gc/proxy.d b/src/gc/proxy.d index d66c6e92a1..8742f1e96d 100644 --- a/src/gc/proxy.d +++ b/src/gc/proxy.d @@ -13,11 +13,10 @@ */ module gc.proxy; -import gc.impl.conservative.gc; -import gc.impl.manual.gc; import gc.impl.proto.gc; import gc.config; import gc.gcinterface; +import gc.registry : createGCInstance; static import core.memory; @@ -36,17 +35,31 @@ private extern (C) { + // do not import GC modules, they might add a dependency to this whole module + void _d_register_conservative_gc(); + void _d_register_manual_gc(); + + // if you don't want to include the default GCs, replace during link by another implementation + void* register_default_gcs() + { + pragma(inline, false); + // do not call, they register implicitly through pragma(crt_constructor) + // avoid being optimized away + auto reg1 = &_d_register_conservative_gc; + auto reg2 = &_d_register_manual_gc; + return reg1 < reg2 ? reg1 : reg2; + } + void gc_init() { instanceLock.lock(); if (!isInstanceInit) { - auto protoInstance = instance; + register_default_gcs(); config.initialize(); - ManualGC.initialize(instance); - ConservativeGC.initialize(instance); - - if (instance is protoInstance) + auto protoInstance = instance; + auto newInstance = createGCInstance(config.gc); + if (newInstance is null) { import core.stdc.stdio : fprintf, stderr; import core.stdc.stdlib : exit; @@ -58,7 +71,7 @@ extern (C) // Shouldn't get here. assert(0); } - + instance = newInstance; // Transfer all ranges and roots to the real GC. (cast(ProtoGC) protoInstance).term(); isInstanceInit = true; @@ -108,9 +121,7 @@ extern (C) instance.runFinalizers((cast(ubyte*)null)[0 .. size_t.max]); break; } - - ManualGC.finalize(instance); - ConservativeGC.finalize(instance); + destroy(instance); } } diff --git a/src/gc/registry.d b/src/gc/registry.d new file mode 100644 index 0000000000..58cc544f23 --- /dev/null +++ b/src/gc/registry.d @@ -0,0 +1,87 @@ +/** + * Contains a registry for GC factories. + * + * Copyright: Copyright Digital Mars 2016. + * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + */ +module gc.registry; + +import gc.gcinterface : GC; + +/*@nogc nothrow:*/ + +/** + * A factory function that instantiates an implementation of the GC interface. + * In case the instance was allocated on the C heap, it is supposed to + * free itself upon calling it's destructor. + * + * The factory should print an error and abort the program if it + * cannot successfully initialize the GC instance. + */ +alias GCFactory = GC function(); + +/** + * Register a GC factory under the given `name`. This function must be called + * from a C constructor before druntime is initialized. + * + * To use the registered GC, it's name must be specified gcopt runtime option, + * e.g. by passing $(TT, --DRT-gcopt=gc:my_gc_name) as application argument. + * + * Params: + * name = name of the GC implementation; should be unique + * factory = function to instantiate the implementation + * Note: The registry does not perform synchronization, as registration is + * assumed to be executed serially, as is the case for C constructors. + * See_Also: + * $(LINK2 https://dlang.org/spec/garbage.html#gc_config, Configuring the Garbage Collector) + */ +void registerGCFactory(string name, GCFactory factory) nothrow @nogc +{ + import core.stdc.stdlib : realloc; + + auto ptr = cast(Entry*)realloc(entries.ptr, (entries.length + 1) * Entry.sizeof); + entries = ptr[0 .. entries.length + 1]; + entries[$ - 1] = Entry(name, factory); +} + +/** + * Called during runtime initialization to initialize a GC instance of given `name`. + * + * Params: + * name = name of the GC to instantiate + * Returns: + * The created GC instance or `null` if no factory for that name was registered + */ +GC createGCInstance(string name) +{ + import core.stdc.stdlib : free; + + foreach (entry; entries) + { + if (entry.name != name) + continue; + auto instance = entry.factory(); + // only one GC at a time for now, so free the registry to not leak + free(entries.ptr); + entries = null; + return instance; + } + return null; +} + +// list of all registerd GCs +package(gc) const(Entry[]) registeredGCFactories(scope int dummy=0) nothrow @nogc +{ + return entries; +} + +private: + +struct Entry +{ + string name; + GCFactory factory; +} + +__gshared Entry[] entries; diff --git a/test/init_fini/Makefile b/test/init_fini/Makefile index 6f01a13cd0..846966f763 100644 --- a/test/init_fini/Makefile +++ b/test/init_fini/Makefile @@ -1,6 +1,6 @@ include ../common.mak -TESTS:=thread_join runtime_args test18996 +TESTS:=thread_join runtime_args test18996 custom_gc .PHONY: all clean all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS))) diff --git a/test/init_fini/src/custom_gc.d b/test/init_fini/src/custom_gc.d new file mode 100644 index 0000000000..effde0a17d --- /dev/null +++ b/test/init_fini/src/custom_gc.d @@ -0,0 +1,198 @@ +import gc.registry; +import gc.gcinterface; +import core.stdc.stdlib; + +static import core.memory; + +extern (C) __gshared string[] rt_options = ["gcopt=gc:malloc"]; + +extern (C) pragma(crt_constructor) void register_mygc() +{ + registerGCFactory("malloc", &MallocGC.initialize); +} + +extern (C) void register_default_gcs() +{ + // remove default GCs +} + +/** Simple GC that requires any pointers passed to it's API + to point to start of the allocation. + */ +class MallocGC : GC +{ +nothrow @nogc: + static GC initialize() + { + import core.stdc.string : memcpy; + + __gshared ubyte[__traits(classInstanceSize, MallocGC)] buf; + + auto init = typeid(MallocGC).initializer(); + assert(init.length == buf.length); + auto instance = cast(MallocGC) memcpy(buf.ptr, init.ptr, init.length); + instance.__ctor(); + return instance; + } + + this() + { + } + + void Dtor() + { + } + + void enable() + { + } + + void disable() + { + } + + void collect() nothrow + { + } + + void collectNoStack() nothrow + { + } + + void minimize() nothrow + { + } + + uint getAttr(void* p) nothrow + { + return 0; + } + + uint setAttr(void* p, uint mask) nothrow + { + return mask; + } + + uint clrAttr(void* p, uint mask) nothrow + { + return mask; + } + + void* malloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.malloc(size + sentinelSize), size); + } + + BlkInfo qalloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + return BlkInfo(malloc(size, bits, ti), size); + } + + void* calloc(size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.calloc(1, size + sentinelSize), size); + } + + void* realloc(void* p, size_t size, uint bits, const TypeInfo ti) nothrow + { + return sentinelAdd(.realloc(p - sentinelSize, size + sentinelSize), size); + } + + size_t extend(void* p, size_t minsize, size_t maxsize, const TypeInfo ti) nothrow + { + return 0; + } + + size_t reserve(size_t size) nothrow + { + return 0; + } + + void free(void* p) nothrow + { + free(p - sentinelSize); + } + + void* addrOf(void* p) nothrow + { + return p; + } + + size_t sizeOf(void* p) nothrow + { + return query(p).size; + } + + BlkInfo query(void* p) nothrow + { + return p ? BlkInfo(p, sentinelGet(p)) : BlkInfo.init; + } + + core.memory.GC.Stats stats() nothrow + { + return core.memory.GC.Stats.init; + } + + core.memory.GC.ProfileStats profileStats() nothrow + { + return typeof(return).init; + } + + void addRoot(void* p) nothrow @nogc + { + } + + void removeRoot(void* p) nothrow @nogc + { + } + + @property RootIterator rootIter() @nogc + { + return null; + } + + void addRange(void* p, size_t sz, const TypeInfo ti) nothrow @nogc + { + } + + void removeRange(void* p) nothrow @nogc + { + } + + @property RangeIterator rangeIter() @nogc + { + return null; + } + + void runFinalizers(in void[] segment) nothrow + { + } + + bool inFinalizer() nothrow + { + return false; + } + +private: + // doesn't care for alignment + static void* sentinelAdd(void* p, size_t value) + { + *cast(size_t*) p = value; + return p + sentinelSize; + } + + static size_t sentinelGet(void* p) + { + return *cast(size_t*)(p - sentinelSize); + } + + enum sentinelSize = size_t.sizeof; +} + +void main() +{ + // test array append cache + char[] s; + foreach (char c; char.min .. char.max + 1) + s ~= c; +} diff --git a/test/init_fini/win64.mak b/test/init_fini/win64.mak new file mode 100644 index 0000000000..160e8c7c68 --- /dev/null +++ b/test/init_fini/win64.mak @@ -0,0 +1,13 @@ +# built from the druntime top-level folder +# to be overwritten by caller +DMD=dmd +MODEL=64 +DRUNTIMELIB=druntime64.lib + +test: custom_gc + +custom_gc: + $(DMD) -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIMELIB) test\init_fini\src\custom_gc.d + custom_gc.exe + del custom_gc.exe custom_gc.obj + diff --git a/win64.mak b/win64.mak index afb876f8d2..e70c5d52a5 100644 --- a/win64.mak +++ b/win64.mak @@ -108,10 +108,13 @@ test_stdcpp: test_gc: $(MAKE) -f test\gc\win64.mak "DMD=$(DMD)" MODEL=$(MODEL) "VCDIR=$(VCDIR)" DRUNTIMELIB=$(DRUNTIME) "CC=$(CC)" test +custom_gc: + $(MAKE) -f test\init_fini\win64.mak "DMD=$(DMD)" MODEL=$(MODEL) "VCDIR=$(VCDIR)" DRUNTIMELIB=$(DRUNTIME) "CC=$(CC)" test + test_loadlib: $(DMD) -m$(MODEL) -conf= -Isrc -defaultlib=$(DRUNTIME) -run test\shared\src\loadlibwin.d -test_all: test_uuid test_aa test_hash test_stdcpp test_gc test_loadlib +test_all: test_uuid test_aa test_hash test_stdcpp test_gc custom_gc test_loadlib ################### zip/install/clean ##########################