Skip to content

Instantly share code, notes, and snippets.

@jacobly0
Last active May 11, 2025 07:14
Show Gist options
  • Save jacobly0/83ea38f670519a4e99397f7d07161cb8 to your computer and use it in GitHub Desktop.
Save jacobly0/83ea38f670519a4e99397f7d07161cb8 to your computer and use it in GitHub Desktop.
Zig cpu feature canonicalization
const std = @import("std");
const Cpu = std.Target.Cpu;
const Query = std.Target.Query;
pub fn main() !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .{};
defer _ = debug_allocator.deinit();
const gpa = debug_allocator.allocator();
var args = try std.process.argsWithAllocator(gpa);
_ = args.skip();
const cpu = try parseCpu(args.next().?);
var cpu_string: std.ArrayListUnmanaged(u8) = .empty;
defer cpu_string.deinit(gpa);
try serializeCpu(gpa, cpu, cpu_string.writer(gpa));
const round_trip_cpu = try parseCpu(cpu_string.items);
if (cpu.arch != round_trip_cpu.arch or cpu.model != round_trip_cpu.model or !cpu.features.eql(round_trip_cpu.features)) std.debug.print("ROUND TRIP FAILED! ", .{});
std.debug.print("{s}\n", .{cpu_string.items});
}
fn parseCpu(triple: []const u8) !Cpu {
var target_it = std.mem.splitScalar(u8, triple, '-');
var arch_it = std.mem.splitScalar(u8, target_it.first(), '.');
const arch = std.meta.stringToEnum(Cpu.Arch, arch_it.first()) orelse return error.UnknownArch;
var remaining = arch_it.next() orelse return Cpu.Model.generic(arch).toCpu(arch);
const model_name_end = std.mem.indexOfAny(u8, remaining, "+~-") orelse remaining.len;
const model_name = remaining[0..model_name_end];
remaining = remaining[model_name_end..];
var query: Query = .{ .cpu_arch = arch };
if (std.mem.eql(u8, model_name, "native")) {
query.cpu_model = .native;
} else if (std.mem.eql(u8, model_name, "baseline")) {
query.cpu_model = .baseline;
} else {
query.cpu_model = .{ .explicit = try arch.parseCpuModel(model_name) };
}
const all_features = arch.allFeaturesList();
while (remaining.len > 0) {
const set = switch (remaining[0]) {
else => unreachable,
'+' => &query.cpu_features_add,
'~' => &query.cpu_features_sub,
'-' => break,
};
const feature_name_end = std.mem.indexOfAnyPos(u8, remaining, 1, "+~-") orelse remaining.len;
const feature_name = remaining[1..feature_name_end];
remaining = remaining[feature_name_end..];
for (all_features, 0..) |feature, feature_index| {
if (!std.mem.eql(u8, feature.name, feature_name)) continue;
set.addFeature(@intCast(feature_index));
break;
} else return error.UnknownFeature;
}
return (try std.zig.system.resolveTargetQuery(query)).cpu;
}
fn serializeCpu(gpa: std.mem.Allocator, cpu: Cpu, writer: anytype) !void {
try writer.writeAll(@tagName(cpu.arch));
try writer.writeByte('.');
try writer.writeAll(cpu.model.name);
const all_features = cpu.arch.allFeaturesList();
const degrees = try gpa.alloc(u8, all_features.len);
defer gpa.free(degrees);
@memset(degrees, 0);
for (all_features) |depender_feature| for (degrees, 0..) |*dependee_degree, dependee_index| {
dependee_degree.* += @intFromBool(depender_feature.dependencies.isEnabled(@intCast(dependee_index)));
};
var add_set: Cpu.Feature.Set = .empty;
var sub_set: Cpu.Feature.Set = .empty;
var already_enabled_set = cpu.model.features;
var remaining_features = all_features.len;
while (remaining_features > 0) {
for (degrees, 0.., all_features) |*depender_degree, depender_index, depender_feature| {
if (depender_degree.* != 0) continue;
depender_degree.* = std.math.maxInt(u8);
remaining_features -= 1;
const already_enabled = already_enabled_set.isEnabled(@intCast(depender_index));
const should_be_enabled = cpu.features.isEnabled(@intCast(depender_index));
if (!already_enabled and should_be_enabled) add_set.addFeature(@intCast(depender_index));
if (already_enabled and !should_be_enabled) sub_set.addFeature(@intCast(depender_index));
if (already_enabled or should_be_enabled) already_enabled_set.addFeatureSet(depender_feature.dependencies);
for (degrees, 0..) |*dependee_degree, dependee_index| {
dependee_degree.* -= @intFromBool(depender_feature.dependencies.isEnabled(@intCast(dependee_index)));
}
}
}
for (all_features, 0..) |feature, feature_index| {
if (add_set.isEnabled(@intCast(feature_index))) {
try writer.print("+{s}", .{feature.name});
} else if (sub_set.isEnabled(@intCast(feature_index))) {
try writer.print("~{s}", .{feature.name});
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment