Skip to content

Commit

Permalink
[runtime] Add an options API.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vargaz committed Oct 13, 2020
1 parent f2090e7 commit f2aff60
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/mono/mono/mini/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2157,6 +2162,13 @@ mono_main (int argc, char* argv[])
enable_debugging = TRUE;
#endif

mono_options_parse_options ((const char**)argv, argc, &argc, error);
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;
Expand Down
5 changes: 4 additions & 1 deletion src/mono/mono/utils/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,10 @@ monoutils_sources = \
refcount.h \
w32api.h \
unlocked.h \
ward.h
ward.h \
options.h \
options-def.h \
options.c

arch_sources =

Expand Down
65 changes: 65 additions & 0 deletions src/mono/mono/utils/options-def.h
Original file line number Diff line number Diff line change
@@ -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
224 changes: 224 additions & 0 deletions src/mono/mono/utils/options.c
Original file line number Diff line number Diff line change
@@ -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;
}
29 changes: 29 additions & 0 deletions src/mono/mono/utils/options.h
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/mono/msvc/libmonoutils-common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@
<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" />
Expand Down
Loading

0 comments on commit f2aff60

Please sign in to comment.