Skip to content

Commit 7de893c

Browse files
authored
Merge pull request #16817 from Sahnvour/shuffle-deps
std.Build: add --seed argument to randomize step dependencies spawning
2 parents 45d7dfa + 1020097 commit 7de893c

File tree

2 files changed

+54
-4
lines changed

2 files changed

+54
-4
lines changed

lib/build_runner.zig

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub fn main() !void {
9595
var max_rss: usize = 0;
9696
var skip_oom_steps: bool = false;
9797
var color: Color = .auto;
98+
var seed: u32 = 0;
9899

99100
const stderr_stream = io.getStdErr().writer();
100101
const stdout_stream = io.getStdOut().writer();
@@ -196,6 +197,17 @@ pub fn main() !void {
196197
std.debug.print("Expected argument after {s}\n\n", .{arg});
197198
usageAndErr(builder, false, stderr_stream);
198199
} };
200+
} else if (mem.eql(u8, arg, "--seed")) {
201+
const next_arg = nextArg(args, &arg_idx) orelse {
202+
std.debug.print("Expected u32 after {s}\n\n", .{arg});
203+
usageAndErr(builder, false, stderr_stream);
204+
};
205+
seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
206+
std.debug.print("unable to parse seed '{s}' as 32-bit integer: {s}", .{
207+
next_arg, @errorName(err),
208+
});
209+
process.exit(1);
210+
};
199211
} else if (mem.eql(u8, arg, "--debug-log")) {
200212
const next_arg = nextArg(args, &arg_idx) orelse {
201213
std.debug.print("Expected argument after {s}\n\n", .{arg});
@@ -329,6 +341,7 @@ pub fn main() !void {
329341
main_progress_node,
330342
thread_pool_options,
331343
&run,
344+
seed,
332345
) catch |err| switch (err) {
333346
error.UncleanExit => process.exit(1),
334347
else => return err,
@@ -355,6 +368,7 @@ fn runStepNames(
355368
parent_prog_node: *std.Progress.Node,
356369
thread_pool_options: std.Thread.Pool.Options,
357370
run: *Run,
371+
seed: u32,
358372
) !void {
359373
const gpa = b.allocator;
360374
var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
@@ -375,8 +389,13 @@ fn runStepNames(
375389
}
376390

377391
const starting_steps = try arena.dupe(*Step, step_stack.keys());
392+
393+
var rng = std.rand.DefaultPrng.init(seed);
394+
const rand = rng.random();
395+
rand.shuffle(*Step, starting_steps);
396+
378397
for (starting_steps) |s| {
379-
checkForDependencyLoop(b, s, &step_stack) catch |err| switch (err) {
398+
constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
380399
error.DependencyLoopDetected => return error.UncleanExit,
381400
else => |e| return e,
382401
};
@@ -748,10 +767,22 @@ fn printTreeStep(
748767
}
749768
}
750769

751-
fn checkForDependencyLoop(
770+
/// Traverse the dependency graph depth-first and make it undirected by having
771+
/// steps know their dependants (they only know dependencies at start).
772+
/// Along the way, check that there is no dependency loop, and record the steps
773+
/// in traversal order in `step_stack`.
774+
/// Each step has its dependencies traversed in random order, this accomplishes
775+
/// two things:
776+
/// - `step_stack` will be in randomized-depth-first order, so the build runner
777+
/// spawns steps in a random (but optimized) order
778+
/// - each step's `dependants` list is also filled in a random order, so that
779+
/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
780+
/// to run in random order
781+
fn constructGraphAndCheckForDependencyLoop(
752782
b: *std.Build,
753783
s: *Step,
754784
step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
785+
rand: std.rand.Random,
755786
) !void {
756787
switch (s.state) {
757788
.precheck_started => {
@@ -762,10 +793,16 @@ fn checkForDependencyLoop(
762793
s.state = .precheck_started;
763794

764795
try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
765-
for (s.dependencies.items) |dep| {
796+
797+
// We dupe to avoid shuffling the steps in the summary, it depends
798+
// on s.dependencies' order.
799+
const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
800+
rand.shuffle(*Step, deps);
801+
802+
for (deps) |dep| {
766803
try step_stack.put(b.allocator, dep, {});
767804
try dep.dependants.append(b.allocator, s);
768-
checkForDependencyLoop(b, dep, step_stack) catch |err| {
805+
constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
769806
if (err == error.DependencyLoopDetected) {
770807
std.debug.print(" {s}\n", .{s.name});
771808
}
@@ -1034,6 +1071,7 @@ fn usage(builder: *std.Build, already_ran_build: bool, out_stream: anytype) !voi
10341071
\\ --global-cache-dir [path] Override path to global Zig cache directory
10351072
\\ --zig-lib-dir [arg] Override path to Zig lib directory
10361073
\\ --build-runner [file] Override path to build runner
1074+
\\ --seed [integer] For shuffling dependency traversal order (default: random)
10371075
\\ --debug-log [scope] Enable debugging the compiler
10381076
\\ --debug-pkg-config Fail if unknown pkg-config flags encountered
10391077
\\ --verbose-link Enable compiler debug output for linking

src/main.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4906,6 +4906,7 @@ pub const usage_build =
49064906
\\ --global-cache-dir [path] Override path to global Zig cache directory
49074907
\\ --zig-lib-dir [arg] Override path to Zig lib directory
49084908
\\ --build-runner [file] Override path to build runner
4909+
\\ --seed [integer] For shuffling dependency traversal order (default: random)
49094910
\\ --fetch Exit after fetching dependency tree
49104911
\\ -h, --help Print this help and exit
49114912
\\
@@ -4945,6 +4946,12 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
49454946
const argv_index_global_cache_dir = child_argv.items.len;
49464947
_ = try child_argv.addOne();
49474948

4949+
try child_argv.appendSlice(&.{
4950+
"--seed",
4951+
try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}),
4952+
});
4953+
const argv_index_seed = child_argv.items.len - 1;
4954+
49484955
{
49494956
var i: usize = 0;
49504957
while (i < args.len) : (i += 1) {
@@ -4993,6 +5000,11 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
49935000
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
49945001
try child_argv.append(arg);
49955002
debug_compile_errors = true;
5003+
} else if (mem.eql(u8, arg, "--seed")) {
5004+
if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg});
5005+
i += 1;
5006+
child_argv.items[argv_index_seed] = args[i];
5007+
continue;
49965008
}
49975009
}
49985010
try child_argv.append(arg);

0 commit comments

Comments
 (0)