| /* Provide prerequisite shuffle support. | |
| Copyright (C) 2022-2025 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 <https://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 + 1]; | |
| } 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] == '\0' ? NULL : config.strval; | |
| } | |
| void | |
| shuffle_set_mode (const char *cmdarg) | |
| { | |
| /* Parse supported '--shuffle' mode. */ | |
| if (strcasecmp (cmdarg, "reverse") == 0) | |
| { | |
| config.mode = sm_reverse; | |
| config.shuffler = reverse_shuffle_array; | |
| strcpy (config.strval, "reverse"); | |
| } | |
| else if (strcasecmp (cmdarg, "identity") == 0) | |
| { | |
| config.mode = sm_identity; | |
| config.shuffler = identity_shuffle_array; | |
| strcpy (config.strval, "identity"); | |
| } | |
| else if (strcasecmp (cmdarg, "none") == 0) | |
| { | |
| config.mode = sm_none; | |
| config.shuffler = NULL; | |
| config.strval[0] = '\0'; | |
| } | |
| else | |
| { | |
| if (strcasecmp (cmdarg, "random") == 0) | |
| config.seed = make_rand (); | |
| else | |
| { | |
| /* Assume explicit seed. */ | |
| const char *err; | |
| config.seed = make_toui (cmdarg, &err); | |
| if (err) | |
| OSS (fatal, NILF, _("invalid shuffle mode: %s: '%s'"), err, cmdarg); | |
| } | |
| config.mode = sm_random; | |
| config.shuffler = random_shuffle_array; | |
| sprintf (config.strval, "%u", config.seed); | |
| } | |
| } | |
| /* Shuffle array elements using RAND(). */ | |
| static void | |
| random_shuffle_array (void **a, size_t len) | |
| { | |
| size_t i; | |
| if (len <= 1) | |
| return; | |
| for (i = len - 1; i >= 1; i--) | |
| { | |
| void *t; | |
| /* Pick random element and swap. */ | |
| unsigned int j = make_rand () % (i + 1); | |
| 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. */ | |
| size_t 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 '->shuf' | |
| 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) | |
| { | |
| /* Do not reshuffle prerequisites if any .WAIT is present. */ | |
| if (dep->wait_here) | |
| return; | |
| 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 prerequisites if .NOTPARALLEL was specified. */ | |
| if (not_parallel) | |
| return; | |
| /* Set specific seed at the top level of recursion. */ | |
| if (config.mode == sm_random) | |
| make_seed (config.seed); | |
| shuffle_deps (deps); | |
| /* Shuffle dependencies. */ | |
| for (dep = deps; dep; dep = dep->next) | |
| shuffle_file_deps_recursive (dep->file); | |
| } |