authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-09-05 22:59:09+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-09-05 22:59:09+02:00
log9eb2e11b854ecb58dd577edb009548f8da9a3927
tree5741c71d7ba2f6525f1b2be22840172855748ec7
parente39a2afa24a8f3db4d40b40703254011acc97efa
parent153371f5313c0ffbffe5a430a4e410f3d8608c53

Merge pull request '`std.zig.system`: some aarch64 detection enhancements/fixes' (#36764) from alexrp/zig:aarch64-detect-2 into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/36764

2 files changed, 65 insertions(+), 79 deletions(-)

lib/std/zig/system/arm.zig+24-24
...@@ -242,6 +242,10 @@ pub const aarch64 = struct {...@@ -242,6 +242,10 @@ pub const aarch64 = struct {
242 return @as(u4, @truncate(input >> offset));242 return @as(u4, @truncate(input >> offset));
243 }243 }
244244
245 inline fn signedBitField(input: u64, offset: u6) i4 {
246 return @as(i4, @bitCast(@as(u4, @truncate(input >> offset))));
247 }
248
245 /// Input array should consist of readouts from 12 system registers such that:249 /// Input array should consist of readouts from 12 system registers such that:
246 /// 0 -> MIDR_EL1250 /// 0 -> MIDR_EL1
247 /// 1 -> ID_AA64PFR0_EL1251 /// 1 -> ID_AA64PFR0_EL1
...@@ -255,18 +259,22 @@ pub const aarch64 = struct {...@@ -255,18 +259,22 @@ pub const aarch64 = struct {
255 /// 9 -> ID_AA64MMFR0_EL1259 /// 9 -> ID_AA64MMFR0_EL1
256 /// 10 -> ID_AA64MMFR1_EL1260 /// 10 -> ID_AA64MMFR1_EL1
257 /// 11 -> ID_AA64MMFR2_EL1261 /// 11 -> ID_AA64MMFR2_EL1
258 pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch, registers: [12]u64) ?Target.Cpu {262 pub fn detectNativeCpuAndFeatures(arch: Target.Cpu.Arch, registers: [12]u64) Target.Cpu {
259 const info = detectNativeCoreInfo(registers[0]);263 const info = detectNativeCoreInfo(registers[0]);
260 const model = cpu_models.isKnown(info, true) orelse return null;264 const model = cpu_models.isKnown(info, true) orelse Target.Cpu.Model.generic(arch);
261265
262 var cpu = Target.Cpu{266 var cpu = Target.Cpu{
263 .arch = arch,267 .arch = arch,
264 .model = model,268 .model = model,
265 .features = Target.Cpu.Feature.Set.empty,269 .features = .empty,
266 };270 };
267271
272 cpu.features.addFeatureSet(model.features);
273
268 detectNativeCpuFeatures(&cpu, registers[1..12]);274 detectNativeCpuFeatures(&cpu, registers[1..12]);
269 addInstructionFusions(&cpu, info);275 addInstructionFusions(&cpu);
276
277 cpu.features.populateDependencies(cpu.arch.allFeaturesList());
270278
271 return cpu;279 return cpu;
272 }280 }
...@@ -318,14 +326,9 @@ pub const aarch64 = struct {...@@ -318,14 +326,9 @@ pub const aarch64 = struct {
318 setFeature(cpu, .sve, bitField(registers[0], 32) >= 1);326 setFeature(cpu, .sve, bitField(registers[0], 32) >= 1);
319 setFeature(cpu, .el3, bitField(registers[0], 12) >= 1);327 setFeature(cpu, .el3, bitField(registers[0], 12) >= 1);
320 setFeature(cpu, .ras, bitField(registers[0], 28) >= 1);328 setFeature(cpu, .ras, bitField(registers[0], 28) >= 1);
321329 setFeature(cpu, .fp_armv8, signedBitField(registers[0], 16) >= 0);
322 if (bitField(registers[0], 20) < 0xF) blk: {330 setFeature(cpu, .neon, signedBitField(registers[0], 20) >= 0);
323 if (bitField(registers[0], 16) != bitField(registers[0], 20)) break :blk; // This should never occur331 setFeature(cpu, .fullfp16, signedBitField(registers[0], 16) >= 1 and signedBitField(registers[0], 20) >= 1);
324
325 setFeature(cpu, .neon, true);
326 setFeature(cpu, .fp_armv8, true);
327 setFeature(cpu, .fullfp16, bitField(registers[0], 20) > 0);
328 }
329332
330 // ID_AA64PFR1_EL1333 // ID_AA64PFR1_EL1
331 setFeature(cpu, .mpam, bitField(registers[1], 16) > 0 and bitField(registers[0], 40) == 0); // MPAM v0.1334 setFeature(cpu, .mpam, bitField(registers[1], 16) > 0 and bitField(registers[0], 40) == 0); // MPAM v0.1
...@@ -336,7 +339,7 @@ pub const aarch64 = struct {...@@ -336,7 +339,7 @@ pub const aarch64 = struct {
336 // ID_AA64DFR0_EL1339 // ID_AA64DFR0_EL1
337 setFeature(cpu, .tracev8_4, bitField(registers[2], 40) >= 1);340 setFeature(cpu, .tracev8_4, bitField(registers[2], 40) >= 1);
338 setFeature(cpu, .spe, bitField(registers[2], 32) >= 1);341 setFeature(cpu, .spe, bitField(registers[2], 32) >= 1);
339 setFeature(cpu, .perfmon, bitField(registers[2], 8) >= 1 and bitField(registers[2], 8) < 0xF);342 setFeature(cpu, .perfmon, signedBitField(registers[2], 8) >= 1);
340343
341 // ID_AA64DFR1_EL1 reserved344 // ID_AA64DFR1_EL1 reserved
342 // ID_AA64AFR0_EL1 reserved / implementation defined345 // ID_AA64AFR0_EL1 reserved / implementation defined
...@@ -387,17 +390,14 @@ pub const aarch64 = struct {...@@ -387,17 +390,14 @@ pub const aarch64 = struct {
387 setFeature(cpu, .uaops, bitField(registers[10], 4) >= 1);390 setFeature(cpu, .uaops, bitField(registers[10], 4) >= 1);
388 }391 }
389392
390 fn addInstructionFusions(cpu: *Target.Cpu, info: CoreInfo) void {393 fn addInstructionFusions(cpu: *Target.Cpu) void {
391 switch (info.implementer) {394 const m = cpu.model;
392 0x41 => switch (info.part) {395 const c = Target.aarch64.cpu;
393 0xd4b, 0xd4c => {396
394 // According to A78C/X1C Core Software Optimization Guide, CPU fuses certain instructions.397 if (m == &c.cortex_a78c or m == &c.cortex_x1c) {
395 setFeature(cpu, .cmp_bcc_fusion, true);398 // According to A78C/X1C Core Software Optimization Guide, CPU fuses certain instructions.
396 setFeature(cpu, .fuse_aes, true);399 setFeature(cpu, .cmp_bcc_fusion, true);
397 },400 setFeature(cpu, .fuse_aes, true);
398 else => {},
399 },
400 else => {},
401 }401 }
402 }402 }
403};403};
lib/std/zig/system/windows.zig+41-55
...@@ -3,11 +3,10 @@ const builtin = @import("builtin");...@@ -3,11 +3,10 @@ const builtin = @import("builtin");
3const assert = std.debug.assert;3const assert = std.debug.assert;
4const mem = std.mem;4const mem = std.mem;
5const Target = std.Target;5const Target = std.Target;
66const WindowsVersion = std.Target.Os.WindowsVersion;
7pub const WindowsVersion = std.Target.Os.WindowsVersion;7const PF = std.os.windows.PF;
8pub const PF = std.os.windows.PF;8const REG = std.os.windows.REG;
9pub const REG = std.os.windows.REG;9const IsProcessorFeaturePresent = std.os.windows.IsProcessorFeaturePresent;
10pub const IsProcessorFeaturePresent = std.os.windows.IsProcessorFeaturePresent;
1110
12/// Returns the highest known WindowsVersion deduced from reported runtime information.11/// Returns the highest known WindowsVersion deduced from reported runtime information.
13/// Discards information about in-between versions we don't differentiate.12/// Discards information about in-between versions we don't differentiate.
...@@ -193,21 +192,19 @@ fn setFeature(comptime Feature: type, cpu: *Target.Cpu, feature: Feature, enable...@@ -193,21 +192,19 @@ fn setFeature(comptime Feature: type, cpu: *Target.Cpu, feature: Feature, enable
193 if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx);192 if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx);
194}193}
195194
196fn getCpuCount() usize {
197 return std.os.windows.peb().NumberOfProcessors;
198}
199
200/// If the fine-grained detection of CPU features via Win registry fails,195/// If the fine-grained detection of CPU features via Win registry fails,
201/// we fallback to a generic CPU model but we override the feature set196/// we fallback to a generic CPU model but we override the feature set
202/// using `SharedUserData` contents.197/// using `SharedUserData` contents.
203/// This is effectively what LLVM does for all ARM chips on Windows.198/// This is effectively what LLVM does for all ARM chips on Windows.
204fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu {199fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu {
205 var cpu = Target.Cpu{200 var cpu: Target.Cpu = .{
206 .arch = arch,201 .arch = arch,
207 .model = Target.Cpu.Model.generic(arch),202 .model = Target.Cpu.Model.generic(arch),
208 .features = Target.Cpu.Feature.Set.empty,203 .features = .empty,
209 };204 };
210205
206 cpu.features.addFeatureSet(cpu.model.features);
207
211 switch (arch) {208 switch (arch) {
212 .aarch64, .aarch64_be => {209 .aarch64, .aarch64_be => {
213 const Feature = Target.aarch64.Feature;210 const Feature = Target.aarch64.Feature;
...@@ -256,6 +253,8 @@ fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu {...@@ -256,6 +253,8 @@ fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu {
256 else => {},253 else => {},
257 }254 }
258255
256 cpu.features.populateDependencies(cpu.arch.allFeaturesList());
257
259 return cpu;258 return cpu;
260}259}
261260
...@@ -263,52 +262,39 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu {...@@ -263,52 +262,39 @@ pub fn detectNativeCpuAndFeatures() ?Target.Cpu {
263 const current_arch = builtin.cpu.arch;262 const current_arch = builtin.cpu.arch;
264 const cpu: ?Target.Cpu = switch (current_arch) {263 const cpu: ?Target.Cpu = switch (current_arch) {
265 .aarch64, .aarch64_be => blk: {264 .aarch64, .aarch64_be => blk: {
266 var cores: [128]Target.Cpu = undefined;265 var registers: [12]u64 = undefined;
267 const core_count = getCpuCount();266
268267 // CP 4000 -> MIDR_EL1
269 if (core_count > cores.len) break :blk null;268 // CP 4020 -> ID_AA64PFR0_EL1
270269 // CP 4021 -> ID_AA64PFR1_EL1
271 var i: usize = 0;270 // CP 4028 -> ID_AA64DFR0_EL1
272 while (i < core_count) : (i += 1) {271 // CP 4029 -> ID_AA64DFR1_EL1
273 // Backing datastore272 // CP 402C -> ID_AA64AFR0_EL1
274 var registers: [12]u64 = undefined;273 // CP 402D -> ID_AA64AFR1_EL1
275274 // CP 4030 -> ID_AA64ISAR0_EL1
276 // Registry key to system ID register mapping275 // CP 4031 -> ID_AA64ISAR1_EL1
277 // CP 4000 -> MIDR_EL1276 // CP 4038 -> ID_AA64MMFR0_EL1
278 // CP 4020 -> ID_AA64PFR0_EL1277 // CP 4039 -> ID_AA64MMFR1_EL1
279 // CP 4021 -> ID_AA64PFR1_EL1278 // CP 403A -> ID_AA64MMFR2_EL1
280 // CP 4028 -> ID_AA64DFR0_EL1279 getCpuInfoFromRegistry(0, .{
281 // CP 4029 -> ID_AA64DFR1_EL1280 .{ .key = "CP 4000", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[0])) },
282 // CP 402C -> ID_AA64AFR0_EL1281 .{ .key = "CP 4020", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[1])) },
283 // CP 402D -> ID_AA64AFR1_EL1282 .{ .key = "CP 4021", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[2])) },
284 // CP 4030 -> ID_AA64ISAR0_EL1283 .{ .key = "CP 4028", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[3])) },
285 // CP 4031 -> ID_AA64ISAR1_EL1284 .{ .key = "CP 4029", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[4])) },
286 // CP 4038 -> ID_AA64MMFR0_EL1285 .{ .key = "CP 402C", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[5])) },
287 // CP 4039 -> ID_AA64MMFR1_EL1286 .{ .key = "CP 402D", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[6])) },
288 // CP 403A -> ID_AA64MMFR2_EL1287 .{ .key = "CP 4030", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[7])) },
289 getCpuInfoFromRegistry(i, .{288 .{ .key = "CP 4031", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[8])) },
290 .{ .key = "CP 4000", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[0])) },289 .{ .key = "CP 4038", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[9])) },
291 .{ .key = "CP 4020", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[1])) },290 .{ .key = "CP 4039", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[10])) },
292 .{ .key = "CP 4021", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[2])) },291 .{ .key = "CP 403A", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[11])) },
293 .{ .key = "CP 4028", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[3])) },292 }) catch break :blk null;
294 .{ .key = "CP 4029", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[4])) },293
295 .{ .key = "CP 402C", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[5])) },294 break :blk @import("arm.zig").aarch64.detectNativeCpuAndFeatures(current_arch, registers);
296 .{ .key = "CP 402D", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[6])) },
297 .{ .key = "CP 4030", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[7])) },
298 .{ .key = "CP 4031", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[8])) },
299 .{ .key = "CP 4038", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[9])) },
300 .{ .key = "CP 4039", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[10])) },
301 .{ .key = "CP 403A", .value_type = REG.ValueType.QWORD, .value_buf = @as(*[8]u8, @ptrCast(&registers[11])) },
302 }) catch break :blk null;
303
304 cores[i] = @import("arm.zig").aarch64.detectNativeCpuAndFeatures(current_arch, registers) orelse
305 break :blk null;
306 }
307
308 // Pick the first core, usually LITTLE in big.LITTLE architecture.
309 break :blk cores[0];
310 },295 },
311 else => null,296 else => null,
312 };297 };
298
313 return cpu orelse genericCpuAndNativeFeatures(current_arch);299 return cpu orelse genericCpuAndNativeFeatures(current_arch);
314}300}