1 /** 2 * Contains the garbage collector configuration. 3 * 4 * Copyright: Copyright Digital Mars 2016 5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 6 */ 7 8 module gc.config; 9 10 import core.stdc.stdlib; 11 import core.stdc.stdio; 12 import core.stdc.ctype; 13 import core.stdc.string; 14 import core.vararg; 15 16 nothrow @nogc: 17 extern extern(C) string[] rt_args(); 18 19 extern extern(C) __gshared bool rt_envvars_enabled; 20 extern extern(C) __gshared bool rt_cmdline_enabled; 21 extern extern(C) __gshared string[] rt_options; 22 23 __gshared Config config; 24 25 struct Config 26 { 27 bool disable; // start disabled 28 ubyte profile; // enable profiling with summary when terminating program 29 string gc = "conservative"; // select gc implementation conservative|manual 30 31 size_t initReserve; // initial reserve (MB) 32 size_t minPoolSize = 1; // initial and minimum pool size (MB) 33 size_t maxPoolSize = 64; // maximum pool size (MB) 34 size_t incPoolSize = 3; // pool size increment (MB) 35 float heapSizeFactor = 2.0; // heap size to used memory ratio 36 37 @nogc nothrow: 38 39 bool initialize() 40 { 41 import core.internal.traits : externDFunc; 42 43 alias rt_configCallBack = string delegate(string) @nogc nothrow; 44 alias fn_configOption = string function(string opt, scope rt_configCallBack dg, bool reverse) @nogc nothrow; 45 46 alias rt_configOption = externDFunc!("rt.config.rt_configOption", fn_configOption); 47 48 string parse(string opt) @nogc nothrow 49 { 50 if (!parseOptions(opt)) 51 return "err"; 52 return null; // continue processing 53 } 54 string s = rt_configOption("gcopt", &parse, true); 55 return s is null; 56 } 57 58 void help() 59 { 60 version (unittest) if (inUnittest) return; 61 62 string s = "GC options are specified as white space separated assignments: 63 disable:0|1 - start disabled (%d) 64 profile:0|1|2 - enable profiling with summary when terminating program (%d) 65 gc:conservative|manual - select gc implementation (default = conservative) 66 67 initReserve:N - initial memory to reserve in MB (%lld) 68 minPoolSize:N - initial and minimum pool size in MB (%lld) 69 maxPoolSize:N - maximum pool size in MB (%lld) 70 incPoolSize:N - pool size increment MB (%lld) 71 heapSizeFactor:N - targeted heap size to used memory ratio (%g) 72 "; 73 printf(s.ptr, disable, profile, cast(long)initReserve, cast(long)minPoolSize, 74 cast(long)maxPoolSize, cast(long)incPoolSize, heapSizeFactor); 75 } 76 77 bool parseOptions(string opt) 78 { 79 opt = skip!isspace(opt); 80 while (opt.length) 81 { 82 auto tail = find!(c => c == ':' || c == '=' || c == ' ')(opt); 83 auto name = opt[0 .. $ - tail.length]; 84 if (name == "help") 85 { 86 help(); 87 opt = skip!isspace(tail); 88 continue; 89 } 90 if (tail.length <= 1 || tail[0] == ' ') 91 return optError("Missing argument for", name); 92 tail = tail[1 .. $]; 93 94 switch (name) 95 { 96 foreach (field; __traits(allMembers, Config)) 97 { 98 static if (!is(typeof(__traits(getMember, this, field)) == function)) 99 { 100 case field: 101 if (!parse(name, tail, __traits(getMember, this, field))) 102 return false; 103 break; 104 } 105 } 106 break; 107 108 default: 109 return optError("Unknown", name); 110 } 111 opt = skip!isspace(tail); 112 } 113 return true; 114 } 115 } 116 117 private: 118 119 bool optError(in char[] msg, in char[] name) 120 { 121 version (unittest) if (inUnittest) return false; 122 123 fprintf(stderr, "%.*s GC option '%.*s'.\n", 124 cast(int)msg.length, msg.ptr, 125 cast(int)name.length, name.ptr); 126 return false; 127 } 128 129 inout(char)[] skip(alias pred)(inout(char)[] str) 130 { 131 return find!(c => !pred(c))(str); 132 } 133 134 inout(char)[] find(alias pred)(inout(char)[] str) 135 { 136 foreach (i; 0 .. str.length) 137 if (pred(str[i])) return str[i .. $]; 138 return null; 139 } 140 141 bool parse(T:size_t)(const(char)[] optname, ref inout(char)[] str, ref T res) 142 in { assert(str.length); } 143 body 144 { 145 size_t i, v; 146 for (; i < str.length && isdigit(str[i]); ++i) 147 v = 10 * v + str[i] - '0'; 148 149 if (!i) 150 return parseError("a number", optname, str); 151 if (v > res.max) 152 return parseError("a number " ~ T.max.stringof ~ " or below", optname, str[0 .. i]); 153 str = str[i .. $]; 154 res = cast(T) v; 155 return true; 156 } 157 158 bool parse(const(char)[] optname, ref inout(char)[] str, ref bool res) 159 in { assert(str.length); } 160 body 161 { 162 if (str[0] == '1' || str[0] == 'y' || str[0] == 'Y') 163 res = true; 164 else if (str[0] == '0' || str[0] == 'n' || str[0] == 'N') 165 res = false; 166 else 167 return parseError("'0/n/N' or '1/y/Y'", optname, str); 168 str = str[1 .. $]; 169 return true; 170 } 171 172 bool parse(const(char)[] optname, ref inout(char)[] str, ref float res) 173 in { assert(str.length); } 174 body 175 { 176 // % uint f %n \0 177 char[1 + 10 + 1 + 2 + 1] fmt=void; 178 // specify max-width 179 immutable n = snprintf(fmt.ptr, fmt.length, "%%%uf%%n", cast(uint)str.length); 180 assert(n > 4 && n < fmt.length); 181 182 int nscanned; 183 version (CRuntime_DigitalMars) 184 { 185 /* Older sscanf's in snn.lib can write to its first argument, causing a crash 186 * if the string is in readonly memory. Recent updates to DMD 187 * https://github.com/dlang/dmd/pull/6546 188 * put string literals in readonly memory. 189 * Although sscanf has been fixed, 190 * http://ftp.digitalmars.com/snn.lib 191 * this workaround is here so it still works with the older snn.lib. 192 */ 193 // Create mutable copy of str 194 const length = str.length; 195 char* mptr = cast(char*)malloc(length + 1); 196 assert(mptr); 197 memcpy(mptr, str.ptr, length); 198 mptr[length] = 0; 199 const result = sscanf(mptr, fmt.ptr, &res, &nscanned); 200 free(mptr); 201 if (result < 1) 202 return parseError("a float", optname, str); 203 } 204 else 205 { 206 if (sscanf(str.ptr, fmt.ptr, &res, &nscanned) < 1) 207 return parseError("a float", optname, str); 208 } 209 str = str[nscanned .. $]; 210 return true; 211 } 212 213 bool parse(const(char)[] optname, ref inout(char)[] str, ref inout(char)[] res) 214 in { assert(str.length); } 215 body 216 { 217 auto tail = str.find!(c => c == ':' || c == '=' || c == ' '); 218 res = str[0 .. $ - tail.length]; 219 if (!res.length) 220 return parseError("an identifier", optname, str); 221 str = tail; 222 return true; 223 } 224 225 bool parseError(in char[] exp, in char[] opt, in char[] got) 226 { 227 version (unittest) if (inUnittest) return false; 228 229 fprintf(stderr, "Expecting %.*s as argument for GC option '%.*s', got '%.*s' instead.\n", 230 cast(int)exp.length, exp.ptr, 231 cast(int)opt.length, opt.ptr, 232 cast(int)got.length, got.ptr); 233 return false; 234 } 235 236 size_t min(size_t a, size_t b) { return a <= b ? a : b; } 237 238 version (unittest) __gshared bool inUnittest; 239 240 unittest 241 { 242 inUnittest = true; 243 scope (exit) inUnittest = false; 244 245 Config conf; 246 assert(!conf.parseOptions("disable")); 247 assert(!conf.parseOptions("disable:")); 248 assert(!conf.parseOptions("disable:5")); 249 assert(conf.parseOptions("disable:y") && conf.disable); 250 assert(conf.parseOptions("disable:n") && !conf.disable); 251 assert(conf.parseOptions("disable:Y") && conf.disable); 252 assert(conf.parseOptions("disable:N") && !conf.disable); 253 assert(conf.parseOptions("disable:1") && conf.disable); 254 assert(conf.parseOptions("disable:0") && !conf.disable); 255 256 assert(conf.parseOptions("disable=y") && conf.disable); 257 assert(conf.parseOptions("disable=n") && !conf.disable); 258 259 assert(conf.parseOptions("profile=0") && conf.profile == 0); 260 assert(conf.parseOptions("profile=1") && conf.profile == 1); 261 assert(conf.parseOptions("profile=2") && conf.profile == 2); 262 assert(!conf.parseOptions("profile=256")); 263 264 assert(conf.parseOptions("disable:1 minPoolSize:16")); 265 assert(conf.disable); 266 assert(conf.minPoolSize == 16); 267 268 assert(conf.parseOptions("heapSizeFactor:3.1")); 269 assert(conf.heapSizeFactor == 3.1f); 270 assert(conf.parseOptions("heapSizeFactor:3.1234567890 disable:0")); 271 assert(conf.heapSizeFactor > 3.123f); 272 assert(!conf.disable); 273 assert(!conf.parseOptions("heapSizeFactor:3.0.2.5")); 274 assert(conf.parseOptions("heapSizeFactor:2")); 275 assert(conf.heapSizeFactor == 2.0f); 276 277 assert(!conf.parseOptions("initReserve:foo")); 278 assert(!conf.parseOptions("initReserve:y")); 279 assert(!conf.parseOptions("initReserve:20.5")); 280 281 assert(conf.parseOptions("help")); 282 assert(conf.parseOptions("help profile:1")); 283 assert(conf.parseOptions("help profile:1 help")); 284 285 assert(conf.parseOptions("gc:manual") && conf.gc == "manual"); 286 assert(conf.parseOptions("gc:my-gc~modified") && conf.gc == "my-gc~modified"); 287 assert(conf.parseOptions("gc:conservative help profile:1") && conf.gc == "conservative" && conf.profile == 1); 288 289 // the config parse doesn't know all available GC names, so should accept unknown ones 290 assert(conf.parseOptions("gc:whatever")); 291 } 292