/** * Contains the garbage collector configuration. * * Copyright: Copyright Digital Mars 2016 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). */ module gc.config; import core.stdc.stdlib; import core.stdc.stdio; import core.stdc.ctype; import core.stdc.string; import core.vararg; nothrow @nogc: extern extern(C) string[] rt_args(); extern extern(C) __gshared bool rt_envvars_enabled; extern extern(C) __gshared bool rt_cmdline_enabled; extern extern(C) __gshared string[] rt_options; __gshared Config config; struct Config { bool disable; // start disabled ubyte profile; // enable profiling with summary when terminating program string gc = "conservative"; // select gc implementation conservative|manual size_t initReserve; // initial reserve (MB) size_t minPoolSize = 1; // initial and minimum pool size (MB) size_t maxPoolSize = 64; // maximum pool size (MB) size_t incPoolSize = 3; // pool size increment (MB) float heapSizeFactor = 2.0; // heap size to used memory ratio @nogc nothrow: bool initialize() { import core.internal.traits : externDFunc; alias rt_configCallBack = string delegate(string) @nogc nothrow; alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow; alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption); string parse(string opt) @nogc nothrow { if (!parseOptions(opt)) return "err"; return null; // continue processing } string s = rt_configOption("gcopt", &parse, true); return s is null; } void help() { version (unittest) if (inUnittest) return; string s = "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|manual - select gc implementation (default = conservative) initReserve:N - initial memory to reserve in MB (%lld) minPoolSize:N - initial and minimum pool size in MB (%lld) maxPoolSize:N - maximum pool size in MB (%lld) incPoolSize:N - pool size increment MB (%lld) heapSizeFactor:N - targeted heap size to used memory ratio (%g) "; printf(s.ptr, disable, profile, cast(long)initReserve, cast(long)minPoolSize, cast(long)maxPoolSize, cast(long)incPoolSize, heapSizeFactor); } bool parseOptions(string opt) { opt = skip!isspace(opt); while (opt.length) { auto tail = find!(c => c == ':' || c == '=' || c == ' ')(opt); auto name = opt[0 .. $ - tail.length]; if (name == "help") { help(); opt = skip!isspace(tail); continue; } if (tail.length <= 1 || tail[0] == ' ') return optError("Missing argument for", name); tail = tail[1 .. $]; switch (name) { foreach (field; __traits(allMembers, Config)) { static if (!is(typeof(__traits(getMember, this, field)) == function)) { case field: if (!parse(name, tail, __traits(getMember, this, field))) return false; break; } } break; default: return optError("Unknown", name); } opt = skip!isspace(tail); } return true; } } private: bool optError(in char[] msg, in char[] name) { version (unittest) if (inUnittest) return false; fprintf(stderr, "%.*s GC option '%.*s'.\n", cast(int)msg.length, msg.ptr, cast(int)name.length, name.ptr); return false; } inout(char)[] skip(alias pred)(inout(char)[] str) { return find!(c => !pred(c))(str); } inout(char)[] find(alias pred)(inout(char)[] str) { foreach (i; 0 .. str.length) if (pred(str[i])) return str[i .. $]; return null; } bool parse(T:size_t)(const(char)[] optname, ref inout(char)[] str, ref T res) in { assert(str.length); } body { size_t i, v; for (; i < str.length && isdigit(str[i]); ++i) v = 10 * v + str[i] - '0'; if (!i) return parseError("a number", optname, str); if (v > res.max) return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i]); str = str[i .. $]; res = cast(T) v; return true; } bool parse(const(char)[] optname, ref inout(char)[] str, ref bool res) in { assert(str.length); } body { if (str[0] == '1' || str[0] == 'y' || str[0] == 'Y') res = true; else if (str[0] == '0' || str[0] == 'n' || str[0] == 'N') res = false; else return parseError("'0/n/N' or '1/y/Y'", optname, str); str = str[1 .. $]; return true; } bool parse(const(char)[] optname, ref inout(char)[] str, ref float res) in { assert(str.length); } body { // % uint f %n \0 char[1 + 10 + 1 + 2 + 1] fmt=void; // specify max-width immutable n = snprintf(fmt.ptr, fmt.length, "%%%uf%%n", cast(uint)str.length); assert(n > 4 && n < fmt.length); int nscanned; version (CRuntime_DigitalMars) { /* Older sscanf's in snn.lib can write to its first argument, causing a crash * if the string is in readonly memory. Recent updates to DMD * https://github.com/dlang/dmd/pull/6546 * put string literals in readonly memory. * Although sscanf has been fixed, * http://ftp.digitalmars.com/snn.lib * this workaround is here so it still works with the older snn.lib. */ // Create mutable copy of str const length = str.length; char* mptr = cast(char*)malloc(length + 1); assert(mptr); memcpy(mptr, str.ptr, length); mptr[length] = 0; const result = sscanf(mptr, fmt.ptr, &res, &nscanned); free(mptr); if (result < 1) return parseError("a float", optname, str); } else { if (sscanf(str.ptr, fmt.ptr, &res, &nscanned) < 1) return parseError("a float", optname, str); } str = str[nscanned .. $]; return true; } bool parse(const(char)[] optname, ref inout(char)[] str, ref inout(char)[] res) in { assert(str.length); } body { auto tail = str.find!(c => c == ':' || c == '=' || c == ' '); res = str[0 .. $ - tail.length]; if (!res.length) return parseError("an identifier", optname, str); str = tail; return true; } bool parseError(in char[] exp, in char[] opt, in char[] got) { version (unittest) if (inUnittest) return false; fprintf(stderr, "Expecting %.*s as argument for GC option '%.*s', got '%.*s' instead.\n", cast(int)exp.length, exp.ptr, cast(int)opt.length, opt.ptr, cast(int)got.length, got.ptr); return false; } size_t min(size_t a, size_t b) { return a <= b ? a : b; } version (unittest) __gshared bool inUnittest; unittest { inUnittest = true; scope (exit) inUnittest = false; Config conf; assert(!conf.parseOptions("disable")); assert(!conf.parseOptions("disable:")); assert(!conf.parseOptions("disable:5")); assert(conf.parseOptions("disable:y") && conf.disable); assert(conf.parseOptions("disable:n") && !conf.disable); assert(conf.parseOptions("disable:Y") && conf.disable); assert(conf.parseOptions("disable:N") && !conf.disable); assert(conf.parseOptions("disable:1") && conf.disable); assert(conf.parseOptions("disable:0") && !conf.disable); assert(conf.parseOptions("disable=y") && conf.disable); assert(conf.parseOptions("disable=n") && !conf.disable); assert(conf.parseOptions("profile=0") && conf.profile == 0); assert(conf.parseOptions("profile=1") && conf.profile == 1); assert(conf.parseOptions("profile=2") && conf.profile == 2); assert(!conf.parseOptions("profile=256")); assert(conf.parseOptions("disable:1 minPoolSize:16")); assert(conf.disable); assert(conf.minPoolSize == 16); assert(conf.parseOptions("heapSizeFactor:3.1")); assert(conf.heapSizeFactor == 3.1f); assert(conf.parseOptions("heapSizeFactor:3.1234567890 disable:0")); assert(conf.heapSizeFactor > 3.123f); assert(!conf.disable); assert(!conf.parseOptions("heapSizeFactor:3.0.2.5")); assert(conf.parseOptions("heapSizeFactor:2")); assert(conf.heapSizeFactor == 2.0f); assert(!conf.parseOptions("initReserve:foo")); assert(!conf.parseOptions("initReserve:y")); assert(!conf.parseOptions("initReserve:20.5")); assert(conf.parseOptions("help")); assert(conf.parseOptions("help profile:1")); assert(conf.parseOptions("help profile:1 help")); assert(conf.parseOptions("gc:manual") && conf.gc == "manual"); assert(conf.parseOptions("gc:my-gc~modified") && conf.gc == "my-gc~modified"); assert(conf.parseOptions("gc:conservative help profile:1") && conf.gc == "conservative" && conf.profile == 1); // the config parse doesn't know all available GC names, so should accept unknown ones assert(conf.parseOptions("gc:whatever")); }