summaryrefslogtreecommitdiff
path: root/rts
diff options
context:
space:
mode:
Diffstat (limited to 'rts')
-rw-r--r--rts/OptParse.c357
-rw-r--r--rts/include/Rts.h1
-rw-r--r--rts/include/rts/Flags.h2
-rw-r--r--rts/include/rts/OptParse.h91
-rw-r--r--rts/rts.cabal.in2
5 files changed, 452 insertions, 1 deletions
diff --git a/rts/OptParse.c b/rts/OptParse.c
new file mode 100644
index 0000000000..c4e4b9ab31
--- /dev/null
+++ b/rts/OptParse.c
@@ -0,0 +1,357 @@
+#include "Rts.h"
+#include "RtsUtils.h"
+
+#if defined(HAVE_CTYPE_H)
+#include <ctype.h>
+#endif
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#define FLAGS_LENGTH ((int)(sizeof(rtsFlags) / sizeof(rtsFlags[0])))
+#define SAFE true
+#define UNSAFE false
+
+#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
+
+#define UNKNOWN_RTS_OPTION(error, arg) \
+ do { \
+ *error = true; \
+ errorBelch("unknown RTS option: %s" ,arg); \
+ return NO_VAL(UNKNOWN_RTS_OPTION); \
+ } while (false)
+
+#define BAD_VALUE(error, arg) \
+ do { \
+ *error = true; \
+ errorBelch("bad value for %s" ,arg); \
+ return NO_VAL(UNKNOWN_RTS_OPTION); \
+ } while (false)
+
+#define UNEXPECTED_ARGUMENT(error, name, arg) \
+ do { \
+ *error = true; \
+ errorBelch("flag %s given an argument when none was expected: %s" , name, arg); \
+ return NO_VAL(UNKNOWN_RTS_OPTION); \
+ } while (false)
+
+RtsFlagName
+rtsFlags[] = {
+ [HELP] = {SAFE, VOID, NULL, "?" , false},
+ [INSTALL_SIGNAL_HANDLERS] = {UNSAFE, BOOL, "install-signal-handlers", NULL, false},
+ [INSTALL_SEH_HANDLERS] = {UNSAFE, BOOL, "install-seh-handlers", NULL, false},
+ [GENERATE_STACK_TRACES] = {UNSAFE, BOOL, "generate-stack-traces", NULL, false},
+ [GENERATE_CRASH_DUMPS] = {UNSAFE, BOOL, "generate-crash-dumps", NULL, false},
+ [NULL_EVENTLOG_WRITER] = {UNSAFE, BOOL, "null-eventlog-writer", NULL, false},
+ [MACHINE_READABLE] = {UNSAFE, BOOL, "machine-readable", NULL, false},
+ [DISABLE_OS_MEM_RET] = {UNSAFE, BOOL, "disable-delayed-os-memory-return", NULL, false},
+ [INTERNAL_COUNTERS] = {SAFE, BOOL, "internal-counters", NULL, false},
+ [IO_MANAGER_FLAG] = {UNSAFE, ENUM, "io-manager", NULL, true},
+ [INFO] = {SAFE, VOID, "info", NULL, false},
+ [EVENTLOG_FLUSH_INTERVAL] = {SAFE, DOUBLE, "eventlog-flush-interval", NULL, true},
+ [COPYING_GC] = {SAFE, VOID, "copying-gc", NULL, false},
+ [NONMOVING_GC] = {SAFE, VOID, "nonmoving-gc", NULL, false},
+ [LARGE_OBJ_ALLOC_AREA] = {UNSAFE, STGWORD64, "large-object-allocation", "AL", true},
+ [MIN_ALLOC_AREA] = {UNSAFE, STGWORD64, "minimum-allocation-area-size", "A", true},
+// #if defined(THREADED_RTS)
+// #if defined(mingw32_HOST_OS)
+ [IO_MANAGER_THREADS] = {UNSAFE, STGWORD64, "io-manager-threads", NULL, true},
+// #endif
+ [NUMA] = {SAFE, STGWORD64, "numa", NULL, false},
+// #endif
+// #if defined(DEBUG) && defined(THREADED_RTS)
+ [DEBUG_NUMA] = {SAFE, STGWORD64, "debug-numa", NULL, true},
+// #endif
+ [LONG_GC_SYNC] = {SAFE, DOUBLE, "long-gc-sync", NULL, false},
+ [NO_AUTO_HEAP_SAMPLES] = {UNSAFE, BOOL, "no-automatic-heap-samples", NULL, false},
+ [NURSERY_CHUNK_SIZE] = {UNSAFE, STGWORD64, "alloc-area-chunksize", "n", true},
+ [GC_BELL] = {UNSAFE, VOID, "gc-bell", "B", false},
+ [COMPACT_GC] = {UNSAFE, DOUBLE, "compact-gc", "c", false},
+ // The 'NULL' of flags. Long name just for debugging
+ [UNKNOWN_RTS_OPTION] = {SAFE, VOID, "UNKNOWN_RTS_OPTION", NULL, false},
+};
+
+static RtsFlagValue
+parse_flag_value(RtsFlagKey i, bool isLongName, char *arg, bool *error);
+
+static double
+parseDouble(const char *arg, bool *error)
+{
+ char *endptr;
+ double out;
+ errno = 0;
+
+ out = strtod(arg, &endptr);
+
+ if (errno != 0 || endptr == arg) {
+ *error = true;
+ return out;
+ }
+
+ while (isspace((unsigned char)*endptr)) {
+ ++endptr;
+ }
+
+ if (*endptr != 0) {
+ *error = true;
+ }
+
+ return out;
+}
+
+
+static StgWord64
+decodeSize(const char *flag, uint32_t offset, StgWord64 min, StgWord64 max);
+
+static int
+find_first_numeric_char(const char *s) {
+ int len = strlen(s);
+ for (int i = 0; i < len; i++) {
+ if (isdigit(s[i])) {
+ return i;
+ }
+ }
+ return len;
+}
+
+static char*
+name_from_arg(bool longName, const char* str) {
+ ASSERT(str != NULL);
+ int i;
+ int str_len = strlen(str);
+ char* substring = stgMallocBytes(str_len + 1, "name_from_arg"); // allocate memory for the substring
+
+ strcpy(substring, str);
+
+ if (!longName) {
+ substring[find_first_numeric_char(str)] = '\0';
+ } else {
+ for (i = 0; i < str_len; i++) {
+ if (substring[i] == '=') {
+ substring[i] = '\0';
+ break;
+ }
+ }
+ }
+
+ return substring;
+}
+
+RtsFlagValue
+parseArg(char *arg, bool *error)
+{
+ // debugBelch("------- parseArg: %s\n", arg);
+ if (!strncmp("-?", arg, 2)) return NO_VAL(HELP);
+ for (int i = 0; i < FLAGS_LENGTH; i++) {
+ bool isLongName = arg[1] == '-';
+
+ RtsFlagName flag = rtsFlags[i];
+ if (isLongName && flag.longName == NULL) continue;
+ if (!isLongName && flag.shortName == NULL) continue;
+
+ char* name = isLongName ? flag.longName : flag.shortName;
+ if (name == NULL) continue;
+
+ char* arg1 = isLongName ? &arg[2] : &arg[1];
+ char* nameFromArg = name_from_arg(isLongName, arg1);
+ if (!strncmp(nameFromArg, name, MAX(strlen(nameFromArg), strlen(name)))) {
+ stgFree(nameFromArg);
+ return parse_flag_value(i, isLongName, arg, error);
+ }
+ stgFree(nameFromArg);
+ }
+ UNKNOWN_RTS_OPTION(error, arg);
+}
+
+static RtsFlagValue
+parse_flag_value(RtsFlagKey i, bool isLongName, char *arg0, bool *error)
+{
+ RtsFlagName flag = rtsFlags[i];
+ char* name = isLongName ? flag.longName : flag.shortName;
+ char *arg = isLongName ? &arg0[2] : &arg0[1];
+ // offset at which value can be found. Does not account for potential `=`
+ int offset = isLongName ? strlen(flag.longName) : strlen(flag.shortName);
+ bool isSwitch = !rtsFlags[i].valueRequired && arg[offset] == '\0';
+ bool hasValue = (isLongName && arg[offset] == '=' && arg[offset+1] != '\0')
+ || (!isLongName && arg[offset] != '\0');
+
+ // missing value - return immediately
+ if (!isSwitch && !hasValue) UNKNOWN_RTS_OPTION(error, arg0);
+ if (isSwitch && hasValue) UNKNOWN_RTS_OPTION(error, arg0);
+
+ switch (flag.valueType) {
+ case VOID: {
+ switch (i) {
+ case GC_BELL:
+ if (hasValue) UNEXPECTED_ARGUMENT(error, name, arg0);
+ }
+ return NO_VAL(i);
+ }
+ case BOOL: {
+ // we forbid having bool flags with '=' with no specified value
+ if (isSwitch) {
+ return BOOL_VAL(i, true);
+ } else {
+ if (!strncmp("yes", &arg[offset + 1], 3)) {
+ return BOOL_VAL(i, true);
+ } else if (!strncmp("no", &arg[offset + 1], 2)) {
+ return BOOL_VAL(i, false);
+ } else {
+ UNKNOWN_RTS_OPTION(error, arg0);
+ }
+ }
+ break;
+ }
+ case ENUM: {
+ switch (i) {
+ case IO_MANAGER_FLAG: {
+ if (!strncmp("native", &arg[offset + 1], 6)) {
+ return ENUM_VAL(i, IO_MNGR_NATIVE);
+ } else if (!strncmp("posix", &arg[offset + 1], 5)) {
+ return ENUM_VAL(i, IO_MNGR_POSIX);
+ }
+ }
+ default:
+ *error = true;
+ errorBelch("invalid enum '%s' for '%s'", &arg[offset + 1], rtsFlags[i].longName);
+ }
+ break;
+ }
+ case DOUBLE: {
+ double res;
+ switch (i) {
+ case EVENTLOG_FLUSH_INTERVAL: {
+ res = parseDouble(arg+offset+1, error);
+ break;
+ }
+ case LONG_GC_SYNC: {
+ res = parseDouble(arg+offset+1, error);
+ break;
+ }
+ case COMPACT_GC: {
+ // special treatment when used as a switch
+ if (!hasValue) return NO_VAL(i);
+ res = parseDouble(arg+offset+1, error);
+ break;
+ }
+ default: {
+ *error = true;
+ errorBelch("invalid double '%s' for '%s'", &arg[offset + 1], rtsFlags[i].longName);
+ }
+ }
+ if (*error) {
+ BAD_VALUE(error, arg);
+ }
+ return DOUBLE_VAL(i, res);
+ }
+ case STGWORD64: {
+ StgWord64 value = 0;
+ // account for '=' that is used with long-form names
+ // some long-from names can have no value though so account for that as well
+ if (isLongName && arg[offset] == '=') offset++;
+ switch (i) {
+ case LARGE_OBJ_ALLOC_AREA: {
+ value = decodeSize(arg, offset, 2*BLOCK_SIZE,
+ HS_INT_MAX) / BLOCK_SIZE;
+ break;
+ }
+ case MIN_ALLOC_AREA: {
+ // minimum two blocks in the nursery, so that we have one
+ // to grab for allocate().
+ value = decodeSize(arg, offset, 2*BLOCK_SIZE,
+ HS_INT_MAX) / BLOCK_SIZE;
+ break;
+ }
+// #if defined(THREADED_RTS)
+// #if defined(mingw32_HOST_OS)
+ case IO_MANAGER_THREADS: {
+ value = (StgWord64)strtol(arg + offset, (char **) NULL, 10);
+ break;
+ }
+// #endif
+ case NUMA: {
+ if (isSwitch) {
+ value = (StgWord64)~0;
+ } else if (hasValue) {
+ value = (StgWord64)strtol(arg + offset, (char **) NULL, 10);
+ } else {
+ *error = true;
+ errorBelch("invalid RTS option: %s", arg0);
+ }
+ break;
+ }
+// #endif
+// #if defined(DEBUG) && defined(THREADED_RTS)
+ case DEBUG_NUMA: {
+ if (isdigit(arg[offset]) && hasValue) {
+ value = (StgWord64)strtol(arg + offset, (char **) NULL, 10);
+ } else {
+ UNKNOWN_RTS_OPTION(error, arg0);
+ }
+ if (value > MAX_NUMA_NODES) {
+ *error = true;
+ errorBelch("%s: Too many NUMA nodes (max %d)",
+ rtsFlags[i].longName, MAX_NUMA_NODES);
+ }
+ break;
+ }
+// #endif
+ case NURSERY_CHUNK_SIZE: {
+ value = decodeSize(arg, offset, 2*BLOCK_SIZE, HS_INT_MAX)
+ / BLOCK_SIZE;
+ break;
+ }
+ }
+ return STGWORD64_VAL(i, value);
+ }
+ default: {
+ UNKNOWN_RTS_OPTION(error, arg0);
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * decodeSize: parse a string containing a size, like 300K or 1.2M
+-------------------------------------------------------------------------- */
+static StgWord64
+decodeSize(const char *flag, uint32_t offset, StgWord64 min, StgWord64 max)
+{
+ char c;
+ const char *s;
+ StgDouble m;
+ StgWord64 val;
+
+ s = flag + offset;
+ // debugBelch("------- decodeSize s: %s\n", s);
+
+ if (!*s)
+ {
+ m = 0;
+ }
+ else
+ {
+ m = atof(s);
+ c = s[strlen(s)-1];
+
+ if (c == 'g' || c == 'G')
+ m *= 1024*1024*1024;
+ else if (c == 'm' || c == 'M')
+ m *= 1024*1024;
+ else if (c == 'k' || c == 'K')
+ m *= 1024;
+ else if (c == 'w' || c == 'W')
+ m *= sizeof(W_);
+ }
+
+ val = (StgWord64)m;
+ // debugBelch("------- decodeSize m: %f\n", m);
+ if (m < 0 || val < min || val > max) {
+ // printf doesn't like 64-bit format specs on Windows
+ // apparently, so fall back to unsigned long.
+ errorBelch("error in RTS option %s: size outside allowed range (%" FMT_Word " - %" FMT_Word ")", flag, (W_)min, (W_)max);
+ stg_exit(EXIT_FAILURE);
+ }
+
+ // debugBelch("------- decodeSize val: %" FMT_Word64 "\n", val);
+ return val;
+}
diff --git a/rts/include/Rts.h b/rts/include/Rts.h
index 90d8e5b324..4a7fd348d2 100644
--- a/rts/include/Rts.h
+++ b/rts/include/Rts.h
@@ -281,6 +281,7 @@ void _warnFail(const char *filename, unsigned int linenum);
#include "rts/StaticPtrTable.h"
#include "rts/Libdw.h"
#include "rts/LibdwPool.h"
+#include "rts/OptParse.h"
/* Misc stuff without a home */
DLL_IMPORT_RTS extern char **prog_argv; /* so we can get at these from Haskell */
diff --git a/rts/include/rts/Flags.h b/rts/include/rts/Flags.h
index e33d97b17c..0184083845 100644
--- a/rts/include/rts/Flags.h
+++ b/rts/include/rts/Flags.h
@@ -2,7 +2,7 @@
*
* (c) The GHC Team, 1998-2009
*
- * Datatypes that holds the command-line flag settings.
+ * Datatypes that hold the command-line flag settings.
*
* Do not #include this file directly: #include "Rts.h" instead.
*
diff --git a/rts/include/rts/OptParse.h b/rts/include/rts/OptParse.h
new file mode 100644
index 0000000000..36c9660ca5
--- /dev/null
+++ b/rts/include/rts/OptParse.h
@@ -0,0 +1,91 @@
+/* -----------------------------------------------------------------------------
+ *
+ * (c) The GHC Team, 1995-2023
+ *
+ * Interface to the RTS's flag parser
+ *
+ * Do not #include this file directly: #include "Rts.h" instead.
+ *
+ * To understand the structure of the RTS headers, see the wiki:
+ * https://gitlab.haskell.org/ghc/ghc/wikis/commentary/source-tree/includes
+ *
+ * ---------------------------------------------------------------------------*/
+
+#pragma once
+
+#include <stdbool.h>
+#include "stg/Types.h"
+
+// order is important - do not sort
+typedef enum _RtsFlagKey {
+ HELP,
+ INSTALL_SIGNAL_HANDLERS,
+ INSTALL_SEH_HANDLERS,
+ GENERATE_STACK_TRACES,
+ GENERATE_CRASH_DUMPS,
+ NULL_EVENTLOG_WRITER,
+ MACHINE_READABLE,
+ DISABLE_OS_MEM_RET,
+ INTERNAL_COUNTERS,
+ IO_MANAGER_FLAG,
+ INFO,
+ EVENTLOG_FLUSH_INTERVAL,
+ COPYING_GC,
+ NONMOVING_GC,
+ LARGE_OBJ_ALLOC_AREA,
+ MIN_ALLOC_AREA,
+ GC_BELL,
+// #if defined(THREADED_RTS)
+// #if defined(mingw32_HOST_OS)
+ IO_MANAGER_THREADS,
+// #endif
+ NUMA,
+// #endif
+// #if defined(DEBUG) && defined(THREADED_RTS)
+ DEBUG_NUMA,
+// #endif
+ LONG_GC_SYNC,
+ NO_AUTO_HEAP_SAMPLES,
+ NURSERY_CHUNK_SIZE,
+ COMPACT_GC,
+
+ UNKNOWN_RTS_OPTION,
+} RtsFlagKey;
+
+typedef enum _RtsFlagValueType {
+ VOID,
+ BOOL,
+ ENUM,
+ DOUBLE,
+ STGWORD64,
+} RtsFlagValueType;
+
+typedef struct _RtsFlagName {
+ bool optionSafe;
+ RtsFlagValueType valueType;
+ char* longName;
+ char* shortName;
+ bool valueRequired;
+} RtsFlagName;
+
+typedef struct _FlagValue {
+ RtsFlagKey key;
+ union {
+ bool boolean;
+ double _double;
+ int _enum;
+ char* text;
+ StgWord64 stgWord64;
+ } as;
+} RtsFlagValue;
+
+#define NO_VAL(flagKey) ((RtsFlagValue){flagKey, {.text = "VOID"}})
+#define BOOL_VAL(flagKey, value) ((RtsFlagValue){flagKey, {.boolean = value}})
+#define ENUM_VAL(flagKey, value) ((RtsFlagValue){flagKey, {._enum = value}})
+#define DOUBLE_VAL(flagKey, value) ((RtsFlagValue){flagKey, {._double = value}})
+#define STGWORD64_VAL(flagKey, value) ((RtsFlagValue){flagKey, {.stgWord64 = value}})
+
+#define IS_VOID(flagKey) ()
+
+extern RtsFlagName rtsFlags[];
+RtsFlagValue parseArg(char *arg, bool *error);
diff --git a/rts/rts.cabal.in b/rts/rts.cabal.in
index e4a78a64cf..ed4e85190b 100644
--- a/rts/rts.cabal.in
+++ b/rts/rts.cabal.in
@@ -227,6 +227,7 @@ library
rts/EventLogWriter.h
rts/FileLock.h
rts/Flags.h
+ rts/OptParse.h
rts/ForeignExports.h
rts/GetTime.h
rts/Globals.h
@@ -547,6 +548,7 @@ library
ReportMemoryMap.c
Messages.c
OldARMAtomic.c
+ OptParse.c
PathUtils.c
Pool.c
Printer.c