Last active
May 11, 2025 07:14
-
-
Save jacobly0/83ea38f670519a4e99397f7d07161cb8 to your computer and use it in GitHub Desktop.
Zig cpu feature canonicalization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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