Commit 78002dbe47

Andrew Kelley <andrew@ziglang.org>
2024-04-29 04:59:56
add detect-cpu subcommand for debugging CPU features
This brings back `detectNativeCpuWithLLVM` so that we can troubleshoot during LLVM upgrades. closes #19793
1 parent 06ee65a
Changed files (2)
src
src/codegen/llvm/bindings.zig
@@ -400,3 +400,9 @@ extern fn ZigLLVMWriteImportLibrary(
     output_lib_path: [*:0]const u8,
     kill_at: bool,
 ) bool;
+
+pub const GetHostCPUName = LLVMGetHostCPUName;
+extern fn LLVMGetHostCPUName() ?[*:0]u8;
+
+pub const GetHostCPUFeatures = LLVMGetHostCPUFeatures;
+extern fn LLVMGetHostCPUFeatures() ?[*:0]u8;
src/main.zig
@@ -117,6 +117,7 @@ const debug_usage = normal_usage ++
     \\
     \\  changelist       Compute mappings from old ZIR to new ZIR
     \\  dump-zir         Dump a file containing cached ZIR
+    \\  detect-cpu       Compare Zig's CPU feature detection vs LLVM
     \\
 ;
 
@@ -352,6 +353,8 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
         return io.getStdOut().writeAll(usage);
     } else if (mem.eql(u8, cmd, "ast-check")) {
         return cmdAstCheck(gpa, arena, cmd_args);
+    } else if (mem.eql(u8, cmd, "detect-cpu")) {
+        return cmdDetectCpu(gpa, arena, cmd_args);
     } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "changelist")) {
         return cmdChangelist(gpa, arena, cmd_args);
     } else if (build_options.enable_debug_extensions and mem.eql(u8, cmd, "dump-zir")) {
@@ -6150,6 +6153,145 @@ fn cmdAstCheck(
     return @import("print_zir.zig").renderAsTextToFile(gpa, &file, io.getStdOut());
 }
 
+fn cmdDetectCpu(
+    gpa: Allocator,
+    arena: Allocator,
+    args: []const []const u8,
+) !void {
+    _ = gpa;
+    _ = arena;
+
+    const detect_cpu_usage =
+        \\Usage: zig detect-cpu [--llvm]
+        \\
+        \\    Print the host CPU name and feature set to stdout.
+        \\
+        \\Options:
+        \\  -h, --help                    Print this help and exit
+        \\  --llvm                        Detect using LLVM API
+        \\
+    ;
+
+    var use_llvm = false;
+
+    {
+        var i: usize = 0;
+        while (i < args.len) : (i += 1) {
+            const arg = args[i];
+            if (mem.startsWith(u8, arg, "-")) {
+                if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
+                    const stdout = io.getStdOut().writer();
+                    try stdout.writeAll(detect_cpu_usage);
+                    return cleanExit();
+                } else if (mem.eql(u8, arg, "--llvm")) {
+                    use_llvm = true;
+                } else {
+                    fatal("unrecognized parameter: '{s}'", .{arg});
+                }
+            } else {
+                fatal("unexpected extra parameter: '{s}'", .{arg});
+            }
+        }
+    }
+
+    if (use_llvm) {
+        if (!build_options.have_llvm)
+            fatal("compiler does not use LLVM; cannot compare CPU features with LLVM", .{});
+
+        const llvm = @import("codegen/llvm/bindings.zig");
+        const name = llvm.GetHostCPUName() orelse fatal("LLVM could not figure out the host cpu name", .{});
+        const features = llvm.GetHostCPUFeatures() orelse fatal("LLVM could not figure out the host cpu feature set", .{});
+        const cpu = try detectNativeCpuWithLLVM(builtin.cpu.arch, name, features);
+        try printCpu(cpu);
+    } else {
+        const host_target = std.zig.resolveTargetQueryOrFatal(.{});
+        try printCpu(host_target.cpu);
+    }
+}
+
+fn detectNativeCpuWithLLVM(
+    arch: std.Target.Cpu.Arch,
+    llvm_cpu_name_z: ?[*:0]const u8,
+    llvm_cpu_features_opt: ?[*:0]const u8,
+) !std.Target.Cpu {
+    var result = std.Target.Cpu.baseline(arch);
+
+    if (llvm_cpu_name_z) |cpu_name_z| {
+        const llvm_cpu_name = mem.span(cpu_name_z);
+
+        for (arch.allCpuModels()) |model| {
+            const this_llvm_name = model.llvm_name orelse continue;
+            if (mem.eql(u8, this_llvm_name, llvm_cpu_name)) {
+                // Here we use the non-dependencies-populated set,
+                // so that subtracting features later in this function
+                // affect the prepopulated set.
+                result = std.Target.Cpu{
+                    .arch = arch,
+                    .model = model,
+                    .features = model.features,
+                };
+                break;
+            }
+        }
+    }
+
+    const all_features = arch.allFeaturesList();
+
+    if (llvm_cpu_features_opt) |llvm_cpu_features| {
+        var it = mem.tokenizeScalar(u8, mem.span(llvm_cpu_features), ',');
+        while (it.next()) |decorated_llvm_feat| {
+            var op: enum {
+                add,
+                sub,
+            } = undefined;
+            var llvm_feat: []const u8 = undefined;
+            if (mem.startsWith(u8, decorated_llvm_feat, "+")) {
+                op = .add;
+                llvm_feat = decorated_llvm_feat[1..];
+            } else if (mem.startsWith(u8, decorated_llvm_feat, "-")) {
+                op = .sub;
+                llvm_feat = decorated_llvm_feat[1..];
+            } else {
+                return error.InvalidLlvmCpuFeaturesFormat;
+            }
+            for (all_features, 0..) |feature, index_usize| {
+                const this_llvm_name = feature.llvm_name orelse continue;
+                if (mem.eql(u8, llvm_feat, this_llvm_name)) {
+                    const index: std.Target.Cpu.Feature.Set.Index = @intCast(index_usize);
+                    switch (op) {
+                        .add => result.features.addFeature(index),
+                        .sub => result.features.removeFeature(index),
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    result.features.populateDependencies(all_features);
+    return result;
+}
+
+fn printCpu(cpu: std.Target.Cpu) !void {
+    var bw = io.bufferedWriter(io.getStdOut().writer());
+    const stdout = bw.writer();
+
+    if (cpu.model.llvm_name) |llvm_name| {
+        try stdout.print("{s}\n", .{llvm_name});
+    }
+
+    const all_features = cpu.arch.allFeaturesList();
+    for (all_features, 0..) |feature, index_usize| {
+        const llvm_name = feature.llvm_name orelse continue;
+        const index: std.Target.Cpu.Feature.Set.Index = @intCast(index_usize);
+        const is_enabled = cpu.features.isEnabled(index);
+        const plus_or_minus = "-+"[@intFromBool(is_enabled)];
+        try stdout.print("{c}{s}\n", .{ plus_or_minus, llvm_name });
+    }
+
+    try bw.flush();
+}
+
 /// This is only enabled for debug builds.
 fn cmdDumpZir(
     gpa: Allocator,