Skip to content

Commit

Permalink
add detect-cpu subcommand for debugging CPU features
Browse files Browse the repository at this point in the history
This brings back `detectNativeCpuWithLLVM` so that we can troubleshoot
during LLVM upgrades.

closes #19793
  • Loading branch information
andrewrk committed Apr 29, 2024
1 parent 9f5ad3d commit dc6cb4c
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/codegen/llvm/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
142 changes: 142 additions & 0 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
\\
;

Expand Down Expand Up @@ -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")) {
Expand Down Expand Up @@ -6198,6 +6201,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,
Expand Down

0 comments on commit dc6cb4c

Please sign in to comment.