authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-25 20:21:27+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-25 20:39:31+01:00
log054dbb6798bd81cbea809999b2fdffc1b59e5ca3
treedd07ce8a033b77471bad19011d6e0c5059f531b3
parent1714b74edfdf1dbc52f8dd1555abb0ff43dfdff8
signaturelock-open Commit is signed but in an unrecognized format.

incr-check: specify backend in manifest, allow multiple targets/backends


10 files changed, 155 insertions(+), 121 deletions(-)

test/incremental/add_decl+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
test/incremental/add_decl_namespaced+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
test/incremental/delete_comptime_decls+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4pub fn main() void {}5pub fn main() void {}
test/incremental/hello+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
test/incremental/modify_inline_fn+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
test/incremental/move_src+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
test/incremental/remove_enum_field+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const MyEnum = enum(u8) {5const MyEnum = enum(u8) {
test/incremental/type_becomes_comptime_only+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const SomeType = u32;5const SomeType = u32;
test/incremental/unreferenced_error+2-1
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1#target=x86_64-linux1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
2#update=initial version3#update=initial version
3#file=main.zig4#file=main.zig
4const std = @import("std");5const std = @import("std");
tools/incr-check.zig+137-112
...@@ -3,13 +3,7 @@ const fatal = std.process.fatal;...@@ -3,13 +3,7 @@ const fatal = std.process.fatal;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4const Cache = std.Build.Cache;4const Cache = std.Build.Cache;
55
6const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--emit none|bin|c] [--zig-cc-binary /path/to/zig]";6const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]";
7
8const EmitMode = enum {
9 none,
10 bin,
11 c,
12};
137
14pub fn main() !void {8pub fn main() !void {
15 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);9 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
...@@ -20,7 +14,6 @@ pub fn main() !void {...@@ -20,7 +14,6 @@ pub fn main() !void {
20 var opt_input_file_name: ?[]const u8 = null;14 var opt_input_file_name: ?[]const u8 = null;
21 var opt_lib_dir: ?[]const u8 = null;15 var opt_lib_dir: ?[]const u8 = null;
22 var opt_cc_zig: ?[]const u8 = null;16 var opt_cc_zig: ?[]const u8 = null;
23 var emit: EmitMode = .bin;
24 var debug_zcu = false;17 var debug_zcu = false;
25 var debug_link = false;18 var debug_link = false;
2619
...@@ -28,11 +21,7 @@ pub fn main() !void {...@@ -28,11 +21,7 @@ pub fn main() !void {
28 _ = arg_it.skip();21 _ = arg_it.skip();
29 while (arg_it.next()) |arg| {22 while (arg_it.next()) |arg| {
30 if (arg.len > 0 and arg[0] == '-') {23 if (arg.len > 0 and arg[0] == '-') {
31 if (std.mem.eql(u8, arg, "--emit")) {24 if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
32 const emit_str = arg_it.next() orelse fatal("expected arg after '--emit'\n{s}", .{usage});
33 emit = std.meta.stringToEnum(EmitMode, emit_str) orelse
34 fatal("invalid emit mode '{s}'\n{s}", .{ emit_str, usage });
35 } else if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
36 opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage});25 opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage});
37 } else if (std.mem.eql(u8, arg, "--debug-zcu")) {26 } else if (std.mem.eql(u8, arg, "--debug-zcu")) {
38 debug_zcu = true;27 debug_zcu = true;
...@@ -76,109 +65,114 @@ pub fn main() !void {...@@ -76,109 +65,114 @@ pub fn main() !void {
76 else65 else
77 null;66 null;
7867
79 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
80 try child_args.appendSlice(arena, &.{
81 resolved_zig_exe,
82 "build-exe",
83 case.root_source_file,
84 "-fincremental",
85 "-target",
86 case.target_query,
87 "--cache-dir",
88 ".local-cache",
89 "--global-cache-dir",
90 ".global_cache",
91 "--listen=-",
92 });
93 if (opt_resolved_lib_dir) |resolved_lib_dir| {
94 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
95 }
96 switch (emit) {
97 .bin => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }),
98 .none => try child_args.append(arena, "-fno-emit-bin"),
99 .c => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
100 }
101 if (debug_zcu) {
102 try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });
103 }
104 if (debug_link) {
105 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
106 }
107
108 const debug_log_verbose = debug_zcu or debug_link;68 const debug_log_verbose = debug_zcu or debug_link;
10969
110 var child = std.process.Child.init(child_args.items, arena);70 for (case.targets) |target| {
111 child.stdin_behavior = .Pipe;71 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
112 child.stdout_behavior = .Pipe;72
113 child.stderr_behavior = .Pipe;73 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
114 child.progress_node = child_prog_node;74 try child_args.appendSlice(arena, &.{
115 child.cwd_dir = tmp_dir;75 resolved_zig_exe,
116 child.cwd = tmp_dir_path;76 "build-exe",
11777 case.root_source_file,
118 var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty;78 "-fincremental",
119 if (emit == .c) {
120 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
121 try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe)
122 else
123 resolved_zig_exe;
124
125 try cc_child_args.appendSlice(arena, &.{
126 resolved_cc_zig_exe,
127 "cc",
128 "-target",79 "-target",
129 case.target_query,80 target.query,
130 "-I",81 "--cache-dir",
131 opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using '--emit c'", .{}),82 ".local-cache",
132 "-o",83 "--global-cache-dir",
84 ".global_cache",
85 "--listen=-",
133 });86 });
134 }87 if (opt_resolved_lib_dir) |resolved_lib_dir| {
88 try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
89 }
90 switch (target.backend) {
91 .sema => try child_args.append(arena, "-fno-emit-bin"),
92 .selfhosted => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }),
93 .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),
94 .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
95 }
96 if (debug_zcu) {
97 try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });
98 }
99 if (debug_link) {
100 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
101 }
135102
136 var eval: Eval = .{103 var child = std.process.Child.init(child_args.items, arena);
137 .arena = arena,104 child.stdin_behavior = .Pipe;
138 .case = case,105 child.stdout_behavior = .Pipe;
139 .tmp_dir = tmp_dir,106 child.stderr_behavior = .Pipe;
140 .tmp_dir_path = tmp_dir_path,107 child.progress_node = child_prog_node;
141 .child = &child,108 child.cwd_dir = tmp_dir;
142 .allow_stderr = debug_log_verbose,109 child.cwd = tmp_dir_path;
143 .emit = emit,110
144 .cc_child_args = &cc_child_args,111 var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty;
145 };112 if (target.backend == .cbe) {
113 const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
114 try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe)
115 else
116 resolved_zig_exe;
117
118 try cc_child_args.appendSlice(arena, &.{
119 resolved_cc_zig_exe,
120 "cc",
121 "-target",
122 target.query,
123 "-I",
124 opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}),
125 "-o",
126 });
127 }
146128
147 try child.spawn();129 var eval: Eval = .{
130 .arena = arena,
131 .case = case,
132 .target = target,
133 .tmp_dir = tmp_dir,
134 .tmp_dir_path = tmp_dir_path,
135 .child = &child,
136 .allow_stderr = debug_log_verbose,
137 .cc_child_args = &cc_child_args,
138 };
148139
149 var poller = std.io.poll(arena, Eval.StreamEnum, .{140 try child.spawn();
150 .stdout = child.stdout.?,
151 .stderr = child.stderr.?,
152 });
153 defer poller.deinit();
154141
155 for (case.updates) |update| {142 var poller = std.io.poll(arena, Eval.StreamEnum, .{
156 var update_node = prog_node.start(update.name, 0);143 .stdout = child.stdout.?,
157 defer update_node.end();144 .stderr = child.stderr.?,
145 });
146 defer poller.deinit();
158147
159 if (debug_log_verbose) {148 for (case.updates) |update| {
160 std.log.info("=== START UPDATE '{s}' ===", .{update.name});149 var update_node = prog_node.start(update.name, 0);
161 }150 defer update_node.end();
162151
163 eval.write(update);152 if (debug_log_verbose) {
164 try eval.requestUpdate();153 std.log.scoped(.status).info("update: '{s}'", .{update.name});
165 try eval.check(&poller, update, update_node);154 }
166 }155
156 eval.write(update);
157 try eval.requestUpdate();
158 try eval.check(&poller, update, update_node);
159 }
167160
168 try eval.end(&poller);161 try eval.end(&poller);
169162
170 waitChild(&child);163 waitChild(&child);
164 }
171}165}
172166
173const Eval = struct {167const Eval = struct {
174 arena: Allocator,168 arena: Allocator,
175 case: Case,169 case: Case,
170 target: Case.Target,
176 tmp_dir: std.fs.Dir,171 tmp_dir: std.fs.Dir,
177 tmp_dir_path: []const u8,172 tmp_dir_path: []const u8,
178 child: *std.process.Child,173 child: *std.process.Child,
179 allow_stderr: bool,174 allow_stderr: bool,
180 emit: EmitMode,175 /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.
181 /// When `emit == .c`, this contains the first few arguments to `zig cc` to build the generated binary.
182 /// The arguments `out.c in.c` must be appended before spawning the subprocess.176 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
183 cc_child_args: *std.ArrayListUnmanaged([]const u8),177 cc_child_args: *std.ArrayListUnmanaged([]const u8),
184178
...@@ -262,7 +256,7 @@ const Eval = struct {...@@ -262,7 +256,7 @@ const Eval = struct {
262 }256 }
263 }257 }
264258
265 if (eval.emit == .none) {259 if (eval.target.backend == .sema) {
266 try eval.checkSuccessOutcome(update, null, prog_node);260 try eval.checkSuccessOutcome(update, null, prog_node);
267 // This message indicates the end of the update.261 // This message indicates the end of the update.
268 stdout.discard(body.len);262 stdout.discard(body.len);
...@@ -275,11 +269,11 @@ const Eval = struct {...@@ -275,11 +269,11 @@ const Eval = struct {
275 const bin_name = try std.zig.binNameAlloc(arena, .{269 const bin_name = try std.zig.binNameAlloc(arena, .{
276 .root_name = name,270 .root_name = name,
277 .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{271 .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{
278 .arch_os_abi = eval.case.target_query,272 .arch_os_abi = eval.target.query,
279 .object_format = switch (eval.emit) {273 .object_format = switch (eval.target.backend) {
280 .none => unreachable,274 .sema => unreachable,
281 .bin => null,275 .selfhosted, .llvm => null,
282 .c => "c",276 .cbe => "c",
283 },277 },
284 })),278 })),
285 .output_mode = .Exe,279 .output_mode = .Exe,
...@@ -335,14 +329,14 @@ const Eval = struct {...@@ -335,14 +329,14 @@ const Eval = struct {
335 .stdout, .exit_code => {},329 .stdout, .exit_code => {},
336 }330 }
337 const emitted_path = opt_emitted_path orelse {331 const emitted_path = opt_emitted_path orelse {
338 std.debug.assert(eval.emit == .none);332 std.debug.assert(eval.target.backend == .sema);
339 return;333 return;
340 };334 };
341335
342 const binary_path = switch (eval.emit) {336 const binary_path = switch (eval.target.backend) {
343 .none => unreachable,337 .sema => unreachable,
344 .bin => emitted_path,338 .selfhosted, .llvm => emitted_path,
345 .c => bin: {339 .cbe => bin: {
346 const rand_int = std.crypto.random.int(u64);340 const rand_int = std.crypto.random.int(u64);
347 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);341 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
348 try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node);342 try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node);
...@@ -468,7 +462,26 @@ const Eval = struct {...@@ -468,7 +462,26 @@ const Eval = struct {
468const Case = struct {462const Case = struct {
469 updates: []Update,463 updates: []Update,
470 root_source_file: []const u8,464 root_source_file: []const u8,
471 target_query: []const u8,465 targets: []const Target,
466
467 const Target = struct {
468 query: []const u8,
469 backend: Backend,
470 const Backend = enum {
471 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
472 /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
473 sema,
474 /// Use the self-hosted code generation backend for this target.
475 /// Corresponds to `-fno-llvm -fno-lld`.
476 selfhosted,
477 /// Use the LLVM backend.
478 /// Corresponds to `-fllvm -flld`.
479 llvm,
480 /// Use the C backend. The output is compiled with `zig cc`.
481 /// Corresponds to `-ofmt=c`.
482 cbe,
483 };
484 };
472485
473 const Update = struct {486 const Update = struct {
474 name: []const u8,487 name: []const u8,
...@@ -498,9 +511,9 @@ const Case = struct {...@@ -498,9 +511,9 @@ const Case = struct {
498 };511 };
499512
500 fn parse(arena: Allocator, bytes: []const u8) !Case {513 fn parse(arena: Allocator, bytes: []const u8) !Case {
514 var targets: std.ArrayListUnmanaged(Target) = .empty;
501 var updates: std.ArrayListUnmanaged(Update) = .empty;515 var updates: std.ArrayListUnmanaged(Update) = .empty;
502 var changes: std.ArrayListUnmanaged(FullContents) = .empty;516 var changes: std.ArrayListUnmanaged(FullContents) = .empty;
503 var target_query: ?[]const u8 = null;
504 var it = std.mem.splitScalar(u8, bytes, '\n');517 var it = std.mem.splitScalar(u8, bytes, '\n');
505 var line_n: usize = 1;518 var line_n: usize = 1;
506 var root_source_file: ?[]const u8 = null;519 var root_source_file: ?[]const u8 = null;
...@@ -512,8 +525,16 @@ const Case = struct {...@@ -512,8 +525,16 @@ const Case = struct {
512 if (val.len == 0) {525 if (val.len == 0) {
513 fatal("line {d}: missing value", .{line_n});526 fatal("line {d}: missing value", .{line_n});
514 } else if (std.mem.eql(u8, key, "target")) {527 } else if (std.mem.eql(u8, key, "target")) {
515 if (target_query != null) fatal("line {d}: duplicate target", .{line_n});528 const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse
516 target_query = val;529 fatal("line {d}: target does not include backend", .{line_n});
530 const query = val[0..split_idx];
531 const backend_str = val[split_idx + 1 ..];
532 const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse
533 fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });
534 try targets.append(arena, .{
535 .query = query,
536 .backend = backend,
537 });
517 } else if (std.mem.eql(u8, key, "update")) {538 } else if (std.mem.eql(u8, key, "update")) {
518 if (updates.items.len > 0) {539 if (updates.items.len > 0) {
519 const last_update = &updates.items[updates.items.len - 1];540 const last_update = &updates.items[updates.items.len - 1];
...@@ -565,15 +586,19 @@ const Case = struct {...@@ -565,15 +586,19 @@ const Case = struct {
565 }586 }
566 }587 }
567588
589 if (targets.items.len == 0) {
590 fatal("missing target", .{});
591 }
592
568 if (changes.items.len > 0) {593 if (changes.items.len > 0) {
569 const last_update = &updates.items[updates.items.len - 1];594 const last_update = &updates.items[updates.items.len - 1];
570 last_update.changes = try changes.toOwnedSlice(arena);595 last_update.changes = changes.items; // arena so no need for toOwnedSlice
571 }596 }
572597
573 return .{598 return .{
574 .updates = updates.items,599 .updates = updates.items,
575 .root_source_file = root_source_file orelse fatal("missing root source file", .{}),600 .root_source_file = root_source_file orelse fatal("missing root source file", .{}),
576 .target_query = target_query orelse fatal("missing target", .{}),601 .targets = targets.items, // arena so no need for toOwnedSlice
577 };602 };
578 }603 }
579};604};