authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-10-06 11:16:27+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-10-06 11:16:27+01:00
log008bb1f1201a4b4987bf00de9daf46185aa9292d
treefa5017a9988957b1a71838ea9fe7d388f6619541
parent516cb5a5e86bb9d30c16d0692e3b9eb706812b42
parent90db7677212f8331733a661615490d37c7bf75d2
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #21518 from mlugg/incremental-ci

incr-check enhancements, and CI for incremental test cases

13 files changed, 354 insertions(+), 79 deletions(-)

build.zig+4
...@@ -577,6 +577,10 @@ pub fn build(b: *std.Build) !void {...@@ -577,6 +577,10 @@ pub fn build(b: *std.Build) !void {
577 } else {577 } else {
578 update_mingw_step.dependOn(&b.addFail("The -Dmingw-src=... option is required for this step").step);578 update_mingw_step.dependOn(&b.addFail("The -Dmingw-src=... option is required for this step").step);
579 }579 }
580
581 const test_incremental_step = b.step("test-incremental", "Run the incremental compilation test cases");
582 try tests.addIncrementalTests(b, test_incremental_step);
583 test_step.dependOn(test_incremental_step);
580}584}
581585
582fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {586fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
lib/std/io.zig+162-30
...@@ -442,6 +442,7 @@ pub fn poll(...@@ -442,6 +442,7 @@ pub fn poll(
442 .overlapped = [1]windows.OVERLAPPED{442 .overlapped = [1]windows.OVERLAPPED{
443 mem.zeroes(windows.OVERLAPPED),443 mem.zeroes(windows.OVERLAPPED),
444 } ** enum_fields.len,444 } ** enum_fields.len,
445 .small_bufs = undefined,
445 .active = .{446 .active = .{
446 .count = 0,447 .count = 0,
447 .handles_buf = undefined,448 .handles_buf = undefined,
...@@ -481,6 +482,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -481,6 +482,7 @@ pub fn Poller(comptime StreamEnum: type) type {
481 windows: if (is_windows) struct {482 windows: if (is_windows) struct {
482 first_read_done: bool,483 first_read_done: bool,
483 overlapped: [enum_fields.len]windows.OVERLAPPED,484 overlapped: [enum_fields.len]windows.OVERLAPPED,
485 small_bufs: [enum_fields.len][128]u8,
484 active: struct {486 active: struct {
485 count: math.IntFittingRange(0, enum_fields.len),487 count: math.IntFittingRange(0, enum_fields.len),
486 handles_buf: [enum_fields.len]windows.HANDLE,488 handles_buf: [enum_fields.len]windows.HANDLE,
...@@ -534,24 +536,31 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -534,24 +536,31 @@ pub fn Poller(comptime StreamEnum: type) type {
534 const bump_amt = 512;536 const bump_amt = 512;
535537
536 if (!self.windows.first_read_done) {538 if (!self.windows.first_read_done) {
537 // Windows Async IO requires an initial call to ReadFile before waiting on the handle539 var already_read_data = false;
538 for (0..enum_fields.len) |i| {540 for (0..enum_fields.len) |i| {
539 const handle = self.windows.active.handles_buf[i];541 const handle = self.windows.active.handles_buf[i];
540 switch (try windowsAsyncRead(542 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
541 handle,543 handle,
542 &self.windows.overlapped[i],544 &self.windows.overlapped[i],
543 &self.fifos[i],545 &self.fifos[i],
546 &self.windows.small_bufs[i],
544 bump_amt,547 bump_amt,
545 )) {548 )) {
546 .pending => {549 .populated, .empty => |state| {
550 if (state == .populated) already_read_data = true;
547 self.windows.active.handles_buf[self.windows.active.count] = handle;551 self.windows.active.handles_buf[self.windows.active.count] = handle;
548 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));552 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
549 self.windows.active.count += 1;553 self.windows.active.count += 1;
550 },554 },
551 .closed => {}, // don't add to the wait_objects list555 .closed => {}, // don't add to the wait_objects list
556 .closed_populated => {
557 // don't add to the wait_objects list, but we did already get data
558 already_read_data = true;
559 },
552 }560 }
553 }561 }
554 self.windows.first_read_done = true;562 self.windows.first_read_done = true;
563 if (already_read_data) return true;
555 }564 }
556565
557 while (true) {566 while (true) {
...@@ -576,32 +585,35 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -576,32 +585,35 @@ pub fn Poller(comptime StreamEnum: type) type {
576585
577 const active_idx = status - windows.WAIT_OBJECT_0;586 const active_idx = status - windows.WAIT_OBJECT_0;
578587
579 const handle = self.windows.active.handles_buf[active_idx];
580 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);588 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
581 var read_bytes: u32 = undefined;589 const handle = self.windows.active.handles_buf[active_idx];
582 if (0 == windows.kernel32.GetOverlappedResult(590
583 handle,591 const overlapped = &self.windows.overlapped[stream_idx];
584 &self.windows.overlapped[stream_idx],592 const stream_fifo = &self.fifos[stream_idx];
585 &read_bytes,593 const small_buf = &self.windows.small_bufs[stream_idx];
586 0,594
587 )) switch (windows.GetLastError()) {595 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
588 .BROKEN_PIPE => {596 .success => |n| n,
597 .closed => {
589 self.windows.active.removeAt(active_idx);598 self.windows.active.removeAt(active_idx);
590 continue;599 continue;
591 },600 },
592 else => |err| return windows.unexpectedError(err),601 .aborted => unreachable,
593 };602 };
603 try stream_fifo.write(small_buf[0..num_bytes_read]);
594604
595 self.fifos[stream_idx].update(read_bytes);605 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
596
597 switch (try windowsAsyncRead(
598 handle,606 handle,
599 &self.windows.overlapped[stream_idx],607 overlapped,
600 &self.fifos[stream_idx],608 stream_fifo,
609 small_buf,
601 bump_amt,610 bump_amt,
602 )) {611 )) {
603 .pending => {},612 .empty => {}, // irrelevant, we already got data from the small buffer
604 .closed => self.windows.active.removeAt(active_idx),613 .populated => {},
614 .closed,
615 .closed_populated, // identical, since we already got data from the small buffer
616 => self.windows.active.removeAt(active_idx),
605 }617 }
606 return true;618 return true;
607 }619 }
...@@ -654,25 +666,145 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -654,25 +666,145 @@ pub fn Poller(comptime StreamEnum: type) type {
654 };666 };
655}667}
656668
657fn windowsAsyncRead(669/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
670/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
671/// compatibility, we point it to this dummy variables, which we never otherwise access.
672/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
673var win_dummy_bytes_read: u32 = undefined;
674
675/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
676/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
677/// is available. `handle` must have no pending asynchronous operation.
678fn windowsAsyncReadToFifoAndQueueSmallRead(
658 handle: windows.HANDLE,679 handle: windows.HANDLE,
659 overlapped: *windows.OVERLAPPED,680 overlapped: *windows.OVERLAPPED,
660 fifo: *PollFifo,681 fifo: *PollFifo,
682 small_buf: *[128]u8,
661 bump_amt: usize,683 bump_amt: usize,
662) !enum { pending, closed } {684) !enum { empty, populated, closed_populated, closed } {
685 var read_any_data = false;
663 while (true) {686 while (true) {
664 const buf = try fifo.writableWithSize(bump_amt);687 const fifo_read_pending = while (true) {
665 var read_bytes: u32 = undefined;688 const buf = try fifo.writableWithSize(bump_amt);
666 const read_result = windows.kernel32.ReadFile(handle, buf.ptr, math.cast(u32, buf.len) orelse math.maxInt(u32), &read_bytes, overlapped);689 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
667 if (read_result == 0) return switch (windows.GetLastError()) {690
668 .IO_PENDING => .pending,691 if (0 == windows.kernel32.ReadFile(
669 .BROKEN_PIPE => .closed,692 handle,
670 else => |err| windows.unexpectedError(err),693 buf.ptr,
694 buf_len,
695 &win_dummy_bytes_read,
696 overlapped,
697 )) switch (windows.GetLastError()) {
698 .IO_PENDING => break true,
699 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
700 else => |err| return windows.unexpectedError(err),
701 };
702
703 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
704 .success => |n| n,
705 .closed => return if (read_any_data) .closed_populated else .closed,
706 .aborted => unreachable,
707 };
708
709 read_any_data = true;
710 fifo.update(num_bytes_read);
711
712 if (num_bytes_read == buf_len) {
713 // We filled the buffer, so there's probably more data available.
714 continue;
715 } else {
716 // We didn't fill the buffer, so assume we're out of data.
717 // There is no pending read.
718 break false;
719 }
671 };720 };
672 fifo.update(read_bytes);721
722 if (fifo_read_pending) cancel_read: {
723 // Cancel the pending read into the FIFO.
724 _ = windows.kernel32.CancelIo(handle);
725
726 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
727 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
728 windows.WAIT_OBJECT_0 => {},
729 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
730 else => unreachable,
731 }
732
733 // If it completed before we canceled, make sure to tell the FIFO!
734 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
735 .success => |n| n,
736 .closed => return if (read_any_data) .closed_populated else .closed,
737 .aborted => break :cancel_read,
738 };
739 read_any_data = true;
740 fifo.update(num_bytes_read);
741 }
742
743 // Try to queue the 1-byte read.
744 if (0 == windows.kernel32.ReadFile(
745 handle,
746 small_buf,
747 small_buf.len,
748 &win_dummy_bytes_read,
749 overlapped,
750 )) switch (windows.GetLastError()) {
751 .IO_PENDING => {
752 // 1-byte read pending as intended
753 return if (read_any_data) .populated else .empty;
754 },
755 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
756 else => |err| return windows.unexpectedError(err),
757 };
758
759 // We got data back this time. Write it to the FIFO and run the main loop again.
760 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
761 .success => |n| n,
762 .closed => return if (read_any_data) .closed_populated else .closed,
763 .aborted => unreachable,
764 };
765 try fifo.write(small_buf[0..num_bytes_read]);
766 read_any_data = true;
673 }767 }
674}768}
675769
770/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
771/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
772///
773/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
774/// operation immediately returns data:
775/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
776/// erroneous results."
777/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
778/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
779/// get the actual number of bytes read."
780/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
781fn windowsGetReadResult(
782 handle: windows.HANDLE,
783 overlapped: *windows.OVERLAPPED,
784 allow_aborted: bool,
785) !union(enum) {
786 success: u32,
787 closed,
788 aborted,
789} {
790 var num_bytes_read: u32 = undefined;
791 if (0 == windows.kernel32.GetOverlappedResult(
792 handle,
793 overlapped,
794 &num_bytes_read,
795 0,
796 )) switch (windows.GetLastError()) {
797 .BROKEN_PIPE => return .closed,
798 .OPERATION_ABORTED => |err| if (allow_aborted) {
799 return .aborted;
800 } else {
801 return windows.unexpectedError(err);
802 },
803 else => |err| return windows.unexpectedError(err),
804 };
805 return .{ .success = num_bytes_read };
806}
807
676/// Given an enum, returns a struct with fields of that enum, each field808/// Given an enum, returns a struct with fields of that enum, each field
677/// representing an I/O stream for polling.809/// representing an I/O stream for polling.
678pub fn PollFiles(comptime StreamEnum: type) type {810pub fn PollFiles(comptime StreamEnum: type) type {
test/incremental/add_decl+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/incremental/add_decl_namespaced+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/incremental/delete_comptime_decls+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5pub fn main() void {}6pub fn main() void {}
test/incremental/hello+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/incremental/modify_inline_fn+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/incremental/move_src+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/incremental/remove_enum_field+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const MyEnum = enum(u8) {6const MyEnum = enum(u8) {
test/incremental/type_becomes_comptime_only+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const SomeType = u32;6const SomeType = u32;
test/incremental/unreferenced_error+1
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1#target=x86_64-linux-selfhosted1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
3#update=initial version4#update=initial version
4#file=main.zig5#file=main.zig
5const std = @import("std");6const std = @import("std");
test/tests.zig+28
...@@ -1509,3 +1509,31 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step...@@ -1509,3 +1509,31 @@ pub fn addDebuggerTests(b: *std.Build, options: DebuggerContext.Options) ?*Step
1509 });1509 });
1510 return step;1510 return step;
1511}1511}
1512
1513pub fn addIncrementalTests(b: *std.Build, test_step: *Step) !void {
1514 const incr_check = b.addExecutable(.{
1515 .name = "incr-check",
1516 .root_source_file = b.path("tools/incr-check.zig"),
1517 .target = b.graph.host,
1518 .optimize = .Debug,
1519 });
1520
1521 var dir = try b.build_root.handle.openDir("test/incremental", .{ .iterate = true });
1522 defer dir.close();
1523
1524 var it = try dir.walk(b.graph.arena);
1525 while (try it.next()) |entry| {
1526 if (entry.kind != .file) continue;
1527
1528 const run = b.addRunArtifact(incr_check);
1529 run.setName(b.fmt("incr-check '{s}'", .{entry.basename}));
1530
1531 run.addArg(b.graph.zig_exe);
1532 run.addFileArg(b.path("test/incremental/").path(b, entry.path));
1533 run.addArgs(&.{ "--zig-lib-dir", b.fmt("{}", .{b.graph.zig_lib_directory}) });
1534
1535 run.addCheck(.{ .expect_term = .{ .Exited = 0 } });
1536
1537 test_step.dependOn(&run.step);
1538 }
1539}
tools/incr-check.zig+151-49
...@@ -1,11 +1,12 @@...@@ -1,11 +1,12 @@
1const std = @import("std");1const std = @import("std");
2const fatal = std.process.fatal;
3const Allocator = std.mem.Allocator;2const Allocator = std.mem.Allocator;
4const Cache = std.Build.Cache;3const Cache = std.Build.Cache;
54
6const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]";5const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--preserve-tmp] [--zig-cc-binary /path/to/zig]";
76
8pub fn main() !void {7pub fn main() !void {
8 const fatal = std.process.fatal;
9
9 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);10 var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
10 defer arena_instance.deinit();11 defer arena_instance.deinit();
11 const arena = arena_instance.allocator();12 const arena = arena_instance.allocator();
...@@ -16,6 +17,7 @@ pub fn main() !void {...@@ -16,6 +17,7 @@ pub fn main() !void {
16 var opt_cc_zig: ?[]const u8 = null;17 var opt_cc_zig: ?[]const u8 = null;
17 var debug_zcu = false;18 var debug_zcu = false;
18 var debug_link = false;19 var debug_link = false;
20 var preserve_tmp = false;
1921
20 var arg_it = try std.process.argsWithAllocator(arena);22 var arg_it = try std.process.argsWithAllocator(arena);
21 _ = arg_it.skip();23 _ = arg_it.skip();
...@@ -27,6 +29,8 @@ pub fn main() !void {...@@ -27,6 +29,8 @@ pub fn main() !void {
27 debug_zcu = true;29 debug_zcu = true;
28 } else if (std.mem.eql(u8, arg, "--debug-link")) {30 } else if (std.mem.eql(u8, arg, "--debug-link")) {
29 debug_link = true;31 debug_link = true;
32 } else if (std.mem.eql(u8, arg, "--preserve-tmp")) {
33 preserve_tmp = true;
30 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {34 } else if (std.mem.eql(u8, arg, "--zig-cc-binary")) {
31 opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage});35 opt_cc_zig = arg_it.next() orelse fatal("expect arg after '--zig-cc-binary'\n{s}", .{usage});
32 } else {36 } else {
...@@ -48,15 +52,29 @@ pub fn main() !void {...@@ -48,15 +52,29 @@ pub fn main() !void {
48 const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32));52 const input_file_bytes = try std.fs.cwd().readFileAlloc(arena, input_file_name, std.math.maxInt(u32));
49 const case = try Case.parse(arena, input_file_bytes);53 const case = try Case.parse(arena, input_file_bytes);
5054
55 // Check now: if there are any targets using the `cbe` backend, we need the lib dir.
56 if (opt_lib_dir == null) {
57 for (case.targets) |target| {
58 if (target.backend == .cbe) {
59 fatal("'--zig-lib-dir' requried when using backend 'cbe'", .{});
60 }
61 }
62 }
63
51 const prog_node = std.Progress.start(.{});64 const prog_node = std.Progress.start(.{});
52 defer prog_node.end();65 defer prog_node.end();
5366
54 const rand_int = std.crypto.random.int(u64);67 const rand_int = std.crypto.random.int(u64);
55 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);68 const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
56 const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});69 var tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
5770 defer {
58 const child_prog_node = prog_node.start("zig build-exe", 0);71 tmp_dir.close();
59 defer child_prog_node.end();72 if (!preserve_tmp) {
73 std.fs.cwd().deleteTree(tmp_dir_path) catch |err| {
74 std.log.warn("failed to delete tree '{s}': {s}", .{ tmp_dir_path, @errorName(err) });
75 };
76 }
77 }
6078
61 // Convert paths to be relative to the cwd of the subprocess.79 // Convert paths to be relative to the cwd of the subprocess.
62 const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe);80 const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe);
...@@ -65,10 +83,21 @@ pub fn main() !void {...@@ -65,10 +83,21 @@ pub fn main() !void {
65 else83 else
66 null;84 null;
6785
86 const host = try std.zig.system.resolveTargetQuery(.{});
87
68 const debug_log_verbose = debug_zcu or debug_link;88 const debug_log_verbose = debug_zcu or debug_link;
6989
70 for (case.targets) |target| {90 for (case.targets) |target| {
71 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });91 const target_prog_node = node: {
92 var name_buf: [std.Progress.Node.max_name_len]u8 = undefined;
93 const name = std.fmt.bufPrint(&name_buf, "{s}-{s}", .{ target.query, @tagName(target.backend) }) catch &name_buf;
94 break :node prog_node.start(name, case.updates.len);
95 };
96 defer target_prog_node.end();
97
98 if (debug_log_verbose) {
99 std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
100 }
72101
73 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;102 var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
74 try child_args.appendSlice(arena, &.{103 try child_args.appendSlice(arena, &.{
...@@ -81,7 +110,7 @@ pub fn main() !void {...@@ -81,7 +110,7 @@ pub fn main() !void {
81 "--cache-dir",110 "--cache-dir",
82 ".local-cache",111 ".local-cache",
83 "--global-cache-dir",112 "--global-cache-dir",
84 ".global_cache",113 ".global-cache",
85 "--listen=-",114 "--listen=-",
86 });115 });
87 if (opt_resolved_lib_dir) |resolved_lib_dir| {116 if (opt_resolved_lib_dir) |resolved_lib_dir| {
...@@ -100,11 +129,14 @@ pub fn main() !void {...@@ -100,11 +129,14 @@ pub fn main() !void {
100 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });129 try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
101 }130 }
102131
132 const zig_prog_node = target_prog_node.start("zig build-exe", 0);
133 defer zig_prog_node.end();
134
103 var child = std.process.Child.init(child_args.items, arena);135 var child = std.process.Child.init(child_args.items, arena);
104 child.stdin_behavior = .Pipe;136 child.stdin_behavior = .Pipe;
105 child.stdout_behavior = .Pipe;137 child.stdout_behavior = .Pipe;
106 child.stderr_behavior = .Pipe;138 child.stderr_behavior = .Pipe;
107 child.progress_node = child_prog_node;139 child.progress_node = zig_prog_node;
108 child.cwd_dir = tmp_dir;140 child.cwd_dir = tmp_dir;
109 child.cwd = tmp_dir_path;141 child.cwd = tmp_dir_path;
110142
...@@ -121,7 +153,7 @@ pub fn main() !void {...@@ -121,7 +153,7 @@ pub fn main() !void {
121 "-target",153 "-target",
122 target.query,154 target.query,
123 "-I",155 "-I",
124 opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}),156 opt_resolved_lib_dir.?, // verified earlier
125 "-o",157 "-o",
126 });158 });
127 }159 }
...@@ -129,11 +161,13 @@ pub fn main() !void {...@@ -129,11 +161,13 @@ pub fn main() !void {
129 var eval: Eval = .{161 var eval: Eval = .{
130 .arena = arena,162 .arena = arena,
131 .case = case,163 .case = case,
164 .host = host,
132 .target = target,165 .target = target,
133 .tmp_dir = tmp_dir,166 .tmp_dir = tmp_dir,
134 .tmp_dir_path = tmp_dir_path,167 .tmp_dir_path = tmp_dir_path,
135 .child = &child,168 .child = &child,
136 .allow_stderr = debug_log_verbose,169 .allow_stderr = debug_log_verbose,
170 .preserve_tmp_on_fatal = preserve_tmp,
137 .cc_child_args = &cc_child_args,171 .cc_child_args = &cc_child_args,
138 };172 };
139173
...@@ -146,7 +180,7 @@ pub fn main() !void {...@@ -146,7 +180,7 @@ pub fn main() !void {
146 defer poller.deinit();180 defer poller.deinit();
147181
148 for (case.updates) |update| {182 for (case.updates) |update| {
149 var update_node = prog_node.start(update.name, 0);183 var update_node = target_prog_node.start(update.name, 0);
150 defer update_node.end();184 defer update_node.end();
151185
152 if (debug_log_verbose) {186 if (debug_log_verbose) {
...@@ -160,18 +194,20 @@ pub fn main() !void {...@@ -160,18 +194,20 @@ pub fn main() !void {
160194
161 try eval.end(&poller);195 try eval.end(&poller);
162196
163 waitChild(&child);197 waitChild(&child, &eval);
164 }198 }
165}199}
166200
167const Eval = struct {201const Eval = struct {
168 arena: Allocator,202 arena: Allocator,
203 host: std.Target,
169 case: Case,204 case: Case,
170 target: Case.Target,205 target: Case.Target,
171 tmp_dir: std.fs.Dir,206 tmp_dir: std.fs.Dir,
172 tmp_dir_path: []const u8,207 tmp_dir_path: []const u8,
173 child: *std.process.Child,208 child: *std.process.Child,
174 allow_stderr: bool,209 allow_stderr: bool,
210 preserve_tmp_on_fatal: bool,
175 /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.211 /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.
176 /// The arguments `out.c in.c` must be appended before spawning the subprocess.212 /// The arguments `out.c in.c` must be appended before spawning the subprocess.
177 cc_child_args: *std.ArrayListUnmanaged([]const u8),213 cc_child_args: *std.ArrayListUnmanaged([]const u8),
...@@ -186,12 +222,12 @@ const Eval = struct {...@@ -186,12 +222,12 @@ const Eval = struct {
186 .sub_path = full_contents.name,222 .sub_path = full_contents.name,
187 .data = full_contents.bytes,223 .data = full_contents.bytes,
188 }) catch |err| {224 }) catch |err| {
189 fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) });225 eval.fatal("failed to update '{s}': {s}", .{ full_contents.name, @errorName(err) });
190 };226 };
191 }227 }
192 for (update.deletes) |doomed_name| {228 for (update.deletes) |doomed_name| {
193 eval.tmp_dir.deleteFile(doomed_name) catch |err| {229 eval.tmp_dir.deleteFile(doomed_name) catch |err| {
194 fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });230 eval.fatal("failed to delete '{s}': {s}", .{ doomed_name, @errorName(err) });
195 };231 };
196 }232 }
197 }233 }
...@@ -233,7 +269,7 @@ const Eval = struct {...@@ -233,7 +269,7 @@ const Eval = struct {
233 if (eval.allow_stderr) {269 if (eval.allow_stderr) {
234 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});270 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});
235 } else {271 } else {
236 fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});272 eval.fatal("error_bundle included unexpected stderr:\n{s}", .{stderr_data});
237 }273 }
238 }274 }
239 if (result_error_bundle.errorMessageCount() != 0) {275 if (result_error_bundle.errorMessageCount() != 0) {
...@@ -252,7 +288,7 @@ const Eval = struct {...@@ -252,7 +288,7 @@ const Eval = struct {
252 if (eval.allow_stderr) {288 if (eval.allow_stderr) {
253 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});289 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});
254 } else {290 } else {
255 fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});291 eval.fatal("emit_digest included unexpected stderr:\n{s}", .{stderr_data});
256 }292 }
257 }293 }
258294
...@@ -268,14 +304,7 @@ const Eval = struct {...@@ -268,14 +304,7 @@ const Eval = struct {
268 const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file));304 const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file));
269 const bin_name = try std.zig.binNameAlloc(arena, .{305 const bin_name = try std.zig.binNameAlloc(arena, .{
270 .root_name = name,306 .root_name = name,
271 .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{307 .target = eval.target.resolved,
272 .arch_os_abi = eval.target.query,
273 .object_format = switch (eval.target.backend) {
274 .sema => unreachable,
275 .selfhosted, .llvm => null,
276 .cbe => "c",
277 },
278 })),
279 .output_mode = .Exe,308 .output_mode = .Exe,
280 });309 });
281 const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name });310 const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name });
...@@ -296,16 +325,15 @@ const Eval = struct {...@@ -296,16 +325,15 @@ const Eval = struct {
296 if (eval.allow_stderr) {325 if (eval.allow_stderr) {
297 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data });326 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data });
298 } else {327 } else {
299 fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });328 eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });
300 }329 }
301 }330 }
302331
303 waitChild(eval.child);332 waitChild(eval.child, eval);
304 fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});333 eval.fatal("update '{s}': compiler failed to send error_bundle or emit_bin_path", .{update.name});
305 }334 }
306335
307 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {336 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
308 _ = eval;
309 switch (update.outcome) {337 switch (update.outcome) {
310 .unknown => return,338 .unknown => return,
311 .compile_errors => |expected_errors| {339 .compile_errors => |expected_errors| {
...@@ -317,7 +345,7 @@ const Eval = struct {...@@ -317,7 +345,7 @@ const Eval = struct {
317 .stdout, .exit_code => {345 .stdout, .exit_code => {
318 const color: std.zig.Color = .auto;346 const color: std.zig.Color = .auto;
319 error_bundle.renderToStdErr(color.renderOptions());347 error_bundle.renderToStdErr(color.renderOptions());
320 fatal("update '{s}': unexpected compile errors", .{update.name});348 eval.fatal("update '{s}': unexpected compile errors", .{update.name});
321 },349 },
322 }350 }
323 }351 }
...@@ -325,7 +353,7 @@ const Eval = struct {...@@ -325,7 +353,7 @@ const Eval = struct {
325 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void {353 fn checkSuccessOutcome(eval: *Eval, update: Case.Update, opt_emitted_path: ?[]const u8, prog_node: std.Progress.Node) !void {
326 switch (update.outcome) {354 switch (update.outcome) {
327 .unknown => return,355 .unknown => return,
328 .compile_errors => fatal("expected compile errors but compilation incorrectly succeeded", .{}),356 .compile_errors => eval.fatal("expected compile errors but compilation incorrectly succeeded", .{}),
329 .stdout, .exit_code => {},357 .stdout, .exit_code => {},
330 }358 }
331 const emitted_path = opt_emitted_path orelse {359 const emitted_path = opt_emitted_path orelse {
...@@ -344,27 +372,73 @@ const Eval = struct {...@@ -344,27 +372,73 @@ const Eval = struct {
344 },372 },
345 };373 };
346374
375 var argv_buf: [2][]const u8 = undefined;
376 const argv: []const []const u8, const is_foreign: bool = switch (std.zig.system.getExternalExecutor(
377 eval.host,
378 &eval.target.resolved,
379 .{ .link_libc = eval.target.backend == .cbe },
380 )) {
381 .bad_dl, .bad_os_or_cpu => {
382 // This binary cannot be executed on this host.
383 if (eval.allow_stderr) {
384 std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{
385 try eval.host.zigTriple(eval.arena),
386 try eval.target.resolved.zigTriple(eval.arena),
387 });
388 }
389 return;
390 },
391 .native, .rosetta => argv: {
392 argv_buf[0] = binary_path;
393 break :argv .{ argv_buf[0..1], false };
394 },
395 .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: {
396 argv_buf[0] = executor_cmd;
397 argv_buf[1] = binary_path;
398 break :argv .{ argv_buf[0..2], true };
399 },
400 };
401
402 const run_prog_node = prog_node.start("run generated executable", 0);
403 defer run_prog_node.end();
404
347 const result = std.process.Child.run(.{405 const result = std.process.Child.run(.{
348 .allocator = eval.arena,406 .allocator = eval.arena,
349 .argv = &.{binary_path},407 .argv = argv,
350 .cwd_dir = eval.tmp_dir,408 .cwd_dir = eval.tmp_dir,
351 .cwd = eval.tmp_dir_path,409 .cwd = eval.tmp_dir_path,
352 }) catch |err| {410 }) catch |err| {
353 fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{411 if (is_foreign) {
412 // Chances are the foreign executor isn't available. Skip this evaluation.
413 if (eval.allow_stderr) {
414 std.log.warn("update '{s}': skipping execution of '{s}' via executor for foreign target '{s}': {s}", .{
415 update.name,
416 binary_path,
417 try eval.target.resolved.zigTriple(eval.arena),
418 @errorName(err),
419 });
420 }
421 return;
422 }
423 eval.fatal("update '{s}': failed to run the generated executable '{s}': {s}", .{
354 update.name, binary_path, @errorName(err),424 update.name, binary_path, @errorName(err),
355 });425 });
356 };426 };
357 if (result.stderr.len != 0) {427
428 // Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
429 // Therefore, we'll ignore stderr when using a foreign executor.
430 if (!is_foreign and result.stderr.len != 0) {
358 std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{431 std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{
359 update.name, binary_path, result.stderr,432 update.name, binary_path, result.stderr,
360 });433 });
361 }434 }
435
362 switch (result.term) {436 switch (result.term) {
363 .Exited => |code| switch (update.outcome) {437 .Exited => |code| switch (update.outcome) {
364 .unknown, .compile_errors => unreachable,438 .unknown, .compile_errors => unreachable,
365 .stdout => |expected_stdout| {439 .stdout => |expected_stdout| {
366 if (code != 0) {440 if (code != 0) {
367 fatal("update '{s}': generated executable '{s}' failed with code {d}", .{441 eval.fatal("update '{s}': generated executable '{s}' failed with code {d}", .{
368 update.name, binary_path, code,442 update.name, binary_path, code,
369 });443 });
370 }444 }
...@@ -373,12 +447,13 @@ const Eval = struct {...@@ -373,12 +447,13 @@ const Eval = struct {
373 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),447 .exit_code => |expected_code| try std.testing.expectEqual(expected_code, result.term.Exited),
374 },448 },
375 .Signal, .Stopped, .Unknown => {449 .Signal, .Stopped, .Unknown => {
376 fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{450 eval.fatal("update '{s}': generated executable '{s}' terminated unexpectedly", .{
377 update.name, binary_path,451 update.name, binary_path,
378 });452 });
379 },453 },
380 }454 }
381 if (result.stderr.len != 0) std.process.exit(1);455
456 if (!is_foreign and result.stderr.len != 0) std.process.exit(1);
382 }457 }
383458
384 fn requestUpdate(eval: *Eval) !void {459 fn requestUpdate(eval: *Eval) !void {
...@@ -390,7 +465,7 @@ const Eval = struct {...@@ -390,7 +465,7 @@ const Eval = struct {
390 }465 }
391466
392 fn end(eval: *Eval, poller: *Poller) !void {467 fn end(eval: *Eval, poller: *Poller) !void {
393 requestExit(eval.child);468 requestExit(eval.child, eval);
394469
395 const Header = std.zig.Server.Message.Header;470 const Header = std.zig.Server.Message.Header;
396 const stdout = poller.fifo(.stdout);471 const stdout = poller.fifo(.stdout);
...@@ -410,7 +485,7 @@ const Eval = struct {...@@ -410,7 +485,7 @@ const Eval = struct {
410485
411 if (stderr.readableLength() > 0) {486 if (stderr.readableLength() > 0) {
412 const stderr_data = try stderr.toOwnedSlice();487 const stderr_data = try stderr.toOwnedSlice();
413 fatal("unexpected stderr:\n{s}", .{stderr_data});488 eval.fatal("unexpected stderr:\n{s}", .{stderr_data});
414 }489 }
415 }490 }
416491
...@@ -430,7 +505,7 @@ const Eval = struct {...@@ -430,7 +505,7 @@ const Eval = struct {
430 .cwd = eval.tmp_dir_path,505 .cwd = eval.tmp_dir_path,
431 .progress_node = child_prog_node,506 .progress_node = child_prog_node,
432 }) catch |err| {507 }) catch |err| {
433 fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{508 eval.fatal("update '{s}': failed to spawn zig cc for '{s}': {s}", .{
434 update.name, c_path, @errorName(err),509 update.name, c_path, @errorName(err),
435 });510 });
436 };511 };
...@@ -441,7 +516,7 @@ const Eval = struct {...@@ -441,7 +516,7 @@ const Eval = struct {
441 update.name, result.stderr,516 update.name, result.stderr,
442 });517 });
443 }518 }
444 fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{519 eval.fatal("update '{s}': zig cc for '{s}' failed with code {d}", .{
445 update.name, c_path, code,520 update.name, c_path, code,
446 });521 });
447 },522 },
...@@ -451,12 +526,22 @@ const Eval = struct {...@@ -451,12 +526,22 @@ const Eval = struct {
451 update.name, result.stderr,526 update.name, result.stderr,
452 });527 });
453 }528 }
454 fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{529 eval.fatal("update '{s}': zig cc for '{s}' terminated unexpectedly", .{
455 update.name, c_path,530 update.name, c_path,
456 });531 });
457 },532 },
458 }533 }
459 }534 }
535
536 fn fatal(eval: *Eval, comptime fmt: []const u8, args: anytype) noreturn {
537 eval.tmp_dir.close();
538 if (!eval.preserve_tmp_on_fatal) {
539 std.fs.cwd().deleteTree(eval.tmp_dir_path) catch |err| {
540 std.log.warn("failed to delete tree '{s}': {s}", .{ eval.tmp_dir_path, @errorName(err) });
541 };
542 }
543 std.process.fatal(fmt, args);
544 }
460};545};
461546
462const Case = struct {547const Case = struct {
...@@ -466,6 +551,7 @@ const Case = struct {...@@ -466,6 +551,7 @@ const Case = struct {
466551
467 const Target = struct {552 const Target = struct {
468 query: []const u8,553 query: []const u8,
554 resolved: std.Target,
469 backend: Backend,555 backend: Backend,
470 const Backend = enum {556 const Backend = enum {
471 /// Run semantic analysis only. Runtime output will not be tested, but we still verify557 /// Run semantic analysis only. Runtime output will not be tested, but we still verify
...@@ -511,6 +597,8 @@ const Case = struct {...@@ -511,6 +597,8 @@ const Case = struct {
511 };597 };
512598
513 fn parse(arena: Allocator, bytes: []const u8) !Case {599 fn parse(arena: Allocator, bytes: []const u8) !Case {
600 const fatal = std.process.fatal;
601
514 var targets: std.ArrayListUnmanaged(Target) = .empty;602 var targets: std.ArrayListUnmanaged(Target) = .empty;
515 var updates: std.ArrayListUnmanaged(Update) = .empty;603 var updates: std.ArrayListUnmanaged(Update) = .empty;
516 var changes: std.ArrayListUnmanaged(FullContents) = .empty;604 var changes: std.ArrayListUnmanaged(FullContents) = .empty;
...@@ -521,18 +609,32 @@ const Case = struct {...@@ -521,18 +609,32 @@ const Case = struct {
521 if (std.mem.startsWith(u8, line, "#")) {609 if (std.mem.startsWith(u8, line, "#")) {
522 var line_it = std.mem.splitScalar(u8, line, '=');610 var line_it = std.mem.splitScalar(u8, line, '=');
523 const key = line_it.first()[1..];611 const key = line_it.first()[1..];
524 const val = line_it.rest();612 const val = std.mem.trimRight(u8, line_it.rest(), "\r"); // windows moment
525 if (val.len == 0) {613 if (val.len == 0) {
526 fatal("line {d}: missing value", .{line_n});614 fatal("line {d}: missing value", .{line_n});
527 } else if (std.mem.eql(u8, key, "target")) {615 } else if (std.mem.eql(u8, key, "target")) {
528 const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse616 const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse
529 fatal("line {d}: target does not include backend", .{line_n});617 fatal("line {d}: target does not include backend", .{line_n});
618
530 const query = val[0..split_idx];619 const query = val[0..split_idx];
620
531 const backend_str = val[split_idx + 1 ..];621 const backend_str = val[split_idx + 1 ..];
532 const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse622 const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse
533 fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });623 fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });
624
625 const parsed_query = std.Build.parseTargetQuery(.{
626 .arch_os_abi = query,
627 .object_format = switch (backend) {
628 .sema, .selfhosted, .llvm => null,
629 .cbe => "c",
630 },
631 }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });
632
633 const resolved = try std.zig.system.resolveTargetQuery(parsed_query);
634
534 try targets.append(arena, .{635 try targets.append(arena, .{
535 .query = query,636 .query = query,
637 .resolved = resolved,
536 .backend = backend,638 .backend = backend,
537 });639 });
538 } else if (std.mem.eql(u8, key, "update")) {640 } else if (std.mem.eql(u8, key, "update")) {
...@@ -603,7 +705,7 @@ const Case = struct {...@@ -603,7 +705,7 @@ const Case = struct {
603 }705 }
604};706};
605707
606fn requestExit(child: *std.process.Child) void {708fn requestExit(child: *std.process.Child, eval: *Eval) void {
607 if (child.stdin == null) return;709 if (child.stdin == null) return;
608710
609 const header: std.zig.Client.Message.Header = .{711 const header: std.zig.Client.Message.Header = .{
...@@ -612,7 +714,7 @@ fn requestExit(child: *std.process.Child) void {...@@ -612,7 +714,7 @@ fn requestExit(child: *std.process.Child) void {
612 };714 };
613 child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) {715 child.stdin.?.writeAll(std.mem.asBytes(&header)) catch |err| switch (err) {
614 error.BrokenPipe => {},716 error.BrokenPipe => {},
615 else => fatal("failed to send exit: {s}", .{@errorName(err)}),717 else => eval.fatal("failed to send exit: {s}", .{@errorName(err)}),
616 };718 };
617719
618 // Send EOF to stdin.720 // Send EOF to stdin.
...@@ -620,11 +722,11 @@ fn requestExit(child: *std.process.Child) void {...@@ -620,11 +722,11 @@ fn requestExit(child: *std.process.Child) void {
620 child.stdin = null;722 child.stdin = null;
621}723}
622724
623fn waitChild(child: *std.process.Child) void {725fn waitChild(child: *std.process.Child, eval: *Eval) void {
624 requestExit(child);726 requestExit(child, eval);
625 const term = child.wait() catch |err| fatal("child process failed: {s}", .{@errorName(err)});727 const term = child.wait() catch |err| eval.fatal("child process failed: {s}", .{@errorName(err)});
626 switch (term) {728 switch (term) {
627 .Exited => |code| if (code != 0) fatal("compiler failed with code {d}", .{code}),729 .Exited => |code| if (code != 0) eval.fatal("compiler failed with code {d}", .{code}),
628 .Signal, .Stopped, .Unknown => fatal("compiler terminated unexpectedly", .{}),730 .Signal, .Stopped, .Unknown => eval.fatal("compiler terminated unexpectedly", .{}),
629 }731 }
630}732}