diff options
author | Sergei Trofimovich <siarheit@google.com> | 2022-06-04 15:48:01 -0400 |
---|---|---|
committer | Paul Smith <psmith@gnu.org> | 2022-06-04 19:04:37 -0400 |
commit | 621d3196fae94e9006a7e9c5ffdaf5ec209bf832 (patch) | |
tree | 15cbbc7aa6f32400fd4c80a6704099584eb722bc /src/shuffle.c | |
parent | e4b7ac21dc1663de1d901cda0b395d819b5c08ab (diff) | |
download | make-git-621d3196fae94e9006a7e9c5ffdaf5ec209bf832.tar.gz |
[SV 62100] Add '--shuffle' option support
Introduce non-deterministic ordering into goal and prerequisite
traversal to help tease out inconsistent failures that may happen
when running in parallel build mode.
Introduce second order into each dependency chain:
1. Existing order is syntactic order reachable via 'dep->next'
2. New order is shuffled order stored as 'dep->shuf' in each 'dep'
When updating goals and prerequisites and '--shuffle' is provided,
use the shuffled order to walk the graph. When automatic variable
are set always use the syntactic order of parameters.
* Makefile.am: Add new src/shuffle.c and src/shuffle.h file.
* build_w32.bat: Ditto.
* builddos.bat: Ditto.
* makefile.com: Ditto.
* po/POTFILES.in: Ditto.
* doc/make.texi: Add documentation for --shuffle.
* doc/make.1: Ditto.
* src/dep.h (DEP): Add the shuf pointer.
* src/filedef.h (struct file): Add was_shuffled flag.
* src/main.c: (shuffle_mode): Global flag for the shuffle mode.
(usage): Add the --shuffle option.
(switches): Ditto.
(main): Set shuffle_mode based on the command line parameter.
Reshuffle prerequisites if requested.
* src/remake.c (update_goal_chain): Walk the shuffled list if enabled.
(update_file_1): Ditto.
* src/shuffle.h: Provide an interface for shuffling prerequisites.
* src/shuffle.c: Implement option parsing and prerequisite shuffling.
* tests/scripts/options/shuffle: Test shuffle option and modes.
Diffstat (limited to 'src/shuffle.c')
-rw-r--r-- | src/shuffle.c | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/src/shuffle.c b/src/shuffle.c new file mode 100644 index 00000000..ea6e836d --- /dev/null +++ b/src/shuffle.c @@ -0,0 +1,240 @@ +/* Provide prerequisite shuffle support. +Copyright (C) 2022 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "makeint.h" + +#include "shuffle.h" + +#include "filedef.h" +#include "dep.h" + +/* Supported shuffle modes. */ +static void random_shuffle_array (void ** a, size_t len); +static void reverse_shuffle_array (void ** a, size_t len); +static void identity_shuffle_array (void ** a, size_t len); + +/* The way goals and rules are shuffled during update. */ +enum shuffle_mode + { + /* No shuffle data is populated or used. */ + sm_none, + /* Random within dependency list. */ + sm_random, + /* Inverse order. */ + sm_reverse, + /* identity order. Differs from SM_NONE by explicitly populating + the traversal order. */ + sm_identity, + }; + +/* Shuffle configuration. */ +static struct + { + enum shuffle_mode mode; + unsigned int seed; + void (*shuffler) (void **a, size_t len); + char strval[INTSTR_LENGTH]; + } config = { sm_none, 0, NULL, "" }; + +/* Return string value of --shuffle= option passed. + If none was passed or --shuffle=none was used function + returns NULL. */ +const char * +shuffle_get_mode () +{ + return config.strval[0] ? config.strval : NULL; +} + +void +shuffle_set_mode (const char *cmdarg) +{ + /* Parse supported '--shuffle' mode. */ + if (strcasecmp (cmdarg, "random") == 0) + { + config.mode = sm_random; + config.seed = (unsigned int) (time (NULL) ^ make_pid ()); + } + else if (strcasecmp (cmdarg, "reverse") == 0) + config.mode = sm_reverse; + else if (strcasecmp (cmdarg, "identity") == 0) + config.mode = sm_identity; + else if (strcasecmp (cmdarg, "none") == 0) + config.mode = sm_none; + /* Assume explicit seed if starts from a digit. */ + else + { + const char *err; + config.mode = sm_random; + config.seed = make_toui (cmdarg, &err); + + if (err) + { + OS (error, NILF, _("invalid shuffle mode: '%s'"), cmdarg); + die (MAKE_FAILURE); + } + } + + switch (config.mode) + { + case sm_random: + config.shuffler = random_shuffle_array; + sprintf (config.strval, "%u", config.seed); + break; + case sm_reverse: + config.shuffler = reverse_shuffle_array; + strcpy (config.strval, "reverse"); + break; + case sm_identity: + config.shuffler = identity_shuffle_array; + strcpy (config.strval, "identity"); + break; + case sm_none: + config.strval[0] = '\0'; + break; + } +} + +/* Shuffle array elements using RAND(). */ +static void +random_shuffle_array (void **a, size_t len) +{ + size_t i; + for (i = 0; i < len; i++) + { + void *t; + + /* Pick random element and swap. */ + unsigned int j = rand () % len; + if (i == j) + continue; + + /* Swap. */ + t = a[i]; + a[i] = a[j]; + a[j] = t; + } +} + +/* Shuffle array elements using reverse order. */ +static void +reverse_shuffle_array (void **a, size_t len) +{ + size_t i; + for (i = 0; i < len / 2; i++) + { + void *t; + + /* Pick mirror and swap. */ + unsigned int j = len - 1 - i; + + /* Swap. */ + t = a[i]; + a[i] = a[j]; + a[j] = t; + } +} + +/* Shuffle array elements using identity order. */ +static void +identity_shuffle_array (void **a UNUSED, size_t len UNUSED) +{ + /* No-op! */ +} + +/* Shuffle list of dependencies by populating '->next' + field in each 'struct dep'. */ +static void +shuffle_deps (struct dep *deps) +{ + size_t ndeps = 0; + struct dep *dep; + void **da; + void **dp; + + for (dep = deps; dep; dep = dep->next) + ndeps++; + + if (ndeps == 0) + return; + + /* Allocate array of all deps, store, shuffle, write back. */ + da = xmalloc (sizeof (struct dep *) * ndeps); + + /* Store locally. */ + for (dep = deps, dp = da; dep; dep = dep->next, dp++) + *dp = dep; + + /* Shuffle. */ + config.shuffler (da, ndeps); + + /* Write back. */ + for (dep = deps, dp = da; dep; dep = dep->next, dp++) + dep->shuf = *dp; + + free (da); +} + +/* Shuffle 'deps' of each 'file' recursively. */ +static void +shuffle_file_deps_recursive (struct file *f) +{ + struct dep *dep; + + /* Implicit rules do not always provide any depends. */ + if (!f) + return; + + /* Avoid repeated shuffles and loops. */ + if (f->was_shuffled) + return; + f->was_shuffled = 1; + + shuffle_deps (f->deps); + + /* Shuffle dependencies. */ + for (dep = f->deps; dep; dep = dep->next) + shuffle_file_deps_recursive (dep->file); +} + +/* Shuffle goal dependencies first, then shuffle dependency list + of each file reachable from goaldep recursively. Used by + --shuffle flag to introduce artificial non-determinism in build + order. .*/ + +void +shuffle_deps_recursive (struct dep *deps) +{ + struct dep *dep; + + /* Exit early if shuffling was not requested. */ + if (config.mode == sm_none) + return; + + /* Do not reshuffle targets if Makefile is explicitly marked as + problematic for parallelism. */ + if (not_parallel) + return; + + /* Set specific seed at the top level of recursion. */ + if (config.mode == sm_random) + srand (config.seed); + + shuffle_deps (deps); + + /* Shuffle dependencies. */ + for (dep = deps; dep; dep = dep->next) + shuffle_file_deps_recursive (dep->file); +} |