Commit 054dbb6798

mlugg <mlugg@mlugg.co.uk>
2024-09-25 21:21:27
incr-check: specify backend in manifest, allow multiple targets/backends
1 parent 1714b74
test/incremental/add_decl
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
test/incremental/add_decl_namespaced
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
test/incremental/delete_comptime_decls
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 pub fn main() void {}
test/incremental/hello
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
test/incremental/modify_inline_fn
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
test/incremental/move_src
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
test/incremental/remove_enum_field
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const MyEnum = enum(u8) {
test/incremental/type_becomes_comptime_only
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const SomeType = u32;
test/incremental/unreferenced_error
@@ -1,4 +1,5 @@
-#target=x86_64-linux
+#target=x86_64-linux-selfhosted
+#target=x86_64-linux-cbe
 #update=initial version
 #file=main.zig
 const std = @import("std");
tools/incr-check.zig
@@ -3,13 +3,7 @@ const fatal = std.process.fatal;
 const Allocator = std.mem.Allocator;
 const Cache = std.Build.Cache;
 
-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]";
-
-const EmitMode = enum {
-    none,
-    bin,
-    c,
-};
+const usage = "usage: incr-check <zig binary path> <input file> [--zig-lib-dir lib] [--debug-zcu] [--debug-link] [--zig-cc-binary /path/to/zig]";
 
 pub fn main() !void {
     var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@@ -20,7 +14,6 @@ pub fn main() !void {
     var opt_input_file_name: ?[]const u8 = null;
     var opt_lib_dir: ?[]const u8 = null;
     var opt_cc_zig: ?[]const u8 = null;
-    var emit: EmitMode = .bin;
     var debug_zcu = false;
     var debug_link = false;
 
@@ -28,11 +21,7 @@ pub fn main() !void {
     _ = arg_it.skip();
     while (arg_it.next()) |arg| {
         if (arg.len > 0 and arg[0] == '-') {
-            if (std.mem.eql(u8, arg, "--emit")) {
-                const emit_str = arg_it.next() orelse fatal("expected arg after '--emit'\n{s}", .{usage});
-                emit = std.meta.stringToEnum(EmitMode, emit_str) orelse
-                    fatal("invalid emit mode '{s}'\n{s}", .{ emit_str, usage });
-            } else if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
+            if (std.mem.eql(u8, arg, "--zig-lib-dir")) {
                 opt_lib_dir = arg_it.next() orelse fatal("expected arg after '--zig-lib-dir'\n{s}", .{usage});
             } else if (std.mem.eql(u8, arg, "--debug-zcu")) {
                 debug_zcu = true;
@@ -76,109 +65,114 @@ pub fn main() !void {
     else
         null;
 
-    var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
-    try child_args.appendSlice(arena, &.{
-        resolved_zig_exe,
-        "build-exe",
-        case.root_source_file,
-        "-fincremental",
-        "-target",
-        case.target_query,
-        "--cache-dir",
-        ".local-cache",
-        "--global-cache-dir",
-        ".global_cache",
-        "--listen=-",
-    });
-    if (opt_resolved_lib_dir) |resolved_lib_dir| {
-        try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
-    }
-    switch (emit) {
-        .bin => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }),
-        .none => try child_args.append(arena, "-fno-emit-bin"),
-        .c => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
-    }
-    if (debug_zcu) {
-        try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });
-    }
-    if (debug_link) {
-        try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
-    }
-
     const debug_log_verbose = debug_zcu or debug_link;
 
-    var child = std.process.Child.init(child_args.items, arena);
-    child.stdin_behavior = .Pipe;
-    child.stdout_behavior = .Pipe;
-    child.stderr_behavior = .Pipe;
-    child.progress_node = child_prog_node;
-    child.cwd_dir = tmp_dir;
-    child.cwd = tmp_dir_path;
-
-    var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty;
-    if (emit == .c) {
-        const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
-            try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe)
-        else
-            resolved_zig_exe;
-
-        try cc_child_args.appendSlice(arena, &.{
-            resolved_cc_zig_exe,
-            "cc",
+    for (case.targets) |target| {
+        std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
+
+        var child_args: std.ArrayListUnmanaged([]const u8) = .empty;
+        try child_args.appendSlice(arena, &.{
+            resolved_zig_exe,
+            "build-exe",
+            case.root_source_file,
+            "-fincremental",
             "-target",
-            case.target_query,
-            "-I",
-            opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using '--emit c'", .{}),
-            "-o",
+            target.query,
+            "--cache-dir",
+            ".local-cache",
+            "--global-cache-dir",
+            ".global_cache",
+            "--listen=-",
         });
-    }
+        if (opt_resolved_lib_dir) |resolved_lib_dir| {
+            try child_args.appendSlice(arena, &.{ "--zig-lib-dir", resolved_lib_dir });
+        }
+        switch (target.backend) {
+            .sema => try child_args.append(arena, "-fno-emit-bin"),
+            .selfhosted => try child_args.appendSlice(arena, &.{ "-fno-llvm", "-fno-lld" }),
+            .llvm => try child_args.appendSlice(arena, &.{ "-fllvm", "-flld" }),
+            .cbe => try child_args.appendSlice(arena, &.{ "-ofmt=c", "-lc" }),
+        }
+        if (debug_zcu) {
+            try child_args.appendSlice(arena, &.{ "--debug-log", "zcu" });
+        }
+        if (debug_link) {
+            try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
+        }
 
-    var eval: Eval = .{
-        .arena = arena,
-        .case = case,
-        .tmp_dir = tmp_dir,
-        .tmp_dir_path = tmp_dir_path,
-        .child = &child,
-        .allow_stderr = debug_log_verbose,
-        .emit = emit,
-        .cc_child_args = &cc_child_args,
-    };
+        var child = std.process.Child.init(child_args.items, arena);
+        child.stdin_behavior = .Pipe;
+        child.stdout_behavior = .Pipe;
+        child.stderr_behavior = .Pipe;
+        child.progress_node = child_prog_node;
+        child.cwd_dir = tmp_dir;
+        child.cwd = tmp_dir_path;
+
+        var cc_child_args: std.ArrayListUnmanaged([]const u8) = .empty;
+        if (target.backend == .cbe) {
+            const resolved_cc_zig_exe = if (opt_cc_zig) |cc_zig_exe|
+                try std.fs.path.relative(arena, tmp_dir_path, cc_zig_exe)
+            else
+                resolved_zig_exe;
+
+            try cc_child_args.appendSlice(arena, &.{
+                resolved_cc_zig_exe,
+                "cc",
+                "-target",
+                target.query,
+                "-I",
+                opt_resolved_lib_dir orelse fatal("'--zig-lib-dir' required when using backend 'cbe'", .{}),
+                "-o",
+            });
+        }
 
-    try child.spawn();
+        var eval: Eval = .{
+            .arena = arena,
+            .case = case,
+            .target = target,
+            .tmp_dir = tmp_dir,
+            .tmp_dir_path = tmp_dir_path,
+            .child = &child,
+            .allow_stderr = debug_log_verbose,
+            .cc_child_args = &cc_child_args,
+        };
 
-    var poller = std.io.poll(arena, Eval.StreamEnum, .{
-        .stdout = child.stdout.?,
-        .stderr = child.stderr.?,
-    });
-    defer poller.deinit();
+        try child.spawn();
 
-    for (case.updates) |update| {
-        var update_node = prog_node.start(update.name, 0);
-        defer update_node.end();
+        var poller = std.io.poll(arena, Eval.StreamEnum, .{
+            .stdout = child.stdout.?,
+            .stderr = child.stderr.?,
+        });
+        defer poller.deinit();
 
-        if (debug_log_verbose) {
-            std.log.info("=== START UPDATE '{s}' ===", .{update.name});
-        }
+        for (case.updates) |update| {
+            var update_node = prog_node.start(update.name, 0);
+            defer update_node.end();
 
-        eval.write(update);
-        try eval.requestUpdate();
-        try eval.check(&poller, update, update_node);
-    }
+            if (debug_log_verbose) {
+                std.log.scoped(.status).info("update: '{s}'", .{update.name});
+            }
+
+            eval.write(update);
+            try eval.requestUpdate();
+            try eval.check(&poller, update, update_node);
+        }
 
-    try eval.end(&poller);
+        try eval.end(&poller);
 
-    waitChild(&child);
+        waitChild(&child);
+    }
 }
 
 const Eval = struct {
     arena: Allocator,
     case: Case,
+    target: Case.Target,
     tmp_dir: std.fs.Dir,
     tmp_dir_path: []const u8,
     child: *std.process.Child,
     allow_stderr: bool,
-    emit: EmitMode,
-    /// When `emit == .c`, this contains the first few arguments to `zig cc` to build the generated binary.
+    /// When `target.backend == .cbe`, this contains the first few arguments to `zig cc` to build the generated binary.
     /// The arguments `out.c in.c` must be appended before spawning the subprocess.
     cc_child_args: *std.ArrayListUnmanaged([]const u8),
 
@@ -262,7 +256,7 @@ const Eval = struct {
                         }
                     }
 
-                    if (eval.emit == .none) {
+                    if (eval.target.backend == .sema) {
                         try eval.checkSuccessOutcome(update, null, prog_node);
                         // This message indicates the end of the update.
                         stdout.discard(body.len);
@@ -275,11 +269,11 @@ const Eval = struct {
                     const bin_name = try std.zig.binNameAlloc(arena, .{
                         .root_name = name,
                         .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{
-                            .arch_os_abi = eval.case.target_query,
-                            .object_format = switch (eval.emit) {
-                                .none => unreachable,
-                                .bin => null,
-                                .c => "c",
+                            .arch_os_abi = eval.target.query,
+                            .object_format = switch (eval.target.backend) {
+                                .sema => unreachable,
+                                .selfhosted, .llvm => null,
+                                .cbe => "c",
                             },
                         })),
                         .output_mode = .Exe,
@@ -335,14 +329,14 @@ const Eval = struct {
             .stdout, .exit_code => {},
         }
         const emitted_path = opt_emitted_path orelse {
-            std.debug.assert(eval.emit == .none);
+            std.debug.assert(eval.target.backend == .sema);
             return;
         };
 
-        const binary_path = switch (eval.emit) {
-            .none => unreachable,
-            .bin => emitted_path,
-            .c => bin: {
+        const binary_path = switch (eval.target.backend) {
+            .sema => unreachable,
+            .selfhosted, .llvm => emitted_path,
+            .cbe => bin: {
                 const rand_int = std.crypto.random.int(u64);
                 const out_bin_name = "./out_" ++ std.fmt.hex(rand_int);
                 try eval.buildCOutput(update, emitted_path, out_bin_name, prog_node);
@@ -468,7 +462,26 @@ const Eval = struct {
 const Case = struct {
     updates: []Update,
     root_source_file: []const u8,
-    target_query: []const u8,
+    targets: []const Target,
+
+    const Target = struct {
+        query: []const u8,
+        backend: Backend,
+        const Backend = enum {
+            /// Run semantic analysis only. Runtime output will not be tested, but we still verify
+            /// that compilation succeeds. Corresponds to `-fno-emit-bin`.
+            sema,
+            /// Use the self-hosted code generation backend for this target.
+            /// Corresponds to `-fno-llvm -fno-lld`.
+            selfhosted,
+            /// Use the LLVM backend.
+            /// Corresponds to `-fllvm -flld`.
+            llvm,
+            /// Use the C backend. The output is compiled with `zig cc`.
+            /// Corresponds to `-ofmt=c`.
+            cbe,
+        };
+    };
 
     const Update = struct {
         name: []const u8,
@@ -498,9 +511,9 @@ const Case = struct {
     };
 
     fn parse(arena: Allocator, bytes: []const u8) !Case {
+        var targets: std.ArrayListUnmanaged(Target) = .empty;
         var updates: std.ArrayListUnmanaged(Update) = .empty;
         var changes: std.ArrayListUnmanaged(FullContents) = .empty;
-        var target_query: ?[]const u8 = null;
         var it = std.mem.splitScalar(u8, bytes, '\n');
         var line_n: usize = 1;
         var root_source_file: ?[]const u8 = null;
@@ -512,8 +525,16 @@ const Case = struct {
                 if (val.len == 0) {
                     fatal("line {d}: missing value", .{line_n});
                 } else if (std.mem.eql(u8, key, "target")) {
-                    if (target_query != null) fatal("line {d}: duplicate target", .{line_n});
-                    target_query = val;
+                    const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse
+                        fatal("line {d}: target does not include backend", .{line_n});
+                    const query = val[0..split_idx];
+                    const backend_str = val[split_idx + 1 ..];
+                    const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse
+                        fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });
+                    try targets.append(arena, .{
+                        .query = query,
+                        .backend = backend,
+                    });
                 } else if (std.mem.eql(u8, key, "update")) {
                     if (updates.items.len > 0) {
                         const last_update = &updates.items[updates.items.len - 1];
@@ -565,15 +586,19 @@ const Case = struct {
             }
         }
 
+        if (targets.items.len == 0) {
+            fatal("missing target", .{});
+        }
+
         if (changes.items.len > 0) {
             const last_update = &updates.items[updates.items.len - 1];
-            last_update.changes = try changes.toOwnedSlice(arena);
+            last_update.changes = changes.items; // arena so no need for toOwnedSlice
         }
 
         return .{
             .updates = updates.items,
             .root_source_file = root_source_file orelse fatal("missing root source file", .{}),
-            .target_query = target_query orelse fatal("missing target", .{}),
+            .targets = targets.items, // arena so no need for toOwnedSlice
         };
     }
 };