| ... | @@ -3,13 +3,7 @@ const fatal = std.process.fatal; | ... | @@ -3,13 +3,7 @@ const fatal = std.process.fatal; |
| 3 | const Allocator = std.mem.Allocator; | 3 | const Allocator = std.mem.Allocator; |
| 4 | const Cache = std.Build.Cache; | 4 | const Cache = std.Build.Cache; |
| 5 | | 5 | |
| 6 | const 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]"; | 6 | const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]"; |
| 7 | | | |
| 8 | const EmitMode = enum { | | |
| 9 | none, | | |
| 10 | bin, | | |
| 11 | c, | | |
| 12 | }; | | |
| 13 | | 7 | |
| 14 | pub fn main() !void { | 8 | pub 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; |
| 26 | | 19 | |
| ... | @@ -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 | else | 65 | else |
| 77 | null; | 66 | null; |
| 78 | | 67 | |
| 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; |
| 109 | | 69 | |
| 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", |
| 117 | | 77 | 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 | } |
| 135 | | 102 | |
| 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 | } |
| 146 | | 128 | |
| 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 | }; |
| 148 | | 139 | |
| 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(); | | |
| 154 | | 141 | |
| 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(); |
| 158 | | 147 | |
| 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(); |
| 162 | | 151 | |
| 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 | } |
| 167 | | 160 | |
| 168 | try eval.end(&poller); | 161 | try eval.end(&poller); |
| 169 | | 162 | |
| 170 | waitChild(&child); | 163 | waitChild(&child); |
| | 164 | } |
| 171 | } | 165 | } |
| 172 | | 166 | |
| 173 | const Eval = struct { | 167 | const 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), |
| 184 | | 178 | |
| ... | @@ -262,7 +256,7 @@ const Eval = struct { | ... | @@ -262,7 +256,7 @@ const Eval = struct { |
| 262 | } | 256 | } |
| 263 | } | 257 | } |
| 264 | | 258 | |
| 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 | }; |
| 341 | | 335 | |
| 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 { |
| 468 | const Case = struct { | 462 | const 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 | }; |
| 472 | | 485 | |
| 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 | }; |
| 499 | | 512 | |
| 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 | } |
| 567 | | 588 | |
| | 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 | } |
| 572 | | 597 | |
| 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 | }; |