authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:14:57-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-02-26 23:43:56-07:00
logdfe430e9f488536c6ce4be23473f60aa5e89ab5a
tree659fda01e693b129a6c28313d4b3c8b075d3e465
parent0157e1196c77702f07d44c63c71246ff5e5616f1

move lazily compiled source files to lib/compiler/


11 files changed, 3398 insertions(+), 3394 deletions(-)

lib/build_runner.zig deleted-1273
...@@ -1,1273 +0,0 @@
1const root = @import("@build");
2const std = @import("std");
3const builtin = @import("builtin");
4const assert = std.debug.assert;
5const io = std.io;
6const fmt = std.fmt;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const File = std.fs.File;
11const Step = std.Build.Step;
12
13pub const dependencies = @import("@dependencies");
14
15pub fn main() !void {
16 // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
17 // one shot program. We don't need to waste time freeing memory and finding places to squish
18 // bytes into. So we free everything all at once at the very end.
19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20 defer single_threaded_arena.deinit();
21
22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
23 .child_allocator = single_threaded_arena.allocator(),
24 };
25 const arena = thread_safe_arena.allocator();
26
27 const args = try process.argsAlloc(arena);
28
29 // skip my own exe name
30 var arg_idx: usize = 1;
31
32 const zig_exe = nextArg(args, &arg_idx) orelse {
33 std.debug.print("Expected path to zig compiler\n", .{});
34 return error.InvalidArgs;
35 };
36 const build_root = nextArg(args, &arg_idx) orelse {
37 std.debug.print("Expected build root directory path\n", .{});
38 return error.InvalidArgs;
39 };
40 const cache_root = nextArg(args, &arg_idx) orelse {
41 std.debug.print("Expected cache root directory path\n", .{});
42 return error.InvalidArgs;
43 };
44 const global_cache_root = nextArg(args, &arg_idx) orelse {
45 std.debug.print("Expected global cache root directory path\n", .{});
46 return error.InvalidArgs;
47 };
48
49 const build_root_directory: std.Build.Cache.Directory = .{
50 .path = build_root,
51 .handle = try std.fs.cwd().openDir(build_root, .{}),
52 };
53
54 const local_cache_directory: std.Build.Cache.Directory = .{
55 .path = cache_root,
56 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
57 };
58
59 const global_cache_directory: std.Build.Cache.Directory = .{
60 .path = global_cache_root,
61 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
62 };
63
64 var graph: std.Build.Graph = .{
65 .arena = arena,
66 .cache = .{
67 .gpa = arena,
68 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
69 },
70 .zig_exe = zig_exe,
71 .env_map = try process.getEnvMap(arena),
72 .global_cache_root = global_cache_directory,
73 };
74
75 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
76 graph.cache.addPrefix(build_root_directory);
77 graph.cache.addPrefix(local_cache_directory);
78 graph.cache.addPrefix(global_cache_directory);
79 graph.cache.hash.addBytes(builtin.zig_version_string);
80
81 const builder = try std.Build.create(
82 &graph,
83 build_root_directory,
84 local_cache_directory,
85 dependencies.root_deps,
86 );
87
88 var targets = ArrayList([]const u8).init(arena);
89 var debug_log_scopes = ArrayList([]const u8).init(arena);
90 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
91
92 var install_prefix: ?[]const u8 = null;
93 var dir_list = std.Build.DirList{};
94 var summary: ?Summary = null;
95 var max_rss: u64 = 0;
96 var skip_oom_steps: bool = false;
97 var color: Color = .auto;
98 var seed: u32 = 0;
99 var prominent_compile_errors: bool = false;
100 var help_menu: bool = false;
101 var steps_menu: bool = false;
102 var output_tmp_nonce: ?[16]u8 = null;
103
104 while (nextArg(args, &arg_idx)) |arg| {
105 if (mem.startsWith(u8, arg, "-Z")) {
106 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
107 output_tmp_nonce = arg[2..18].*;
108 } else if (mem.startsWith(u8, arg, "-D")) {
109 const option_contents = arg[2..];
110 if (option_contents.len == 0)
111 fatalWithHint("expected option name after '-D'", .{});
112 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
113 const option_name = option_contents[0..name_end];
114 const option_value = option_contents[name_end + 1 ..];
115 if (try builder.addUserInputOption(option_name, option_value))
116 fatal(" access the help menu with 'zig build -h'", .{});
117 } else {
118 if (try builder.addUserInputFlag(option_contents))
119 fatal(" access the help menu with 'zig build -h'", .{});
120 }
121 } else if (mem.startsWith(u8, arg, "-")) {
122 if (mem.eql(u8, arg, "--verbose")) {
123 builder.verbose = true;
124 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
125 help_menu = true;
126 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
127 install_prefix = nextArgOrFatal(args, &arg_idx);
128 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
129 steps_menu = true;
130 } else if (mem.startsWith(u8, arg, "-fsys=")) {
131 const name = arg["-fsys=".len..];
132 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
133 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
134 const name = arg["-fno-sys=".len..];
135 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
136 } else if (mem.eql(u8, arg, "--release")) {
137 builder.release_mode = .any;
138 } else if (mem.startsWith(u8, arg, "--release=")) {
139 const text = arg["--release=".len..];
140 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
141 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
142 arg, text,
143 });
144 };
145 } else if (mem.eql(u8, arg, "--host-target")) {
146 graph.host_query_options.arch_os_abi = nextArgOrFatal(args, &arg_idx);
147 } else if (mem.eql(u8, arg, "--host-cpu")) {
148 graph.host_query_options.cpu_features = nextArgOrFatal(args, &arg_idx);
149 } else if (mem.eql(u8, arg, "--host-dynamic-linker")) {
150 graph.host_query_options.dynamic_linker = nextArgOrFatal(args, &arg_idx);
151 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
152 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
153 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
154 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
155 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
156 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
157 } else if (mem.eql(u8, arg, "--sysroot")) {
158 builder.sysroot = nextArgOrFatal(args, &arg_idx);
159 } else if (mem.eql(u8, arg, "--maxrss")) {
160 const max_rss_text = nextArgOrFatal(args, &arg_idx);
161 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
162 std.debug.print("invalid byte size: '{s}': {s}\n", .{
163 max_rss_text, @errorName(err),
164 });
165 process.exit(1);
166 };
167 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
168 skip_oom_steps = true;
169 } else if (mem.eql(u8, arg, "--search-prefix")) {
170 const search_prefix = nextArgOrFatal(args, &arg_idx);
171 builder.addSearchPrefix(search_prefix);
172 } else if (mem.eql(u8, arg, "--libc")) {
173 builder.libc_file = nextArgOrFatal(args, &arg_idx);
174 } else if (mem.eql(u8, arg, "--color")) {
175 const next_arg = nextArg(args, &arg_idx) orelse
176 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
177 color = std.meta.stringToEnum(Color, next_arg) orelse {
178 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
179 arg, next_arg,
180 });
181 };
182 } else if (mem.eql(u8, arg, "--summary")) {
183 const next_arg = nextArg(args, &arg_idx) orelse
184 fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
185 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
186 fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
187 arg, next_arg,
188 });
189 };
190 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
191 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
192 } else if (mem.eql(u8, arg, "--seed")) {
193 const next_arg = nextArg(args, &arg_idx) orelse
194 fatalWithHint("expected u32 after '{s}'", .{arg});
195 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
196 fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
197 next_arg, @errorName(err),
198 });
199 };
200 } else if (mem.eql(u8, arg, "--debug-log")) {
201 const next_arg = nextArgOrFatal(args, &arg_idx);
202 try debug_log_scopes.append(next_arg);
203 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
204 builder.debug_pkg_config = true;
205 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
206 builder.debug_compile_errors = true;
207 } else if (mem.eql(u8, arg, "--system")) {
208 // The usage text shows another argument after this parameter
209 // but it is handled by the parent process. The build runner
210 // only sees this flag.
211 graph.system_package_mode = true;
212 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
213 builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
214 } else if (mem.eql(u8, arg, "--verbose-link")) {
215 builder.verbose_link = true;
216 } else if (mem.eql(u8, arg, "--verbose-air")) {
217 builder.verbose_air = true;
218 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
219 builder.verbose_llvm_ir = "-";
220 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
221 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
222 } else if (mem.eql(u8, arg, "--verbose-llvm-bc=")) {
223 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
224 } else if (mem.eql(u8, arg, "--verbose-cimport")) {
225 builder.verbose_cimport = true;
226 } else if (mem.eql(u8, arg, "--verbose-cc")) {
227 builder.verbose_cc = true;
228 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
229 builder.verbose_llvm_cpu_features = true;
230 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
231 prominent_compile_errors = true;
232 } else if (mem.eql(u8, arg, "-fwine")) {
233 builder.enable_wine = true;
234 } else if (mem.eql(u8, arg, "-fno-wine")) {
235 builder.enable_wine = false;
236 } else if (mem.eql(u8, arg, "-fqemu")) {
237 builder.enable_qemu = true;
238 } else if (mem.eql(u8, arg, "-fno-qemu")) {
239 builder.enable_qemu = false;
240 } else if (mem.eql(u8, arg, "-fwasmtime")) {
241 builder.enable_wasmtime = true;
242 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
243 builder.enable_wasmtime = false;
244 } else if (mem.eql(u8, arg, "-frosetta")) {
245 builder.enable_rosetta = true;
246 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
247 builder.enable_rosetta = false;
248 } else if (mem.eql(u8, arg, "-fdarling")) {
249 builder.enable_darling = true;
250 } else if (mem.eql(u8, arg, "-fno-darling")) {
251 builder.enable_darling = false;
252 } else if (mem.eql(u8, arg, "-freference-trace")) {
253 builder.reference_trace = 256;
254 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
255 const num = arg["-freference-trace=".len..];
256 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
257 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
258 process.exit(1);
259 };
260 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
261 builder.reference_trace = null;
262 } else if (mem.startsWith(u8, arg, "-j")) {
263 const num = arg["-j".len..];
264 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
265 std.debug.print("unable to parse jobs count '{s}': {s}", .{
266 num, @errorName(err),
267 });
268 process.exit(1);
269 };
270 if (n_jobs < 1) {
271 std.debug.print("number of jobs must be at least 1\n", .{});
272 process.exit(1);
273 }
274 thread_pool_options.n_jobs = n_jobs;
275 } else if (mem.eql(u8, arg, "--")) {
276 builder.args = argsRest(args, arg_idx);
277 break;
278 } else {
279 fatalWithHint("unrecognized argument: '{s}'", .{arg});
280 }
281 } else {
282 try targets.append(arg);
283 }
284 }
285
286 const host_query = std.Build.parseTargetQuery(graph.host_query_options) catch |err| switch (err) {
287 error.ParseFailed => process.exit(1),
288 };
289 builder.host = .{
290 .query = .{},
291 .result = try std.zig.system.resolveTargetQuery(host_query),
292 };
293
294 const stderr = std.io.getStdErr();
295 const ttyconf = get_tty_conf(color, stderr);
296 switch (ttyconf) {
297 .no_color => try graph.env_map.put("NO_COLOR", "1"),
298 .escape_codes => try graph.env_map.put("YES_COLOR", "1"),
299 .windows_api => {},
300 }
301
302 var progress: std.Progress = .{ .dont_print_on_dumb = true };
303 const main_progress_node = progress.start("", 0);
304
305 builder.debug_log_scopes = debug_log_scopes.items;
306 builder.resolveInstallPrefix(install_prefix, dir_list);
307 {
308 var prog_node = main_progress_node.start("user build.zig logic", 0);
309 defer prog_node.end();
310 try builder.runBuild(root);
311 }
312
313 if (graph.needed_lazy_dependencies.entries.len != 0) {
314 var buffer: std.ArrayListUnmanaged(u8) = .{};
315 for (graph.needed_lazy_dependencies.keys()) |k| {
316 try buffer.appendSlice(arena, k);
317 try buffer.append(arena, '\n');
318 }
319 const s = std.fs.path.sep_str;
320 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
321 local_cache_directory.handle.writeFile2(.{
322 .sub_path = tmp_sub_path,
323 .data = buffer.items,
324 .flags = .{ .exclusive = true },
325 }) catch |err| {
326 fatal("unable to write configuration results to '{}{s}': {s}", .{
327 local_cache_directory, tmp_sub_path, @errorName(err),
328 });
329 };
330 process.exit(3); // Indicate configure phase failed with meaningful stdout.
331 }
332
333 if (builder.validateUserInputDidItFail()) {
334 fatal(" access the help menu with 'zig build -h'", .{});
335 }
336
337 validateSystemLibraryOptions(builder);
338
339 const stdout_writer = io.getStdOut().writer();
340
341 if (help_menu)
342 return usage(builder, stdout_writer);
343
344 if (steps_menu)
345 return steps(builder, stdout_writer);
346
347 var run: Run = .{
348 .max_rss = max_rss,
349 .max_rss_is_default = false,
350 .max_rss_mutex = .{},
351 .skip_oom_steps = skip_oom_steps,
352 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
353 .prominent_compile_errors = prominent_compile_errors,
354
355 .claimed_rss = 0,
356 .summary = summary,
357 .ttyconf = ttyconf,
358 .stderr = stderr,
359 };
360
361 if (run.max_rss == 0) {
362 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
363 run.max_rss_is_default = true;
364 }
365
366 runStepNames(
367 arena,
368 builder,
369 targets.items,
370 main_progress_node,
371 thread_pool_options,
372 &run,
373 seed,
374 ) catch |err| switch (err) {
375 error.UncleanExit => process.exit(1),
376 else => return err,
377 };
378}
379
380const Run = struct {
381 max_rss: u64,
382 max_rss_is_default: bool,
383 max_rss_mutex: std.Thread.Mutex,
384 skip_oom_steps: bool,
385 memory_blocked_steps: std.ArrayList(*Step),
386 prominent_compile_errors: bool,
387
388 claimed_rss: usize,
389 summary: ?Summary,
390 ttyconf: std.io.tty.Config,
391 stderr: File,
392};
393
394fn runStepNames(
395 arena: std.mem.Allocator,
396 b: *std.Build,
397 step_names: []const []const u8,
398 parent_prog_node: *std.Progress.Node,
399 thread_pool_options: std.Thread.Pool.Options,
400 run: *Run,
401 seed: u32,
402) !void {
403 const gpa = b.allocator;
404 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
405 defer step_stack.deinit(gpa);
406
407 if (step_names.len == 0) {
408 try step_stack.put(gpa, b.default_step, {});
409 } else {
410 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
411 for (0..step_names.len) |i| {
412 const step_name = step_names[step_names.len - i - 1];
413 const s = b.top_level_steps.get(step_name) orelse {
414 std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
415 process.exit(1);
416 };
417 step_stack.putAssumeCapacity(&s.step, {});
418 }
419 }
420
421 const starting_steps = try arena.dupe(*Step, step_stack.keys());
422
423 var rng = std.Random.DefaultPrng.init(seed);
424 const rand = rng.random();
425 rand.shuffle(*Step, starting_steps);
426
427 for (starting_steps) |s| {
428 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
429 error.DependencyLoopDetected => return error.UncleanExit,
430 else => |e| return e,
431 };
432 }
433
434 {
435 // Check that we have enough memory to complete the build.
436 var any_problems = false;
437 for (step_stack.keys()) |s| {
438 if (s.max_rss == 0) continue;
439 if (s.max_rss > run.max_rss) {
440 if (run.skip_oom_steps) {
441 s.state = .skipped_oom;
442 } else {
443 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
444 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
445 });
446 any_problems = true;
447 }
448 }
449 }
450 if (any_problems) {
451 if (run.max_rss_is_default) {
452 std.debug.print("note: use --maxrss to override the default", .{});
453 }
454 return error.UncleanExit;
455 }
456 }
457
458 var thread_pool: std.Thread.Pool = undefined;
459 try thread_pool.init(thread_pool_options);
460 defer thread_pool.deinit();
461
462 {
463 defer parent_prog_node.end();
464
465 var step_prog = parent_prog_node.start("steps", step_stack.count());
466 defer step_prog.end();
467
468 var wait_group: std.Thread.WaitGroup = .{};
469 defer wait_group.wait();
470
471 // Here we spawn the initial set of tasks with a nice heuristic -
472 // dependency order. Each worker when it finishes a step will then
473 // check whether it should run any dependants.
474 const steps_slice = step_stack.keys();
475 for (0..steps_slice.len) |i| {
476 const step = steps_slice[steps_slice.len - i - 1];
477 if (step.state == .skipped_oom) continue;
478
479 wait_group.start();
480 thread_pool.spawn(workerMakeOneStep, .{
481 &wait_group, &thread_pool, b, step, &step_prog, run,
482 }) catch @panic("OOM");
483 }
484 }
485 assert(run.memory_blocked_steps.items.len == 0);
486
487 var test_skip_count: usize = 0;
488 var test_fail_count: usize = 0;
489 var test_pass_count: usize = 0;
490 var test_leak_count: usize = 0;
491 var test_count: usize = 0;
492
493 var success_count: usize = 0;
494 var skipped_count: usize = 0;
495 var failure_count: usize = 0;
496 var pending_count: usize = 0;
497 var total_compile_errors: usize = 0;
498 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
499 defer compile_error_steps.deinit(gpa);
500
501 for (step_stack.keys()) |s| {
502 test_fail_count += s.test_results.fail_count;
503 test_skip_count += s.test_results.skip_count;
504 test_leak_count += s.test_results.leak_count;
505 test_pass_count += s.test_results.passCount();
506 test_count += s.test_results.test_count;
507
508 switch (s.state) {
509 .precheck_unstarted => unreachable,
510 .precheck_started => unreachable,
511 .running => unreachable,
512 .precheck_done => {
513 // precheck_done is equivalent to dependency_failure in the case of
514 // transitive dependencies. For example:
515 // A -> B -> C (failure)
516 // B will be marked as dependency_failure, while A may never be queued, and thus
517 // remain in the initial state of precheck_done.
518 s.state = .dependency_failure;
519 pending_count += 1;
520 },
521 .dependency_failure => pending_count += 1,
522 .success => success_count += 1,
523 .skipped, .skipped_oom => skipped_count += 1,
524 .failure => {
525 failure_count += 1;
526 const compile_errors_len = s.result_error_bundle.errorMessageCount();
527 if (compile_errors_len > 0) {
528 total_compile_errors += compile_errors_len;
529 try compile_error_steps.append(gpa, s);
530 }
531 },
532 }
533 }
534
535 // A proper command line application defaults to silently succeeding.
536 // The user may request verbose mode if they have a different preference.
537 if (failure_count == 0 and run.summary != Summary.all) return cleanExit();
538
539 const ttyconf = run.ttyconf;
540 const stderr = run.stderr;
541
542 if (run.summary != Summary.none) {
543 const total_count = success_count + failure_count + pending_count + skipped_count;
544 ttyconf.setColor(stderr, .cyan) catch {};
545 stderr.writeAll("Build Summary:") catch {};
546 ttyconf.setColor(stderr, .reset) catch {};
547 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
548 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
549 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
550
551 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
552 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
553 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
554 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
555
556 if (run.summary == null) {
557 ttyconf.setColor(stderr, .dim) catch {};
558 stderr.writeAll(" (disable with --summary none)") catch {};
559 ttyconf.setColor(stderr, .reset) catch {};
560 }
561 stderr.writeAll("\n") catch {};
562 const failures_only = run.summary != Summary.all;
563
564 // Print a fancy tree with build results.
565 var print_node: PrintNode = .{ .parent = null };
566 if (step_names.len == 0) {
567 print_node.last = true;
568 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
569 } else {
570 const last_index = if (!failures_only) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 if (b.top_level_steps.get(step_names[i]).?.step.state != .success) break :blk i;
575 }
576 break :blk b.top_level_steps.count();
577 };
578 for (step_names, 0..) |step_name, i| {
579 const tls = b.top_level_steps.get(step_name).?;
580 print_node.last = i + 1 == last_index;
581 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
582 }
583 }
584 }
585
586 if (failure_count == 0) return cleanExit();
587
588 // Finally, render compile errors at the bottom of the terminal.
589 // We use a separate compile_error_steps array list because step_stack is destructively
590 // mutated in printTreeStep above.
591 if (run.prominent_compile_errors and total_compile_errors > 0) {
592 for (compile_error_steps.items) |s| {
593 if (s.result_error_bundle.errorMessageCount() > 0) {
594 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
595 }
596 }
597
598 // Signal to parent process that we have printed compile errors. The
599 // parent process may choose to omit the "following command failed"
600 // line in this case.
601 process.exit(2);
602 }
603
604 process.exit(1);
605}
606
607const PrintNode = struct {
608 parent: ?*PrintNode,
609 last: bool = false,
610};
611
612fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
613 const parent = node.parent orelse return;
614 if (parent.parent == null) return;
615 try printPrefix(parent, stderr, ttyconf);
616 if (parent.last) {
617 try stderr.writeAll(" ");
618 } else {
619 try stderr.writeAll(switch (ttyconf) {
620 .no_color, .windows_api => "| ",
621 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
622 });
623 }
624}
625
626fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
627 try stderr.writeAll(switch (ttyconf) {
628 .no_color, .windows_api => "+- ",
629 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
630 });
631}
632
633fn printStepStatus(
634 s: *Step,
635 stderr: File,
636 ttyconf: std.io.tty.Config,
637 run: *const Run,
638) !void {
639 switch (s.state) {
640 .precheck_unstarted => unreachable,
641 .precheck_started => unreachable,
642 .precheck_done => unreachable,
643 .running => unreachable,
644
645 .dependency_failure => {
646 try ttyconf.setColor(stderr, .dim);
647 try stderr.writeAll(" transitive failure\n");
648 try ttyconf.setColor(stderr, .reset);
649 },
650
651 .success => {
652 try ttyconf.setColor(stderr, .green);
653 if (s.result_cached) {
654 try stderr.writeAll(" cached");
655 } else if (s.test_results.test_count > 0) {
656 const pass_count = s.test_results.passCount();
657 try stderr.writer().print(" {d} passed", .{pass_count});
658 if (s.test_results.skip_count > 0) {
659 try ttyconf.setColor(stderr, .yellow);
660 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
661 }
662 } else {
663 try stderr.writeAll(" success");
664 }
665 try ttyconf.setColor(stderr, .reset);
666 if (s.result_duration_ns) |ns| {
667 try ttyconf.setColor(stderr, .dim);
668 if (ns >= std.time.ns_per_min) {
669 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
670 } else if (ns >= std.time.ns_per_s) {
671 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
672 } else if (ns >= std.time.ns_per_ms) {
673 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
674 } else if (ns >= std.time.ns_per_us) {
675 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
676 } else {
677 try stderr.writer().print(" {d}ns", .{ns});
678 }
679 try ttyconf.setColor(stderr, .reset);
680 }
681 if (s.result_peak_rss != 0) {
682 const rss = s.result_peak_rss;
683 try ttyconf.setColor(stderr, .dim);
684 if (rss >= 1000_000_000) {
685 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
686 } else if (rss >= 1000_000) {
687 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
688 } else if (rss >= 1000) {
689 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
690 } else {
691 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
692 }
693 try ttyconf.setColor(stderr, .reset);
694 }
695 try stderr.writeAll("\n");
696 },
697 .skipped, .skipped_oom => |skip| {
698 try ttyconf.setColor(stderr, .yellow);
699 try stderr.writeAll(" skipped");
700 if (skip == .skipped_oom) {
701 try stderr.writeAll(" (not enough memory)");
702 try ttyconf.setColor(stderr, .dim);
703 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
704 try ttyconf.setColor(stderr, .yellow);
705 }
706 try stderr.writeAll("\n");
707 try ttyconf.setColor(stderr, .reset);
708 },
709 .failure => try printStepFailure(s, stderr, ttyconf),
710 }
711}
712
713fn printStepFailure(
714 s: *Step,
715 stderr: File,
716 ttyconf: std.io.tty.Config,
717) !void {
718 if (s.result_error_bundle.errorMessageCount() > 0) {
719 try ttyconf.setColor(stderr, .red);
720 try stderr.writer().print(" {d} errors\n", .{
721 s.result_error_bundle.errorMessageCount(),
722 });
723 try ttyconf.setColor(stderr, .reset);
724 } else if (!s.test_results.isSuccess()) {
725 try stderr.writer().print(" {d}/{d} passed", .{
726 s.test_results.passCount(), s.test_results.test_count,
727 });
728 if (s.test_results.fail_count > 0) {
729 try stderr.writeAll(", ");
730 try ttyconf.setColor(stderr, .red);
731 try stderr.writer().print("{d} failed", .{
732 s.test_results.fail_count,
733 });
734 try ttyconf.setColor(stderr, .reset);
735 }
736 if (s.test_results.skip_count > 0) {
737 try stderr.writeAll(", ");
738 try ttyconf.setColor(stderr, .yellow);
739 try stderr.writer().print("{d} skipped", .{
740 s.test_results.skip_count,
741 });
742 try ttyconf.setColor(stderr, .reset);
743 }
744 if (s.test_results.leak_count > 0) {
745 try stderr.writeAll(", ");
746 try ttyconf.setColor(stderr, .red);
747 try stderr.writer().print("{d} leaked", .{
748 s.test_results.leak_count,
749 });
750 try ttyconf.setColor(stderr, .reset);
751 }
752 try stderr.writeAll("\n");
753 } else if (s.result_error_msgs.items.len > 0) {
754 try ttyconf.setColor(stderr, .red);
755 try stderr.writeAll(" failure\n");
756 try ttyconf.setColor(stderr, .reset);
757 } else {
758 assert(s.result_stderr.len > 0);
759 try ttyconf.setColor(stderr, .red);
760 try stderr.writeAll(" stderr\n");
761 try ttyconf.setColor(stderr, .reset);
762 }
763}
764
765fn printTreeStep(
766 b: *std.Build,
767 s: *Step,
768 run: *const Run,
769 stderr: File,
770 ttyconf: std.io.tty.Config,
771 parent_node: *PrintNode,
772 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
773 failures_only: bool,
774) !void {
775 const first = step_stack.swapRemove(s);
776 if (failures_only and s.state == .success) return;
777 try printPrefix(parent_node, stderr, ttyconf);
778
779 if (!first) try ttyconf.setColor(stderr, .dim);
780 if (parent_node.parent != null) {
781 if (parent_node.last) {
782 try printChildNodePrefix(stderr, ttyconf);
783 } else {
784 try stderr.writeAll(switch (ttyconf) {
785 .no_color, .windows_api => "+- ",
786 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
787 });
788 }
789 }
790
791 // dep_prefix omitted here because it is redundant with the tree.
792 try stderr.writeAll(s.name);
793
794 if (first) {
795 try printStepStatus(s, stderr, ttyconf, run);
796
797 const last_index = if (!failures_only) s.dependencies.items.len -| 1 else blk: {
798 var i: usize = s.dependencies.items.len;
799 while (i > 0) {
800 i -= 1;
801 if (s.dependencies.items[i].state != .success) break :blk i;
802 }
803 break :blk s.dependencies.items.len -| 1;
804 };
805 for (s.dependencies.items, 0..) |dep, i| {
806 var print_node: PrintNode = .{
807 .parent = parent_node,
808 .last = i == last_index,
809 };
810 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack, failures_only);
811 }
812 } else {
813 if (s.dependencies.items.len == 0) {
814 try stderr.writeAll(" (reused)\n");
815 } else {
816 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
817 s.dependencies.items.len,
818 });
819 }
820 try ttyconf.setColor(stderr, .reset);
821 }
822}
823
824/// Traverse the dependency graph depth-first and make it undirected by having
825/// steps know their dependants (they only know dependencies at start).
826/// Along the way, check that there is no dependency loop, and record the steps
827/// in traversal order in `step_stack`.
828/// Each step has its dependencies traversed in random order, this accomplishes
829/// two things:
830/// - `step_stack` will be in randomized-depth-first order, so the build runner
831/// spawns steps in a random (but optimized) order
832/// - each step's `dependants` list is also filled in a random order, so that
833/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
834/// to run in random order
835fn constructGraphAndCheckForDependencyLoop(
836 b: *std.Build,
837 s: *Step,
838 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
839 rand: std.Random,
840) !void {
841 switch (s.state) {
842 .precheck_started => {
843 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
844 return error.DependencyLoopDetected;
845 },
846 .precheck_unstarted => {
847 s.state = .precheck_started;
848
849 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
850
851 // We dupe to avoid shuffling the steps in the summary, it depends
852 // on s.dependencies' order.
853 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
854 rand.shuffle(*Step, deps);
855
856 for (deps) |dep| {
857 try step_stack.put(b.allocator, dep, {});
858 try dep.dependants.append(b.allocator, s);
859 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
860 if (err == error.DependencyLoopDetected) {
861 std.debug.print(" {s}\n", .{s.name});
862 }
863 return err;
864 };
865 }
866
867 s.state = .precheck_done;
868 },
869 .precheck_done => {},
870
871 // These don't happen until we actually run the step graph.
872 .dependency_failure => unreachable,
873 .running => unreachable,
874 .success => unreachable,
875 .failure => unreachable,
876 .skipped => unreachable,
877 .skipped_oom => unreachable,
878 }
879}
880
881fn workerMakeOneStep(
882 wg: *std.Thread.WaitGroup,
883 thread_pool: *std.Thread.Pool,
884 b: *std.Build,
885 s: *Step,
886 prog_node: *std.Progress.Node,
887 run: *Run,
888) void {
889 defer wg.finish();
890
891 // First, check the conditions for running this step. If they are not met,
892 // then we return without doing the step, relying on another worker to
893 // queue this step up again when dependencies are met.
894 for (s.dependencies.items) |dep| {
895 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
896 .success, .skipped => continue,
897 .failure, .dependency_failure, .skipped_oom => {
898 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
899 return;
900 },
901 .precheck_done, .running => {
902 // dependency is not finished yet.
903 return;
904 },
905 .precheck_unstarted => unreachable,
906 .precheck_started => unreachable,
907 }
908 }
909
910 if (s.max_rss != 0) {
911 run.max_rss_mutex.lock();
912 defer run.max_rss_mutex.unlock();
913
914 // Avoid running steps twice.
915 if (s.state != .precheck_done) {
916 // Another worker got the job.
917 return;
918 }
919
920 const new_claimed_rss = run.claimed_rss + s.max_rss;
921 if (new_claimed_rss > run.max_rss) {
922 // Running this step right now could possibly exceed the allotted RSS.
923 // Add this step to the queue of memory-blocked steps.
924 run.memory_blocked_steps.append(s) catch @panic("OOM");
925 return;
926 }
927
928 run.claimed_rss = new_claimed_rss;
929 s.state = .running;
930 } else {
931 // Avoid running steps twice.
932 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
933 // Another worker got the job.
934 return;
935 }
936 }
937
938 var sub_prog_node = prog_node.start(s.name, 0);
939 sub_prog_node.activate();
940 defer sub_prog_node.end();
941
942 const make_result = s.make(&sub_prog_node);
943
944 // No matter the result, we want to display error/warning messages.
945 const show_compile_errors = !run.prominent_compile_errors and
946 s.result_error_bundle.errorMessageCount() > 0;
947 const show_error_msgs = s.result_error_msgs.items.len > 0;
948 const show_stderr = s.result_stderr.len > 0;
949
950 if (show_error_msgs or show_compile_errors or show_stderr) {
951 sub_prog_node.context.lock_stderr();
952 defer sub_prog_node.context.unlock_stderr();
953
954 printErrorMessages(b, s, run) catch {};
955 }
956
957 handle_result: {
958 if (make_result) |_| {
959 @atomicStore(Step.State, &s.state, .success, .SeqCst);
960 } else |err| switch (err) {
961 error.MakeFailed => {
962 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
963 break :handle_result;
964 },
965 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
966 }
967
968 // Successful completion of a step, so we queue up its dependants as well.
969 for (s.dependants.items) |dep| {
970 wg.start();
971 thread_pool.spawn(workerMakeOneStep, .{
972 wg, thread_pool, b, dep, prog_node, run,
973 }) catch @panic("OOM");
974 }
975 }
976
977 // If this is a step that claims resources, we must now queue up other
978 // steps that are waiting for resources.
979 if (s.max_rss != 0) {
980 run.max_rss_mutex.lock();
981 defer run.max_rss_mutex.unlock();
982
983 // Give the memory back to the scheduler.
984 run.claimed_rss -= s.max_rss;
985 // Avoid kicking off too many tasks that we already know will not have
986 // enough resources.
987 var remaining = run.max_rss - run.claimed_rss;
988 var i: usize = 0;
989 var j: usize = 0;
990 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
991 const dep = run.memory_blocked_steps.items[j];
992 assert(dep.max_rss != 0);
993 if (dep.max_rss <= remaining) {
994 remaining -= dep.max_rss;
995
996 wg.start();
997 thread_pool.spawn(workerMakeOneStep, .{
998 wg, thread_pool, b, dep, prog_node, run,
999 }) catch @panic("OOM");
1000 } else {
1001 run.memory_blocked_steps.items[i] = dep;
1002 i += 1;
1003 }
1004 }
1005 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1006 }
1007}
1008
1009fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1010 const gpa = b.allocator;
1011 const stderr = run.stderr;
1012 const ttyconf = run.ttyconf;
1013
1014 // Provide context for where these error messages are coming from by
1015 // printing the corresponding Step subtree.
1016
1017 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1018 defer step_stack.deinit(gpa);
1019 try step_stack.append(gpa, failing_step);
1020 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1021 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1022 }
1023
1024 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1025 try ttyconf.setColor(stderr, .dim);
1026 var indent: usize = 0;
1027 while (step_stack.popOrNull()) |s| : (indent += 1) {
1028 if (indent > 0) {
1029 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1030 try printChildNodePrefix(stderr, ttyconf);
1031 }
1032
1033 try stderr.writeAll(s.name);
1034
1035 if (s == failing_step) {
1036 try printStepFailure(s, stderr, ttyconf);
1037 } else {
1038 try stderr.writeAll("\n");
1039 }
1040 }
1041 try ttyconf.setColor(stderr, .reset);
1042
1043 if (failing_step.result_stderr.len > 0) {
1044 try stderr.writeAll(failing_step.result_stderr);
1045 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1046 try stderr.writeAll("\n");
1047 }
1048 }
1049
1050 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1051 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1052
1053 for (failing_step.result_error_msgs.items) |msg| {
1054 try ttyconf.setColor(stderr, .red);
1055 try stderr.writeAll("error: ");
1056 try ttyconf.setColor(stderr, .reset);
1057 try stderr.writeAll(msg);
1058 try stderr.writeAll("\n");
1059 }
1060}
1061
1062fn steps(builder: *std.Build, out_stream: anytype) !void {
1063 const allocator = builder.allocator;
1064 for (builder.top_level_steps.values()) |top_level_step| {
1065 const name = if (&top_level_step.step == builder.default_step)
1066 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1067 else
1068 top_level_step.step.name;
1069 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1070 }
1071}
1072
1073fn usage(b: *std.Build, out_stream: anytype) !void {
1074 try out_stream.print(
1075 \\Usage: {s} build [steps] [options]
1076 \\
1077 \\Steps:
1078 \\
1079 , .{b.graph.zig_exe});
1080 try steps(b, out_stream);
1081
1082 try out_stream.writeAll(
1083 \\
1084 \\General Options:
1085 \\ -p, --prefix [path] Where to install files (default: zig-out)
1086 \\ --prefix-lib-dir [path] Where to install libraries
1087 \\ --prefix-exe-dir [path] Where to install executables
1088 \\ --prefix-include-dir [path] Where to install C header files
1089 \\
1090 \\ --release[=mode] Request release mode, optionally specifying a
1091 \\ preferred optimization mode: fast, safe, small
1092 \\
1093 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1094 \\ execute macOS programs on Linux hosts
1095 \\ (default: no)
1096 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1097 \\ foreign-architecture programs on Linux hosts
1098 \\ (default: no)
1099 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1100 \\ for multiple foreign architectures, allowing
1101 \\ execution of non-native programs that link with glibc.
1102 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1103 \\ ARM64 macOS hosts. (default: no)
1104 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1105 \\ execute WASI binaries. (default: no)
1106 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1107 \\ Windows programs on Linux hosts. (default: no)
1108 \\
1109 \\ -h, --help Print this help and exit
1110 \\ -l, --list-steps Print available steps
1111 \\ --verbose Print commands before executing them
1112 \\ --color [auto|off|on] Enable or disable colored error messages
1113 \\ --prominent-compile-errors Buffer compile errors and display at end
1114 \\ --summary [mode] Control the printing of the build summary
1115 \\ all Print the build summary in its entirety
1116 \\ failures (Default) Only print failed steps
1117 \\ none Do not print the build summary
1118 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1119 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1120 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1121 \\ --fetch Exit after fetching dependency tree
1122 \\
1123 \\Project-Specific Options:
1124 \\
1125 );
1126
1127 const arena = b.allocator;
1128 if (b.available_options_list.items.len == 0) {
1129 try out_stream.print(" (none)\n", .{});
1130 } else {
1131 for (b.available_options_list.items) |option| {
1132 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1133 option.name,
1134 @tagName(option.type_id),
1135 });
1136 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1137 if (option.enum_options) |enum_options| {
1138 const padding = " " ** 33;
1139 try out_stream.writeAll(padding ++ "Supported Values:\n");
1140 for (enum_options) |enum_option| {
1141 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1142 }
1143 }
1144 }
1145 }
1146
1147 try out_stream.writeAll(
1148 \\
1149 \\System Integration Options:
1150 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1151 \\ --sysroot [path] Set the system root directory (usually /)
1152 \\ --libc [file] Provide a file which specifies libc paths
1153 \\
1154 \\ --host-target [triple] Use the provided target as the host
1155 \\ --host-cpu [cpu] Use the provided CPU as the host
1156 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1157 \\
1158 \\ --system [pkgdir] Disable package fetching; enable all integrations
1159 \\ -fsys=[name] Enable a system integration
1160 \\ -fno-sys=[name] Disable a system integration
1161 \\
1162 \\ Available System Integrations: Enabled:
1163 \\
1164 );
1165 if (b.graph.system_library_options.entries.len == 0) {
1166 try out_stream.writeAll(" (none) -\n");
1167 } else {
1168 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1169 const status = switch (v) {
1170 .declared_enabled => "yes",
1171 .declared_disabled => "no",
1172 .user_enabled, .user_disabled => unreachable, // already emitted error
1173 };
1174 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1175 }
1176 }
1177
1178 try out_stream.writeAll(
1179 \\
1180 \\Advanced Options:
1181 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1182 \\ -fno-reference-trace Disable reference trace
1183 \\ --build-file [file] Override path to build.zig
1184 \\ --cache-dir [path] Override path to local Zig cache directory
1185 \\ --global-cache-dir [path] Override path to global Zig cache directory
1186 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1187 \\ --build-runner [file] Override path to build runner
1188 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1189 \\ --debug-log [scope] Enable debugging the compiler
1190 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1191 \\ --verbose-link Enable compiler debug output for linking
1192 \\ --verbose-air Enable compiler debug output for Zig AIR
1193 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1194 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1195 \\ --verbose-cimport Enable compiler debug output for C imports
1196 \\ --verbose-cc Enable compiler debug output for C compilation
1197 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1198 \\
1199 );
1200}
1201
1202fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1203 if (idx.* >= args.len) return null;
1204 defer idx.* += 1;
1205 return args[idx.*];
1206}
1207
1208fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1209 return nextArg(args, idx) orelse {
1210 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1211 process.exit(1);
1212 };
1213}
1214
1215fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1216 if (idx >= args.len) return null;
1217 return args[idx..];
1218}
1219
1220fn cleanExit() void {
1221 // Perhaps in the future there could be an Advanced Options flag such as
1222 // --debug-build-runner-leaks which would make this function return instead
1223 // of calling exit.
1224 process.exit(0);
1225}
1226
1227const Color = enum { auto, off, on };
1228const Summary = enum { all, failures, none };
1229
1230fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1231 return switch (color) {
1232 .auto => std.io.tty.detectConfig(stderr),
1233 .on => .escape_codes,
1234 .off => .no_color,
1235 };
1236}
1237
1238fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1239 return .{
1240 .ttyconf = ttyconf,
1241 .include_source_line = ttyconf != .no_color,
1242 .include_reference_trace = ttyconf != .no_color,
1243 };
1244}
1245
1246fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1247 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1248 process.exit(1);
1249}
1250
1251fn fatal(comptime f: []const u8, args: anytype) noreturn {
1252 std.debug.print(f ++ "\n", args);
1253 process.exit(1);
1254}
1255
1256fn validateSystemLibraryOptions(b: *std.Build) void {
1257 var bad = false;
1258 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1259 switch (v) {
1260 .user_disabled, .user_enabled => {
1261 // The user tried to enable or disable a system library integration, but
1262 // the build script did not recognize that option.
1263 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1264 bad = true;
1265 },
1266 .declared_disabled, .declared_enabled => {},
1267 }
1268 }
1269 if (bad) {
1270 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1271 process.exit(1);
1272 }
1273}
lib/compiler/build_runner.zig created+1273
...@@ -0,0 +1,1273 @@
1const root = @import("@build");
2const std = @import("std");
3const builtin = @import("builtin");
4const assert = std.debug.assert;
5const io = std.io;
6const fmt = std.fmt;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const File = std.fs.File;
11const Step = std.Build.Step;
12
13pub const dependencies = @import("@dependencies");
14
15pub fn main() !void {
16 // Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
17 // one shot program. We don't need to waste time freeing memory and finding places to squish
18 // bytes into. So we free everything all at once at the very end.
19 var single_threaded_arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
20 defer single_threaded_arena.deinit();
21
22 var thread_safe_arena: std.heap.ThreadSafeAllocator = .{
23 .child_allocator = single_threaded_arena.allocator(),
24 };
25 const arena = thread_safe_arena.allocator();
26
27 const args = try process.argsAlloc(arena);
28
29 // skip my own exe name
30 var arg_idx: usize = 1;
31
32 const zig_exe = nextArg(args, &arg_idx) orelse {
33 std.debug.print("Expected path to zig compiler\n", .{});
34 return error.InvalidArgs;
35 };
36 const build_root = nextArg(args, &arg_idx) orelse {
37 std.debug.print("Expected build root directory path\n", .{});
38 return error.InvalidArgs;
39 };
40 const cache_root = nextArg(args, &arg_idx) orelse {
41 std.debug.print("Expected cache root directory path\n", .{});
42 return error.InvalidArgs;
43 };
44 const global_cache_root = nextArg(args, &arg_idx) orelse {
45 std.debug.print("Expected global cache root directory path\n", .{});
46 return error.InvalidArgs;
47 };
48
49 const build_root_directory: std.Build.Cache.Directory = .{
50 .path = build_root,
51 .handle = try std.fs.cwd().openDir(build_root, .{}),
52 };
53
54 const local_cache_directory: std.Build.Cache.Directory = .{
55 .path = cache_root,
56 .handle = try std.fs.cwd().makeOpenPath(cache_root, .{}),
57 };
58
59 const global_cache_directory: std.Build.Cache.Directory = .{
60 .path = global_cache_root,
61 .handle = try std.fs.cwd().makeOpenPath(global_cache_root, .{}),
62 };
63
64 var graph: std.Build.Graph = .{
65 .arena = arena,
66 .cache = .{
67 .gpa = arena,
68 .manifest_dir = try local_cache_directory.handle.makeOpenPath("h", .{}),
69 },
70 .zig_exe = zig_exe,
71 .env_map = try process.getEnvMap(arena),
72 .global_cache_root = global_cache_directory,
73 };
74
75 graph.cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() });
76 graph.cache.addPrefix(build_root_directory);
77 graph.cache.addPrefix(local_cache_directory);
78 graph.cache.addPrefix(global_cache_directory);
79 graph.cache.hash.addBytes(builtin.zig_version_string);
80
81 const builder = try std.Build.create(
82 &graph,
83 build_root_directory,
84 local_cache_directory,
85 dependencies.root_deps,
86 );
87
88 var targets = ArrayList([]const u8).init(arena);
89 var debug_log_scopes = ArrayList([]const u8).init(arena);
90 var thread_pool_options: std.Thread.Pool.Options = .{ .allocator = arena };
91
92 var install_prefix: ?[]const u8 = null;
93 var dir_list = std.Build.DirList{};
94 var summary: ?Summary = null;
95 var max_rss: u64 = 0;
96 var skip_oom_steps: bool = false;
97 var color: Color = .auto;
98 var seed: u32 = 0;
99 var prominent_compile_errors: bool = false;
100 var help_menu: bool = false;
101 var steps_menu: bool = false;
102 var output_tmp_nonce: ?[16]u8 = null;
103
104 while (nextArg(args, &arg_idx)) |arg| {
105 if (mem.startsWith(u8, arg, "-Z")) {
106 if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg});
107 output_tmp_nonce = arg[2..18].*;
108 } else if (mem.startsWith(u8, arg, "-D")) {
109 const option_contents = arg[2..];
110 if (option_contents.len == 0)
111 fatalWithHint("expected option name after '-D'", .{});
112 if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| {
113 const option_name = option_contents[0..name_end];
114 const option_value = option_contents[name_end + 1 ..];
115 if (try builder.addUserInputOption(option_name, option_value))
116 fatal(" access the help menu with 'zig build -h'", .{});
117 } else {
118 if (try builder.addUserInputFlag(option_contents))
119 fatal(" access the help menu with 'zig build -h'", .{});
120 }
121 } else if (mem.startsWith(u8, arg, "-")) {
122 if (mem.eql(u8, arg, "--verbose")) {
123 builder.verbose = true;
124 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
125 help_menu = true;
126 } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) {
127 install_prefix = nextArgOrFatal(args, &arg_idx);
128 } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) {
129 steps_menu = true;
130 } else if (mem.startsWith(u8, arg, "-fsys=")) {
131 const name = arg["-fsys=".len..];
132 graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM");
133 } else if (mem.startsWith(u8, arg, "-fno-sys=")) {
134 const name = arg["-fno-sys=".len..];
135 graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM");
136 } else if (mem.eql(u8, arg, "--release")) {
137 builder.release_mode = .any;
138 } else if (mem.startsWith(u8, arg, "--release=")) {
139 const text = arg["--release=".len..];
140 builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse {
141 fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{
142 arg, text,
143 });
144 };
145 } else if (mem.eql(u8, arg, "--host-target")) {
146 graph.host_query_options.arch_os_abi = nextArgOrFatal(args, &arg_idx);
147 } else if (mem.eql(u8, arg, "--host-cpu")) {
148 graph.host_query_options.cpu_features = nextArgOrFatal(args, &arg_idx);
149 } else if (mem.eql(u8, arg, "--host-dynamic-linker")) {
150 graph.host_query_options.dynamic_linker = nextArgOrFatal(args, &arg_idx);
151 } else if (mem.eql(u8, arg, "--prefix-lib-dir")) {
152 dir_list.lib_dir = nextArgOrFatal(args, &arg_idx);
153 } else if (mem.eql(u8, arg, "--prefix-exe-dir")) {
154 dir_list.exe_dir = nextArgOrFatal(args, &arg_idx);
155 } else if (mem.eql(u8, arg, "--prefix-include-dir")) {
156 dir_list.include_dir = nextArgOrFatal(args, &arg_idx);
157 } else if (mem.eql(u8, arg, "--sysroot")) {
158 builder.sysroot = nextArgOrFatal(args, &arg_idx);
159 } else if (mem.eql(u8, arg, "--maxrss")) {
160 const max_rss_text = nextArgOrFatal(args, &arg_idx);
161 max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| {
162 std.debug.print("invalid byte size: '{s}': {s}\n", .{
163 max_rss_text, @errorName(err),
164 });
165 process.exit(1);
166 };
167 } else if (mem.eql(u8, arg, "--skip-oom-steps")) {
168 skip_oom_steps = true;
169 } else if (mem.eql(u8, arg, "--search-prefix")) {
170 const search_prefix = nextArgOrFatal(args, &arg_idx);
171 builder.addSearchPrefix(search_prefix);
172 } else if (mem.eql(u8, arg, "--libc")) {
173 builder.libc_file = nextArgOrFatal(args, &arg_idx);
174 } else if (mem.eql(u8, arg, "--color")) {
175 const next_arg = nextArg(args, &arg_idx) orelse
176 fatalWithHint("expected [auto|on|off] after '{s}'", .{arg});
177 color = std.meta.stringToEnum(Color, next_arg) orelse {
178 fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{
179 arg, next_arg,
180 });
181 };
182 } else if (mem.eql(u8, arg, "--summary")) {
183 const next_arg = nextArg(args, &arg_idx) orelse
184 fatalWithHint("expected [all|failures|none] after '{s}'", .{arg});
185 summary = std.meta.stringToEnum(Summary, next_arg) orelse {
186 fatalWithHint("expected [all|failures|none] after '{s}', found '{s}'", .{
187 arg, next_arg,
188 });
189 };
190 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
191 builder.zig_lib_dir = .{ .cwd_relative = nextArgOrFatal(args, &arg_idx) };
192 } else if (mem.eql(u8, arg, "--seed")) {
193 const next_arg = nextArg(args, &arg_idx) orelse
194 fatalWithHint("expected u32 after '{s}'", .{arg});
195 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
196 fatal("unable to parse seed '{s}' as 32-bit integer: {s}\n", .{
197 next_arg, @errorName(err),
198 });
199 };
200 } else if (mem.eql(u8, arg, "--debug-log")) {
201 const next_arg = nextArgOrFatal(args, &arg_idx);
202 try debug_log_scopes.append(next_arg);
203 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
204 builder.debug_pkg_config = true;
205 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
206 builder.debug_compile_errors = true;
207 } else if (mem.eql(u8, arg, "--system")) {
208 // The usage text shows another argument after this parameter
209 // but it is handled by the parent process. The build runner
210 // only sees this flag.
211 graph.system_package_mode = true;
212 } else if (mem.eql(u8, arg, "--glibc-runtimes")) {
213 builder.glibc_runtimes_dir = nextArgOrFatal(args, &arg_idx);
214 } else if (mem.eql(u8, arg, "--verbose-link")) {
215 builder.verbose_link = true;
216 } else if (mem.eql(u8, arg, "--verbose-air")) {
217 builder.verbose_air = true;
218 } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) {
219 builder.verbose_llvm_ir = "-";
220 } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) {
221 builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..];
222 } else if (mem.eql(u8, arg, "--verbose-llvm-bc=")) {
223 builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..];
224 } else if (mem.eql(u8, arg, "--verbose-cimport")) {
225 builder.verbose_cimport = true;
226 } else if (mem.eql(u8, arg, "--verbose-cc")) {
227 builder.verbose_cc = true;
228 } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) {
229 builder.verbose_llvm_cpu_features = true;
230 } else if (mem.eql(u8, arg, "--prominent-compile-errors")) {
231 prominent_compile_errors = true;
232 } else if (mem.eql(u8, arg, "-fwine")) {
233 builder.enable_wine = true;
234 } else if (mem.eql(u8, arg, "-fno-wine")) {
235 builder.enable_wine = false;
236 } else if (mem.eql(u8, arg, "-fqemu")) {
237 builder.enable_qemu = true;
238 } else if (mem.eql(u8, arg, "-fno-qemu")) {
239 builder.enable_qemu = false;
240 } else if (mem.eql(u8, arg, "-fwasmtime")) {
241 builder.enable_wasmtime = true;
242 } else if (mem.eql(u8, arg, "-fno-wasmtime")) {
243 builder.enable_wasmtime = false;
244 } else if (mem.eql(u8, arg, "-frosetta")) {
245 builder.enable_rosetta = true;
246 } else if (mem.eql(u8, arg, "-fno-rosetta")) {
247 builder.enable_rosetta = false;
248 } else if (mem.eql(u8, arg, "-fdarling")) {
249 builder.enable_darling = true;
250 } else if (mem.eql(u8, arg, "-fno-darling")) {
251 builder.enable_darling = false;
252 } else if (mem.eql(u8, arg, "-freference-trace")) {
253 builder.reference_trace = 256;
254 } else if (mem.startsWith(u8, arg, "-freference-trace=")) {
255 const num = arg["-freference-trace=".len..];
256 builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
257 std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) });
258 process.exit(1);
259 };
260 } else if (mem.eql(u8, arg, "-fno-reference-trace")) {
261 builder.reference_trace = null;
262 } else if (mem.startsWith(u8, arg, "-j")) {
263 const num = arg["-j".len..];
264 const n_jobs = std.fmt.parseUnsigned(u32, num, 10) catch |err| {
265 std.debug.print("unable to parse jobs count '{s}': {s}", .{
266 num, @errorName(err),
267 });
268 process.exit(1);
269 };
270 if (n_jobs < 1) {
271 std.debug.print("number of jobs must be at least 1\n", .{});
272 process.exit(1);
273 }
274 thread_pool_options.n_jobs = n_jobs;
275 } else if (mem.eql(u8, arg, "--")) {
276 builder.args = argsRest(args, arg_idx);
277 break;
278 } else {
279 fatalWithHint("unrecognized argument: '{s}'", .{arg});
280 }
281 } else {
282 try targets.append(arg);
283 }
284 }
285
286 const host_query = std.Build.parseTargetQuery(graph.host_query_options) catch |err| switch (err) {
287 error.ParseFailed => process.exit(1),
288 };
289 builder.host = .{
290 .query = .{},
291 .result = try std.zig.system.resolveTargetQuery(host_query),
292 };
293
294 const stderr = std.io.getStdErr();
295 const ttyconf = get_tty_conf(color, stderr);
296 switch (ttyconf) {
297 .no_color => try graph.env_map.put("NO_COLOR", "1"),
298 .escape_codes => try graph.env_map.put("YES_COLOR", "1"),
299 .windows_api => {},
300 }
301
302 var progress: std.Progress = .{ .dont_print_on_dumb = true };
303 const main_progress_node = progress.start("", 0);
304
305 builder.debug_log_scopes = debug_log_scopes.items;
306 builder.resolveInstallPrefix(install_prefix, dir_list);
307 {
308 var prog_node = main_progress_node.start("user build.zig logic", 0);
309 defer prog_node.end();
310 try builder.runBuild(root);
311 }
312
313 if (graph.needed_lazy_dependencies.entries.len != 0) {
314 var buffer: std.ArrayListUnmanaged(u8) = .{};
315 for (graph.needed_lazy_dependencies.keys()) |k| {
316 try buffer.appendSlice(arena, k);
317 try buffer.append(arena, '\n');
318 }
319 const s = std.fs.path.sep_str;
320 const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{}));
321 local_cache_directory.handle.writeFile2(.{
322 .sub_path = tmp_sub_path,
323 .data = buffer.items,
324 .flags = .{ .exclusive = true },
325 }) catch |err| {
326 fatal("unable to write configuration results to '{}{s}': {s}", .{
327 local_cache_directory, tmp_sub_path, @errorName(err),
328 });
329 };
330 process.exit(3); // Indicate configure phase failed with meaningful stdout.
331 }
332
333 if (builder.validateUserInputDidItFail()) {
334 fatal(" access the help menu with 'zig build -h'", .{});
335 }
336
337 validateSystemLibraryOptions(builder);
338
339 const stdout_writer = io.getStdOut().writer();
340
341 if (help_menu)
342 return usage(builder, stdout_writer);
343
344 if (steps_menu)
345 return steps(builder, stdout_writer);
346
347 var run: Run = .{
348 .max_rss = max_rss,
349 .max_rss_is_default = false,
350 .max_rss_mutex = .{},
351 .skip_oom_steps = skip_oom_steps,
352 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
353 .prominent_compile_errors = prominent_compile_errors,
354
355 .claimed_rss = 0,
356 .summary = summary,
357 .ttyconf = ttyconf,
358 .stderr = stderr,
359 };
360
361 if (run.max_rss == 0) {
362 run.max_rss = process.totalSystemMemory() catch std.math.maxInt(u64);
363 run.max_rss_is_default = true;
364 }
365
366 runStepNames(
367 arena,
368 builder,
369 targets.items,
370 main_progress_node,
371 thread_pool_options,
372 &run,
373 seed,
374 ) catch |err| switch (err) {
375 error.UncleanExit => process.exit(1),
376 else => return err,
377 };
378}
379
380const Run = struct {
381 max_rss: u64,
382 max_rss_is_default: bool,
383 max_rss_mutex: std.Thread.Mutex,
384 skip_oom_steps: bool,
385 memory_blocked_steps: std.ArrayList(*Step),
386 prominent_compile_errors: bool,
387
388 claimed_rss: usize,
389 summary: ?Summary,
390 ttyconf: std.io.tty.Config,
391 stderr: File,
392};
393
394fn runStepNames(
395 arena: std.mem.Allocator,
396 b: *std.Build,
397 step_names: []const []const u8,
398 parent_prog_node: *std.Progress.Node,
399 thread_pool_options: std.Thread.Pool.Options,
400 run: *Run,
401 seed: u32,
402) !void {
403 const gpa = b.allocator;
404 var step_stack: std.AutoArrayHashMapUnmanaged(*Step, void) = .{};
405 defer step_stack.deinit(gpa);
406
407 if (step_names.len == 0) {
408 try step_stack.put(gpa, b.default_step, {});
409 } else {
410 try step_stack.ensureUnusedCapacity(gpa, step_names.len);
411 for (0..step_names.len) |i| {
412 const step_name = step_names[step_names.len - i - 1];
413 const s = b.top_level_steps.get(step_name) orelse {
414 std.debug.print("no step named '{s}'\n access the help menu with 'zig build -h'\n", .{step_name});
415 process.exit(1);
416 };
417 step_stack.putAssumeCapacity(&s.step, {});
418 }
419 }
420
421 const starting_steps = try arena.dupe(*Step, step_stack.keys());
422
423 var rng = std.Random.DefaultPrng.init(seed);
424 const rand = rng.random();
425 rand.shuffle(*Step, starting_steps);
426
427 for (starting_steps) |s| {
428 constructGraphAndCheckForDependencyLoop(b, s, &step_stack, rand) catch |err| switch (err) {
429 error.DependencyLoopDetected => return error.UncleanExit,
430 else => |e| return e,
431 };
432 }
433
434 {
435 // Check that we have enough memory to complete the build.
436 var any_problems = false;
437 for (step_stack.keys()) |s| {
438 if (s.max_rss == 0) continue;
439 if (s.max_rss > run.max_rss) {
440 if (run.skip_oom_steps) {
441 s.state = .skipped_oom;
442 } else {
443 std.debug.print("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory\n", .{
444 s.owner.dep_prefix, s.name, s.max_rss, run.max_rss,
445 });
446 any_problems = true;
447 }
448 }
449 }
450 if (any_problems) {
451 if (run.max_rss_is_default) {
452 std.debug.print("note: use --maxrss to override the default", .{});
453 }
454 return error.UncleanExit;
455 }
456 }
457
458 var thread_pool: std.Thread.Pool = undefined;
459 try thread_pool.init(thread_pool_options);
460 defer thread_pool.deinit();
461
462 {
463 defer parent_prog_node.end();
464
465 var step_prog = parent_prog_node.start("steps", step_stack.count());
466 defer step_prog.end();
467
468 var wait_group: std.Thread.WaitGroup = .{};
469 defer wait_group.wait();
470
471 // Here we spawn the initial set of tasks with a nice heuristic -
472 // dependency order. Each worker when it finishes a step will then
473 // check whether it should run any dependants.
474 const steps_slice = step_stack.keys();
475 for (0..steps_slice.len) |i| {
476 const step = steps_slice[steps_slice.len - i - 1];
477 if (step.state == .skipped_oom) continue;
478
479 wait_group.start();
480 thread_pool.spawn(workerMakeOneStep, .{
481 &wait_group, &thread_pool, b, step, &step_prog, run,
482 }) catch @panic("OOM");
483 }
484 }
485 assert(run.memory_blocked_steps.items.len == 0);
486
487 var test_skip_count: usize = 0;
488 var test_fail_count: usize = 0;
489 var test_pass_count: usize = 0;
490 var test_leak_count: usize = 0;
491 var test_count: usize = 0;
492
493 var success_count: usize = 0;
494 var skipped_count: usize = 0;
495 var failure_count: usize = 0;
496 var pending_count: usize = 0;
497 var total_compile_errors: usize = 0;
498 var compile_error_steps: std.ArrayListUnmanaged(*Step) = .{};
499 defer compile_error_steps.deinit(gpa);
500
501 for (step_stack.keys()) |s| {
502 test_fail_count += s.test_results.fail_count;
503 test_skip_count += s.test_results.skip_count;
504 test_leak_count += s.test_results.leak_count;
505 test_pass_count += s.test_results.passCount();
506 test_count += s.test_results.test_count;
507
508 switch (s.state) {
509 .precheck_unstarted => unreachable,
510 .precheck_started => unreachable,
511 .running => unreachable,
512 .precheck_done => {
513 // precheck_done is equivalent to dependency_failure in the case of
514 // transitive dependencies. For example:
515 // A -> B -> C (failure)
516 // B will be marked as dependency_failure, while A may never be queued, and thus
517 // remain in the initial state of precheck_done.
518 s.state = .dependency_failure;
519 pending_count += 1;
520 },
521 .dependency_failure => pending_count += 1,
522 .success => success_count += 1,
523 .skipped, .skipped_oom => skipped_count += 1,
524 .failure => {
525 failure_count += 1;
526 const compile_errors_len = s.result_error_bundle.errorMessageCount();
527 if (compile_errors_len > 0) {
528 total_compile_errors += compile_errors_len;
529 try compile_error_steps.append(gpa, s);
530 }
531 },
532 }
533 }
534
535 // A proper command line application defaults to silently succeeding.
536 // The user may request verbose mode if they have a different preference.
537 if (failure_count == 0 and run.summary != Summary.all) return cleanExit();
538
539 const ttyconf = run.ttyconf;
540 const stderr = run.stderr;
541
542 if (run.summary != Summary.none) {
543 const total_count = success_count + failure_count + pending_count + skipped_count;
544 ttyconf.setColor(stderr, .cyan) catch {};
545 stderr.writeAll("Build Summary:") catch {};
546 ttyconf.setColor(stderr, .reset) catch {};
547 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
548 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
549 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
550
551 if (test_count > 0) stderr.writer().print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {};
552 if (test_skip_count > 0) stderr.writer().print("; {d} skipped", .{test_skip_count}) catch {};
553 if (test_fail_count > 0) stderr.writer().print("; {d} failed", .{test_fail_count}) catch {};
554 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
555
556 if (run.summary == null) {
557 ttyconf.setColor(stderr, .dim) catch {};
558 stderr.writeAll(" (disable with --summary none)") catch {};
559 ttyconf.setColor(stderr, .reset) catch {};
560 }
561 stderr.writeAll("\n") catch {};
562 const failures_only = run.summary != Summary.all;
563
564 // Print a fancy tree with build results.
565 var print_node: PrintNode = .{ .parent = null };
566 if (step_names.len == 0) {
567 print_node.last = true;
568 printTreeStep(b, b.default_step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
569 } else {
570 const last_index = if (!failures_only) b.top_level_steps.count() else blk: {
571 var i: usize = step_names.len;
572 while (i > 0) {
573 i -= 1;
574 if (b.top_level_steps.get(step_names[i]).?.step.state != .success) break :blk i;
575 }
576 break :blk b.top_level_steps.count();
577 };
578 for (step_names, 0..) |step_name, i| {
579 const tls = b.top_level_steps.get(step_name).?;
580 print_node.last = i + 1 == last_index;
581 printTreeStep(b, &tls.step, run, stderr, ttyconf, &print_node, &step_stack, failures_only) catch {};
582 }
583 }
584 }
585
586 if (failure_count == 0) return cleanExit();
587
588 // Finally, render compile errors at the bottom of the terminal.
589 // We use a separate compile_error_steps array list because step_stack is destructively
590 // mutated in printTreeStep above.
591 if (run.prominent_compile_errors and total_compile_errors > 0) {
592 for (compile_error_steps.items) |s| {
593 if (s.result_error_bundle.errorMessageCount() > 0) {
594 s.result_error_bundle.renderToStdErr(renderOptions(ttyconf));
595 }
596 }
597
598 // Signal to parent process that we have printed compile errors. The
599 // parent process may choose to omit the "following command failed"
600 // line in this case.
601 process.exit(2);
602 }
603
604 process.exit(1);
605}
606
607const PrintNode = struct {
608 parent: ?*PrintNode,
609 last: bool = false,
610};
611
612fn printPrefix(node: *PrintNode, stderr: File, ttyconf: std.io.tty.Config) !void {
613 const parent = node.parent orelse return;
614 if (parent.parent == null) return;
615 try printPrefix(parent, stderr, ttyconf);
616 if (parent.last) {
617 try stderr.writeAll(" ");
618 } else {
619 try stderr.writeAll(switch (ttyconf) {
620 .no_color, .windows_api => "| ",
621 .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │
622 });
623 }
624}
625
626fn printChildNodePrefix(stderr: File, ttyconf: std.io.tty.Config) !void {
627 try stderr.writeAll(switch (ttyconf) {
628 .no_color, .windows_api => "+- ",
629 .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─
630 });
631}
632
633fn printStepStatus(
634 s: *Step,
635 stderr: File,
636 ttyconf: std.io.tty.Config,
637 run: *const Run,
638) !void {
639 switch (s.state) {
640 .precheck_unstarted => unreachable,
641 .precheck_started => unreachable,
642 .precheck_done => unreachable,
643 .running => unreachable,
644
645 .dependency_failure => {
646 try ttyconf.setColor(stderr, .dim);
647 try stderr.writeAll(" transitive failure\n");
648 try ttyconf.setColor(stderr, .reset);
649 },
650
651 .success => {
652 try ttyconf.setColor(stderr, .green);
653 if (s.result_cached) {
654 try stderr.writeAll(" cached");
655 } else if (s.test_results.test_count > 0) {
656 const pass_count = s.test_results.passCount();
657 try stderr.writer().print(" {d} passed", .{pass_count});
658 if (s.test_results.skip_count > 0) {
659 try ttyconf.setColor(stderr, .yellow);
660 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
661 }
662 } else {
663 try stderr.writeAll(" success");
664 }
665 try ttyconf.setColor(stderr, .reset);
666 if (s.result_duration_ns) |ns| {
667 try ttyconf.setColor(stderr, .dim);
668 if (ns >= std.time.ns_per_min) {
669 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
670 } else if (ns >= std.time.ns_per_s) {
671 try stderr.writer().print(" {d}s", .{ns / std.time.ns_per_s});
672 } else if (ns >= std.time.ns_per_ms) {
673 try stderr.writer().print(" {d}ms", .{ns / std.time.ns_per_ms});
674 } else if (ns >= std.time.ns_per_us) {
675 try stderr.writer().print(" {d}us", .{ns / std.time.ns_per_us});
676 } else {
677 try stderr.writer().print(" {d}ns", .{ns});
678 }
679 try ttyconf.setColor(stderr, .reset);
680 }
681 if (s.result_peak_rss != 0) {
682 const rss = s.result_peak_rss;
683 try ttyconf.setColor(stderr, .dim);
684 if (rss >= 1000_000_000) {
685 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
686 } else if (rss >= 1000_000) {
687 try stderr.writer().print(" MaxRSS:{d}M", .{rss / 1000_000});
688 } else if (rss >= 1000) {
689 try stderr.writer().print(" MaxRSS:{d}K", .{rss / 1000});
690 } else {
691 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
692 }
693 try ttyconf.setColor(stderr, .reset);
694 }
695 try stderr.writeAll("\n");
696 },
697 .skipped, .skipped_oom => |skip| {
698 try ttyconf.setColor(stderr, .yellow);
699 try stderr.writeAll(" skipped");
700 if (skip == .skipped_oom) {
701 try stderr.writeAll(" (not enough memory)");
702 try ttyconf.setColor(stderr, .dim);
703 try stderr.writer().print(" upper bound of {d} exceeded runner limit ({d})", .{ s.max_rss, run.max_rss });
704 try ttyconf.setColor(stderr, .yellow);
705 }
706 try stderr.writeAll("\n");
707 try ttyconf.setColor(stderr, .reset);
708 },
709 .failure => try printStepFailure(s, stderr, ttyconf),
710 }
711}
712
713fn printStepFailure(
714 s: *Step,
715 stderr: File,
716 ttyconf: std.io.tty.Config,
717) !void {
718 if (s.result_error_bundle.errorMessageCount() > 0) {
719 try ttyconf.setColor(stderr, .red);
720 try stderr.writer().print(" {d} errors\n", .{
721 s.result_error_bundle.errorMessageCount(),
722 });
723 try ttyconf.setColor(stderr, .reset);
724 } else if (!s.test_results.isSuccess()) {
725 try stderr.writer().print(" {d}/{d} passed", .{
726 s.test_results.passCount(), s.test_results.test_count,
727 });
728 if (s.test_results.fail_count > 0) {
729 try stderr.writeAll(", ");
730 try ttyconf.setColor(stderr, .red);
731 try stderr.writer().print("{d} failed", .{
732 s.test_results.fail_count,
733 });
734 try ttyconf.setColor(stderr, .reset);
735 }
736 if (s.test_results.skip_count > 0) {
737 try stderr.writeAll(", ");
738 try ttyconf.setColor(stderr, .yellow);
739 try stderr.writer().print("{d} skipped", .{
740 s.test_results.skip_count,
741 });
742 try ttyconf.setColor(stderr, .reset);
743 }
744 if (s.test_results.leak_count > 0) {
745 try stderr.writeAll(", ");
746 try ttyconf.setColor(stderr, .red);
747 try stderr.writer().print("{d} leaked", .{
748 s.test_results.leak_count,
749 });
750 try ttyconf.setColor(stderr, .reset);
751 }
752 try stderr.writeAll("\n");
753 } else if (s.result_error_msgs.items.len > 0) {
754 try ttyconf.setColor(stderr, .red);
755 try stderr.writeAll(" failure\n");
756 try ttyconf.setColor(stderr, .reset);
757 } else {
758 assert(s.result_stderr.len > 0);
759 try ttyconf.setColor(stderr, .red);
760 try stderr.writeAll(" stderr\n");
761 try ttyconf.setColor(stderr, .reset);
762 }
763}
764
765fn printTreeStep(
766 b: *std.Build,
767 s: *Step,
768 run: *const Run,
769 stderr: File,
770 ttyconf: std.io.tty.Config,
771 parent_node: *PrintNode,
772 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
773 failures_only: bool,
774) !void {
775 const first = step_stack.swapRemove(s);
776 if (failures_only and s.state == .success) return;
777 try printPrefix(parent_node, stderr, ttyconf);
778
779 if (!first) try ttyconf.setColor(stderr, .dim);
780 if (parent_node.parent != null) {
781 if (parent_node.last) {
782 try printChildNodePrefix(stderr, ttyconf);
783 } else {
784 try stderr.writeAll(switch (ttyconf) {
785 .no_color, .windows_api => "+- ",
786 .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─
787 });
788 }
789 }
790
791 // dep_prefix omitted here because it is redundant with the tree.
792 try stderr.writeAll(s.name);
793
794 if (first) {
795 try printStepStatus(s, stderr, ttyconf, run);
796
797 const last_index = if (!failures_only) s.dependencies.items.len -| 1 else blk: {
798 var i: usize = s.dependencies.items.len;
799 while (i > 0) {
800 i -= 1;
801 if (s.dependencies.items[i].state != .success) break :blk i;
802 }
803 break :blk s.dependencies.items.len -| 1;
804 };
805 for (s.dependencies.items, 0..) |dep, i| {
806 var print_node: PrintNode = .{
807 .parent = parent_node,
808 .last = i == last_index,
809 };
810 try printTreeStep(b, dep, run, stderr, ttyconf, &print_node, step_stack, failures_only);
811 }
812 } else {
813 if (s.dependencies.items.len == 0) {
814 try stderr.writeAll(" (reused)\n");
815 } else {
816 try stderr.writer().print(" (+{d} more reused dependencies)\n", .{
817 s.dependencies.items.len,
818 });
819 }
820 try ttyconf.setColor(stderr, .reset);
821 }
822}
823
824/// Traverse the dependency graph depth-first and make it undirected by having
825/// steps know their dependants (they only know dependencies at start).
826/// Along the way, check that there is no dependency loop, and record the steps
827/// in traversal order in `step_stack`.
828/// Each step has its dependencies traversed in random order, this accomplishes
829/// two things:
830/// - `step_stack` will be in randomized-depth-first order, so the build runner
831/// spawns steps in a random (but optimized) order
832/// - each step's `dependants` list is also filled in a random order, so that
833/// when it finishes executing in `workerMakeOneStep`, it spawns next steps
834/// to run in random order
835fn constructGraphAndCheckForDependencyLoop(
836 b: *std.Build,
837 s: *Step,
838 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
839 rand: std.Random,
840) !void {
841 switch (s.state) {
842 .precheck_started => {
843 std.debug.print("dependency loop detected:\n {s}\n", .{s.name});
844 return error.DependencyLoopDetected;
845 },
846 .precheck_unstarted => {
847 s.state = .precheck_started;
848
849 try step_stack.ensureUnusedCapacity(b.allocator, s.dependencies.items.len);
850
851 // We dupe to avoid shuffling the steps in the summary, it depends
852 // on s.dependencies' order.
853 const deps = b.allocator.dupe(*Step, s.dependencies.items) catch @panic("OOM");
854 rand.shuffle(*Step, deps);
855
856 for (deps) |dep| {
857 try step_stack.put(b.allocator, dep, {});
858 try dep.dependants.append(b.allocator, s);
859 constructGraphAndCheckForDependencyLoop(b, dep, step_stack, rand) catch |err| {
860 if (err == error.DependencyLoopDetected) {
861 std.debug.print(" {s}\n", .{s.name});
862 }
863 return err;
864 };
865 }
866
867 s.state = .precheck_done;
868 },
869 .precheck_done => {},
870
871 // These don't happen until we actually run the step graph.
872 .dependency_failure => unreachable,
873 .running => unreachable,
874 .success => unreachable,
875 .failure => unreachable,
876 .skipped => unreachable,
877 .skipped_oom => unreachable,
878 }
879}
880
881fn workerMakeOneStep(
882 wg: *std.Thread.WaitGroup,
883 thread_pool: *std.Thread.Pool,
884 b: *std.Build,
885 s: *Step,
886 prog_node: *std.Progress.Node,
887 run: *Run,
888) void {
889 defer wg.finish();
890
891 // First, check the conditions for running this step. If they are not met,
892 // then we return without doing the step, relying on another worker to
893 // queue this step up again when dependencies are met.
894 for (s.dependencies.items) |dep| {
895 switch (@atomicLoad(Step.State, &dep.state, .SeqCst)) {
896 .success, .skipped => continue,
897 .failure, .dependency_failure, .skipped_oom => {
898 @atomicStore(Step.State, &s.state, .dependency_failure, .SeqCst);
899 return;
900 },
901 .precheck_done, .running => {
902 // dependency is not finished yet.
903 return;
904 },
905 .precheck_unstarted => unreachable,
906 .precheck_started => unreachable,
907 }
908 }
909
910 if (s.max_rss != 0) {
911 run.max_rss_mutex.lock();
912 defer run.max_rss_mutex.unlock();
913
914 // Avoid running steps twice.
915 if (s.state != .precheck_done) {
916 // Another worker got the job.
917 return;
918 }
919
920 const new_claimed_rss = run.claimed_rss + s.max_rss;
921 if (new_claimed_rss > run.max_rss) {
922 // Running this step right now could possibly exceed the allotted RSS.
923 // Add this step to the queue of memory-blocked steps.
924 run.memory_blocked_steps.append(s) catch @panic("OOM");
925 return;
926 }
927
928 run.claimed_rss = new_claimed_rss;
929 s.state = .running;
930 } else {
931 // Avoid running steps twice.
932 if (@cmpxchgStrong(Step.State, &s.state, .precheck_done, .running, .SeqCst, .SeqCst) != null) {
933 // Another worker got the job.
934 return;
935 }
936 }
937
938 var sub_prog_node = prog_node.start(s.name, 0);
939 sub_prog_node.activate();
940 defer sub_prog_node.end();
941
942 const make_result = s.make(&sub_prog_node);
943
944 // No matter the result, we want to display error/warning messages.
945 const show_compile_errors = !run.prominent_compile_errors and
946 s.result_error_bundle.errorMessageCount() > 0;
947 const show_error_msgs = s.result_error_msgs.items.len > 0;
948 const show_stderr = s.result_stderr.len > 0;
949
950 if (show_error_msgs or show_compile_errors or show_stderr) {
951 sub_prog_node.context.lock_stderr();
952 defer sub_prog_node.context.unlock_stderr();
953
954 printErrorMessages(b, s, run) catch {};
955 }
956
957 handle_result: {
958 if (make_result) |_| {
959 @atomicStore(Step.State, &s.state, .success, .SeqCst);
960 } else |err| switch (err) {
961 error.MakeFailed => {
962 @atomicStore(Step.State, &s.state, .failure, .SeqCst);
963 break :handle_result;
964 },
965 error.MakeSkipped => @atomicStore(Step.State, &s.state, .skipped, .SeqCst),
966 }
967
968 // Successful completion of a step, so we queue up its dependants as well.
969 for (s.dependants.items) |dep| {
970 wg.start();
971 thread_pool.spawn(workerMakeOneStep, .{
972 wg, thread_pool, b, dep, prog_node, run,
973 }) catch @panic("OOM");
974 }
975 }
976
977 // If this is a step that claims resources, we must now queue up other
978 // steps that are waiting for resources.
979 if (s.max_rss != 0) {
980 run.max_rss_mutex.lock();
981 defer run.max_rss_mutex.unlock();
982
983 // Give the memory back to the scheduler.
984 run.claimed_rss -= s.max_rss;
985 // Avoid kicking off too many tasks that we already know will not have
986 // enough resources.
987 var remaining = run.max_rss - run.claimed_rss;
988 var i: usize = 0;
989 var j: usize = 0;
990 while (j < run.memory_blocked_steps.items.len) : (j += 1) {
991 const dep = run.memory_blocked_steps.items[j];
992 assert(dep.max_rss != 0);
993 if (dep.max_rss <= remaining) {
994 remaining -= dep.max_rss;
995
996 wg.start();
997 thread_pool.spawn(workerMakeOneStep, .{
998 wg, thread_pool, b, dep, prog_node, run,
999 }) catch @panic("OOM");
1000 } else {
1001 run.memory_blocked_steps.items[i] = dep;
1002 i += 1;
1003 }
1004 }
1005 run.memory_blocked_steps.shrinkRetainingCapacity(i);
1006 }
1007}
1008
1009fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
1010 const gpa = b.allocator;
1011 const stderr = run.stderr;
1012 const ttyconf = run.ttyconf;
1013
1014 // Provide context for where these error messages are coming from by
1015 // printing the corresponding Step subtree.
1016
1017 var step_stack: std.ArrayListUnmanaged(*Step) = .{};
1018 defer step_stack.deinit(gpa);
1019 try step_stack.append(gpa, failing_step);
1020 while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) {
1021 try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]);
1022 }
1023
1024 // Now, `step_stack` has the subtree that we want to print, in reverse order.
1025 try ttyconf.setColor(stderr, .dim);
1026 var indent: usize = 0;
1027 while (step_stack.popOrNull()) |s| : (indent += 1) {
1028 if (indent > 0) {
1029 try stderr.writer().writeByteNTimes(' ', (indent - 1) * 3);
1030 try printChildNodePrefix(stderr, ttyconf);
1031 }
1032
1033 try stderr.writeAll(s.name);
1034
1035 if (s == failing_step) {
1036 try printStepFailure(s, stderr, ttyconf);
1037 } else {
1038 try stderr.writeAll("\n");
1039 }
1040 }
1041 try ttyconf.setColor(stderr, .reset);
1042
1043 if (failing_step.result_stderr.len > 0) {
1044 try stderr.writeAll(failing_step.result_stderr);
1045 if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) {
1046 try stderr.writeAll("\n");
1047 }
1048 }
1049
1050 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1051 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
1052
1053 for (failing_step.result_error_msgs.items) |msg| {
1054 try ttyconf.setColor(stderr, .red);
1055 try stderr.writeAll("error: ");
1056 try ttyconf.setColor(stderr, .reset);
1057 try stderr.writeAll(msg);
1058 try stderr.writeAll("\n");
1059 }
1060}
1061
1062fn steps(builder: *std.Build, out_stream: anytype) !void {
1063 const allocator = builder.allocator;
1064 for (builder.top_level_steps.values()) |top_level_step| {
1065 const name = if (&top_level_step.step == builder.default_step)
1066 try fmt.allocPrint(allocator, "{s} (default)", .{top_level_step.step.name})
1067 else
1068 top_level_step.step.name;
1069 try out_stream.print(" {s:<28} {s}\n", .{ name, top_level_step.description });
1070 }
1071}
1072
1073fn usage(b: *std.Build, out_stream: anytype) !void {
1074 try out_stream.print(
1075 \\Usage: {s} build [steps] [options]
1076 \\
1077 \\Steps:
1078 \\
1079 , .{b.graph.zig_exe});
1080 try steps(b, out_stream);
1081
1082 try out_stream.writeAll(
1083 \\
1084 \\General Options:
1085 \\ -p, --prefix [path] Where to install files (default: zig-out)
1086 \\ --prefix-lib-dir [path] Where to install libraries
1087 \\ --prefix-exe-dir [path] Where to install executables
1088 \\ --prefix-include-dir [path] Where to install C header files
1089 \\
1090 \\ --release[=mode] Request release mode, optionally specifying a
1091 \\ preferred optimization mode: fast, safe, small
1092 \\
1093 \\ -fdarling, -fno-darling Integration with system-installed Darling to
1094 \\ execute macOS programs on Linux hosts
1095 \\ (default: no)
1096 \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute
1097 \\ foreign-architecture programs on Linux hosts
1098 \\ (default: no)
1099 \\ --glibc-runtimes [path] Enhances QEMU integration by providing glibc built
1100 \\ for multiple foreign architectures, allowing
1101 \\ execution of non-native programs that link with glibc.
1102 \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on
1103 \\ ARM64 macOS hosts. (default: no)
1104 \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to
1105 \\ execute WASI binaries. (default: no)
1106 \\ -fwine, -fno-wine Integration with system-installed Wine to execute
1107 \\ Windows programs on Linux hosts. (default: no)
1108 \\
1109 \\ -h, --help Print this help and exit
1110 \\ -l, --list-steps Print available steps
1111 \\ --verbose Print commands before executing them
1112 \\ --color [auto|off|on] Enable or disable colored error messages
1113 \\ --prominent-compile-errors Buffer compile errors and display at end
1114 \\ --summary [mode] Control the printing of the build summary
1115 \\ all Print the build summary in its entirety
1116 \\ failures (Default) Only print failed steps
1117 \\ none Do not print the build summary
1118 \\ -j<N> Limit concurrent jobs (default is to use all CPU cores)
1119 \\ --maxrss <bytes> Limit memory usage (default is to use available memory)
1120 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1121 \\ --fetch Exit after fetching dependency tree
1122 \\
1123 \\Project-Specific Options:
1124 \\
1125 );
1126
1127 const arena = b.allocator;
1128 if (b.available_options_list.items.len == 0) {
1129 try out_stream.print(" (none)\n", .{});
1130 } else {
1131 for (b.available_options_list.items) |option| {
1132 const name = try fmt.allocPrint(arena, " -D{s}=[{s}]", .{
1133 option.name,
1134 @tagName(option.type_id),
1135 });
1136 try out_stream.print("{s:<30} {s}\n", .{ name, option.description });
1137 if (option.enum_options) |enum_options| {
1138 const padding = " " ** 33;
1139 try out_stream.writeAll(padding ++ "Supported Values:\n");
1140 for (enum_options) |enum_option| {
1141 try out_stream.print(padding ++ " {s}\n", .{enum_option});
1142 }
1143 }
1144 }
1145 }
1146
1147 try out_stream.writeAll(
1148 \\
1149 \\System Integration Options:
1150 \\ --search-prefix [path] Add a path to look for binaries, libraries, headers
1151 \\ --sysroot [path] Set the system root directory (usually /)
1152 \\ --libc [file] Provide a file which specifies libc paths
1153 \\
1154 \\ --host-target [triple] Use the provided target as the host
1155 \\ --host-cpu [cpu] Use the provided CPU as the host
1156 \\ --host-dynamic-linker [path] Use the provided dynamic linker as the host
1157 \\
1158 \\ --system [pkgdir] Disable package fetching; enable all integrations
1159 \\ -fsys=[name] Enable a system integration
1160 \\ -fno-sys=[name] Disable a system integration
1161 \\
1162 \\ Available System Integrations: Enabled:
1163 \\
1164 );
1165 if (b.graph.system_library_options.entries.len == 0) {
1166 try out_stream.writeAll(" (none) -\n");
1167 } else {
1168 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1169 const status = switch (v) {
1170 .declared_enabled => "yes",
1171 .declared_disabled => "no",
1172 .user_enabled, .user_disabled => unreachable, // already emitted error
1173 };
1174 try out_stream.print(" {s:<43} {s}\n", .{ k, status });
1175 }
1176 }
1177
1178 try out_stream.writeAll(
1179 \\
1180 \\Advanced Options:
1181 \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error
1182 \\ -fno-reference-trace Disable reference trace
1183 \\ --build-file [file] Override path to build.zig
1184 \\ --cache-dir [path] Override path to local Zig cache directory
1185 \\ --global-cache-dir [path] Override path to global Zig cache directory
1186 \\ --zig-lib-dir [arg] Override path to Zig lib directory
1187 \\ --build-runner [file] Override path to build runner
1188 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1189 \\ --debug-log [scope] Enable debugging the compiler
1190 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1191 \\ --verbose-link Enable compiler debug output for linking
1192 \\ --verbose-air Enable compiler debug output for Zig AIR
1193 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
1194 \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC
1195 \\ --verbose-cimport Enable compiler debug output for C imports
1196 \\ --verbose-cc Enable compiler debug output for C compilation
1197 \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
1198 \\
1199 );
1200}
1201
1202fn nextArg(args: [][:0]const u8, idx: *usize) ?[:0]const u8 {
1203 if (idx.* >= args.len) return null;
1204 defer idx.* += 1;
1205 return args[idx.*];
1206}
1207
1208fn nextArgOrFatal(args: [][:0]const u8, idx: *usize) [:0]const u8 {
1209 return nextArg(args, idx) orelse {
1210 std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.*]});
1211 process.exit(1);
1212 };
1213}
1214
1215fn argsRest(args: [][:0]const u8, idx: usize) ?[][:0]const u8 {
1216 if (idx >= args.len) return null;
1217 return args[idx..];
1218}
1219
1220fn cleanExit() void {
1221 // Perhaps in the future there could be an Advanced Options flag such as
1222 // --debug-build-runner-leaks which would make this function return instead
1223 // of calling exit.
1224 process.exit(0);
1225}
1226
1227const Color = enum { auto, off, on };
1228const Summary = enum { all, failures, none };
1229
1230fn get_tty_conf(color: Color, stderr: File) std.io.tty.Config {
1231 return switch (color) {
1232 .auto => std.io.tty.detectConfig(stderr),
1233 .on => .escape_codes,
1234 .off => .no_color,
1235 };
1236}
1237
1238fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
1239 return .{
1240 .ttyconf = ttyconf,
1241 .include_source_line = ttyconf != .no_color,
1242 .include_reference_trace = ttyconf != .no_color,
1243 };
1244}
1245
1246fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn {
1247 std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args);
1248 process.exit(1);
1249}
1250
1251fn fatal(comptime f: []const u8, args: anytype) noreturn {
1252 std.debug.print(f ++ "\n", args);
1253 process.exit(1);
1254}
1255
1256fn validateSystemLibraryOptions(b: *std.Build) void {
1257 var bad = false;
1258 for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| {
1259 switch (v) {
1260 .user_disabled, .user_enabled => {
1261 // The user tried to enable or disable a system library integration, but
1262 // the build script did not recognize that option.
1263 std.debug.print("system library name not recognized by build script: '{s}'\n", .{k});
1264 bad = true;
1265 },
1266 .declared_disabled, .declared_enabled => {},
1267 }
1268 }
1269 if (bad) {
1270 std.debug.print(" access the help menu with 'zig build -h'\n", .{});
1271 process.exit(1);
1272 }
1273}
lib/compiler/fmt.zig created+342
...@@ -0,0 +1,342 @@
1const std = @import("std");
2const mem = std.mem;
3const fs = std.fs;
4const process = std.process;
5const Allocator = std.mem.Allocator;
6const warn = std.log.warn;
7const Color = std.zig.Color;
8
9const usage_fmt =
10 \\Usage: zig fmt [file]...
11 \\
12 \\ Formats the input files and modifies them in-place.
13 \\ Arguments can be files or directories, which are searched
14 \\ recursively.
15 \\
16 \\Options:
17 \\ -h, --help Print this help and exit
18 \\ --color [auto|off|on] Enable or disable colored error messages
19 \\ --stdin Format code from stdin; output to stdout
20 \\ --check List non-conforming files and exit with an error
21 \\ if the list is non-empty
22 \\ --ast-check Run zig ast-check on every file
23 \\ --exclude [file] Exclude file or directory from formatting
24 \\
25 \\
26;
27
28const Fmt = struct {
29 seen: SeenMap,
30 any_error: bool,
31 check_ast: bool,
32 color: Color,
33 gpa: Allocator,
34 arena: Allocator,
35 out_buffer: std.ArrayList(u8),
36
37 const SeenMap = std.AutoHashMap(fs.File.INode, void);
38};
39
40pub fn main() !void {
41 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
42 defer arena_instance.deinit();
43 const arena = arena_instance.allocator();
44 const gpa = arena;
45
46 const args = try process.argsAlloc(arena);
47
48 var color: Color = .auto;
49 var stdin_flag: bool = false;
50 var check_flag: bool = false;
51 var check_ast_flag: bool = false;
52 var input_files = std.ArrayList([]const u8).init(gpa);
53 defer input_files.deinit();
54 var excluded_files = std.ArrayList([]const u8).init(gpa);
55 defer excluded_files.deinit();
56
57 {
58 var i: usize = 1;
59 while (i < args.len) : (i += 1) {
60 const arg = args[i];
61 if (mem.startsWith(u8, arg, "-")) {
62 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
63 const stdout = std.io.getStdOut().writer();
64 try stdout.writeAll(usage_fmt);
65 return process.cleanExit();
66 } else if (mem.eql(u8, arg, "--color")) {
67 if (i + 1 >= args.len) {
68 fatal("expected [auto|on|off] after --color", .{});
69 }
70 i += 1;
71 const next_arg = args[i];
72 color = std.meta.stringToEnum(Color, next_arg) orelse {
73 fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg});
74 };
75 } else if (mem.eql(u8, arg, "--stdin")) {
76 stdin_flag = true;
77 } else if (mem.eql(u8, arg, "--check")) {
78 check_flag = true;
79 } else if (mem.eql(u8, arg, "--ast-check")) {
80 check_ast_flag = true;
81 } else if (mem.eql(u8, arg, "--exclude")) {
82 if (i + 1 >= args.len) {
83 fatal("expected parameter after --exclude", .{});
84 }
85 i += 1;
86 const next_arg = args[i];
87 try excluded_files.append(next_arg);
88 } else {
89 fatal("unrecognized parameter: '{s}'", .{arg});
90 }
91 } else {
92 try input_files.append(arg);
93 }
94 }
95 }
96
97 if (stdin_flag) {
98 if (input_files.items.len != 0) {
99 fatal("cannot use --stdin with positional arguments", .{});
100 }
101
102 const stdin = std.io.getStdIn();
103 const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| {
104 fatal("unable to read stdin: {}", .{err});
105 };
106 defer gpa.free(source_code);
107
108 var tree = std.zig.Ast.parse(gpa, source_code, .zig) catch |err| {
109 fatal("error parsing stdin: {}", .{err});
110 };
111 defer tree.deinit(gpa);
112
113 if (check_ast_flag) {
114 var zir = try std.zig.AstGen.generate(gpa, tree);
115
116 if (zir.hasCompileErrors()) {
117 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
118 try wip_errors.init(gpa);
119 defer wip_errors.deinit();
120 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
121 var error_bundle = try wip_errors.toOwnedBundle("");
122 defer error_bundle.deinit(gpa);
123 error_bundle.renderToStdErr(color.renderOptions());
124 process.exit(2);
125 }
126 } else if (tree.errors.len != 0) {
127 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);
128 process.exit(2);
129 }
130 const formatted = try tree.render(gpa);
131 defer gpa.free(formatted);
132
133 if (check_flag) {
134 const code: u8 = @intFromBool(mem.eql(u8, formatted, source_code));
135 process.exit(code);
136 }
137
138 return std.io.getStdOut().writeAll(formatted);
139 }
140
141 if (input_files.items.len == 0) {
142 fatal("expected at least one source file argument", .{});
143 }
144
145 var fmt = Fmt{
146 .gpa = gpa,
147 .arena = arena,
148 .seen = Fmt.SeenMap.init(gpa),
149 .any_error = false,
150 .check_ast = check_ast_flag,
151 .color = color,
152 .out_buffer = std.ArrayList(u8).init(gpa),
153 };
154 defer fmt.seen.deinit();
155 defer fmt.out_buffer.deinit();
156
157 // Mark any excluded files/directories as already seen,
158 // so that they are skipped later during actual processing
159 for (excluded_files.items) |file_path| {
160 const stat = fs.cwd().statFile(file_path) catch |err| switch (err) {
161 error.FileNotFound => continue,
162 // On Windows, statFile does not work for directories
163 error.IsDir => dir: {
164 var dir = try fs.cwd().openDir(file_path, .{});
165 defer dir.close();
166 break :dir try dir.stat();
167 },
168 else => |e| return e,
169 };
170 try fmt.seen.put(stat.inode, {});
171 }
172
173 for (input_files.items) |file_path| {
174 try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path);
175 }
176 if (fmt.any_error) {
177 process.exit(1);
178 }
179}
180
181const FmtError = error{
182 SystemResources,
183 OperationAborted,
184 IoPending,
185 BrokenPipe,
186 Unexpected,
187 WouldBlock,
188 FileClosed,
189 DestinationAddressRequired,
190 DiskQuota,
191 FileTooBig,
192 InputOutput,
193 NoSpaceLeft,
194 AccessDenied,
195 OutOfMemory,
196 RenameAcrossMountPoints,
197 ReadOnlyFileSystem,
198 LinkQuotaExceeded,
199 FileBusy,
200 EndOfStream,
201 Unseekable,
202 NotOpenForWriting,
203 UnsupportedEncoding,
204 ConnectionResetByPeer,
205 SocketNotConnected,
206 LockViolation,
207 NetNameDeleted,
208 InvalidArgument,
209} || fs.File.OpenError;
210
211fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {
212 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
213 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
214 else => {
215 warn("unable to format '{s}': {s}", .{ file_path, @errorName(err) });
216 fmt.any_error = true;
217 return;
218 },
219 };
220}
221
222fn fmtPathDir(
223 fmt: *Fmt,
224 file_path: []const u8,
225 check_mode: bool,
226 parent_dir: fs.Dir,
227 parent_sub_path: []const u8,
228) FmtError!void {
229 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
230 defer dir.close();
231
232 const stat = try dir.stat();
233 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
234
235 var dir_it = dir.iterate();
236 while (try dir_it.next()) |entry| {
237 const is_dir = entry.kind == .directory;
238
239 if (is_dir and (mem.eql(u8, entry.name, "zig-cache") or mem.eql(u8, entry.name, "zig-out"))) continue;
240
241 if (is_dir or entry.kind == .file and (mem.endsWith(u8, entry.name, ".zig") or mem.endsWith(u8, entry.name, ".zon"))) {
242 const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
243 defer fmt.gpa.free(full_path);
244
245 if (is_dir) {
246 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
247 } else {
248 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
249 warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
250 fmt.any_error = true;
251 return;
252 };
253 }
254 }
255 }
256}
257
258fn fmtPathFile(
259 fmt: *Fmt,
260 file_path: []const u8,
261 check_mode: bool,
262 dir: fs.Dir,
263 sub_path: []const u8,
264) FmtError!void {
265 const source_file = try dir.openFile(sub_path, .{});
266 var file_closed = false;
267 errdefer if (!file_closed) source_file.close();
268
269 const stat = try source_file.stat();
270
271 if (stat.kind == .directory)
272 return error.IsDir;
273
274 const gpa = fmt.gpa;
275 const source_code = try std.zig.readSourceFileToEndAlloc(
276 gpa,
277 source_file,
278 std.math.cast(usize, stat.size) orelse return error.FileTooBig,
279 );
280 defer gpa.free(source_code);
281
282 source_file.close();
283 file_closed = true;
284
285 // Add to set after no longer possible to get error.IsDir.
286 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
287
288 var tree = try std.zig.Ast.parse(gpa, source_code, .zig);
289 defer tree.deinit(gpa);
290
291 if (tree.errors.len != 0) {
292 try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
293 fmt.any_error = true;
294 return;
295 }
296
297 if (fmt.check_ast) {
298 if (stat.size > std.zig.max_src_size)
299 return error.FileTooBig;
300
301 var zir = try std.zig.AstGen.generate(gpa, tree);
302 defer zir.deinit(gpa);
303
304 if (zir.hasCompileErrors()) {
305 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
306 try wip_errors.init(gpa);
307 defer wip_errors.deinit();
308 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
309 var error_bundle = try wip_errors.toOwnedBundle("");
310 defer error_bundle.deinit(gpa);
311 error_bundle.renderToStdErr(fmt.color.renderOptions());
312 fmt.any_error = true;
313 }
314 }
315
316 // As a heuristic, we make enough capacity for the same as the input source.
317 fmt.out_buffer.shrinkRetainingCapacity(0);
318 try fmt.out_buffer.ensureTotalCapacity(source_code.len);
319
320 try tree.renderToArrayList(&fmt.out_buffer, .{});
321 if (mem.eql(u8, fmt.out_buffer.items, source_code))
322 return;
323
324 if (check_mode) {
325 const stdout = std.io.getStdOut().writer();
326 try stdout.print("{s}\n", .{file_path});
327 fmt.any_error = true;
328 } else {
329 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });
330 defer af.deinit();
331
332 try af.file.writeAll(fmt.out_buffer.items);
333 try af.finish();
334 const stdout = std.io.getStdOut().writer();
335 try stdout.print("{s}\n", .{file_path});
336 }
337}
338
339fn fatal(comptime format: []const u8, args: anytype) noreturn {
340 std.log.err(format, args);
341 process.exit(1);
342}
lib/compiler/reduce.zig created+426
...@@ -0,0 +1,426 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const Ast = std.zig.Ast;
6const Walk = @import("reduce/Walk.zig");
7const AstGen = std.zig.AstGen;
8const Zir = std.zig.Zir;
9
10const usage =
11 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
12 \\
13 \\root_source_file.zig is relative to --main-mod-path.
14 \\
15 \\checker:
16 \\ An executable that communicates interestingness by returning these exit codes:
17 \\ exit(0): interesting
18 \\ exit(1): unknown (infinite loop or other mishap)
19 \\ exit(other): not interesting
20 \\
21 \\options:
22 \\ --seed [integer] Override the random seed. Defaults to 0
23 \\ --skip-smoke-test Skip interestingness check smoke test
24 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
25 \\ deps: [dep],[dep],...
26 \\ dep: [[import=]name]
27 \\ --deps [dep],[dep],... Set dependency names for the root package
28 \\ dep: [[import=]name]
29 \\ --main-mod-path Set the directory of the root module
30 \\
31 \\argv:
32 \\ Forwarded directly to the interestingness script.
33 \\
34;
35
36const Interestingness = enum { interesting, unknown, boring };
37
38// Roadmap:
39// - add thread pool
40// - add support for parsing the module flags
41// - more fancy transformations
42// - @import inlining of modules
43// - removing statements or blocks of code
44// - replacing operands of `and` and `or` with `true` and `false`
45// - replacing if conditions with `true` and `false`
46// - reduce flags sent to the compiler
47// - integrate with the build system?
48
49pub fn main() !void {
50 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
51 defer arena_instance.deinit();
52 const arena = arena_instance.allocator();
53
54 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
55 const gpa = general_purpose_allocator.allocator();
56
57 const args = try std.process.argsAlloc(arena);
58
59 var opt_checker_path: ?[]const u8 = null;
60 var opt_root_source_file_path: ?[]const u8 = null;
61 var argv: []const []const u8 = &.{};
62 var seed: u32 = 0;
63 var skip_smoke_test = false;
64
65 {
66 var i: usize = 1;
67 while (i < args.len) : (i += 1) {
68 const arg = args[i];
69 if (mem.startsWith(u8, arg, "-")) {
70 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
71 const stdout = std.io.getStdOut().writer();
72 try stdout.writeAll(usage);
73 return std.process.cleanExit();
74 } else if (mem.eql(u8, arg, "--")) {
75 argv = args[i + 1 ..];
76 break;
77 } else if (mem.eql(u8, arg, "--skip-smoke-test")) {
78 skip_smoke_test = true;
79 } else if (mem.eql(u8, arg, "--main-mod-path")) {
80 @panic("TODO: implement --main-mod-path");
81 } else if (mem.eql(u8, arg, "--mod")) {
82 @panic("TODO: implement --mod");
83 } else if (mem.eql(u8, arg, "--deps")) {
84 @panic("TODO: implement --deps");
85 } else if (mem.eql(u8, arg, "--seed")) {
86 i += 1;
87 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
88 const next_arg = args[i];
89 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
90 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
91 next_arg, @errorName(err),
92 });
93 };
94 } else {
95 fatal("unrecognized parameter: '{s}'", .{arg});
96 }
97 } else if (opt_checker_path == null) {
98 opt_checker_path = arg;
99 } else if (opt_root_source_file_path == null) {
100 opt_root_source_file_path = arg;
101 } else {
102 fatal("unexpected extra parameter: '{s}'", .{arg});
103 }
104 }
105 }
106
107 const checker_path = opt_checker_path orelse
108 fatal("missing interestingness checker argument; see -h for usage", .{});
109 const root_source_file_path = opt_root_source_file_path orelse
110 fatal("missing root source file path argument; see -h for usage", .{});
111
112 var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{};
113 try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1);
114 interestingness_argv.appendAssumeCapacity(checker_path);
115 interestingness_argv.appendSliceAssumeCapacity(argv);
116
117 var rendered = std.ArrayList(u8).init(gpa);
118 defer rendered.deinit();
119
120 var astgen_input = std.ArrayList(u8).init(gpa);
121 defer astgen_input.deinit();
122
123 var tree = try parse(gpa, root_source_file_path);
124 defer {
125 gpa.free(tree.source);
126 tree.deinit(gpa);
127 }
128
129 if (!skip_smoke_test) {
130 std.debug.print("smoke testing the interestingness check...\n", .{});
131 switch (try runCheck(arena, interestingness_argv.items)) {
132 .interesting => {},
133 .boring, .unknown => |t| {
134 fatal("interestingness check returned {s} for unmodified input\n", .{
135 @tagName(t),
136 });
137 },
138 }
139 }
140
141 var fixups: Ast.Fixups = .{};
142 defer fixups.deinit(gpa);
143
144 var more_fixups: Ast.Fixups = .{};
145 defer more_fixups.deinit(gpa);
146
147 var rng = std.Random.DefaultPrng.init(seed);
148
149 // 1. Walk the AST of the source file looking for independent
150 // reductions and collecting them all into an array list.
151 // 2. Randomize the list of transformations. A future enhancement will add
152 // priority weights to the sorting but for now they are completely
153 // shuffled.
154 // 3. Apply a subset consisting of 1/2 of the transformations and check for
155 // interestingness.
156 // 4. If not interesting, half the subset size again and check again.
157 // 5. Repeat until the subset size is 1, then march the transformation
158 // index forward by 1 with each non-interesting attempt.
159 //
160 // At any point if a subset of transformations succeeds in producing an interesting
161 // result, restart the whole process, reparsing the AST and re-generating the list
162 // of all possible transformations and shuffling it again.
163
164 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
165 defer transformations.deinit();
166 try Walk.findTransformations(arena, &tree, &transformations);
167 sortTransformations(transformations.items, rng.random());
168
169 fresh: while (transformations.items.len > 0) {
170 std.debug.print("found {d} possible transformations\n", .{
171 transformations.items.len,
172 });
173 var subset_size: usize = transformations.items.len;
174 var start_index: usize = 0;
175
176 while (start_index < transformations.items.len) {
177 const prev_subset_size = subset_size;
178 subset_size = @max(1, subset_size * 3 / 4);
179 if (prev_subset_size > 1 and subset_size == 1)
180 start_index = 0;
181
182 const this_set = transformations.items[start_index..][0..subset_size];
183 std.debug.print("trying {d} random transformations: ", .{subset_size});
184 for (this_set[0..@min(this_set.len, 20)]) |t| {
185 std.debug.print("{s} ", .{@tagName(t)});
186 }
187 std.debug.print("\n", .{});
188 try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
189
190 rendered.clearRetainingCapacity();
191 try tree.renderToArrayList(&rendered, fixups);
192
193 // The transformations we applied may have resulted in unused locals,
194 // in which case we would like to add the respective discards.
195 {
196 try astgen_input.resize(rendered.items.len);
197 @memcpy(astgen_input.items, rendered.items);
198 try astgen_input.append(0);
199 const source_with_null = astgen_input.items[0 .. astgen_input.items.len - 1 :0];
200 var astgen_tree = try Ast.parse(gpa, source_with_null, .zig);
201 defer astgen_tree.deinit(gpa);
202 if (astgen_tree.errors.len != 0) {
203 @panic("syntax errors occurred");
204 }
205 var zir = try AstGen.generate(gpa, astgen_tree);
206 defer zir.deinit(gpa);
207
208 if (zir.hasCompileErrors()) {
209 more_fixups.clearRetainingCapacity();
210 const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)];
211 assert(payload_index != 0);
212 const header = zir.extraData(Zir.Inst.CompileErrors, payload_index);
213 var extra_index = header.end;
214 for (0..header.data.items_len) |_| {
215 const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
216 extra_index = item.end;
217 const msg = zir.nullTerminatedString(item.data.msg);
218 if (mem.eql(u8, msg, "unused local constant") or
219 mem.eql(u8, msg, "unused local variable") or
220 mem.eql(u8, msg, "unused function parameter") or
221 mem.eql(u8, msg, "unused capture"))
222 {
223 const ident_token = item.data.token;
224 try more_fixups.unused_var_decls.put(gpa, ident_token, {});
225 } else {
226 std.debug.print("found other ZIR error: '{s}'\n", .{msg});
227 }
228 }
229 if (more_fixups.count() != 0) {
230 rendered.clearRetainingCapacity();
231 try astgen_tree.renderToArrayList(&rendered, more_fixups);
232 }
233 }
234 }
235
236 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
237 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
238
239 const interestingness = try runCheck(arena, interestingness_argv.items);
240 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
241 subset_size, @tagName(interestingness), start_index, transformations.items.len,
242 });
243 switch (interestingness) {
244 .interesting => {
245 const new_tree = try parse(gpa, root_source_file_path);
246 gpa.free(tree.source);
247 tree.deinit(gpa);
248 tree = new_tree;
249
250 try Walk.findTransformations(arena, &tree, &transformations);
251 sortTransformations(transformations.items, rng.random());
252
253 continue :fresh;
254 },
255 .unknown, .boring => {
256 // Continue to try the next set of transformations.
257 // If we tested only one transformation, move on to the next one.
258 if (subset_size == 1) {
259 start_index += 1;
260 } else {
261 start_index += subset_size;
262 if (start_index + subset_size > transformations.items.len) {
263 start_index = 0;
264 }
265 }
266 },
267 }
268 }
269 std.debug.print("all {d} remaining transformations are uninteresting\n", .{
270 transformations.items.len,
271 });
272
273 // Revert the source back to not be transformed.
274 fixups.clearRetainingCapacity();
275 rendered.clearRetainingCapacity();
276 try tree.renderToArrayList(&rendered, fixups);
277 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
278
279 return std.process.cleanExit();
280 }
281 std.debug.print("no more transformations found\n", .{});
282 return std.process.cleanExit();
283}
284
285fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
286 rng.shuffle(Walk.Transformation, transformations);
287 // Stable sort based on priority to keep randomness as the secondary sort.
288 // TODO: introduce transformation priorities
289 // std.mem.sort(transformations);
290}
291
292fn termToInteresting(term: std.process.Child.Term) Interestingness {
293 return switch (term) {
294 .Exited => |code| switch (code) {
295 0 => .interesting,
296 1 => .unknown,
297 else => .boring,
298 },
299 else => b: {
300 std.debug.print("interestingness check aborted unexpectedly\n", .{});
301 break :b .boring;
302 },
303 };
304}
305
306fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
307 const result = try std.process.Child.run(.{
308 .allocator = arena,
309 .argv = argv,
310 });
311 if (result.stderr.len != 0)
312 std.debug.print("{s}", .{result.stderr});
313 return termToInteresting(result.term);
314}
315
316fn transformationsToFixups(
317 gpa: Allocator,
318 arena: Allocator,
319 root_source_file_path: []const u8,
320 transforms: []const Walk.Transformation,
321 fixups: *Ast.Fixups,
322) !void {
323 fixups.clearRetainingCapacity();
324
325 for (transforms) |t| switch (t) {
326 .gut_function => |fn_decl_node| {
327 try fixups.gut_functions.put(gpa, fn_decl_node, {});
328 },
329 .delete_node => |decl_node| {
330 try fixups.omit_nodes.put(gpa, decl_node, {});
331 },
332 .delete_var_decl => |delete_var_decl| {
333 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
334 for (delete_var_decl.references.items) |ident_node| {
335 try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
336 }
337 },
338 .replace_with_undef => |node| {
339 try fixups.replace_nodes_with_string.put(gpa, node, "undefined");
340 },
341 .replace_with_true => |node| {
342 try fixups.replace_nodes_with_string.put(gpa, node, "true");
343 },
344 .replace_with_false => |node| {
345 try fixups.replace_nodes_with_string.put(gpa, node, "false");
346 },
347 .replace_node => |r| {
348 try fixups.replace_nodes_with_node.put(gpa, r.to_replace, r.replacement);
349 },
350 .inline_imported_file => |inline_imported_file| {
351 const full_imported_path = try std.fs.path.join(gpa, &.{
352 std.fs.path.dirname(root_source_file_path) orelse ".",
353 inline_imported_file.imported_string,
354 });
355 defer gpa.free(full_imported_path);
356 var other_file_ast = try parse(gpa, full_imported_path);
357 defer {
358 gpa.free(other_file_ast.source);
359 other_file_ast.deinit(gpa);
360 }
361
362 var inlined_fixups: Ast.Fixups = .{};
363 defer inlined_fixups.deinit(gpa);
364 if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| {
365 inlined_fixups.rebase_imported_paths = dirname;
366 }
367 for (inline_imported_file.in_scope_names.keys()) |name| {
368 // This name needs to be mangled in order to not cause an
369 // ambiguous reference error.
370 var i: u32 = 2;
371 const mangled = while (true) : (i += 1) {
372 const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i });
373 if (!inline_imported_file.in_scope_names.contains(mangled))
374 break mangled;
375 gpa.free(mangled);
376 };
377 try inlined_fixups.rename_identifiers.put(gpa, name, mangled);
378 }
379 defer {
380 for (inlined_fixups.rename_identifiers.values()) |v| {
381 gpa.free(v);
382 }
383 }
384
385 var other_source = std.ArrayList(u8).init(gpa);
386 defer other_source.deinit();
387 try other_source.appendSlice("struct {\n");
388 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
389 try other_source.appendSlice("}");
390
391 try fixups.replace_nodes_with_string.put(
392 gpa,
393 inline_imported_file.builtin_call_node,
394 try arena.dupe(u8, other_source.items),
395 );
396 },
397 };
398}
399
400fn parse(gpa: Allocator, file_path: []const u8) !Ast {
401 const source_code = std.fs.cwd().readFileAllocOptions(
402 gpa,
403 file_path,
404 std.math.maxInt(u32),
405 null,
406 1,
407 0,
408 ) catch |err| {
409 fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
410 };
411 errdefer gpa.free(source_code);
412
413 var tree = try Ast.parse(gpa, source_code, .zig);
414 errdefer tree.deinit(gpa);
415
416 if (tree.errors.len != 0) {
417 @panic("syntax errors occurred");
418 }
419
420 return tree;
421}
422
423fn fatal(comptime format: []const u8, args: anytype) noreturn {
424 std.log.err(format, args);
425 std.process.exit(1);
426}
lib/compiler/reduce/Walk.zig created+1102
...@@ -0,0 +1,1102 @@
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5const BuiltinFn = std.zig.BuiltinFn;
6
7ast: *const Ast,
8transformations: *std.ArrayList(Transformation),
9unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
12gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
14
15pub const Transformation = union(enum) {
16 /// Replace the fn decl AST Node with one whose body is only `@trap()` with
17 /// discarded parameters.
18 gut_function: Ast.Node.Index,
19 /// Omit a global declaration.
20 delete_node: Ast.Node.Index,
21 /// Delete a local variable declaration and replace all of its references
22 /// with `undefined`.
23 delete_var_decl: struct {
24 var_decl_node: Ast.Node.Index,
25 /// Identifier nodes that reference the variable.
26 references: std.ArrayListUnmanaged(Ast.Node.Index),
27 },
28 /// Replace an expression with `undefined`.
29 replace_with_undef: Ast.Node.Index,
30 /// Replace an expression with `true`.
31 replace_with_true: Ast.Node.Index,
32 /// Replace an expression with `false`.
33 replace_with_false: Ast.Node.Index,
34 /// Replace a node with another node.
35 replace_node: struct {
36 to_replace: Ast.Node.Index,
37 replacement: Ast.Node.Index,
38 },
39 /// Replace an `@import` with the imported file contents wrapped in a struct.
40 inline_imported_file: InlineImportedFile,
41
42 pub const InlineImportedFile = struct {
43 builtin_call_node: Ast.Node.Index,
44 imported_string: []const u8,
45 /// Identifier names that must be renamed in the inlined code or else
46 /// will cause ambiguous reference errors.
47 in_scope_names: std.StringArrayHashMapUnmanaged(void),
48 };
49};
50
51pub const Error = error{OutOfMemory};
52
53/// The result will be priority shuffled.
54pub fn findTransformations(
55 arena: std.mem.Allocator,
56 ast: *const Ast,
57 transformations: *std.ArrayList(Transformation),
58) !void {
59 transformations.clearRetainingCapacity();
60
61 var walk: Walk = .{
62 .ast = ast,
63 .transformations = transformations,
64 .gpa = transformations.allocator,
65 .arena = arena,
66 .unreferenced_globals = .{},
67 .in_scope_names = .{},
68 .replace_names = .{},
69 };
70 defer {
71 walk.unreferenced_globals.deinit(walk.gpa);
72 walk.in_scope_names.deinit(walk.gpa);
73 walk.replace_names.deinit(walk.gpa);
74 }
75
76 try walkMembers(&walk, walk.ast.rootDecls());
77
78 const unreferenced_globals = walk.unreferenced_globals.values();
79 try transformations.ensureUnusedCapacity(unreferenced_globals.len);
80 for (unreferenced_globals) |node| {
81 transformations.appendAssumeCapacity(.{ .delete_node = node });
82 }
83}
84
85fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
86 // First we scan for globals so that we can delete them while walking.
87 try scanDecls(w, members, .add);
88
89 for (members) |member| {
90 try walkMember(w, member);
91 }
92
93 try scanDecls(w, members, .remove);
94}
95
96const ScanDeclsAction = enum { add, remove };
97
98fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
99 const ast = w.ast;
100 const gpa = w.gpa;
101 const node_tags = ast.nodes.items(.tag);
102 const main_tokens = ast.nodes.items(.main_token);
103 const token_tags = ast.tokens.items(.tag);
104
105 for (members) |member_node| {
106 const name_token = switch (node_tags[member_node]) {
107 .global_var_decl,
108 .local_var_decl,
109 .simple_var_decl,
110 .aligned_var_decl,
111 => main_tokens[member_node] + 1,
112
113 .fn_proto_simple,
114 .fn_proto_multi,
115 .fn_proto_one,
116 .fn_proto,
117 .fn_decl,
118 => main_tokens[member_node] + 1,
119
120 else => continue,
121 };
122
123 assert(token_tags[name_token] == .identifier);
124 const name_bytes = ast.tokenSlice(name_token);
125
126 switch (action) {
127 .add => {
128 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
129
130 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
131 if (!gop.found_existing) gop.value_ptr.* = 0;
132 gop.value_ptr.* += 1;
133 },
134 .remove => {
135 const entry = w.in_scope_names.getEntry(name_bytes).?;
136 if (entry.value_ptr.* <= 1) {
137 assert(w.in_scope_names.swapRemove(name_bytes));
138 } else {
139 entry.value_ptr.* -= 1;
140 }
141 },
142 }
143 }
144}
145
146fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
147 const ast = w.ast;
148 const datas = ast.nodes.items(.data);
149 switch (ast.nodes.items(.tag)[decl]) {
150 .fn_decl => {
151 const fn_proto = datas[decl].lhs;
152 try walkExpression(w, fn_proto);
153 const body_node = datas[decl].rhs;
154 if (!isFnBodyGutted(ast, body_node)) {
155 w.replace_names.clearRetainingCapacity();
156 try w.transformations.append(.{ .gut_function = decl });
157 try walkExpression(w, body_node);
158 }
159 },
160 .fn_proto_simple,
161 .fn_proto_multi,
162 .fn_proto_one,
163 .fn_proto,
164 => {
165 try walkExpression(w, decl);
166 },
167
168 .@"usingnamespace" => {
169 try w.transformations.append(.{ .delete_node = decl });
170 const expr = datas[decl].lhs;
171 try walkExpression(w, expr);
172 },
173
174 .global_var_decl,
175 .local_var_decl,
176 .simple_var_decl,
177 .aligned_var_decl,
178 => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
179
180 .test_decl => {
181 try w.transformations.append(.{ .delete_node = decl });
182 try walkExpression(w, datas[decl].rhs);
183 },
184
185 .container_field_init,
186 .container_field_align,
187 .container_field,
188 => {
189 try w.transformations.append(.{ .delete_node = decl });
190 try walkContainerField(w, ast.fullContainerField(decl).?);
191 },
192
193 .@"comptime" => {
194 try w.transformations.append(.{ .delete_node = decl });
195 try walkExpression(w, decl);
196 },
197
198 .root => unreachable,
199 else => unreachable,
200 }
201}
202
203fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
204 const ast = w.ast;
205 const token_tags = ast.tokens.items(.tag);
206 const main_tokens = ast.nodes.items(.main_token);
207 const node_tags = ast.nodes.items(.tag);
208 const datas = ast.nodes.items(.data);
209 switch (node_tags[node]) {
210 .identifier => {
211 const name_ident = main_tokens[node];
212 assert(token_tags[name_ident] == .identifier);
213 const name_bytes = ast.tokenSlice(name_ident);
214 _ = w.unreferenced_globals.swapRemove(name_bytes);
215 if (w.replace_names.get(name_bytes)) |index| {
216 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
217 }
218 },
219
220 .number_literal,
221 .char_literal,
222 .unreachable_literal,
223 .anyframe_literal,
224 .string_literal,
225 => {},
226
227 .multiline_string_literal => {},
228
229 .error_value => {},
230
231 .block_two,
232 .block_two_semicolon,
233 => {
234 const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
235 if (datas[node].lhs == 0) {
236 return walkBlock(w, node, statements[0..0]);
237 } else if (datas[node].rhs == 0) {
238 return walkBlock(w, node, statements[0..1]);
239 } else {
240 return walkBlock(w, node, statements[0..2]);
241 }
242 },
243 .block,
244 .block_semicolon,
245 => {
246 const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
247 return walkBlock(w, node, statements);
248 },
249
250 .@"errdefer" => {
251 const expr = datas[node].rhs;
252 return walkExpression(w, expr);
253 },
254
255 .@"defer" => {
256 const expr = datas[node].rhs;
257 return walkExpression(w, expr);
258 },
259 .@"comptime", .@"nosuspend" => {
260 const block = datas[node].lhs;
261 return walkExpression(w, block);
262 },
263
264 .@"suspend" => {
265 const body = datas[node].lhs;
266 return walkExpression(w, body);
267 },
268
269 .@"catch" => {
270 try walkExpression(w, datas[node].lhs); // target
271 try walkExpression(w, datas[node].rhs); // fallback
272 },
273
274 .field_access => {
275 const field_access = datas[node];
276 try walkExpression(w, field_access.lhs);
277 },
278
279 .error_union,
280 .switch_range,
281 => {
282 const infix = datas[node];
283 try walkExpression(w, infix.lhs);
284 return walkExpression(w, infix.rhs);
285 },
286 .for_range => {
287 const infix = datas[node];
288 try walkExpression(w, infix.lhs);
289 if (infix.rhs != 0) {
290 return walkExpression(w, infix.rhs);
291 }
292 },
293
294 .add,
295 .add_wrap,
296 .add_sat,
297 .array_cat,
298 .array_mult,
299 .assign,
300 .assign_bit_and,
301 .assign_bit_or,
302 .assign_shl,
303 .assign_shl_sat,
304 .assign_shr,
305 .assign_bit_xor,
306 .assign_div,
307 .assign_sub,
308 .assign_sub_wrap,
309 .assign_sub_sat,
310 .assign_mod,
311 .assign_add,
312 .assign_add_wrap,
313 .assign_add_sat,
314 .assign_mul,
315 .assign_mul_wrap,
316 .assign_mul_sat,
317 .bang_equal,
318 .bit_and,
319 .bit_or,
320 .shl,
321 .shl_sat,
322 .shr,
323 .bit_xor,
324 .bool_and,
325 .bool_or,
326 .div,
327 .equal_equal,
328 .greater_or_equal,
329 .greater_than,
330 .less_or_equal,
331 .less_than,
332 .merge_error_sets,
333 .mod,
334 .mul,
335 .mul_wrap,
336 .mul_sat,
337 .sub,
338 .sub_wrap,
339 .sub_sat,
340 .@"orelse",
341 => {
342 const infix = datas[node];
343 try walkExpression(w, infix.lhs);
344 try walkExpression(w, infix.rhs);
345 },
346
347 .assign_destructure => {
348 const lhs_count = ast.extra_data[datas[node].lhs];
349 assert(lhs_count > 1);
350 const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
351 const rhs = datas[node].rhs;
352
353 for (lhs_exprs) |lhs_node| {
354 switch (node_tags[lhs_node]) {
355 .global_var_decl,
356 .local_var_decl,
357 .simple_var_decl,
358 .aligned_var_decl,
359 => try walkLocalVarDecl(w, ast.fullVarDecl(lhs_node).?),
360
361 else => try walkExpression(w, lhs_node),
362 }
363 }
364 return walkExpression(w, rhs);
365 },
366
367 .bit_not,
368 .bool_not,
369 .negation,
370 .negation_wrap,
371 .optional_type,
372 .address_of,
373 => {
374 return walkExpression(w, datas[node].lhs);
375 },
376
377 .@"try",
378 .@"resume",
379 .@"await",
380 => {
381 return walkExpression(w, datas[node].lhs);
382 },
383
384 .array_type,
385 .array_type_sentinel,
386 => {},
387
388 .ptr_type_aligned,
389 .ptr_type_sentinel,
390 .ptr_type,
391 .ptr_type_bit_range,
392 => {},
393
394 .array_init_one,
395 .array_init_one_comma,
396 .array_init_dot_two,
397 .array_init_dot_two_comma,
398 .array_init_dot,
399 .array_init_dot_comma,
400 .array_init,
401 .array_init_comma,
402 => {
403 var elements: [2]Ast.Node.Index = undefined;
404 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
405 },
406
407 .struct_init_one,
408 .struct_init_one_comma,
409 .struct_init_dot_two,
410 .struct_init_dot_two_comma,
411 .struct_init_dot,
412 .struct_init_dot_comma,
413 .struct_init,
414 .struct_init_comma,
415 => {
416 var buf: [2]Ast.Node.Index = undefined;
417 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
418 },
419
420 .call_one,
421 .call_one_comma,
422 .async_call_one,
423 .async_call_one_comma,
424 .call,
425 .call_comma,
426 .async_call,
427 .async_call_comma,
428 => {
429 var buf: [1]Ast.Node.Index = undefined;
430 return walkCall(w, ast.fullCall(&buf, node).?);
431 },
432
433 .array_access => {
434 const suffix = datas[node];
435 try walkExpression(w, suffix.lhs);
436 try walkExpression(w, suffix.rhs);
437 },
438
439 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
440
441 .deref => {
442 try walkExpression(w, datas[node].lhs);
443 },
444
445 .unwrap_optional => {
446 try walkExpression(w, datas[node].lhs);
447 },
448
449 .@"break" => {
450 const label_token = datas[node].lhs;
451 const target = datas[node].rhs;
452 if (label_token == 0 and target == 0) {
453 // no expressions
454 } else if (label_token == 0 and target != 0) {
455 try walkExpression(w, target);
456 } else if (label_token != 0 and target == 0) {
457 try walkIdentifier(w, label_token);
458 } else if (label_token != 0 and target != 0) {
459 try walkExpression(w, target);
460 }
461 },
462
463 .@"continue" => {
464 const label = datas[node].lhs;
465 if (label != 0) {
466 return walkIdentifier(w, label); // label
467 }
468 },
469
470 .@"return" => {
471 if (datas[node].lhs != 0) {
472 try walkExpression(w, datas[node].lhs);
473 }
474 },
475
476 .grouped_expression => {
477 try walkExpression(w, datas[node].lhs);
478 },
479
480 .container_decl,
481 .container_decl_trailing,
482 .container_decl_arg,
483 .container_decl_arg_trailing,
484 .container_decl_two,
485 .container_decl_two_trailing,
486 .tagged_union,
487 .tagged_union_trailing,
488 .tagged_union_enum_tag,
489 .tagged_union_enum_tag_trailing,
490 .tagged_union_two,
491 .tagged_union_two_trailing,
492 => {
493 var buf: [2]Ast.Node.Index = undefined;
494 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
495 },
496
497 .error_set_decl => {
498 const error_token = main_tokens[node];
499 const lbrace = error_token + 1;
500 const rbrace = datas[node].rhs;
501
502 var i = lbrace + 1;
503 while (i < rbrace) : (i += 1) {
504 switch (token_tags[i]) {
505 .doc_comment => unreachable, // TODO
506 .identifier => try walkIdentifier(w, i),
507 .comma => {},
508 else => unreachable,
509 }
510 }
511 },
512
513 .builtin_call_two, .builtin_call_two_comma => {
514 if (datas[node].lhs == 0) {
515 return walkBuiltinCall(w, node, &.{});
516 } else if (datas[node].rhs == 0) {
517 return walkBuiltinCall(w, node, &.{datas[node].lhs});
518 } else {
519 return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
520 }
521 },
522 .builtin_call, .builtin_call_comma => {
523 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
524 return walkBuiltinCall(w, node, params);
525 },
526
527 .fn_proto_simple,
528 .fn_proto_multi,
529 .fn_proto_one,
530 .fn_proto,
531 => {
532 var buf: [1]Ast.Node.Index = undefined;
533 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
534 },
535
536 .anyframe_type => {
537 if (datas[node].rhs != 0) {
538 return walkExpression(w, datas[node].rhs);
539 }
540 },
541
542 .@"switch",
543 .switch_comma,
544 => {
545 const condition = datas[node].lhs;
546 const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
547 const cases = ast.extra_data[extra.start..extra.end];
548
549 try walkExpression(w, condition); // condition expression
550 try walkExpressions(w, cases);
551 },
552
553 .switch_case_one,
554 .switch_case_inline_one,
555 .switch_case,
556 .switch_case_inline,
557 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
558
559 .while_simple,
560 .while_cont,
561 .@"while",
562 => return walkWhile(w, node, ast.fullWhile(node).?),
563
564 .for_simple,
565 .@"for",
566 => return walkFor(w, ast.fullFor(node).?),
567
568 .if_simple,
569 .@"if",
570 => return walkIf(w, node, ast.fullIf(node).?),
571
572 .asm_simple,
573 .@"asm",
574 => return walkAsm(w, ast.fullAsm(node).?),
575
576 .enum_literal => {
577 return walkIdentifier(w, main_tokens[node]); // name
578 },
579
580 .fn_decl => unreachable,
581 .container_field => unreachable,
582 .container_field_init => unreachable,
583 .container_field_align => unreachable,
584 .root => unreachable,
585 .global_var_decl => unreachable,
586 .local_var_decl => unreachable,
587 .simple_var_decl => unreachable,
588 .aligned_var_decl => unreachable,
589 .@"usingnamespace" => unreachable,
590 .test_decl => unreachable,
591 .asm_output => unreachable,
592 .asm_input => unreachable,
593 }
594}
595
596fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
597 _ = decl_node;
598
599 if (var_decl.ast.type_node != 0) {
600 try walkExpression(w, var_decl.ast.type_node);
601 }
602
603 if (var_decl.ast.align_node != 0) {
604 try walkExpression(w, var_decl.ast.align_node);
605 }
606
607 if (var_decl.ast.addrspace_node != 0) {
608 try walkExpression(w, var_decl.ast.addrspace_node);
609 }
610
611 if (var_decl.ast.section_node != 0) {
612 try walkExpression(w, var_decl.ast.section_node);
613 }
614
615 if (var_decl.ast.init_node != 0) {
616 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
617 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
618 }
619 try walkExpression(w, var_decl.ast.init_node);
620 }
621}
622
623fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
624 try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
625
626 if (var_decl.ast.type_node != 0) {
627 try walkExpression(w, var_decl.ast.type_node);
628 }
629
630 if (var_decl.ast.align_node != 0) {
631 try walkExpression(w, var_decl.ast.align_node);
632 }
633
634 if (var_decl.ast.addrspace_node != 0) {
635 try walkExpression(w, var_decl.ast.addrspace_node);
636 }
637
638 if (var_decl.ast.section_node != 0) {
639 try walkExpression(w, var_decl.ast.section_node);
640 }
641
642 if (var_decl.ast.init_node != 0) {
643 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
644 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
645 }
646 try walkExpression(w, var_decl.ast.init_node);
647 }
648}
649
650fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
651 if (field.ast.type_expr != 0) {
652 try walkExpression(w, field.ast.type_expr); // type
653 }
654 if (field.ast.align_expr != 0) {
655 try walkExpression(w, field.ast.align_expr); // alignment
656 }
657 if (field.ast.value_expr != 0) {
658 try walkExpression(w, field.ast.value_expr); // value
659 }
660}
661
662fn walkBlock(
663 w: *Walk,
664 block_node: Ast.Node.Index,
665 statements: []const Ast.Node.Index,
666) Error!void {
667 _ = block_node;
668 const ast = w.ast;
669 const node_tags = ast.nodes.items(.tag);
670
671 for (statements) |stmt| {
672 switch (node_tags[stmt]) {
673 .global_var_decl,
674 .local_var_decl,
675 .simple_var_decl,
676 .aligned_var_decl,
677 => {
678 const var_decl = ast.fullVarDecl(stmt).?;
679 if (var_decl.ast.init_node != 0 and
680 isUndefinedIdent(w.ast, var_decl.ast.init_node))
681 {
682 try w.transformations.append(.{ .delete_var_decl = .{
683 .var_decl_node = stmt,
684 .references = .{},
685 } });
686 const name_tok = var_decl.ast.mut_token + 1;
687 const name_bytes = ast.tokenSlice(name_tok);
688 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
689 } else {
690 try walkLocalVarDecl(w, var_decl);
691 }
692 },
693
694 else => {
695 switch (categorizeStmt(ast, stmt)) {
696 // Don't try to remove `_ = foo;` discards; those are handled separately.
697 .discard_identifier => {},
698 // definitely try to remove `_ = undefined;` though.
699 .discard_undefined, .trap_call, .other => {
700 try w.transformations.append(.{ .delete_node = stmt });
701 },
702 }
703 try walkExpression(w, stmt);
704 },
705 }
706 }
707}
708
709fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
710 try walkExpression(w, array_type.ast.elem_count);
711 if (array_type.ast.sentinel != 0) {
712 try walkExpression(w, array_type.ast.sentinel);
713 }
714 return walkExpression(w, array_type.ast.elem_type);
715}
716
717fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
718 if (array_init.ast.type_expr != 0) {
719 try walkExpression(w, array_init.ast.type_expr); // T
720 }
721 for (array_init.ast.elements) |elem_init| {
722 try walkExpression(w, elem_init);
723 }
724}
725
726fn walkStructInit(
727 w: *Walk,
728 struct_node: Ast.Node.Index,
729 struct_init: Ast.full.StructInit,
730) Error!void {
731 _ = struct_node;
732 if (struct_init.ast.type_expr != 0) {
733 try walkExpression(w, struct_init.ast.type_expr); // T
734 }
735 for (struct_init.ast.fields) |field_init| {
736 try walkExpression(w, field_init);
737 }
738}
739
740fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
741 try walkExpression(w, call.ast.fn_expr);
742 try walkParamList(w, call.ast.params);
743}
744
745fn walkSlice(
746 w: *Walk,
747 slice_node: Ast.Node.Index,
748 slice: Ast.full.Slice,
749) Error!void {
750 _ = slice_node;
751 try walkExpression(w, slice.ast.sliced);
752 try walkExpression(w, slice.ast.start);
753 if (slice.ast.end != 0) {
754 try walkExpression(w, slice.ast.end);
755 }
756 if (slice.ast.sentinel != 0) {
757 try walkExpression(w, slice.ast.sentinel);
758 }
759}
760
761fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
762 const ast = w.ast;
763 const token_tags = ast.tokens.items(.tag);
764 assert(token_tags[name_ident] == .identifier);
765 const name_bytes = ast.tokenSlice(name_ident);
766 _ = w.unreferenced_globals.swapRemove(name_bytes);
767}
768
769fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
770 _ = w;
771 _ = name_ident;
772}
773
774fn walkContainerDecl(
775 w: *Walk,
776 container_decl_node: Ast.Node.Index,
777 container_decl: Ast.full.ContainerDecl,
778) Error!void {
779 _ = container_decl_node;
780 if (container_decl.ast.arg != 0) {
781 try walkExpression(w, container_decl.ast.arg);
782 }
783 try walkMembers(w, container_decl.ast.members);
784}
785
786fn walkBuiltinCall(
787 w: *Walk,
788 call_node: Ast.Node.Index,
789 params: []const Ast.Node.Index,
790) Error!void {
791 const ast = w.ast;
792 const main_tokens = ast.nodes.items(.main_token);
793 const builtin_token = main_tokens[call_node];
794 const builtin_name = ast.tokenSlice(builtin_token);
795 const info = BuiltinFn.list.get(builtin_name).?;
796 switch (info.tag) {
797 .import => {
798 const operand_node = params[0];
799 const str_lit_token = main_tokens[operand_node];
800 const token_bytes = ast.tokenSlice(str_lit_token);
801 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
802 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
803 unreachable;
804 try w.transformations.append(.{ .inline_imported_file = .{
805 .builtin_call_node = call_node,
806 .imported_string = imported_string,
807 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
808 w.arena,
809 w.in_scope_names.keys(),
810 &.{},
811 ),
812 } });
813 }
814 },
815 else => {},
816 }
817 for (params) |param_node| {
818 try walkExpression(w, param_node);
819 }
820}
821
822fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
823 const ast = w.ast;
824
825 {
826 var it = fn_proto.iterate(ast);
827 while (it.next()) |param| {
828 if (param.type_expr != 0) {
829 try walkExpression(w, param.type_expr);
830 }
831 }
832 }
833
834 if (fn_proto.ast.align_expr != 0) {
835 try walkExpression(w, fn_proto.ast.align_expr);
836 }
837
838 if (fn_proto.ast.addrspace_expr != 0) {
839 try walkExpression(w, fn_proto.ast.addrspace_expr);
840 }
841
842 if (fn_proto.ast.section_expr != 0) {
843 try walkExpression(w, fn_proto.ast.section_expr);
844 }
845
846 if (fn_proto.ast.callconv_expr != 0) {
847 try walkExpression(w, fn_proto.ast.callconv_expr);
848 }
849
850 try walkExpression(w, fn_proto.ast.return_type);
851}
852
853fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
854 for (expressions) |expression| {
855 try walkExpression(w, expression);
856 }
857}
858
859fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
860 for (switch_case.ast.values) |value_expr| {
861 try walkExpression(w, value_expr);
862 }
863 try walkExpression(w, switch_case.ast.target_expr);
864}
865
866fn walkWhile(w: *Walk, node_index: Ast.Node.Index, while_node: Ast.full.While) Error!void {
867 assert(while_node.ast.cond_expr != 0);
868 assert(while_node.ast.then_expr != 0);
869
870 // Perform these transformations in this priority order:
871 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
872 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
873 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
874 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
875 if (!isTrueIdent(w.ast, while_node.ast.cond_expr) and
876 (while_node.ast.else_expr == 0 or isEmptyBlock(w.ast, while_node.ast.else_expr)))
877 {
878 try w.transformations.ensureUnusedCapacity(1);
879 w.transformations.appendAssumeCapacity(.{ .replace_with_true = while_node.ast.cond_expr });
880 } else if (!isFalseIdent(w.ast, while_node.ast.cond_expr) and isEmptyBlock(w.ast, while_node.ast.then_expr)) {
881 try w.transformations.ensureUnusedCapacity(1);
882 w.transformations.appendAssumeCapacity(.{ .replace_with_false = while_node.ast.cond_expr });
883 } else if (isTrueIdent(w.ast, while_node.ast.cond_expr)) {
884 try w.transformations.ensureUnusedCapacity(1);
885 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
886 .to_replace = node_index,
887 .replacement = while_node.ast.then_expr,
888 } });
889 } else if (isFalseIdent(w.ast, while_node.ast.cond_expr)) {
890 try w.transformations.ensureUnusedCapacity(1);
891 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
892 .to_replace = node_index,
893 .replacement = while_node.ast.else_expr,
894 } });
895 }
896
897 try walkExpression(w, while_node.ast.cond_expr); // condition
898
899 if (while_node.ast.cont_expr != 0) {
900 try walkExpression(w, while_node.ast.cont_expr);
901 }
902
903 if (while_node.ast.then_expr != 0) {
904 try walkExpression(w, while_node.ast.then_expr);
905 }
906 if (while_node.ast.else_expr != 0) {
907 try walkExpression(w, while_node.ast.else_expr);
908 }
909}
910
911fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
912 try walkParamList(w, for_node.ast.inputs);
913 if (for_node.ast.then_expr != 0) {
914 try walkExpression(w, for_node.ast.then_expr);
915 }
916 if (for_node.ast.else_expr != 0) {
917 try walkExpression(w, for_node.ast.else_expr);
918 }
919}
920
921fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
922 assert(if_node.ast.cond_expr != 0);
923 assert(if_node.ast.then_expr != 0);
924
925 // Perform these transformations in this priority order:
926 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
927 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
928 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
929 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
930 if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
931 (if_node.ast.else_expr == 0 or isEmptyBlock(w.ast, if_node.ast.else_expr)))
932 {
933 try w.transformations.ensureUnusedCapacity(1);
934 w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
935 } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
936 try w.transformations.ensureUnusedCapacity(1);
937 w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
938 } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
939 try w.transformations.ensureUnusedCapacity(1);
940 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
941 .to_replace = node_index,
942 .replacement = if_node.ast.then_expr,
943 } });
944 } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
945 try w.transformations.ensureUnusedCapacity(1);
946 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
947 .to_replace = node_index,
948 .replacement = if_node.ast.else_expr,
949 } });
950 }
951
952 try walkExpression(w, if_node.ast.cond_expr); // condition
953
954 if (if_node.ast.then_expr != 0) {
955 try walkExpression(w, if_node.ast.then_expr);
956 }
957 if (if_node.ast.else_expr != 0) {
958 try walkExpression(w, if_node.ast.else_expr);
959 }
960}
961
962fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
963 try walkExpression(w, asm_node.ast.template);
964 for (asm_node.ast.items) |item| {
965 try walkExpression(w, item);
966 }
967}
968
969fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
970 for (params) |param_node| {
971 try walkExpression(w, param_node);
972 }
973}
974
975/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
976fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
977 // skip over discards
978 const node_tags = ast.nodes.items(.tag);
979 const datas = ast.nodes.items(.data);
980 var statements_buf: [2]Ast.Node.Index = undefined;
981 const statements = switch (node_tags[body_node]) {
982 .block_two,
983 .block_two_semicolon,
984 => blk: {
985 statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
986 break :blk if (datas[body_node].lhs == 0)
987 statements_buf[0..0]
988 else if (datas[body_node].rhs == 0)
989 statements_buf[0..1]
990 else
991 statements_buf[0..2];
992 },
993
994 .block,
995 .block_semicolon,
996 => ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
997
998 else => return false,
999 };
1000 var i: usize = 0;
1001 while (i < statements.len) : (i += 1) {
1002 switch (categorizeStmt(ast, statements[i])) {
1003 .discard_identifier => continue,
1004 .trap_call => return i + 1 == statements.len,
1005 else => return false,
1006 }
1007 }
1008 return false;
1009}
1010
1011const StmtCategory = enum {
1012 discard_undefined,
1013 discard_identifier,
1014 trap_call,
1015 other,
1016};
1017
1018fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
1019 const node_tags = ast.nodes.items(.tag);
1020 const datas = ast.nodes.items(.data);
1021 const main_tokens = ast.nodes.items(.main_token);
1022 switch (node_tags[stmt]) {
1023 .builtin_call_two, .builtin_call_two_comma => {
1024 if (datas[stmt].lhs == 0) {
1025 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
1026 } else if (datas[stmt].rhs == 0) {
1027 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
1028 } else {
1029 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
1030 }
1031 },
1032 .builtin_call, .builtin_call_comma => {
1033 const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
1034 return categorizeBuiltinCall(ast, main_tokens[stmt], params);
1035 },
1036 .assign => {
1037 const infix = datas[stmt];
1038 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) {
1039 const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]);
1040 if (std.mem.eql(u8, name_bytes, "undefined")) {
1041 return .discard_undefined;
1042 } else {
1043 return .discard_identifier;
1044 }
1045 }
1046 return .other;
1047 },
1048 else => return .other,
1049 }
1050}
1051
1052fn categorizeBuiltinCall(
1053 ast: *const Ast,
1054 builtin_token: Ast.TokenIndex,
1055 params: []const Ast.Node.Index,
1056) StmtCategory {
1057 if (params.len != 0) return .other;
1058 const name_bytes = ast.tokenSlice(builtin_token);
1059 if (std.mem.eql(u8, name_bytes, "@trap"))
1060 return .trap_call;
1061 return .other;
1062}
1063
1064fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1065 return isMatchingIdent(ast, node, "_");
1066}
1067
1068fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1069 return isMatchingIdent(ast, node, "undefined");
1070}
1071
1072fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1073 return isMatchingIdent(ast, node, "true");
1074}
1075
1076fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1077 return isMatchingIdent(ast, node, "false");
1078}
1079
1080fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
1081 const node_tags = ast.nodes.items(.tag);
1082 const main_tokens = ast.nodes.items(.main_token);
1083 switch (node_tags[node]) {
1084 .identifier => {
1085 const token_index = main_tokens[node];
1086 const name_bytes = ast.tokenSlice(token_index);
1087 return std.mem.eql(u8, name_bytes, string);
1088 },
1089 else => return false,
1090 }
1091}
1092
1093fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
1094 const node_tags = ast.nodes.items(.tag);
1095 const node_data = ast.nodes.items(.data);
1096 switch (node_tags[node]) {
1097 .block_two => {
1098 return node_data[node].lhs == 0 and node_data[node].rhs == 0;
1099 },
1100 else => return false,
1101 }
1102}
lib/compiler/test_runner.zig created+249
...@@ -0,0 +1,249 @@
1//! Default test runner for unit tests.
2const std = @import("std");
3const io = std.io;
4const builtin = @import("builtin");
5
6pub const std_options = .{
7 .logFn = log,
8};
9
10var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
13
14pub fn main() void {
15 if (builtin.zig_backend == .stage2_aarch64) {
16 return mainSimple() catch @panic("test failure");
17 }
18
19 const args = std.process.argsAlloc(fba.allocator()) catch
20 @panic("unable to parse command line args");
21
22 var listen = false;
23
24 for (args[1..]) |arg| {
25 if (std.mem.eql(u8, arg, "--listen=-")) {
26 listen = true;
27 } else {
28 @panic("unrecognized command line argument");
29 }
30 }
31
32 if (listen) {
33 return mainServer() catch @panic("internal test runner failure");
34 } else {
35 return mainTerminal();
36 }
37}
38
39fn mainServer() !void {
40 var server = try std.zig.Server.init(.{
41 .gpa = fba.allocator(),
42 .in = std.io.getStdIn(),
43 .out = std.io.getStdOut(),
44 .zig_version = builtin.zig_version_string,
45 });
46 defer server.deinit();
47
48 while (true) {
49 const hdr = try server.receiveMessage();
50 switch (hdr.tag) {
51 .exit => {
52 return std.process.exit(0);
53 },
54 .query_test_metadata => {
55 std.testing.allocator_instance = .{};
56 defer if (std.testing.allocator_instance.deinit() == .leak) {
57 @panic("internal test runner memory leak");
58 };
59
60 var string_bytes: std.ArrayListUnmanaged(u8) = .{};
61 defer string_bytes.deinit(std.testing.allocator);
62 try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
63
64 const test_fns = builtin.test_functions;
65 const names = try std.testing.allocator.alloc(u32, test_fns.len);
66 defer std.testing.allocator.free(names);
67 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
68 defer std.testing.allocator.free(expected_panic_msgs);
69
70 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
71 name.* = @as(u32, @intCast(string_bytes.items.len));
72 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
73 string_bytes.appendSliceAssumeCapacity(test_fn.name);
74 string_bytes.appendAssumeCapacity(0);
75 expected_panic_msg.* = 0;
76 }
77
78 try server.serveTestMetadata(.{
79 .names = names,
80 .expected_panic_msgs = expected_panic_msgs,
81 .string_bytes = string_bytes.items,
82 });
83 },
84
85 .run_test => {
86 std.testing.allocator_instance = .{};
87 log_err_count = 0;
88 const index = try server.receiveBody_u32();
89 const test_fn = builtin.test_functions[index];
90 var fail = false;
91 var skip = false;
92 var leak = false;
93 test_fn.func() catch |err| switch (err) {
94 error.SkipZigTest => skip = true,
95 else => {
96 fail = true;
97 if (@errorReturnTrace()) |trace| {
98 std.debug.dumpStackTrace(trace.*);
99 }
100 },
101 };
102 leak = std.testing.allocator_instance.deinit() == .leak;
103 try server.serveTestResults(.{
104 .index = index,
105 .flags = .{
106 .fail = fail,
107 .skip = skip,
108 .leak = leak,
109 .log_err_count = std.math.lossyCast(std.meta.FieldType(
110 std.zig.Server.Message.TestResults.Flags,
111 .log_err_count,
112 ), log_err_count),
113 },
114 });
115 },
116
117 else => {
118 std.debug.print("unsupported message: {x}", .{@intFromEnum(hdr.tag)});
119 std.process.exit(1);
120 },
121 }
122 }
123}
124
125fn mainTerminal() void {
126 const test_fn_list = builtin.test_functions;
127 var ok_count: usize = 0;
128 var skip_count: usize = 0;
129 var fail_count: usize = 0;
130 var progress = std.Progress{
131 .dont_print_on_dumb = true,
132 };
133 const root_node = progress.start("Test", test_fn_list.len);
134 const have_tty = progress.terminal != null and
135 (progress.supports_ansi_escape_codes or progress.is_windows_terminal);
136
137 var async_frame_buffer: []align(builtin.target.stackAlignment()) u8 = undefined;
138 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
139 // ignores the alignment of the slice.
140 async_frame_buffer = &[_]u8{};
141
142 var leaks: usize = 0;
143 for (test_fn_list, 0..) |test_fn, i| {
144 std.testing.allocator_instance = .{};
145 defer {
146 if (std.testing.allocator_instance.deinit() == .leak) {
147 leaks += 1;
148 }
149 }
150 std.testing.log_level = .warn;
151
152 var test_node = root_node.start(test_fn.name, 0);
153 test_node.activate();
154 progress.refresh();
155 if (!have_tty) {
156 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
157 }
158 if (test_fn.func()) |_| {
159 ok_count += 1;
160 test_node.end();
161 if (!have_tty) std.debug.print("OK\n", .{});
162 } else |err| switch (err) {
163 error.SkipZigTest => {
164 skip_count += 1;
165 progress.log("SKIP\n", .{});
166 test_node.end();
167 },
168 else => {
169 fail_count += 1;
170 progress.log("FAIL ({s})\n", .{@errorName(err)});
171 if (@errorReturnTrace()) |trace| {
172 std.debug.dumpStackTrace(trace.*);
173 }
174 test_node.end();
175 },
176 }
177 }
178 root_node.end();
179 if (ok_count == test_fn_list.len) {
180 std.debug.print("All {d} tests passed.\n", .{ok_count});
181 } else {
182 std.debug.print("{d} passed; {d} skipped; {d} failed.\n", .{ ok_count, skip_count, fail_count });
183 }
184 if (log_err_count != 0) {
185 std.debug.print("{d} errors were logged.\n", .{log_err_count});
186 }
187 if (leaks != 0) {
188 std.debug.print("{d} tests leaked memory.\n", .{leaks});
189 }
190 if (leaks != 0 or log_err_count != 0 or fail_count != 0) {
191 std.process.exit(1);
192 }
193}
194
195pub fn log(
196 comptime message_level: std.log.Level,
197 comptime scope: @Type(.EnumLiteral),
198 comptime format: []const u8,
199 args: anytype,
200) void {
201 if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {
202 log_err_count +|= 1;
203 }
204 if (@intFromEnum(message_level) <= @intFromEnum(std.testing.log_level)) {
205 std.debug.print(
206 "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",
207 args,
208 );
209 }
210}
211
212/// Simpler main(), exercising fewer language features, so that
213/// work-in-progress backends can handle it.
214pub fn mainSimple() anyerror!void {
215 const enable_print = false;
216 const print_all = false;
217
218 var passed: u64 = 0;
219 var skipped: u64 = 0;
220 var failed: u64 = 0;
221 const stderr = if (enable_print) std.io.getStdErr() else {};
222 for (builtin.test_functions) |test_fn| {
223 if (enable_print and print_all) {
224 stderr.writeAll(test_fn.name) catch {};
225 stderr.writeAll("... ") catch {};
226 }
227 test_fn.func() catch |err| {
228 if (enable_print and !print_all) {
229 stderr.writeAll(test_fn.name) catch {};
230 stderr.writeAll("... ") catch {};
231 }
232 if (err != error.SkipZigTest) {
233 if (enable_print) stderr.writeAll("FAIL\n") catch {};
234 failed += 1;
235 if (!enable_print) return err;
236 continue;
237 }
238 if (enable_print) stderr.writeAll("SKIP\n") catch {};
239 skipped += 1;
240 continue;
241 };
242 if (enable_print and print_all) stderr.writeAll("PASS\n") catch {};
243 passed += 1;
244 }
245 if (enable_print) {
246 stderr.writer().print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
247 if (failed != 0) std.process.exit(1);
248 }
249}
lib/std/zig/fmt.zig deleted-342
...@@ -1,342 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const fs = std.fs;
4const process = std.process;
5const Allocator = std.mem.Allocator;
6const warn = std.log.warn;
7const Color = std.zig.Color;
8
9const usage_fmt =
10 \\Usage: zig fmt [file]...
11 \\
12 \\ Formats the input files and modifies them in-place.
13 \\ Arguments can be files or directories, which are searched
14 \\ recursively.
15 \\
16 \\Options:
17 \\ -h, --help Print this help and exit
18 \\ --color [auto|off|on] Enable or disable colored error messages
19 \\ --stdin Format code from stdin; output to stdout
20 \\ --check List non-conforming files and exit with an error
21 \\ if the list is non-empty
22 \\ --ast-check Run zig ast-check on every file
23 \\ --exclude [file] Exclude file or directory from formatting
24 \\
25 \\
26;
27
28const Fmt = struct {
29 seen: SeenMap,
30 any_error: bool,
31 check_ast: bool,
32 color: Color,
33 gpa: Allocator,
34 arena: Allocator,
35 out_buffer: std.ArrayList(u8),
36
37 const SeenMap = std.AutoHashMap(fs.File.INode, void);
38};
39
40pub fn main() !void {
41 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
42 defer arena_instance.deinit();
43 const arena = arena_instance.allocator();
44 const gpa = arena;
45
46 const args = try process.argsAlloc(arena);
47
48 var color: Color = .auto;
49 var stdin_flag: bool = false;
50 var check_flag: bool = false;
51 var check_ast_flag: bool = false;
52 var input_files = std.ArrayList([]const u8).init(gpa);
53 defer input_files.deinit();
54 var excluded_files = std.ArrayList([]const u8).init(gpa);
55 defer excluded_files.deinit();
56
57 {
58 var i: usize = 1;
59 while (i < args.len) : (i += 1) {
60 const arg = args[i];
61 if (mem.startsWith(u8, arg, "-")) {
62 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
63 const stdout = std.io.getStdOut().writer();
64 try stdout.writeAll(usage_fmt);
65 return process.cleanExit();
66 } else if (mem.eql(u8, arg, "--color")) {
67 if (i + 1 >= args.len) {
68 fatal("expected [auto|on|off] after --color", .{});
69 }
70 i += 1;
71 const next_arg = args[i];
72 color = std.meta.stringToEnum(Color, next_arg) orelse {
73 fatal("expected [auto|on|off] after --color, found '{s}'", .{next_arg});
74 };
75 } else if (mem.eql(u8, arg, "--stdin")) {
76 stdin_flag = true;
77 } else if (mem.eql(u8, arg, "--check")) {
78 check_flag = true;
79 } else if (mem.eql(u8, arg, "--ast-check")) {
80 check_ast_flag = true;
81 } else if (mem.eql(u8, arg, "--exclude")) {
82 if (i + 1 >= args.len) {
83 fatal("expected parameter after --exclude", .{});
84 }
85 i += 1;
86 const next_arg = args[i];
87 try excluded_files.append(next_arg);
88 } else {
89 fatal("unrecognized parameter: '{s}'", .{arg});
90 }
91 } else {
92 try input_files.append(arg);
93 }
94 }
95 }
96
97 if (stdin_flag) {
98 if (input_files.items.len != 0) {
99 fatal("cannot use --stdin with positional arguments", .{});
100 }
101
102 const stdin = std.io.getStdIn();
103 const source_code = std.zig.readSourceFileToEndAlloc(gpa, stdin, null) catch |err| {
104 fatal("unable to read stdin: {}", .{err});
105 };
106 defer gpa.free(source_code);
107
108 var tree = std.zig.Ast.parse(gpa, source_code, .zig) catch |err| {
109 fatal("error parsing stdin: {}", .{err});
110 };
111 defer tree.deinit(gpa);
112
113 if (check_ast_flag) {
114 var zir = try std.zig.AstGen.generate(gpa, tree);
115
116 if (zir.hasCompileErrors()) {
117 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
118 try wip_errors.init(gpa);
119 defer wip_errors.deinit();
120 try wip_errors.addZirErrorMessages(zir, tree, source_code, "<stdin>");
121 var error_bundle = try wip_errors.toOwnedBundle("");
122 defer error_bundle.deinit(gpa);
123 error_bundle.renderToStdErr(color.renderOptions());
124 process.exit(2);
125 }
126 } else if (tree.errors.len != 0) {
127 try std.zig.printAstErrorsToStderr(gpa, tree, "<stdin>", color);
128 process.exit(2);
129 }
130 const formatted = try tree.render(gpa);
131 defer gpa.free(formatted);
132
133 if (check_flag) {
134 const code: u8 = @intFromBool(mem.eql(u8, formatted, source_code));
135 process.exit(code);
136 }
137
138 return std.io.getStdOut().writeAll(formatted);
139 }
140
141 if (input_files.items.len == 0) {
142 fatal("expected at least one source file argument", .{});
143 }
144
145 var fmt = Fmt{
146 .gpa = gpa,
147 .arena = arena,
148 .seen = Fmt.SeenMap.init(gpa),
149 .any_error = false,
150 .check_ast = check_ast_flag,
151 .color = color,
152 .out_buffer = std.ArrayList(u8).init(gpa),
153 };
154 defer fmt.seen.deinit();
155 defer fmt.out_buffer.deinit();
156
157 // Mark any excluded files/directories as already seen,
158 // so that they are skipped later during actual processing
159 for (excluded_files.items) |file_path| {
160 const stat = fs.cwd().statFile(file_path) catch |err| switch (err) {
161 error.FileNotFound => continue,
162 // On Windows, statFile does not work for directories
163 error.IsDir => dir: {
164 var dir = try fs.cwd().openDir(file_path, .{});
165 defer dir.close();
166 break :dir try dir.stat();
167 },
168 else => |e| return e,
169 };
170 try fmt.seen.put(stat.inode, {});
171 }
172
173 for (input_files.items) |file_path| {
174 try fmtPath(&fmt, file_path, check_flag, fs.cwd(), file_path);
175 }
176 if (fmt.any_error) {
177 process.exit(1);
178 }
179}
180
181const FmtError = error{
182 SystemResources,
183 OperationAborted,
184 IoPending,
185 BrokenPipe,
186 Unexpected,
187 WouldBlock,
188 FileClosed,
189 DestinationAddressRequired,
190 DiskQuota,
191 FileTooBig,
192 InputOutput,
193 NoSpaceLeft,
194 AccessDenied,
195 OutOfMemory,
196 RenameAcrossMountPoints,
197 ReadOnlyFileSystem,
198 LinkQuotaExceeded,
199 FileBusy,
200 EndOfStream,
201 Unseekable,
202 NotOpenForWriting,
203 UnsupportedEncoding,
204 ConnectionResetByPeer,
205 SocketNotConnected,
206 LockViolation,
207 NetNameDeleted,
208 InvalidArgument,
209} || fs.File.OpenError;
210
211fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_path: []const u8) FmtError!void {
212 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
213 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
214 else => {
215 warn("unable to format '{s}': {s}", .{ file_path, @errorName(err) });
216 fmt.any_error = true;
217 return;
218 },
219 };
220}
221
222fn fmtPathDir(
223 fmt: *Fmt,
224 file_path: []const u8,
225 check_mode: bool,
226 parent_dir: fs.Dir,
227 parent_sub_path: []const u8,
228) FmtError!void {
229 var dir = try parent_dir.openDir(parent_sub_path, .{ .iterate = true });
230 defer dir.close();
231
232 const stat = try dir.stat();
233 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
234
235 var dir_it = dir.iterate();
236 while (try dir_it.next()) |entry| {
237 const is_dir = entry.kind == .directory;
238
239 if (is_dir and (mem.eql(u8, entry.name, "zig-cache") or mem.eql(u8, entry.name, "zig-out"))) continue;
240
241 if (is_dir or entry.kind == .file and (mem.endsWith(u8, entry.name, ".zig") or mem.endsWith(u8, entry.name, ".zon"))) {
242 const full_path = try fs.path.join(fmt.gpa, &[_][]const u8{ file_path, entry.name });
243 defer fmt.gpa.free(full_path);
244
245 if (is_dir) {
246 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
247 } else {
248 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
249 warn("unable to format '{s}': {s}", .{ full_path, @errorName(err) });
250 fmt.any_error = true;
251 return;
252 };
253 }
254 }
255 }
256}
257
258fn fmtPathFile(
259 fmt: *Fmt,
260 file_path: []const u8,
261 check_mode: bool,
262 dir: fs.Dir,
263 sub_path: []const u8,
264) FmtError!void {
265 const source_file = try dir.openFile(sub_path, .{});
266 var file_closed = false;
267 errdefer if (!file_closed) source_file.close();
268
269 const stat = try source_file.stat();
270
271 if (stat.kind == .directory)
272 return error.IsDir;
273
274 const gpa = fmt.gpa;
275 const source_code = try std.zig.readSourceFileToEndAlloc(
276 gpa,
277 source_file,
278 std.math.cast(usize, stat.size) orelse return error.FileTooBig,
279 );
280 defer gpa.free(source_code);
281
282 source_file.close();
283 file_closed = true;
284
285 // Add to set after no longer possible to get error.IsDir.
286 if (try fmt.seen.fetchPut(stat.inode, {})) |_| return;
287
288 var tree = try std.zig.Ast.parse(gpa, source_code, .zig);
289 defer tree.deinit(gpa);
290
291 if (tree.errors.len != 0) {
292 try std.zig.printAstErrorsToStderr(gpa, tree, file_path, fmt.color);
293 fmt.any_error = true;
294 return;
295 }
296
297 if (fmt.check_ast) {
298 if (stat.size > std.zig.max_src_size)
299 return error.FileTooBig;
300
301 var zir = try std.zig.AstGen.generate(gpa, tree);
302 defer zir.deinit(gpa);
303
304 if (zir.hasCompileErrors()) {
305 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
306 try wip_errors.init(gpa);
307 defer wip_errors.deinit();
308 try wip_errors.addZirErrorMessages(zir, tree, source_code, file_path);
309 var error_bundle = try wip_errors.toOwnedBundle("");
310 defer error_bundle.deinit(gpa);
311 error_bundle.renderToStdErr(fmt.color.renderOptions());
312 fmt.any_error = true;
313 }
314 }
315
316 // As a heuristic, we make enough capacity for the same as the input source.
317 fmt.out_buffer.shrinkRetainingCapacity(0);
318 try fmt.out_buffer.ensureTotalCapacity(source_code.len);
319
320 try tree.renderToArrayList(&fmt.out_buffer, .{});
321 if (mem.eql(u8, fmt.out_buffer.items, source_code))
322 return;
323
324 if (check_mode) {
325 const stdout = std.io.getStdOut().writer();
326 try stdout.print("{s}\n", .{file_path});
327 fmt.any_error = true;
328 } else {
329 var af = try dir.atomicFile(sub_path, .{ .mode = stat.mode });
330 defer af.deinit();
331
332 try af.file.writeAll(fmt.out_buffer.items);
333 try af.finish();
334 const stdout = std.io.getStdOut().writer();
335 try stdout.print("{s}\n", .{file_path});
336 }
337}
338
339fn fatal(comptime format: []const u8, args: anytype) noreturn {
340 std.log.err(format, args);
341 process.exit(1);
342}
lib/std/zig/reduce.zig deleted-426
...@@ -1,426 +0,0 @@
1const std = @import("std");
2const mem = std.mem;
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5const Ast = std.zig.Ast;
6const Walk = @import("reduce/Walk.zig");
7const AstGen = std.zig.AstGen;
8const Zir = std.zig.Zir;
9
10const usage =
11 \\zig reduce [options] ./checker root_source_file.zig [-- [argv]]
12 \\
13 \\root_source_file.zig is relative to --main-mod-path.
14 \\
15 \\checker:
16 \\ An executable that communicates interestingness by returning these exit codes:
17 \\ exit(0): interesting
18 \\ exit(1): unknown (infinite loop or other mishap)
19 \\ exit(other): not interesting
20 \\
21 \\options:
22 \\ --seed [integer] Override the random seed. Defaults to 0
23 \\ --skip-smoke-test Skip interestingness check smoke test
24 \\ --mod [name]:[deps]:[src] Make a module available for dependency under the given name
25 \\ deps: [dep],[dep],...
26 \\ dep: [[import=]name]
27 \\ --deps [dep],[dep],... Set dependency names for the root package
28 \\ dep: [[import=]name]
29 \\ --main-mod-path Set the directory of the root module
30 \\
31 \\argv:
32 \\ Forwarded directly to the interestingness script.
33 \\
34;
35
36const Interestingness = enum { interesting, unknown, boring };
37
38// Roadmap:
39// - add thread pool
40// - add support for parsing the module flags
41// - more fancy transformations
42// - @import inlining of modules
43// - removing statements or blocks of code
44// - replacing operands of `and` and `or` with `true` and `false`
45// - replacing if conditions with `true` and `false`
46// - reduce flags sent to the compiler
47// - integrate with the build system?
48
49pub fn main() !void {
50 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
51 defer arena_instance.deinit();
52 const arena = arena_instance.allocator();
53
54 var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
55 const gpa = general_purpose_allocator.allocator();
56
57 const args = try std.process.argsAlloc(arena);
58
59 var opt_checker_path: ?[]const u8 = null;
60 var opt_root_source_file_path: ?[]const u8 = null;
61 var argv: []const []const u8 = &.{};
62 var seed: u32 = 0;
63 var skip_smoke_test = false;
64
65 {
66 var i: usize = 1;
67 while (i < args.len) : (i += 1) {
68 const arg = args[i];
69 if (mem.startsWith(u8, arg, "-")) {
70 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
71 const stdout = std.io.getStdOut().writer();
72 try stdout.writeAll(usage);
73 return std.process.cleanExit();
74 } else if (mem.eql(u8, arg, "--")) {
75 argv = args[i + 1 ..];
76 break;
77 } else if (mem.eql(u8, arg, "--skip-smoke-test")) {
78 skip_smoke_test = true;
79 } else if (mem.eql(u8, arg, "--main-mod-path")) {
80 @panic("TODO: implement --main-mod-path");
81 } else if (mem.eql(u8, arg, "--mod")) {
82 @panic("TODO: implement --mod");
83 } else if (mem.eql(u8, arg, "--deps")) {
84 @panic("TODO: implement --deps");
85 } else if (mem.eql(u8, arg, "--seed")) {
86 i += 1;
87 if (i >= args.len) fatal("expected 32-bit integer after {s}", .{arg});
88 const next_arg = args[i];
89 seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| {
90 fatal("unable to parse seed '{s}' as 32-bit integer: {s}", .{
91 next_arg, @errorName(err),
92 });
93 };
94 } else {
95 fatal("unrecognized parameter: '{s}'", .{arg});
96 }
97 } else if (opt_checker_path == null) {
98 opt_checker_path = arg;
99 } else if (opt_root_source_file_path == null) {
100 opt_root_source_file_path = arg;
101 } else {
102 fatal("unexpected extra parameter: '{s}'", .{arg});
103 }
104 }
105 }
106
107 const checker_path = opt_checker_path orelse
108 fatal("missing interestingness checker argument; see -h for usage", .{});
109 const root_source_file_path = opt_root_source_file_path orelse
110 fatal("missing root source file path argument; see -h for usage", .{});
111
112 var interestingness_argv: std.ArrayListUnmanaged([]const u8) = .{};
113 try interestingness_argv.ensureUnusedCapacity(arena, argv.len + 1);
114 interestingness_argv.appendAssumeCapacity(checker_path);
115 interestingness_argv.appendSliceAssumeCapacity(argv);
116
117 var rendered = std.ArrayList(u8).init(gpa);
118 defer rendered.deinit();
119
120 var astgen_input = std.ArrayList(u8).init(gpa);
121 defer astgen_input.deinit();
122
123 var tree = try parse(gpa, root_source_file_path);
124 defer {
125 gpa.free(tree.source);
126 tree.deinit(gpa);
127 }
128
129 if (!skip_smoke_test) {
130 std.debug.print("smoke testing the interestingness check...\n", .{});
131 switch (try runCheck(arena, interestingness_argv.items)) {
132 .interesting => {},
133 .boring, .unknown => |t| {
134 fatal("interestingness check returned {s} for unmodified input\n", .{
135 @tagName(t),
136 });
137 },
138 }
139 }
140
141 var fixups: Ast.Fixups = .{};
142 defer fixups.deinit(gpa);
143
144 var more_fixups: Ast.Fixups = .{};
145 defer more_fixups.deinit(gpa);
146
147 var rng = std.Random.DefaultPrng.init(seed);
148
149 // 1. Walk the AST of the source file looking for independent
150 // reductions and collecting them all into an array list.
151 // 2. Randomize the list of transformations. A future enhancement will add
152 // priority weights to the sorting but for now they are completely
153 // shuffled.
154 // 3. Apply a subset consisting of 1/2 of the transformations and check for
155 // interestingness.
156 // 4. If not interesting, half the subset size again and check again.
157 // 5. Repeat until the subset size is 1, then march the transformation
158 // index forward by 1 with each non-interesting attempt.
159 //
160 // At any point if a subset of transformations succeeds in producing an interesting
161 // result, restart the whole process, reparsing the AST and re-generating the list
162 // of all possible transformations and shuffling it again.
163
164 var transformations = std.ArrayList(Walk.Transformation).init(gpa);
165 defer transformations.deinit();
166 try Walk.findTransformations(arena, &tree, &transformations);
167 sortTransformations(transformations.items, rng.random());
168
169 fresh: while (transformations.items.len > 0) {
170 std.debug.print("found {d} possible transformations\n", .{
171 transformations.items.len,
172 });
173 var subset_size: usize = transformations.items.len;
174 var start_index: usize = 0;
175
176 while (start_index < transformations.items.len) {
177 const prev_subset_size = subset_size;
178 subset_size = @max(1, subset_size * 3 / 4);
179 if (prev_subset_size > 1 and subset_size == 1)
180 start_index = 0;
181
182 const this_set = transformations.items[start_index..][0..subset_size];
183 std.debug.print("trying {d} random transformations: ", .{subset_size});
184 for (this_set[0..@min(this_set.len, 20)]) |t| {
185 std.debug.print("{s} ", .{@tagName(t)});
186 }
187 std.debug.print("\n", .{});
188 try transformationsToFixups(gpa, arena, root_source_file_path, this_set, &fixups);
189
190 rendered.clearRetainingCapacity();
191 try tree.renderToArrayList(&rendered, fixups);
192
193 // The transformations we applied may have resulted in unused locals,
194 // in which case we would like to add the respective discards.
195 {
196 try astgen_input.resize(rendered.items.len);
197 @memcpy(astgen_input.items, rendered.items);
198 try astgen_input.append(0);
199 const source_with_null = astgen_input.items[0 .. astgen_input.items.len - 1 :0];
200 var astgen_tree = try Ast.parse(gpa, source_with_null, .zig);
201 defer astgen_tree.deinit(gpa);
202 if (astgen_tree.errors.len != 0) {
203 @panic("syntax errors occurred");
204 }
205 var zir = try AstGen.generate(gpa, astgen_tree);
206 defer zir.deinit(gpa);
207
208 if (zir.hasCompileErrors()) {
209 more_fixups.clearRetainingCapacity();
210 const payload_index = zir.extra[@intFromEnum(Zir.ExtraIndex.compile_errors)];
211 assert(payload_index != 0);
212 const header = zir.extraData(Zir.Inst.CompileErrors, payload_index);
213 var extra_index = header.end;
214 for (0..header.data.items_len) |_| {
215 const item = zir.extraData(Zir.Inst.CompileErrors.Item, extra_index);
216 extra_index = item.end;
217 const msg = zir.nullTerminatedString(item.data.msg);
218 if (mem.eql(u8, msg, "unused local constant") or
219 mem.eql(u8, msg, "unused local variable") or
220 mem.eql(u8, msg, "unused function parameter") or
221 mem.eql(u8, msg, "unused capture"))
222 {
223 const ident_token = item.data.token;
224 try more_fixups.unused_var_decls.put(gpa, ident_token, {});
225 } else {
226 std.debug.print("found other ZIR error: '{s}'\n", .{msg});
227 }
228 }
229 if (more_fixups.count() != 0) {
230 rendered.clearRetainingCapacity();
231 try astgen_tree.renderToArrayList(&rendered, more_fixups);
232 }
233 }
234 }
235
236 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
237 // std.debug.print("trying this code:\n{s}\n", .{rendered.items});
238
239 const interestingness = try runCheck(arena, interestingness_argv.items);
240 std.debug.print("{d} random transformations: {s}. {d}/{d}\n", .{
241 subset_size, @tagName(interestingness), start_index, transformations.items.len,
242 });
243 switch (interestingness) {
244 .interesting => {
245 const new_tree = try parse(gpa, root_source_file_path);
246 gpa.free(tree.source);
247 tree.deinit(gpa);
248 tree = new_tree;
249
250 try Walk.findTransformations(arena, &tree, &transformations);
251 sortTransformations(transformations.items, rng.random());
252
253 continue :fresh;
254 },
255 .unknown, .boring => {
256 // Continue to try the next set of transformations.
257 // If we tested only one transformation, move on to the next one.
258 if (subset_size == 1) {
259 start_index += 1;
260 } else {
261 start_index += subset_size;
262 if (start_index + subset_size > transformations.items.len) {
263 start_index = 0;
264 }
265 }
266 },
267 }
268 }
269 std.debug.print("all {d} remaining transformations are uninteresting\n", .{
270 transformations.items.len,
271 });
272
273 // Revert the source back to not be transformed.
274 fixups.clearRetainingCapacity();
275 rendered.clearRetainingCapacity();
276 try tree.renderToArrayList(&rendered, fixups);
277 try std.fs.cwd().writeFile(root_source_file_path, rendered.items);
278
279 return std.process.cleanExit();
280 }
281 std.debug.print("no more transformations found\n", .{});
282 return std.process.cleanExit();
283}
284
285fn sortTransformations(transformations: []Walk.Transformation, rng: std.Random) void {
286 rng.shuffle(Walk.Transformation, transformations);
287 // Stable sort based on priority to keep randomness as the secondary sort.
288 // TODO: introduce transformation priorities
289 // std.mem.sort(transformations);
290}
291
292fn termToInteresting(term: std.process.Child.Term) Interestingness {
293 return switch (term) {
294 .Exited => |code| switch (code) {
295 0 => .interesting,
296 1 => .unknown,
297 else => .boring,
298 },
299 else => b: {
300 std.debug.print("interestingness check aborted unexpectedly\n", .{});
301 break :b .boring;
302 },
303 };
304}
305
306fn runCheck(arena: std.mem.Allocator, argv: []const []const u8) !Interestingness {
307 const result = try std.process.Child.run(.{
308 .allocator = arena,
309 .argv = argv,
310 });
311 if (result.stderr.len != 0)
312 std.debug.print("{s}", .{result.stderr});
313 return termToInteresting(result.term);
314}
315
316fn transformationsToFixups(
317 gpa: Allocator,
318 arena: Allocator,
319 root_source_file_path: []const u8,
320 transforms: []const Walk.Transformation,
321 fixups: *Ast.Fixups,
322) !void {
323 fixups.clearRetainingCapacity();
324
325 for (transforms) |t| switch (t) {
326 .gut_function => |fn_decl_node| {
327 try fixups.gut_functions.put(gpa, fn_decl_node, {});
328 },
329 .delete_node => |decl_node| {
330 try fixups.omit_nodes.put(gpa, decl_node, {});
331 },
332 .delete_var_decl => |delete_var_decl| {
333 try fixups.omit_nodes.put(gpa, delete_var_decl.var_decl_node, {});
334 for (delete_var_decl.references.items) |ident_node| {
335 try fixups.replace_nodes_with_string.put(gpa, ident_node, "undefined");
336 }
337 },
338 .replace_with_undef => |node| {
339 try fixups.replace_nodes_with_string.put(gpa, node, "undefined");
340 },
341 .replace_with_true => |node| {
342 try fixups.replace_nodes_with_string.put(gpa, node, "true");
343 },
344 .replace_with_false => |node| {
345 try fixups.replace_nodes_with_string.put(gpa, node, "false");
346 },
347 .replace_node => |r| {
348 try fixups.replace_nodes_with_node.put(gpa, r.to_replace, r.replacement);
349 },
350 .inline_imported_file => |inline_imported_file| {
351 const full_imported_path = try std.fs.path.join(gpa, &.{
352 std.fs.path.dirname(root_source_file_path) orelse ".",
353 inline_imported_file.imported_string,
354 });
355 defer gpa.free(full_imported_path);
356 var other_file_ast = try parse(gpa, full_imported_path);
357 defer {
358 gpa.free(other_file_ast.source);
359 other_file_ast.deinit(gpa);
360 }
361
362 var inlined_fixups: Ast.Fixups = .{};
363 defer inlined_fixups.deinit(gpa);
364 if (std.fs.path.dirname(inline_imported_file.imported_string)) |dirname| {
365 inlined_fixups.rebase_imported_paths = dirname;
366 }
367 for (inline_imported_file.in_scope_names.keys()) |name| {
368 // This name needs to be mangled in order to not cause an
369 // ambiguous reference error.
370 var i: u32 = 2;
371 const mangled = while (true) : (i += 1) {
372 const mangled = try std.fmt.allocPrint(gpa, "{s}{d}", .{ name, i });
373 if (!inline_imported_file.in_scope_names.contains(mangled))
374 break mangled;
375 gpa.free(mangled);
376 };
377 try inlined_fixups.rename_identifiers.put(gpa, name, mangled);
378 }
379 defer {
380 for (inlined_fixups.rename_identifiers.values()) |v| {
381 gpa.free(v);
382 }
383 }
384
385 var other_source = std.ArrayList(u8).init(gpa);
386 defer other_source.deinit();
387 try other_source.appendSlice("struct {\n");
388 try other_file_ast.renderToArrayList(&other_source, inlined_fixups);
389 try other_source.appendSlice("}");
390
391 try fixups.replace_nodes_with_string.put(
392 gpa,
393 inline_imported_file.builtin_call_node,
394 try arena.dupe(u8, other_source.items),
395 );
396 },
397 };
398}
399
400fn parse(gpa: Allocator, file_path: []const u8) !Ast {
401 const source_code = std.fs.cwd().readFileAllocOptions(
402 gpa,
403 file_path,
404 std.math.maxInt(u32),
405 null,
406 1,
407 0,
408 ) catch |err| {
409 fatal("unable to open '{s}': {s}", .{ file_path, @errorName(err) });
410 };
411 errdefer gpa.free(source_code);
412
413 var tree = try Ast.parse(gpa, source_code, .zig);
414 errdefer tree.deinit(gpa);
415
416 if (tree.errors.len != 0) {
417 @panic("syntax errors occurred");
418 }
419
420 return tree;
421}
422
423fn fatal(comptime format: []const u8, args: anytype) noreturn {
424 std.log.err(format, args);
425 std.process.exit(1);
426}
lib/std/zig/reduce/Walk.zig deleted-1102
...@@ -1,1102 +0,0 @@
1const std = @import("std");
2const Ast = std.zig.Ast;
3const Walk = @This();
4const assert = std.debug.assert;
5const BuiltinFn = std.zig.BuiltinFn;
6
7ast: *const Ast,
8transformations: *std.ArrayList(Transformation),
9unreferenced_globals: std.StringArrayHashMapUnmanaged(Ast.Node.Index),
10in_scope_names: std.StringArrayHashMapUnmanaged(u32),
11replace_names: std.StringArrayHashMapUnmanaged(u32),
12gpa: std.mem.Allocator,
13arena: std.mem.Allocator,
14
15pub const Transformation = union(enum) {
16 /// Replace the fn decl AST Node with one whose body is only `@trap()` with
17 /// discarded parameters.
18 gut_function: Ast.Node.Index,
19 /// Omit a global declaration.
20 delete_node: Ast.Node.Index,
21 /// Delete a local variable declaration and replace all of its references
22 /// with `undefined`.
23 delete_var_decl: struct {
24 var_decl_node: Ast.Node.Index,
25 /// Identifier nodes that reference the variable.
26 references: std.ArrayListUnmanaged(Ast.Node.Index),
27 },
28 /// Replace an expression with `undefined`.
29 replace_with_undef: Ast.Node.Index,
30 /// Replace an expression with `true`.
31 replace_with_true: Ast.Node.Index,
32 /// Replace an expression with `false`.
33 replace_with_false: Ast.Node.Index,
34 /// Replace a node with another node.
35 replace_node: struct {
36 to_replace: Ast.Node.Index,
37 replacement: Ast.Node.Index,
38 },
39 /// Replace an `@import` with the imported file contents wrapped in a struct.
40 inline_imported_file: InlineImportedFile,
41
42 pub const InlineImportedFile = struct {
43 builtin_call_node: Ast.Node.Index,
44 imported_string: []const u8,
45 /// Identifier names that must be renamed in the inlined code or else
46 /// will cause ambiguous reference errors.
47 in_scope_names: std.StringArrayHashMapUnmanaged(void),
48 };
49};
50
51pub const Error = error{OutOfMemory};
52
53/// The result will be priority shuffled.
54pub fn findTransformations(
55 arena: std.mem.Allocator,
56 ast: *const Ast,
57 transformations: *std.ArrayList(Transformation),
58) !void {
59 transformations.clearRetainingCapacity();
60
61 var walk: Walk = .{
62 .ast = ast,
63 .transformations = transformations,
64 .gpa = transformations.allocator,
65 .arena = arena,
66 .unreferenced_globals = .{},
67 .in_scope_names = .{},
68 .replace_names = .{},
69 };
70 defer {
71 walk.unreferenced_globals.deinit(walk.gpa);
72 walk.in_scope_names.deinit(walk.gpa);
73 walk.replace_names.deinit(walk.gpa);
74 }
75
76 try walkMembers(&walk, walk.ast.rootDecls());
77
78 const unreferenced_globals = walk.unreferenced_globals.values();
79 try transformations.ensureUnusedCapacity(unreferenced_globals.len);
80 for (unreferenced_globals) |node| {
81 transformations.appendAssumeCapacity(.{ .delete_node = node });
82 }
83}
84
85fn walkMembers(w: *Walk, members: []const Ast.Node.Index) Error!void {
86 // First we scan for globals so that we can delete them while walking.
87 try scanDecls(w, members, .add);
88
89 for (members) |member| {
90 try walkMember(w, member);
91 }
92
93 try scanDecls(w, members, .remove);
94}
95
96const ScanDeclsAction = enum { add, remove };
97
98fn scanDecls(w: *Walk, members: []const Ast.Node.Index, action: ScanDeclsAction) Error!void {
99 const ast = w.ast;
100 const gpa = w.gpa;
101 const node_tags = ast.nodes.items(.tag);
102 const main_tokens = ast.nodes.items(.main_token);
103 const token_tags = ast.tokens.items(.tag);
104
105 for (members) |member_node| {
106 const name_token = switch (node_tags[member_node]) {
107 .global_var_decl,
108 .local_var_decl,
109 .simple_var_decl,
110 .aligned_var_decl,
111 => main_tokens[member_node] + 1,
112
113 .fn_proto_simple,
114 .fn_proto_multi,
115 .fn_proto_one,
116 .fn_proto,
117 .fn_decl,
118 => main_tokens[member_node] + 1,
119
120 else => continue,
121 };
122
123 assert(token_tags[name_token] == .identifier);
124 const name_bytes = ast.tokenSlice(name_token);
125
126 switch (action) {
127 .add => {
128 try w.unreferenced_globals.put(gpa, name_bytes, member_node);
129
130 const gop = try w.in_scope_names.getOrPut(gpa, name_bytes);
131 if (!gop.found_existing) gop.value_ptr.* = 0;
132 gop.value_ptr.* += 1;
133 },
134 .remove => {
135 const entry = w.in_scope_names.getEntry(name_bytes).?;
136 if (entry.value_ptr.* <= 1) {
137 assert(w.in_scope_names.swapRemove(name_bytes));
138 } else {
139 entry.value_ptr.* -= 1;
140 }
141 },
142 }
143 }
144}
145
146fn walkMember(w: *Walk, decl: Ast.Node.Index) Error!void {
147 const ast = w.ast;
148 const datas = ast.nodes.items(.data);
149 switch (ast.nodes.items(.tag)[decl]) {
150 .fn_decl => {
151 const fn_proto = datas[decl].lhs;
152 try walkExpression(w, fn_proto);
153 const body_node = datas[decl].rhs;
154 if (!isFnBodyGutted(ast, body_node)) {
155 w.replace_names.clearRetainingCapacity();
156 try w.transformations.append(.{ .gut_function = decl });
157 try walkExpression(w, body_node);
158 }
159 },
160 .fn_proto_simple,
161 .fn_proto_multi,
162 .fn_proto_one,
163 .fn_proto,
164 => {
165 try walkExpression(w, decl);
166 },
167
168 .@"usingnamespace" => {
169 try w.transformations.append(.{ .delete_node = decl });
170 const expr = datas[decl].lhs;
171 try walkExpression(w, expr);
172 },
173
174 .global_var_decl,
175 .local_var_decl,
176 .simple_var_decl,
177 .aligned_var_decl,
178 => try walkGlobalVarDecl(w, decl, ast.fullVarDecl(decl).?),
179
180 .test_decl => {
181 try w.transformations.append(.{ .delete_node = decl });
182 try walkExpression(w, datas[decl].rhs);
183 },
184
185 .container_field_init,
186 .container_field_align,
187 .container_field,
188 => {
189 try w.transformations.append(.{ .delete_node = decl });
190 try walkContainerField(w, ast.fullContainerField(decl).?);
191 },
192
193 .@"comptime" => {
194 try w.transformations.append(.{ .delete_node = decl });
195 try walkExpression(w, decl);
196 },
197
198 .root => unreachable,
199 else => unreachable,
200 }
201}
202
203fn walkExpression(w: *Walk, node: Ast.Node.Index) Error!void {
204 const ast = w.ast;
205 const token_tags = ast.tokens.items(.tag);
206 const main_tokens = ast.nodes.items(.main_token);
207 const node_tags = ast.nodes.items(.tag);
208 const datas = ast.nodes.items(.data);
209 switch (node_tags[node]) {
210 .identifier => {
211 const name_ident = main_tokens[node];
212 assert(token_tags[name_ident] == .identifier);
213 const name_bytes = ast.tokenSlice(name_ident);
214 _ = w.unreferenced_globals.swapRemove(name_bytes);
215 if (w.replace_names.get(name_bytes)) |index| {
216 try w.transformations.items[index].delete_var_decl.references.append(w.arena, node);
217 }
218 },
219
220 .number_literal,
221 .char_literal,
222 .unreachable_literal,
223 .anyframe_literal,
224 .string_literal,
225 => {},
226
227 .multiline_string_literal => {},
228
229 .error_value => {},
230
231 .block_two,
232 .block_two_semicolon,
233 => {
234 const statements = [2]Ast.Node.Index{ datas[node].lhs, datas[node].rhs };
235 if (datas[node].lhs == 0) {
236 return walkBlock(w, node, statements[0..0]);
237 } else if (datas[node].rhs == 0) {
238 return walkBlock(w, node, statements[0..1]);
239 } else {
240 return walkBlock(w, node, statements[0..2]);
241 }
242 },
243 .block,
244 .block_semicolon,
245 => {
246 const statements = ast.extra_data[datas[node].lhs..datas[node].rhs];
247 return walkBlock(w, node, statements);
248 },
249
250 .@"errdefer" => {
251 const expr = datas[node].rhs;
252 return walkExpression(w, expr);
253 },
254
255 .@"defer" => {
256 const expr = datas[node].rhs;
257 return walkExpression(w, expr);
258 },
259 .@"comptime", .@"nosuspend" => {
260 const block = datas[node].lhs;
261 return walkExpression(w, block);
262 },
263
264 .@"suspend" => {
265 const body = datas[node].lhs;
266 return walkExpression(w, body);
267 },
268
269 .@"catch" => {
270 try walkExpression(w, datas[node].lhs); // target
271 try walkExpression(w, datas[node].rhs); // fallback
272 },
273
274 .field_access => {
275 const field_access = datas[node];
276 try walkExpression(w, field_access.lhs);
277 },
278
279 .error_union,
280 .switch_range,
281 => {
282 const infix = datas[node];
283 try walkExpression(w, infix.lhs);
284 return walkExpression(w, infix.rhs);
285 },
286 .for_range => {
287 const infix = datas[node];
288 try walkExpression(w, infix.lhs);
289 if (infix.rhs != 0) {
290 return walkExpression(w, infix.rhs);
291 }
292 },
293
294 .add,
295 .add_wrap,
296 .add_sat,
297 .array_cat,
298 .array_mult,
299 .assign,
300 .assign_bit_and,
301 .assign_bit_or,
302 .assign_shl,
303 .assign_shl_sat,
304 .assign_shr,
305 .assign_bit_xor,
306 .assign_div,
307 .assign_sub,
308 .assign_sub_wrap,
309 .assign_sub_sat,
310 .assign_mod,
311 .assign_add,
312 .assign_add_wrap,
313 .assign_add_sat,
314 .assign_mul,
315 .assign_mul_wrap,
316 .assign_mul_sat,
317 .bang_equal,
318 .bit_and,
319 .bit_or,
320 .shl,
321 .shl_sat,
322 .shr,
323 .bit_xor,
324 .bool_and,
325 .bool_or,
326 .div,
327 .equal_equal,
328 .greater_or_equal,
329 .greater_than,
330 .less_or_equal,
331 .less_than,
332 .merge_error_sets,
333 .mod,
334 .mul,
335 .mul_wrap,
336 .mul_sat,
337 .sub,
338 .sub_wrap,
339 .sub_sat,
340 .@"orelse",
341 => {
342 const infix = datas[node];
343 try walkExpression(w, infix.lhs);
344 try walkExpression(w, infix.rhs);
345 },
346
347 .assign_destructure => {
348 const lhs_count = ast.extra_data[datas[node].lhs];
349 assert(lhs_count > 1);
350 const lhs_exprs = ast.extra_data[datas[node].lhs + 1 ..][0..lhs_count];
351 const rhs = datas[node].rhs;
352
353 for (lhs_exprs) |lhs_node| {
354 switch (node_tags[lhs_node]) {
355 .global_var_decl,
356 .local_var_decl,
357 .simple_var_decl,
358 .aligned_var_decl,
359 => try walkLocalVarDecl(w, ast.fullVarDecl(lhs_node).?),
360
361 else => try walkExpression(w, lhs_node),
362 }
363 }
364 return walkExpression(w, rhs);
365 },
366
367 .bit_not,
368 .bool_not,
369 .negation,
370 .negation_wrap,
371 .optional_type,
372 .address_of,
373 => {
374 return walkExpression(w, datas[node].lhs);
375 },
376
377 .@"try",
378 .@"resume",
379 .@"await",
380 => {
381 return walkExpression(w, datas[node].lhs);
382 },
383
384 .array_type,
385 .array_type_sentinel,
386 => {},
387
388 .ptr_type_aligned,
389 .ptr_type_sentinel,
390 .ptr_type,
391 .ptr_type_bit_range,
392 => {},
393
394 .array_init_one,
395 .array_init_one_comma,
396 .array_init_dot_two,
397 .array_init_dot_two_comma,
398 .array_init_dot,
399 .array_init_dot_comma,
400 .array_init,
401 .array_init_comma,
402 => {
403 var elements: [2]Ast.Node.Index = undefined;
404 return walkArrayInit(w, ast.fullArrayInit(&elements, node).?);
405 },
406
407 .struct_init_one,
408 .struct_init_one_comma,
409 .struct_init_dot_two,
410 .struct_init_dot_two_comma,
411 .struct_init_dot,
412 .struct_init_dot_comma,
413 .struct_init,
414 .struct_init_comma,
415 => {
416 var buf: [2]Ast.Node.Index = undefined;
417 return walkStructInit(w, node, ast.fullStructInit(&buf, node).?);
418 },
419
420 .call_one,
421 .call_one_comma,
422 .async_call_one,
423 .async_call_one_comma,
424 .call,
425 .call_comma,
426 .async_call,
427 .async_call_comma,
428 => {
429 var buf: [1]Ast.Node.Index = undefined;
430 return walkCall(w, ast.fullCall(&buf, node).?);
431 },
432
433 .array_access => {
434 const suffix = datas[node];
435 try walkExpression(w, suffix.lhs);
436 try walkExpression(w, suffix.rhs);
437 },
438
439 .slice_open, .slice, .slice_sentinel => return walkSlice(w, node, ast.fullSlice(node).?),
440
441 .deref => {
442 try walkExpression(w, datas[node].lhs);
443 },
444
445 .unwrap_optional => {
446 try walkExpression(w, datas[node].lhs);
447 },
448
449 .@"break" => {
450 const label_token = datas[node].lhs;
451 const target = datas[node].rhs;
452 if (label_token == 0 and target == 0) {
453 // no expressions
454 } else if (label_token == 0 and target != 0) {
455 try walkExpression(w, target);
456 } else if (label_token != 0 and target == 0) {
457 try walkIdentifier(w, label_token);
458 } else if (label_token != 0 and target != 0) {
459 try walkExpression(w, target);
460 }
461 },
462
463 .@"continue" => {
464 const label = datas[node].lhs;
465 if (label != 0) {
466 return walkIdentifier(w, label); // label
467 }
468 },
469
470 .@"return" => {
471 if (datas[node].lhs != 0) {
472 try walkExpression(w, datas[node].lhs);
473 }
474 },
475
476 .grouped_expression => {
477 try walkExpression(w, datas[node].lhs);
478 },
479
480 .container_decl,
481 .container_decl_trailing,
482 .container_decl_arg,
483 .container_decl_arg_trailing,
484 .container_decl_two,
485 .container_decl_two_trailing,
486 .tagged_union,
487 .tagged_union_trailing,
488 .tagged_union_enum_tag,
489 .tagged_union_enum_tag_trailing,
490 .tagged_union_two,
491 .tagged_union_two_trailing,
492 => {
493 var buf: [2]Ast.Node.Index = undefined;
494 return walkContainerDecl(w, node, ast.fullContainerDecl(&buf, node).?);
495 },
496
497 .error_set_decl => {
498 const error_token = main_tokens[node];
499 const lbrace = error_token + 1;
500 const rbrace = datas[node].rhs;
501
502 var i = lbrace + 1;
503 while (i < rbrace) : (i += 1) {
504 switch (token_tags[i]) {
505 .doc_comment => unreachable, // TODO
506 .identifier => try walkIdentifier(w, i),
507 .comma => {},
508 else => unreachable,
509 }
510 }
511 },
512
513 .builtin_call_two, .builtin_call_two_comma => {
514 if (datas[node].lhs == 0) {
515 return walkBuiltinCall(w, node, &.{});
516 } else if (datas[node].rhs == 0) {
517 return walkBuiltinCall(w, node, &.{datas[node].lhs});
518 } else {
519 return walkBuiltinCall(w, node, &.{ datas[node].lhs, datas[node].rhs });
520 }
521 },
522 .builtin_call, .builtin_call_comma => {
523 const params = ast.extra_data[datas[node].lhs..datas[node].rhs];
524 return walkBuiltinCall(w, node, params);
525 },
526
527 .fn_proto_simple,
528 .fn_proto_multi,
529 .fn_proto_one,
530 .fn_proto,
531 => {
532 var buf: [1]Ast.Node.Index = undefined;
533 return walkFnProto(w, ast.fullFnProto(&buf, node).?);
534 },
535
536 .anyframe_type => {
537 if (datas[node].rhs != 0) {
538 return walkExpression(w, datas[node].rhs);
539 }
540 },
541
542 .@"switch",
543 .switch_comma,
544 => {
545 const condition = datas[node].lhs;
546 const extra = ast.extraData(datas[node].rhs, Ast.Node.SubRange);
547 const cases = ast.extra_data[extra.start..extra.end];
548
549 try walkExpression(w, condition); // condition expression
550 try walkExpressions(w, cases);
551 },
552
553 .switch_case_one,
554 .switch_case_inline_one,
555 .switch_case,
556 .switch_case_inline,
557 => return walkSwitchCase(w, ast.fullSwitchCase(node).?),
558
559 .while_simple,
560 .while_cont,
561 .@"while",
562 => return walkWhile(w, node, ast.fullWhile(node).?),
563
564 .for_simple,
565 .@"for",
566 => return walkFor(w, ast.fullFor(node).?),
567
568 .if_simple,
569 .@"if",
570 => return walkIf(w, node, ast.fullIf(node).?),
571
572 .asm_simple,
573 .@"asm",
574 => return walkAsm(w, ast.fullAsm(node).?),
575
576 .enum_literal => {
577 return walkIdentifier(w, main_tokens[node]); // name
578 },
579
580 .fn_decl => unreachable,
581 .container_field => unreachable,
582 .container_field_init => unreachable,
583 .container_field_align => unreachable,
584 .root => unreachable,
585 .global_var_decl => unreachable,
586 .local_var_decl => unreachable,
587 .simple_var_decl => unreachable,
588 .aligned_var_decl => unreachable,
589 .@"usingnamespace" => unreachable,
590 .test_decl => unreachable,
591 .asm_output => unreachable,
592 .asm_input => unreachable,
593 }
594}
595
596fn walkGlobalVarDecl(w: *Walk, decl_node: Ast.Node.Index, var_decl: Ast.full.VarDecl) Error!void {
597 _ = decl_node;
598
599 if (var_decl.ast.type_node != 0) {
600 try walkExpression(w, var_decl.ast.type_node);
601 }
602
603 if (var_decl.ast.align_node != 0) {
604 try walkExpression(w, var_decl.ast.align_node);
605 }
606
607 if (var_decl.ast.addrspace_node != 0) {
608 try walkExpression(w, var_decl.ast.addrspace_node);
609 }
610
611 if (var_decl.ast.section_node != 0) {
612 try walkExpression(w, var_decl.ast.section_node);
613 }
614
615 if (var_decl.ast.init_node != 0) {
616 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
617 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
618 }
619 try walkExpression(w, var_decl.ast.init_node);
620 }
621}
622
623fn walkLocalVarDecl(w: *Walk, var_decl: Ast.full.VarDecl) Error!void {
624 try walkIdentifierNew(w, var_decl.ast.mut_token + 1); // name
625
626 if (var_decl.ast.type_node != 0) {
627 try walkExpression(w, var_decl.ast.type_node);
628 }
629
630 if (var_decl.ast.align_node != 0) {
631 try walkExpression(w, var_decl.ast.align_node);
632 }
633
634 if (var_decl.ast.addrspace_node != 0) {
635 try walkExpression(w, var_decl.ast.addrspace_node);
636 }
637
638 if (var_decl.ast.section_node != 0) {
639 try walkExpression(w, var_decl.ast.section_node);
640 }
641
642 if (var_decl.ast.init_node != 0) {
643 if (!isUndefinedIdent(w.ast, var_decl.ast.init_node)) {
644 try w.transformations.append(.{ .replace_with_undef = var_decl.ast.init_node });
645 }
646 try walkExpression(w, var_decl.ast.init_node);
647 }
648}
649
650fn walkContainerField(w: *Walk, field: Ast.full.ContainerField) Error!void {
651 if (field.ast.type_expr != 0) {
652 try walkExpression(w, field.ast.type_expr); // type
653 }
654 if (field.ast.align_expr != 0) {
655 try walkExpression(w, field.ast.align_expr); // alignment
656 }
657 if (field.ast.value_expr != 0) {
658 try walkExpression(w, field.ast.value_expr); // value
659 }
660}
661
662fn walkBlock(
663 w: *Walk,
664 block_node: Ast.Node.Index,
665 statements: []const Ast.Node.Index,
666) Error!void {
667 _ = block_node;
668 const ast = w.ast;
669 const node_tags = ast.nodes.items(.tag);
670
671 for (statements) |stmt| {
672 switch (node_tags[stmt]) {
673 .global_var_decl,
674 .local_var_decl,
675 .simple_var_decl,
676 .aligned_var_decl,
677 => {
678 const var_decl = ast.fullVarDecl(stmt).?;
679 if (var_decl.ast.init_node != 0 and
680 isUndefinedIdent(w.ast, var_decl.ast.init_node))
681 {
682 try w.transformations.append(.{ .delete_var_decl = .{
683 .var_decl_node = stmt,
684 .references = .{},
685 } });
686 const name_tok = var_decl.ast.mut_token + 1;
687 const name_bytes = ast.tokenSlice(name_tok);
688 try w.replace_names.put(w.gpa, name_bytes, @intCast(w.transformations.items.len - 1));
689 } else {
690 try walkLocalVarDecl(w, var_decl);
691 }
692 },
693
694 else => {
695 switch (categorizeStmt(ast, stmt)) {
696 // Don't try to remove `_ = foo;` discards; those are handled separately.
697 .discard_identifier => {},
698 // definitely try to remove `_ = undefined;` though.
699 .discard_undefined, .trap_call, .other => {
700 try w.transformations.append(.{ .delete_node = stmt });
701 },
702 }
703 try walkExpression(w, stmt);
704 },
705 }
706 }
707}
708
709fn walkArrayType(w: *Walk, array_type: Ast.full.ArrayType) Error!void {
710 try walkExpression(w, array_type.ast.elem_count);
711 if (array_type.ast.sentinel != 0) {
712 try walkExpression(w, array_type.ast.sentinel);
713 }
714 return walkExpression(w, array_type.ast.elem_type);
715}
716
717fn walkArrayInit(w: *Walk, array_init: Ast.full.ArrayInit) Error!void {
718 if (array_init.ast.type_expr != 0) {
719 try walkExpression(w, array_init.ast.type_expr); // T
720 }
721 for (array_init.ast.elements) |elem_init| {
722 try walkExpression(w, elem_init);
723 }
724}
725
726fn walkStructInit(
727 w: *Walk,
728 struct_node: Ast.Node.Index,
729 struct_init: Ast.full.StructInit,
730) Error!void {
731 _ = struct_node;
732 if (struct_init.ast.type_expr != 0) {
733 try walkExpression(w, struct_init.ast.type_expr); // T
734 }
735 for (struct_init.ast.fields) |field_init| {
736 try walkExpression(w, field_init);
737 }
738}
739
740fn walkCall(w: *Walk, call: Ast.full.Call) Error!void {
741 try walkExpression(w, call.ast.fn_expr);
742 try walkParamList(w, call.ast.params);
743}
744
745fn walkSlice(
746 w: *Walk,
747 slice_node: Ast.Node.Index,
748 slice: Ast.full.Slice,
749) Error!void {
750 _ = slice_node;
751 try walkExpression(w, slice.ast.sliced);
752 try walkExpression(w, slice.ast.start);
753 if (slice.ast.end != 0) {
754 try walkExpression(w, slice.ast.end);
755 }
756 if (slice.ast.sentinel != 0) {
757 try walkExpression(w, slice.ast.sentinel);
758 }
759}
760
761fn walkIdentifier(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
762 const ast = w.ast;
763 const token_tags = ast.tokens.items(.tag);
764 assert(token_tags[name_ident] == .identifier);
765 const name_bytes = ast.tokenSlice(name_ident);
766 _ = w.unreferenced_globals.swapRemove(name_bytes);
767}
768
769fn walkIdentifierNew(w: *Walk, name_ident: Ast.TokenIndex) Error!void {
770 _ = w;
771 _ = name_ident;
772}
773
774fn walkContainerDecl(
775 w: *Walk,
776 container_decl_node: Ast.Node.Index,
777 container_decl: Ast.full.ContainerDecl,
778) Error!void {
779 _ = container_decl_node;
780 if (container_decl.ast.arg != 0) {
781 try walkExpression(w, container_decl.ast.arg);
782 }
783 try walkMembers(w, container_decl.ast.members);
784}
785
786fn walkBuiltinCall(
787 w: *Walk,
788 call_node: Ast.Node.Index,
789 params: []const Ast.Node.Index,
790) Error!void {
791 const ast = w.ast;
792 const main_tokens = ast.nodes.items(.main_token);
793 const builtin_token = main_tokens[call_node];
794 const builtin_name = ast.tokenSlice(builtin_token);
795 const info = BuiltinFn.list.get(builtin_name).?;
796 switch (info.tag) {
797 .import => {
798 const operand_node = params[0];
799 const str_lit_token = main_tokens[operand_node];
800 const token_bytes = ast.tokenSlice(str_lit_token);
801 if (std.mem.endsWith(u8, token_bytes, ".zig\"")) {
802 const imported_string = std.zig.string_literal.parseAlloc(w.arena, token_bytes) catch
803 unreachable;
804 try w.transformations.append(.{ .inline_imported_file = .{
805 .builtin_call_node = call_node,
806 .imported_string = imported_string,
807 .in_scope_names = try std.StringArrayHashMapUnmanaged(void).init(
808 w.arena,
809 w.in_scope_names.keys(),
810 &.{},
811 ),
812 } });
813 }
814 },
815 else => {},
816 }
817 for (params) |param_node| {
818 try walkExpression(w, param_node);
819 }
820}
821
822fn walkFnProto(w: *Walk, fn_proto: Ast.full.FnProto) Error!void {
823 const ast = w.ast;
824
825 {
826 var it = fn_proto.iterate(ast);
827 while (it.next()) |param| {
828 if (param.type_expr != 0) {
829 try walkExpression(w, param.type_expr);
830 }
831 }
832 }
833
834 if (fn_proto.ast.align_expr != 0) {
835 try walkExpression(w, fn_proto.ast.align_expr);
836 }
837
838 if (fn_proto.ast.addrspace_expr != 0) {
839 try walkExpression(w, fn_proto.ast.addrspace_expr);
840 }
841
842 if (fn_proto.ast.section_expr != 0) {
843 try walkExpression(w, fn_proto.ast.section_expr);
844 }
845
846 if (fn_proto.ast.callconv_expr != 0) {
847 try walkExpression(w, fn_proto.ast.callconv_expr);
848 }
849
850 try walkExpression(w, fn_proto.ast.return_type);
851}
852
853fn walkExpressions(w: *Walk, expressions: []const Ast.Node.Index) Error!void {
854 for (expressions) |expression| {
855 try walkExpression(w, expression);
856 }
857}
858
859fn walkSwitchCase(w: *Walk, switch_case: Ast.full.SwitchCase) Error!void {
860 for (switch_case.ast.values) |value_expr| {
861 try walkExpression(w, value_expr);
862 }
863 try walkExpression(w, switch_case.ast.target_expr);
864}
865
866fn walkWhile(w: *Walk, node_index: Ast.Node.Index, while_node: Ast.full.While) Error!void {
867 assert(while_node.ast.cond_expr != 0);
868 assert(while_node.ast.then_expr != 0);
869
870 // Perform these transformations in this priority order:
871 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
872 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
873 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
874 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
875 if (!isTrueIdent(w.ast, while_node.ast.cond_expr) and
876 (while_node.ast.else_expr == 0 or isEmptyBlock(w.ast, while_node.ast.else_expr)))
877 {
878 try w.transformations.ensureUnusedCapacity(1);
879 w.transformations.appendAssumeCapacity(.{ .replace_with_true = while_node.ast.cond_expr });
880 } else if (!isFalseIdent(w.ast, while_node.ast.cond_expr) and isEmptyBlock(w.ast, while_node.ast.then_expr)) {
881 try w.transformations.ensureUnusedCapacity(1);
882 w.transformations.appendAssumeCapacity(.{ .replace_with_false = while_node.ast.cond_expr });
883 } else if (isTrueIdent(w.ast, while_node.ast.cond_expr)) {
884 try w.transformations.ensureUnusedCapacity(1);
885 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
886 .to_replace = node_index,
887 .replacement = while_node.ast.then_expr,
888 } });
889 } else if (isFalseIdent(w.ast, while_node.ast.cond_expr)) {
890 try w.transformations.ensureUnusedCapacity(1);
891 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
892 .to_replace = node_index,
893 .replacement = while_node.ast.else_expr,
894 } });
895 }
896
897 try walkExpression(w, while_node.ast.cond_expr); // condition
898
899 if (while_node.ast.cont_expr != 0) {
900 try walkExpression(w, while_node.ast.cont_expr);
901 }
902
903 if (while_node.ast.then_expr != 0) {
904 try walkExpression(w, while_node.ast.then_expr);
905 }
906 if (while_node.ast.else_expr != 0) {
907 try walkExpression(w, while_node.ast.else_expr);
908 }
909}
910
911fn walkFor(w: *Walk, for_node: Ast.full.For) Error!void {
912 try walkParamList(w, for_node.ast.inputs);
913 if (for_node.ast.then_expr != 0) {
914 try walkExpression(w, for_node.ast.then_expr);
915 }
916 if (for_node.ast.else_expr != 0) {
917 try walkExpression(w, for_node.ast.else_expr);
918 }
919}
920
921fn walkIf(w: *Walk, node_index: Ast.Node.Index, if_node: Ast.full.If) Error!void {
922 assert(if_node.ast.cond_expr != 0);
923 assert(if_node.ast.then_expr != 0);
924
925 // Perform these transformations in this priority order:
926 // 1. If the `else` expression is missing or an empty block, replace the condition with `if (true)` if it is not already.
927 // 2. If the `then` block is empty, replace the condition with `if (false)` if it is not already.
928 // 3. If the condition is `if (true)`, replace the `if` expression with the contents of the `then` expression.
929 // 4. If the condition is `if (false)`, replace the `if` expression with the contents of the `else` expression.
930 if (!isTrueIdent(w.ast, if_node.ast.cond_expr) and
931 (if_node.ast.else_expr == 0 or isEmptyBlock(w.ast, if_node.ast.else_expr)))
932 {
933 try w.transformations.ensureUnusedCapacity(1);
934 w.transformations.appendAssumeCapacity(.{ .replace_with_true = if_node.ast.cond_expr });
935 } else if (!isFalseIdent(w.ast, if_node.ast.cond_expr) and isEmptyBlock(w.ast, if_node.ast.then_expr)) {
936 try w.transformations.ensureUnusedCapacity(1);
937 w.transformations.appendAssumeCapacity(.{ .replace_with_false = if_node.ast.cond_expr });
938 } else if (isTrueIdent(w.ast, if_node.ast.cond_expr)) {
939 try w.transformations.ensureUnusedCapacity(1);
940 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
941 .to_replace = node_index,
942 .replacement = if_node.ast.then_expr,
943 } });
944 } else if (isFalseIdent(w.ast, if_node.ast.cond_expr)) {
945 try w.transformations.ensureUnusedCapacity(1);
946 w.transformations.appendAssumeCapacity(.{ .replace_node = .{
947 .to_replace = node_index,
948 .replacement = if_node.ast.else_expr,
949 } });
950 }
951
952 try walkExpression(w, if_node.ast.cond_expr); // condition
953
954 if (if_node.ast.then_expr != 0) {
955 try walkExpression(w, if_node.ast.then_expr);
956 }
957 if (if_node.ast.else_expr != 0) {
958 try walkExpression(w, if_node.ast.else_expr);
959 }
960}
961
962fn walkAsm(w: *Walk, asm_node: Ast.full.Asm) Error!void {
963 try walkExpression(w, asm_node.ast.template);
964 for (asm_node.ast.items) |item| {
965 try walkExpression(w, item);
966 }
967}
968
969fn walkParamList(w: *Walk, params: []const Ast.Node.Index) Error!void {
970 for (params) |param_node| {
971 try walkExpression(w, param_node);
972 }
973}
974
975/// Check if it is already gutted (i.e. its body replaced with `@trap()`).
976fn isFnBodyGutted(ast: *const Ast, body_node: Ast.Node.Index) bool {
977 // skip over discards
978 const node_tags = ast.nodes.items(.tag);
979 const datas = ast.nodes.items(.data);
980 var statements_buf: [2]Ast.Node.Index = undefined;
981 const statements = switch (node_tags[body_node]) {
982 .block_two,
983 .block_two_semicolon,
984 => blk: {
985 statements_buf[0..2].* = .{ datas[body_node].lhs, datas[body_node].rhs };
986 break :blk if (datas[body_node].lhs == 0)
987 statements_buf[0..0]
988 else if (datas[body_node].rhs == 0)
989 statements_buf[0..1]
990 else
991 statements_buf[0..2];
992 },
993
994 .block,
995 .block_semicolon,
996 => ast.extra_data[datas[body_node].lhs..datas[body_node].rhs],
997
998 else => return false,
999 };
1000 var i: usize = 0;
1001 while (i < statements.len) : (i += 1) {
1002 switch (categorizeStmt(ast, statements[i])) {
1003 .discard_identifier => continue,
1004 .trap_call => return i + 1 == statements.len,
1005 else => return false,
1006 }
1007 }
1008 return false;
1009}
1010
1011const StmtCategory = enum {
1012 discard_undefined,
1013 discard_identifier,
1014 trap_call,
1015 other,
1016};
1017
1018fn categorizeStmt(ast: *const Ast, stmt: Ast.Node.Index) StmtCategory {
1019 const node_tags = ast.nodes.items(.tag);
1020 const datas = ast.nodes.items(.data);
1021 const main_tokens = ast.nodes.items(.main_token);
1022 switch (node_tags[stmt]) {
1023 .builtin_call_two, .builtin_call_two_comma => {
1024 if (datas[stmt].lhs == 0) {
1025 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{});
1026 } else if (datas[stmt].rhs == 0) {
1027 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{datas[stmt].lhs});
1028 } else {
1029 return categorizeBuiltinCall(ast, main_tokens[stmt], &.{ datas[stmt].lhs, datas[stmt].rhs });
1030 }
1031 },
1032 .builtin_call, .builtin_call_comma => {
1033 const params = ast.extra_data[datas[stmt].lhs..datas[stmt].rhs];
1034 return categorizeBuiltinCall(ast, main_tokens[stmt], params);
1035 },
1036 .assign => {
1037 const infix = datas[stmt];
1038 if (isDiscardIdent(ast, infix.lhs) and node_tags[infix.rhs] == .identifier) {
1039 const name_bytes = ast.tokenSlice(main_tokens[infix.rhs]);
1040 if (std.mem.eql(u8, name_bytes, "undefined")) {
1041 return .discard_undefined;
1042 } else {
1043 return .discard_identifier;
1044 }
1045 }
1046 return .other;
1047 },
1048 else => return .other,
1049 }
1050}
1051
1052fn categorizeBuiltinCall(
1053 ast: *const Ast,
1054 builtin_token: Ast.TokenIndex,
1055 params: []const Ast.Node.Index,
1056) StmtCategory {
1057 if (params.len != 0) return .other;
1058 const name_bytes = ast.tokenSlice(builtin_token);
1059 if (std.mem.eql(u8, name_bytes, "@trap"))
1060 return .trap_call;
1061 return .other;
1062}
1063
1064fn isDiscardIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1065 return isMatchingIdent(ast, node, "_");
1066}
1067
1068fn isUndefinedIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1069 return isMatchingIdent(ast, node, "undefined");
1070}
1071
1072fn isTrueIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1073 return isMatchingIdent(ast, node, "true");
1074}
1075
1076fn isFalseIdent(ast: *const Ast, node: Ast.Node.Index) bool {
1077 return isMatchingIdent(ast, node, "false");
1078}
1079
1080fn isMatchingIdent(ast: *const Ast, node: Ast.Node.Index, string: []const u8) bool {
1081 const node_tags = ast.nodes.items(.tag);
1082 const main_tokens = ast.nodes.items(.main_token);
1083 switch (node_tags[node]) {
1084 .identifier => {
1085 const token_index = main_tokens[node];
1086 const name_bytes = ast.tokenSlice(token_index);
1087 return std.mem.eql(u8, name_bytes, string);
1088 },
1089 else => return false,
1090 }
1091}
1092
1093fn isEmptyBlock(ast: *const Ast, node: Ast.Node.Index) bool {
1094 const node_tags = ast.nodes.items(.tag);
1095 const node_data = ast.nodes.items(.data);
1096 switch (node_tags[node]) {
1097 .block_two => {
1098 return node_data[node].lhs == 0 and node_data[node].rhs == 0;
1099 },
1100 else => return false,
1101 }
1102}
lib/test_runner.zig deleted-249
...@@ -1,249 +0,0 @@
1//! Default test runner for unit tests.
2const std = @import("std");
3const io = std.io;
4const builtin = @import("builtin");
5
6pub const std_options = .{
7 .logFn = log,
8};
9
10var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
13
14pub fn main() void {
15 if (builtin.zig_backend == .stage2_aarch64) {
16 return mainSimple() catch @panic("test failure");
17 }
18
19 const args = std.process.argsAlloc(fba.allocator()) catch
20 @panic("unable to parse command line args");
21
22 var listen = false;
23
24 for (args[1..]) |arg| {
25 if (std.mem.eql(u8, arg, "--listen=-")) {
26 listen = true;
27 } else {
28 @panic("unrecognized command line argument");
29 }
30 }
31
32 if (listen) {
33 return mainServer() catch @panic("internal test runner failure");
34 } else {
35 return mainTerminal();
36 }
37}
38
39fn mainServer() !void {
40 var server = try std.zig.Server.init(.{
41 .gpa = fba.allocator(),
42 .in = std.io.getStdIn(),
43 .out = std.io.getStdOut(),
44 .zig_version = builtin.zig_version_string,
45 });
46 defer server.deinit();
47
48 while (true) {
49 const hdr = try server.receiveMessage();
50 switch (hdr.tag) {
51 .exit => {
52 return std.process.exit(0);
53 },
54 .query_test_metadata => {
55 std.testing.allocator_instance = .{};
56 defer if (std.testing.allocator_instance.deinit() == .leak) {
57 @panic("internal test runner memory leak");
58 };
59
60 var string_bytes: std.ArrayListUnmanaged(u8) = .{};
61 defer string_bytes.deinit(std.testing.allocator);
62 try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
63
64 const test_fns = builtin.test_functions;
65 const names = try std.testing.allocator.alloc(u32, test_fns.len);
66 defer std.testing.allocator.free(names);
67 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
68 defer std.testing.allocator.free(expected_panic_msgs);
69
70 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
71 name.* = @as(u32, @intCast(string_bytes.items.len));
72 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
73 string_bytes.appendSliceAssumeCapacity(test_fn.name);
74 string_bytes.appendAssumeCapacity(0);
75 expected_panic_msg.* = 0;
76 }
77
78 try server.serveTestMetadata(.{
79 .names = names,
80 .expected_panic_msgs = expected_panic_msgs,
81 .string_bytes = string_bytes.items,
82 });
83 },
84
85 .run_test => {
86 std.testing.allocator_instance = .{};
87 log_err_count = 0;
88 const index = try server.receiveBody_u32();
89 const test_fn = builtin.test_functions[index];
90 var fail = false;
91 var skip = false;
92 var leak = false;
93 test_fn.func() catch |err| switch (err) {
94 error.SkipZigTest => skip = true,
95 else => {
96 fail = true;
97 if (@errorReturnTrace()) |trace| {
98 std.debug.dumpStackTrace(trace.*);
99 }
100 },
101 };
102 leak = std.testing.allocator_instance.deinit() == .leak;
103 try server.serveTestResults(.{
104 .index = index,
105 .flags = .{
106 .fail = fail,
107 .skip = skip,
108 .leak = leak,
109 .log_err_count = std.math.lossyCast(std.meta.FieldType(
110 std.zig.Server.Message.TestResults.Flags,
111 .log_err_count,
112 ), log_err_count),
113 },
114 });
115 },
116
117 else => {
118 std.debug.print("unsupported message: {x}", .{@intFromEnum(hdr.tag)});
119 std.process.exit(1);
120 },
121 }
122 }
123}
124
125fn mainTerminal() void {
126 const test_fn_list = builtin.test_functions;
127 var ok_count: usize = 0;
128 var skip_count: usize = 0;
129 var fail_count: usize = 0;
130 var progress = std.Progress{
131 .dont_print_on_dumb = true,
132 };
133 const root_node = progress.start("Test", test_fn_list.len);
134 const have_tty = progress.terminal != null and
135 (progress.supports_ansi_escape_codes or progress.is_windows_terminal);
136
137 var async_frame_buffer: []align(builtin.target.stackAlignment()) u8 = undefined;
138 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
139 // ignores the alignment of the slice.
140 async_frame_buffer = &[_]u8{};
141
142 var leaks: usize = 0;
143 for (test_fn_list, 0..) |test_fn, i| {
144 std.testing.allocator_instance = .{};
145 defer {
146 if (std.testing.allocator_instance.deinit() == .leak) {
147 leaks += 1;
148 }
149 }
150 std.testing.log_level = .warn;
151
152 var test_node = root_node.start(test_fn.name, 0);
153 test_node.activate();
154 progress.refresh();
155 if (!have_tty) {
156 std.debug.print("{d}/{d} {s}... ", .{ i + 1, test_fn_list.len, test_fn.name });
157 }
158 if (test_fn.func()) |_| {
159 ok_count += 1;
160 test_node.end();
161 if (!have_tty) std.debug.print("OK\n", .{});
162 } else |err| switch (err) {
163 error.SkipZigTest => {
164 skip_count += 1;
165 progress.log("SKIP\n", .{});
166 test_node.end();
167 },
168 else => {
169 fail_count += 1;
170 progress.log("FAIL ({s})\n", .{@errorName(err)});
171 if (@errorReturnTrace()) |trace| {
172 std.debug.dumpStackTrace(trace.*);
173 }
174 test_node.end();
175 },
176 }
177 }
178 root_node.end();
179 if (ok_count == test_fn_list.len) {
180 std.debug.print("All {d} tests passed.\n", .{ok_count});
181 } else {
182 std.debug.print("{d} passed; {d} skipped; {d} failed.\n", .{ ok_count, skip_count, fail_count });
183 }
184 if (log_err_count != 0) {
185 std.debug.print("{d} errors were logged.\n", .{log_err_count});
186 }
187 if (leaks != 0) {
188 std.debug.print("{d} tests leaked memory.\n", .{leaks});
189 }
190 if (leaks != 0 or log_err_count != 0 or fail_count != 0) {
191 std.process.exit(1);
192 }
193}
194
195pub fn log(
196 comptime message_level: std.log.Level,
197 comptime scope: @Type(.EnumLiteral),
198 comptime format: []const u8,
199 args: anytype,
200) void {
201 if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {
202 log_err_count +|= 1;
203 }
204 if (@intFromEnum(message_level) <= @intFromEnum(std.testing.log_level)) {
205 std.debug.print(
206 "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",
207 args,
208 );
209 }
210}
211
212/// Simpler main(), exercising fewer language features, so that
213/// work-in-progress backends can handle it.
214pub fn mainSimple() anyerror!void {
215 const enable_print = false;
216 const print_all = false;
217
218 var passed: u64 = 0;
219 var skipped: u64 = 0;
220 var failed: u64 = 0;
221 const stderr = if (enable_print) std.io.getStdErr() else {};
222 for (builtin.test_functions) |test_fn| {
223 if (enable_print and print_all) {
224 stderr.writeAll(test_fn.name) catch {};
225 stderr.writeAll("... ") catch {};
226 }
227 test_fn.func() catch |err| {
228 if (enable_print and !print_all) {
229 stderr.writeAll(test_fn.name) catch {};
230 stderr.writeAll("... ") catch {};
231 }
232 if (err != error.SkipZigTest) {
233 if (enable_print) stderr.writeAll("FAIL\n") catch {};
234 failed += 1;
235 if (!enable_print) return err;
236 continue;
237 }
238 if (enable_print) stderr.writeAll("SKIP\n") catch {};
239 skipped += 1;
240 continue;
241 };
242 if (enable_print and print_all) stderr.writeAll("PASS\n") catch {};
243 passed += 1;
244 }
245 if (enable_print) {
246 stderr.writer().print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
247 if (failed != 0) std.process.exit(1);
248 }
249}
src/main.zig+6-2
...@@ -2739,6 +2739,7 @@ fn buildOutputType(...@@ -2739,6 +2739,7 @@ fn buildOutputType(
2739 .paths = .{2739 .paths = .{
2740 .root = .{2740 .root = .{
2741 .root_dir = zig_lib_directory,2741 .root_dir = zig_lib_directory,
2742 .sub_path = "compiler",
2742 },2743 },
2743 .root_src_path = "test_runner.zig",2744 .root_src_path = "test_runner.zig",
2744 },2745 },
...@@ -5385,7 +5386,10 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -5385,7 +5386,10 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
5385 },5386 },
5386 .root_src_path = fs.path.basename(runner),5387 .root_src_path = fs.path.basename(runner),
5387 } else .{5388 } else .{
5388 .root = .{ .root_dir = zig_lib_directory },5389 .root = .{
5390 .root_dir = zig_lib_directory,
5391 .sub_path = "compiler",
5392 },
5389 .root_src_path = "build_runner.zig",5393 .root_src_path = "build_runner.zig",
5390 };5394 };
53915395
...@@ -5767,7 +5771,7 @@ fn jitCmd(...@@ -5767,7 +5771,7 @@ fn jitCmd(
5767 const main_mod_paths: Package.Module.CreateOptions.Paths = .{5771 const main_mod_paths: Package.Module.CreateOptions.Paths = .{
5768 .root = .{5772 .root = .{
5769 .root_dir = zig_lib_directory,5773 .root_dir = zig_lib_directory,
5770 .sub_path = "std/zig",5774 .sub_path = "compiler",
5771 },5775 },
5772 .root_src_path = root_src_path,5776 .root_src_path = root_src_path,
5773 };5777 };