| ... | ... | @@ -3,13 +3,7 @@ const fatal = std.process.fatal; |
| 3 | 3 | const Allocator = std.mem.Allocator; |
| 4 | 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]"; |
| 7 | | |
| 8 | | const EmitMode = enum { |
| 9 | | none, |
| 10 | | bin, |
| 11 | | c, |
| 12 | | }; |
| 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]"; |
| 13 | 7 | |
| 14 | 8 | pub fn main() !void { |
| 15 | 9 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| ... | ... | @@ -20,7 +14,6 @@ pub fn main() !void { |
| 20 | 14 | var opt_input_file_name: ?[]const u8 = null; |
| 21 | 15 | var opt_lib_dir: ?[]const u8 = null; |
| 22 | 16 | var opt_cc_zig: ?[]const u8 = null; |
| 23 | | var emit: EmitMode = .bin; |
| 24 | 17 | var debug_zcu = false; |
| 25 | 18 | var debug_link = false; |
| 26 | 19 | |
| ... | ... | @@ -28,11 +21,7 @@ pub fn main() !void { |
| 28 | 21 | _ = arg_it.skip(); |
| 29 | 22 | while (arg_it.next()) |arg| { |
| 30 | 23 | if (arg.len > 0 and arg[0] == '-') { |
| 31 | | if (std.mem.eql(u8, arg, "--emit")) { |
| 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")) { |
| 24 | if (std.mem.eql(u8, arg, "--zig-lib-dir")) { |
| 36 | 25 | opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage}); |
| 37 | 26 | } else if (std.mem.eql(u8, arg, "--debug-zcu")) { |
| 38 | 27 | debug_zcu = true; |
| ... | ... | @@ -76,109 +65,114 @@ pub fn main() !void { |
| 76 | 65 | else |
| 77 | 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 | 68 | const debug_log_verbose = debug_zcu or debug_link; |
| 109 | 69 | |
| 110 | | var child = std.process.Child.init(child_args.items, arena); |
| 111 | | child.stdin_behavior = .Pipe; |
| 112 | | child.stdout_behavior = .Pipe; |
| 113 | | child.stderr_behavior = .Pipe; |
| 114 | | child.progress_node = child_prog_node; |
| 115 | | child.cwd_dir = tmp_dir; |
| 116 | | child.cwd = tmp_dir_path; |
| 117 | | |
| 118 | | var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty; |
| 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", |
| 70 | for (case.targets) |target| { |
| 71 | std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) }); |
| 72 | |
| 73 | var child_args: std.ArrayListUnmanaged([]const u8) = .empty; |
| 74 | try child_args.appendSlice(arena, &.{ |
| 75 | resolved_zig_exe, |
| 76 | "build-exe", |
| 77 | case.root_source_file, |
| 78 | "-fincremental", |
| 128 | 79 | "-target", |
| 129 | | case.target_query, |
| 130 | | "-I", |
| 131 | | opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using '--emit c'", .{}), |
| 132 | | "-o", |
| 80 | target.query, |
| 81 | "--cache-dir", |
| 82 | ".local-cache", |
| 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 = .{ |
| 137 | | .arena = arena, |
| 138 | | .case = case, |
| 139 | | .tmp_dir = tmp_dir, |
| 140 | | .tmp_dir_path = tmp_dir_path, |
| 141 | | .child = &child, |
| 142 | | .allow_stderr = debug_log_verbose, |
| 143 | | .emit = emit, |
| 144 | | .cc_child_args = &cc_child_args, |
| 145 | | }; |
| 103 | var child = std.process.Child.init(child_args.items, arena); |
| 104 | child.stdin_behavior = .Pipe; |
| 105 | child.stdout_behavior = .Pipe; |
| 106 | child.stderr_behavior = .Pipe; |
| 107 | child.progress_node = child_prog_node; |
| 108 | child.cwd_dir = tmp_dir; |
| 109 | child.cwd = tmp_dir_path; |
| 110 | |
| 111 | var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty; |
| 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, .{ |
| 150 | | .stdout = child.stdout.?, |
| 151 | | .stderr = child.stderr.?, |
| 152 | | }); |
| 153 | | defer poller.deinit(); |
| 140 | try child.spawn(); |
| 154 | 141 | |
| 155 | | for (case.updates) |update| { |
| 156 | | var update_node = prog_node.start(update.name, 0); |
| 157 | | defer update_node.end(); |
| 142 | var poller = std.io.poll(arena, Eval.StreamEnum, .{ |
| 143 | .stdout = child.stdout.?, |
| 144 | .stderr = child.stderr.?, |
| 145 | }); |
| 146 | defer poller.deinit(); |
| 158 | 147 | |
| 159 | | if (debug_log_verbose) { |
| 160 | | std.log.info("=== START UPDATE '{s}' ===", .{update.name}); |
| 161 | | } |
| 148 | for (case.updates) |update| { |
| 149 | var update_node = prog_node.start(update.name, 0); |
| 150 | defer update_node.end(); |
| 162 | 151 | |
| 163 | | eval.write(update); |
| 164 | | try eval.requestUpdate(); |
| 165 | | try eval.check(&poller, update, update_node); |
| 166 | | } |
| 152 | if (debug_log_verbose) { |
| 153 | std.log.scoped(.status).info("update: '{s}'", .{update.name}); |
| 154 | } |
| 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 | 167 | const Eval = struct { |
| 174 | 168 | arena: Allocator, |
| 175 | 169 | case: Case, |
| 170 | target: Case.Target, |
| 176 | 171 | tmp_dir: std.fs.Dir, |
| 177 | 172 | tmp_dir_path: []const u8, |
| 178 | 173 | child: *std.process.Child, |
| 179 | 174 | allow_stderr: bool, |
| 180 | | emit: EmitMode, |
| 181 | | /// When `emit == .c`, this contains the first few arguments to `zig cc` to build the generated binary. |
| 175 | /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary. |
| 182 | 176 | /// The arguments `out.c in.c` must be appended before spawning the subprocess. |
| 183 | 177 | cc_child_args: *std.ArrayListUnmanaged([]const u8), |
| 184 | 178 | |
| ... | ... | @@ -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 | 260 | try eval.checkSuccessOutcome(update, null, prog_node); |
| 267 | 261 | // This message indicates the end of the update. |
| 268 | 262 | stdout.discard(body.len); |
| ... | ... | @@ -275,11 +269,11 @@ const Eval = struct { |
| 275 | 269 | const bin_name = try std.zig.binNameAlloc(arena, .{ |
| 276 | 270 | .root_name = name, |
| 277 | 271 | .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{ |
| 278 | | .arch_os_abi = eval.case.target_query, |
| 279 | | .object_format = switch (eval.emit) { |
| 280 | | .none => unreachable, |
| 281 | | .bin => null, |
| 282 | | .c => "c", |
| 272 | .arch_os_abi = eval.target.query, |
| 273 | .object_format = switch (eval.target.backend) { |
| 274 | .sema => unreachable, |
| 275 | .selfhosted, .llvm => null, |
| 276 | .cbe => "c", |
| 283 | 277 | }, |
| 284 | 278 | })), |
| 285 | 279 | .output_mode = .Exe, |
| ... | ... | @@ -335,14 +329,14 @@ const Eval = struct { |
| 335 | 329 | .stdout, .exit_code => {}, |
| 336 | 330 | } |
| 337 | 331 | const emitted_path = opt_emitted_path orelse { |
| 338 | | std.debug.assert(eval.emit == .none); |
| 332 | std.debug.assert(eval.target.backend == .sema); |
| 339 | 333 | return; |
| 340 | 334 | }; |
| 341 | 335 | |
| 342 | | const binary_path = switch (eval.emit) { |
| 343 | | .none => unreachable, |
| 344 | | .bin => emitted_path, |
| 345 | | .c => bin: { |
| 336 | const binary_path = switch (eval.target.backend) { |
| 337 | .sema => unreachable, |
| 338 | .selfhosted, .llvm => emitted_path, |
| 339 | .cbe => bin: { |
| 346 | 340 | const rand_int = std.crypto.random.int(u64); |
| 347 | 341 | const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); |
| 348 | 342 | try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node); |
| ... | ... | @@ -468,7 +462,26 @@ const Eval = struct { |
| 468 | 462 | const Case = struct { |
| 469 | 463 | updates: []Update, |
| 470 | 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 | 486 | const Update = struct { |
| 474 | 487 | name: []const u8, |
| ... | ... | @@ -498,9 +511,9 @@ const Case = struct { |
| 498 | 511 | }; |
| 499 | 512 | |
| 500 | 513 | fn parse(arena: Allocator, bytes: []const u8) !Case { |
| 514 | var targets: std.ArrayListUnmanaged(Target) = .empty; |
| 501 | 515 | var updates: std.ArrayListUnmanaged(Update) = .empty; |
| 502 | 516 | var changes: std.ArrayListUnmanaged(FullContents) = .empty; |
| 503 | | var target_query: ?[]const u8 = null; |
| 504 | 517 | var it = std.mem.splitScalar(u8, bytes, '\n'); |
| 505 | 518 | var line_n: usize = 1; |
| 506 | 519 | var root_source_file: ?[]const u8 = null; |
| ... | ... | @@ -512,8 +525,16 @@ const Case = struct { |
| 512 | 525 | if (val.len == 0) { |
| 513 | 526 | fatal("line {d}: missing value", .{line_n}); |
| 514 | 527 | } else if (std.mem.eql(u8, key, "target")) { |
| 515 | | if (target_query != null) fatal("line {d}: duplicate target", .{line_n}); |
| 516 | | target_query = val; |
| 528 | const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse |
| 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 | 538 | } else if (std.mem.eql(u8, key, "update")) { |
| 518 | 539 | if (updates.items.len > 0) { |
| 519 | 540 | const last_update = &updates.items[updates.items.len - 1]; |
| ... | ... | @@ -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 | 593 | if (changes.items.len > 0) { |
| 569 | 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 | 598 | return .{ |
| 574 | 599 | .updates = updates.items, |
| 575 | 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 | }; |