[runtime] Add an options API. (#32595)
authormonojenkins <jo.shields+jenkins@xamarin.com>
Thu, 15 Oct 2020 15:25:38 +0000 (11:25 -0400)
committerGitHub <noreply@github.com>
Thu, 15 Oct 2020 15:25:38 +0000 (17:25 +0200)
* [runtime] Add an options API.

Add a general options API to the runtime, based on the flags API in Google V8:

```https://chromium.googlesource.com/v8/v8.git/+/refs/heads/master/src/flags/```

Supported features:
* Definition of runtime options in a declarative way.
* Options are mapped to C globals.
* BOOL/INT/STRING data types.
* Generic option parsing code.
* Generic usage code.
* Read-only flags for build-time optimization.

This is designed to replace the many option parsing functions in
the runtime, MONO_DEBUG, the many mono_set_... functions etc.

* Fix the build.

Co-authored-by: vargaz <vargaz@users.noreply.github.com>
Co-authored-by: Zoltan Varga <vargaz@gmail.com>
src/mono/mono/mini/driver.c
src/mono/mono/utils/CMakeLists.txt
src/mono/mono/utils/Makefile.am
src/mono/mono/utils/options-def.h [new file with mode: 0644]
src/mono/mono/utils/options.c [new file with mode: 0644]
src/mono/mono/utils/options.h [new file with mode: 0644]
src/mono/msvc/libmonoutils-common.targets
src/mono/msvc/libmonoutils-common.targets.filters

index e7e0ed5..5115400 100644 (file)
@@ -52,6 +52,7 @@
 #include "mono/utils/mono-counters.h"
 #include "mono/utils/mono-hwcap.h"
 #include "mono/utils/mono-logger-internals.h"
+#include "mono/utils/options.h"
 #include "mono/metadata/w32handle.h"
 #include "mono/metadata/callspec.h"
 #include "mono/metadata/custom-attrs-internals.h"
@@ -1655,6 +1656,9 @@ mini_usage (void)
                "    --handlers             Install custom handlers, use --help-handlers for details.\n"
                "    --aot-path=PATH        List of additional directories to search for AOT images.\n"
          );
+
+       g_print ("\nOptions:\n");
+       mono_options_print_usage ();
 }
 
 static void
@@ -2115,6 +2119,7 @@ mono_main (int argc, char* argv[])
 #ifdef HOST_WIN32
        int mixed_mode = FALSE;
 #endif
+       ERROR_DECL (error);
 
 #ifdef MOONLIGHT
 #ifndef HOST_WIN32
@@ -2157,6 +2162,14 @@ mono_main (int argc, char* argv[])
        enable_debugging = TRUE;
 #endif
 
+       mono_options_parse_options ((const char**)argv + 1, argc - 1, &argc, error);
+       argc ++;
+       if (!is_ok (error)) {
+               g_printerr ("%s", mono_error_get_message (error));
+               mono_error_cleanup (error);
+               return 1;
+       }
+
        for (i = 1; i < argc; ++i) {
                if (argv [i] [0] != '-')
                        break;
index 8b57108..76b5ad4 100644 (file)
@@ -182,7 +182,10 @@ set(utils_common_sources
     refcount.h
     w32api.h
     unlocked.h
-    ward.h)
+    ward.h
+    options.h
+    options-def.h
+    options.c)
 
 if(MONO_CROSS_COMPILE)
 set(utils_arch_sources mach-support-unknown.c)
index bf0b6be..8d9bd11 100644 (file)
@@ -233,7 +233,10 @@ monoutils_sources = \
        w32api.h        \
        w32subset.h     \
        unlocked.h      \
-       ward.h
+       ward.h \
+       options.h \
+       options-def.h \
+       options.c
 
 arch_sources = 
 
diff --git a/src/mono/mono/utils/options-def.h b/src/mono/mono/utils/options-def.h
new file mode 100644 (file)
index 0000000..00a13ec
--- /dev/null
@@ -0,0 +1,65 @@
+/**
+ * \file Runtime options
+ *
+ * Copyright 2020 Microsoft
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
+ */
+
+/*
+ * This file defines all the flags/options which can be set at runtime.
+ *
+ * Options defined here generate a C variable named mono_<flag name> initialized to its default value.
+ * The variables are exported using MONO_API.
+ * The _READONLY variants generate C const variables so the compiler can optimize away their usage.
+ * Option types:
+ * BOOL - gboolean
+ * INT - int
+ * STRING - (malloc-ed) char*
+ *
+ * Option can be set on the command line using:
+ * --[no-]-option (bool)
+ * --option=value (int/string)
+ * --option value (int/string)
+ */
+
+/*
+ * This is a template header, the file including this needs to define this macro:
+ * DEFINE_OPTION_FULL(flag_type, ctype, c_name, cmd_name, def_value, comment)
+ * Optionally, define
+ * DEFINE_OPTION_READONLY as well.
+ */
+#ifndef DEFINE_OPTION_FULL
+#error ""
+#endif
+#ifndef DEFINE_OPTION_READONLY
+#define DEFINE_OPTION_READONLY(flag_type, ctype, c_name, cmd_name, def_value, comment) DEFINE_OPTION_FULL(flag_type, ctype, c_name, cmd_name, def_value, comment)
+#endif
+
+/* Types of flags */
+#define DEFINE_BOOL(name, cmd_name, def_value, comment) DEFINE_OPTION_FULL(MONO_OPTION_BOOL, gboolean, name, cmd_name, def_value, comment)
+#define DEFINE_BOOL_READONLY(name, cmd_name, def_value, comment) DEFINE_OPTION_READONLY(MONO_OPTION_BOOL_READONLY, gboolean, name, cmd_name, def_value, comment)
+#define DEFINE_INT(name, cmd_name, def_value, comment) DEFINE_OPTION_FULL(MONO_OPTION_INT, int, name, cmd_name, def_value, comment)
+#define DEFINE_STRING(name, cmd_name, def_value, comment) DEFINE_OPTION_FULL(MONO_OPTION_STRING, char*, name, cmd_name, def_value, comment)
+
+/*
+ * List of runtime flags
+ */
+
+// FIXME: To avoid empty arrays, remove later
+DEFINE_BOOL(bool_flag, "bool-flag", FALSE, "Example")
+
+/*
+DEFINE_BOOL(bool_flag, "bool-flag", FALSE, "Example")
+DEFINE_INT(int_flag, "int-flag", 0, "Example")
+DEFINE_STRING(string_flag, "string-flag", NULL, "Example")
+
+#ifdef ENABLE_EXAMPLE
+DEFINE_BOOL(readonly_flag, "readonly-flag", FALSE, "Example")
+#else
+DEFINE_BOOL_READONLY(readonly_flag, "readonly-flag", FALSE, "Example")
+#endif
+*/
+
+/* Cleanup */
+#undef DEFINE_OPTION_FULL
+#undef DEFINE_OPTION_READONLY
diff --git a/src/mono/mono/utils/options.c b/src/mono/mono/utils/options.c
new file mode 100644 (file)
index 0000000..f25b0fd
--- /dev/null
@@ -0,0 +1,224 @@
+/**
+ * \file Runtime options
+ *
+ * Copyright 2020 Microsoft
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
+ */
+
+#include <stdio.h>
+
+#include "options.h"
+#include "mono/utils/mono-error-internals.h"
+
+typedef enum {
+       MONO_OPTION_BOOL,
+       MONO_OPTION_BOOL_READONLY,
+       MONO_OPTION_INT,
+       MONO_OPTION_STRING
+} MonoOptionType;
+
+/* Define flags */
+#define DEFINE_OPTION_FULL(option_type, ctype, c_name, cmd_name, def_value, comment) \
+       ctype mono_opt_##c_name = def_value;
+#define DEFINE_OPTION_READONLY(option_type, ctype, c_name, cmd_name, def_value, comment)
+#include "options-def.h"
+
+/* Flag metadata */
+typedef struct {
+       MonoOptionType option_type;
+       gpointer addr;
+       const char *cmd_name;
+       int cmd_name_len;
+} OptionData;
+
+static OptionData option_meta[] = {
+#define DEFINE_OPTION_FULL(option_type, ctype, c_name, cmd_name, def_value, comment) \
+       { option_type, &mono_opt_##c_name, cmd_name, sizeof (cmd_name) - 1 },
+#define DEFINE_OPTION_READONLY(option_type, ctype, c_name, cmd_name, def_value, comment) \
+       { option_type, NULL, cmd_name, sizeof (cmd_name) - 1 },
+#include "options-def.h"
+};
+
+static const char*
+option_type_to_str (MonoOptionType type)
+{
+       switch (type) {
+       case MONO_OPTION_BOOL:
+               return "bool";
+       case MONO_OPTION_BOOL_READONLY:
+               return "bool (read-only)";
+       case MONO_OPTION_INT:
+               return "int";
+       case MONO_OPTION_STRING:
+               return "string";
+       default:
+               g_assert_not_reached ();
+               return NULL;
+       }
+}
+
+static char *
+option_value_to_str (MonoOptionType type, gconstpointer addr)
+{
+       switch (type) {
+       case MONO_OPTION_BOOL:
+       case MONO_OPTION_BOOL_READONLY:
+               return *(gboolean*)addr ? g_strdup ("true") : g_strdup ("false");
+       case MONO_OPTION_INT:
+               return g_strdup_printf ("%d", *(int*)addr);
+       case MONO_OPTION_STRING:
+               return *(char**)addr ? g_strdup_printf ("%s", *(char**)addr) : g_strdup ("\"\"");
+       default:
+               g_assert_not_reached ();
+               return NULL;
+       }
+}
+
+void
+mono_options_print_usage (void)
+{
+#define DEFINE_OPTION_FULL(option_type, ctype, c_name, cmd_name, def_value, comment) do { \
+               char *val = option_value_to_str (option_type, &mono_opt_##c_name); \
+               g_printf ("  --%s (%s)\n\ttype: %s  default: %s\n", cmd_name, comment, option_type_to_str (option_type), val); \
+               g_free (val); \
+       } while (0);
+#include "options-def.h"
+}
+
+/*
+ * mono_optiond_parse_options:
+ *
+ *   Set options based on the command line arguments in ARGV/ARGC.
+ * Remove processed arguments from ARGV and set *OUT_ARGC to the
+ * number of remaining arguments.
+ *
+ * NOTE: This only sets the variables, the caller might need to do
+ * additional processing based on the new values of the variables.
+ */
+void
+mono_options_parse_options (const char **argv, int argc, int *out_argc, MonoError *error)
+{
+       int aindex = 0;
+       int i;
+       GHashTable *option_hash = NULL;
+
+       while (aindex < argc) {
+               const char *arg = argv [aindex];
+
+               if (!(arg [0] == '-' && arg [1] == '-')) {
+                       aindex ++;
+                       continue;
+               }
+               arg = arg + 2;
+
+               if (option_hash == NULL) {
+                       /* Compute a hash to avoid n^2 behavior */
+                       option_hash = g_hash_table_new (g_str_hash, g_str_equal);
+                       for (i = 0; i < G_N_ELEMENTS (option_meta); ++i) {
+                               g_hash_table_insert (option_hash, (gpointer)option_meta [i].cmd_name, &option_meta [i]);
+                       }
+               }
+
+               /* Compute flag name */
+               char *arg_copy = g_strdup (arg);
+               char *optname = arg_copy;
+               int len = strlen (arg);
+               int equals_sign_index = -1;
+               /* Handle no- prefix */
+               if (optname [0] == 'n' && optname [1] == 'o' && optname [2] == '-') {
+                       optname += 3;
+               } else {
+                       /* Handle option=value */
+                       for (int i = 0; i < len; ++i) {
+                               if (optname [i] == '=') {
+                                       equals_sign_index = i;
+                                       optname [i] = '\0';
+                                       break;
+                               }
+                       }
+               }
+
+               OptionData *option = (OptionData*)g_hash_table_lookup (option_hash, optname);
+               g_free (arg_copy);
+
+               if (!option) {
+                       aindex ++;
+                       continue;
+               }
+
+               switch (option->option_type) {
+               case MONO_OPTION_BOOL:
+               case MONO_OPTION_BOOL_READONLY: {
+                       gboolean negate = FALSE;
+                       if (len == option->cmd_name_len) {
+                       } else if (arg [0] == 'n' && arg [1] == 'o' && arg [2] == '-' && len == option->cmd_name_len + 3) {
+                               negate = TRUE;
+                       } else {
+                               break;
+                       }
+                       if (option->option_type == MONO_OPTION_BOOL_READONLY) {
+                               mono_error_set_error (error, 1, "Unable to set option '%s' as it's read-only.\n", arg);
+                               break;
+                       }
+                       *(gboolean*)option->addr = negate ? FALSE : TRUE;
+                       argv [aindex] = NULL;
+                       break;
+               }
+               case MONO_OPTION_INT:
+               case MONO_OPTION_STRING: {
+                       const char *value = NULL;
+
+                       if (len == option->cmd_name_len) {
+                               // --option value
+                               if (aindex + 1 == argc) {
+                                       mono_error_set_error (error, 1, "Missing value for option '%s'.\n", option->cmd_name);
+                                       break;
+                               }
+                               value = argv [aindex + 1];
+                               argv [aindex] = NULL;
+                               argv [aindex + 1] = NULL;
+                               aindex ++;
+                       } else if (equals_sign_index != -1) {
+                               // option=value
+                               value = arg + equals_sign_index + 1;
+                               argv [aindex] = NULL;
+                       } else {
+                               g_assert_not_reached ();
+                       }
+
+                       if (option->option_type == MONO_OPTION_STRING) {
+                               *(char**)option->addr = g_strdup (value);
+                       } else {
+                               char *endp;
+                               long v = strtol (value, &endp, 10);
+                               if (!value [0] || *endp) {
+                                       mono_error_set_error (error, 1, "Invalid value for option '%s': '%s'.\n", option->cmd_name, value);
+                                       break;
+                               }
+                               *(int*)option->addr = (int)v;
+                       }
+                       break;
+               }
+               default:
+                       g_assert_not_reached ();
+                       break;
+               }
+
+               if (!is_ok (error))
+                       break;
+               aindex ++;
+       }
+
+       if (option_hash)
+               g_hash_table_destroy (option_hash);
+       if (!is_ok (error))
+               return;
+
+       /* Remove processed arguments */
+       aindex = 0;
+       for (i = 0; i < argc; ++i) {
+               if (argv [i])
+                       argv [aindex ++] = argv [i];
+       }
+       *out_argc = aindex;
+}
diff --git a/src/mono/mono/utils/options.h b/src/mono/mono/utils/options.h
new file mode 100644 (file)
index 0000000..bb3d24b
--- /dev/null
@@ -0,0 +1,29 @@
+/**
+ * \file Runtime options
+ *
+ * Copyright 2020 Microsoft
+ * Licensed under the MIT license. See LICENSE file in the project root for full license information.
+ */
+#ifndef __MONO_UTILS_FLAGS_H__
+#define __MONO_UTILS_FLAGS_H__
+
+#include <config.h>
+#include <glib.h>
+
+#include "mono/utils/mono-error.h"
+
+/* Declare list of options */
+/* Each option will declare an exported C variable named mono_opt_... */
+MONO_BEGIN_DECLS
+#define DEFINE_OPTION_FULL(flag_type, ctype, c_name, cmd_name, def_value, comment) \
+       MONO_API_DATA ctype mono_opt_##c_name;
+#define DEFINE_OPTION_READONLY(flag_type, ctype, c_name, cmd_name, def_value, comment) \
+       static const ctype mono_opt_##c_name = def_value;
+#include "options-def.h"
+MONO_END_DECLS
+
+void mono_options_print_usage (void);
+
+void mono_options_parse_options (const char **args, int argc, int *out_argc, MonoError *error);
+
+#endif
index 28e3d1c..d53b74b 100644 (file)
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\unlocked.h" />
     <ClCompile Include="$(MonoSourceLocation)\mono\utils\mono-state.c" />
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\mono-state.h" />
+    <ClInclude Include="$(MonoSourceLocation)\mono\utils\options.h" />
+    <ClCompile Include="$(MonoSourceLocation)\mono\utils\options.c" />
+    <ClInclude Include="$(MonoSourceLocation)\mono\utils\options-def.h" />
   </ItemGroup>
   <ItemGroup Label="libmonoutilsinclude_headers">
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\mono-logger.h" />
index c60ab51..5692e16 100644 (file)
     <ClCompile Include="$(MonoSourceLocation)\mono\utils\checked-build.c">
       <Filter>Source Files$(MonoUtilsFilterSubFolder)\common</Filter>
     </ClCompile>
+    <ClCompile Include="$(MonoSourceLocation)\mono\utils\options.c">
+      <Filter>Header Files$(MonoUtilsFilterSubFolder)\common</Filter>
+    </ClCompile>
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\checked-build.h">
       <Filter>Header Files$(MonoUtilsFilterSubFolder)\common</Filter>
     </ClInclude>
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\mono-state.h">
       <Filter>Header Files$(MonoUtilsFilterSubFolder)\common</Filter>
     </ClInclude>
+    <ClInclude Include="$(MonoSourceLocation)\mono\utils\options.h">
+      <Filter>Header Files$(MonoUtilsFilterSubFolder)\common</Filter>
+    </ClInclude>
+    <ClInclude Include="$(MonoSourceLocation)\mono\utils\options-def.h">
+      <Filter>Header Files$(MonoUtilsFilterSubFolder)\common</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup Label="libmonoutilsinclude_headers">
     <ClInclude Include="$(MonoSourceLocation)\mono\utils\mono-logger.h">