| ... | @@ -51,23 +51,22 @@ pub fn detectRuntimeVersion() WindowsVersion { | ... | @@ -51,23 +51,22 @@ pub fn detectRuntimeVersion() WindowsVersion { |
| 51 | // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits | 51 | // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits |
| 52 | const max_value_len = 2048; | 52 | const max_value_len = 2048; |
| 53 | | 53 | |
| 54 | const RegistryPair = struct { | 54 | fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { |
| 55 | key: []const u8, | 55 | const ArgsType = @TypeOf(args); |
| 56 | value: std.os.windows.ULONG, | 56 | const args_type_info = @typeInfo(ArgsType); |
| 57 | }; | 57 | |
| 58 | | 58 | if (args_type_info != .Struct) { |
| 59 | fn getCpuInfoFromRegistry( | 59 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); |
| 60 | core: usize, | 60 | } |
| 61 | comptime pairs_num: comptime_int, | 61 | |
| 62 | comptime pairs: [pairs_num]RegistryPair, | 62 | const fields_info = args_type_info.Struct.fields; |
| 63 | out_buf: *[pairs_num][max_value_len]u8, | 63 | |
| 64 | ) !void { | | |
| 65 | // Originally, I wanted to issue a single call with a more complex table structure such that we | 64 | // Originally, I wanted to issue a single call with a more complex table structure such that we |
| 66 | // would sequentially visit each CPU#d subkey in the registry and pull the value of interest into | 65 | // would sequentially visit each CPU#d subkey in the registry and pull the value of interest into |
| 67 | // a buffer, however, NT seems to be expecting a single buffer per each table meaning we would | 66 | // a buffer, however, NT seems to be expecting a single buffer per each table meaning we would |
| 68 | // end up pulling only the last CPU core info, overwriting everything else. | 67 | // end up pulling only the last CPU core info, overwriting everything else. |
| 69 | // If anyone can come up with a solution to this, please do! | 68 | // If anyone can come up with a solution to this, please do! |
| 70 | const table_size = 1 + pairs.len; | 69 | const table_size = 1 + fields_info.len; |
| 71 | var table: [table_size + 1]std.os.windows.RTL_QUERY_REGISTRY_TABLE = undefined; | 70 | var table: [table_size + 1]std.os.windows.RTL_QUERY_REGISTRY_TABLE = undefined; |
| 72 | | 71 | |
| 73 | const topkey = std.unicode.utf8ToUtf16LeStringLiteral("\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor"); | 72 | const topkey = std.unicode.utf8ToUtf16LeStringLiteral("\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor"); |
| ... | @@ -90,9 +89,9 @@ fn getCpuInfoFromRegistry( | ... | @@ -90,9 +89,9 @@ fn getCpuInfoFromRegistry( |
| 90 | .DefaultLength = 0, | 89 | .DefaultLength = 0, |
| 91 | }; | 90 | }; |
| 92 | | 91 | |
| 93 | inline for (pairs) |pair, i| { | 92 | inline for (fields_info) |field, i| { |
| 94 | const ctx: *anyopaque = blk: { | 93 | const ctx: *anyopaque = blk: { |
| 95 | switch (pair.value) { | 94 | switch (@field(args, field.name).value_type) { |
| 96 | REG.SZ, | 95 | REG.SZ, |
| 97 | REG.EXPAND_SZ, | 96 | REG.EXPAND_SZ, |
| 98 | REG.MULTI_SZ, | 97 | REG.MULTI_SZ, |
| ... | @@ -121,12 +120,15 @@ fn getCpuInfoFromRegistry( | ... | @@ -121,12 +120,15 @@ fn getCpuInfoFromRegistry( |
| 121 | else => unreachable, | 120 | else => unreachable, |
| 122 | } | 121 | } |
| 123 | }; | 122 | }; |
| 124 | const key_namee = std.unicode.utf8ToUtf16LeStringLiteral(pair.key); | 123 | |
| | 124 | var key_buf: [max_value_len / 2 + 1]u16 = undefined; |
| | 125 | const key_len = try std.unicode.utf8ToUtf16Le(&key_buf, @field(args, field.name).key); |
| | 126 | key_buf[key_len] = 0; |
| 125 | | 127 | |
| 126 | table[i + 1] = .{ | 128 | table[i + 1] = .{ |
| 127 | .QueryRoutine = null, | 129 | .QueryRoutine = null, |
| 128 | .Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED, | 130 | .Flags = std.os.windows.RTL_QUERY_REGISTRY_DIRECT | std.os.windows.RTL_QUERY_REGISTRY_REQUIRED, |
| 129 | .Name = @intToPtr([*:0]u16, @ptrToInt(key_namee)), | 131 | .Name = key_buf[0..key_len :0], |
| 130 | .EntryContext = ctx, | 132 | .EntryContext = ctx, |
| 131 | .DefaultType = REG.NONE, | 133 | .DefaultType = REG.NONE, |
| 132 | .DefaultData = null, | 134 | .DefaultData = null, |
| ... | @@ -154,16 +156,15 @@ fn getCpuInfoFromRegistry( | ... | @@ -154,16 +156,15 @@ fn getCpuInfoFromRegistry( |
| 154 | ); | 156 | ); |
| 155 | switch (res) { | 157 | switch (res) { |
| 156 | .SUCCESS => { | 158 | .SUCCESS => { |
| 157 | inline for (pairs) |pair, i| switch (pair.value) { | 159 | inline for (fields_info) |field, i| switch (@field(args, field.name).value_type) { |
| 158 | REG.NONE => unreachable, | | |
| 159 | | | |
| 160 | REG.SZ, | 160 | REG.SZ, |
| 161 | REG.EXPAND_SZ, | 161 | REG.EXPAND_SZ, |
| 162 | REG.MULTI_SZ, | 162 | REG.MULTI_SZ, |
| 163 | => { | 163 | => { |
| | 164 | var buf = @field(args, field.name).value_buf; |
| 164 | const entry = @ptrCast(*align(1) const std.os.windows.UNICODE_STRING, table[i + 1].EntryContext); | 165 | const entry = @ptrCast(*align(1) const std.os.windows.UNICODE_STRING, table[i + 1].EntryContext); |
| 165 | const len = try std.unicode.utf16leToUtf8(out_buf[i][0..], entry.Buffer[0 .. entry.Length / 2]); | 166 | const len = try std.unicode.utf16leToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); |
| 166 | out_buf[i][len] = 0; | 167 | buf[len] = 0; |
| 167 | }, | 168 | }, |
| 168 | | 169 | |
| 169 | REG.DWORD, | 170 | REG.DWORD, |
| ... | @@ -171,12 +172,12 @@ fn getCpuInfoFromRegistry( | ... | @@ -171,12 +172,12 @@ fn getCpuInfoFromRegistry( |
| 171 | REG.QWORD, | 172 | REG.QWORD, |
| 172 | => { | 173 | => { |
| 173 | const entry = @ptrCast([*]align(1) const u8, table[i + 1].EntryContext); | 174 | const entry = @ptrCast([*]align(1) const u8, table[i + 1].EntryContext); |
| 174 | switch (pair.value) { | 175 | switch (@field(args, field.name).value_type) { |
| 175 | REG.DWORD, REG.DWORD_BIG_ENDIAN => { | 176 | REG.DWORD, REG.DWORD_BIG_ENDIAN => { |
| 176 | mem.copy(u8, out_buf[i][0..4], entry[0..4]); | 177 | mem.copy(u8, @field(args, field.name).value_buf[0..4], entry[0..4]); |
| 177 | }, | 178 | }, |
| 178 | REG.QWORD => { | 179 | REG.QWORD => { |
| 179 | mem.copy(u8, out_buf[i][0..8], entry[0..8]); | 180 | mem.copy(u8, @field(args, field.name).value_buf[0..8], entry[0..8]); |
| 180 | }, | 181 | }, |
| 181 | else => unreachable, | 182 | else => unreachable, |
| 182 | } | 183 | } |
| ... | @@ -189,173 +190,95 @@ fn getCpuInfoFromRegistry( | ... | @@ -189,173 +190,95 @@ fn getCpuInfoFromRegistry( |
| 189 | } | 190 | } |
| 190 | } | 191 | } |
| 191 | | 192 | |
| 192 | fn getCpuCount() usize { | 193 | fn setFeature(comptime Feature: type, cpu: *Target.Cpu, feature: Feature, enabled: bool) void { |
| 193 | return std.os.windows.peb().NumberOfProcessors; | 194 | const idx = @as(Target.Cpu.Feature.Set.Index, @enumToInt(feature)); |
| 194 | } | | |
| 195 | | | |
| 196 | const ArmCpuInfoImpl = struct { | | |
| 197 | cores: [4]CoreInfo = undefined, | | |
| 198 | core_no: usize = 0, | | |
| 199 | have_fields: usize = 0, | | |
| 200 | | | |
| 201 | const CoreInfo = @import("arm.zig").CoreInfo; | | |
| 202 | const cpu_models = @import("arm.zig").cpu_models; | | |
| 203 | | | |
| 204 | const Data = struct { | | |
| 205 | cp_4000: []const u8, | | |
| 206 | identifier: []const u8, | | |
| 207 | }; | | |
| 208 | | | |
| 209 | fn parseDataHook(self: *ArmCpuInfoImpl, data: Data) !void { | | |
| 210 | const info = &self.cores[self.core_no]; | | |
| 211 | info.* = .{}; | | |
| 212 | | | |
| 213 | // CPU part | | |
| 214 | info.part = mem.readIntLittle(u16, data.cp_4000[0..2]) >> 4; | | |
| 215 | self.have_fields += 1; | | |
| 216 | | | |
| 217 | // CPU implementer | | |
| 218 | info.implementer = data.cp_4000[3]; | | |
| 219 | self.have_fields += 1; | | |
| 220 | | | |
| 221 | var tokens = mem.tokenize(u8, data.identifier, " "); | | |
| 222 | while (tokens.next()) |token| { | | |
| 223 | if (mem.eql(u8, "Family", token)) { | | |
| 224 | // CPU architecture | | |
| 225 | const family = tokens.next() orelse continue; | | |
| 226 | info.architecture = try std.fmt.parseInt(u8, family, 10); | | |
| 227 | self.have_fields += 1; | | |
| 228 | break; | | |
| 229 | } | | |
| 230 | } else return; | | |
| 231 | | | |
| 232 | self.addOne(); | | |
| 233 | } | | |
| 234 | | | |
| 235 | fn addOne(self: *ArmCpuInfoImpl) void { | | |
| 236 | if (self.have_fields == 3 and self.core_no < self.cores.len) { | | |
| 237 | if (self.core_no > 0) { | | |
| 238 | // Deduplicate the core info. | | |
| 239 | for (self.cores[0..self.core_no]) |it| { | | |
| 240 | if (std.meta.eql(it, self.cores[self.core_no])) | | |
| 241 | return; | | |
| 242 | } | | |
| 243 | } | | |
| 244 | self.core_no += 1; | | |
| 245 | } | | |
| 246 | } | | |
| 247 | | | |
| 248 | fn finalize(self: ArmCpuInfoImpl, arch: Target.Cpu.Arch) ?Target.Cpu { | | |
| 249 | if (self.core_no == 0) return null; | | |
| 250 | | | |
| 251 | const is_64bit = switch (arch) { | | |
| 252 | .aarch64, .aarch64_be, .aarch64_32 => true, | | |
| 253 | else => false, | | |
| 254 | }; | | |
| 255 | | | |
| 256 | var known_models: [self.cores.len]?*const Target.Cpu.Model = undefined; | | |
| 257 | for (self.cores[0..self.core_no]) |core, i| { | | |
| 258 | known_models[i] = cpu_models.isKnown(core, is_64bit); | | |
| 259 | } | | |
| 260 | | | |
| 261 | // XXX We pick the first core on big.LITTLE systems, hopefully the | | |
| 262 | // LITTLE one. | | |
| 263 | const model = known_models[0] orelse return null; | | |
| 264 | return Target.Cpu{ | | |
| 265 | .arch = arch, | | |
| 266 | .model = model, | | |
| 267 | .features = model.features, | | |
| 268 | }; | | |
| 269 | } | | |
| 270 | }; | | |
| 271 | | | |
| 272 | const ArmCpuInfoParser = CpuInfoParser(ArmCpuInfoImpl); | | |
| 273 | | 195 | |
| 274 | fn CpuInfoParser(comptime impl: anytype) type { | 196 | if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx); |
| 275 | return struct { | 197 | } |
| 276 | fn parse(arch: Target.Cpu.Arch) !?Target.Cpu { | | |
| 277 | var obj: impl = .{}; | | |
| 278 | var out_buf: [2][max_value_len]u8 = undefined; | | |
| 279 | | | |
| 280 | var i: usize = 0; | | |
| 281 | while (i < getCpuCount()) : (i += 1) { | | |
| 282 | try getCpuInfoFromRegistry(i, 2, .{ | | |
| 283 | .{ .key = "CP 4000", .value = REG.QWORD }, | | |
| 284 | .{ .key = "Identifier", .value = REG.SZ }, | | |
| 285 | }, &out_buf); | | |
| 286 | | | |
| 287 | const cp_4000 = out_buf[0][0..8]; | | |
| 288 | const identifier = mem.sliceTo(out_buf[1][0..], 0); | | |
| 289 | | | |
| 290 | try obj.parseDataHook(.{ | | |
| 291 | .cp_4000 = cp_4000, | | |
| 292 | .identifier = identifier, | | |
| 293 | }); | | |
| 294 | } | | |
| 295 | | 198 | |
| 296 | return obj.finalize(arch); | 199 | fn getCpuCount() usize { |
| 297 | } | 200 | return std.os.windows.peb().NumberOfProcessors; |
| 298 | }; | | |
| 299 | } | 201 | } |
| 300 | | 202 | |
| 301 | fn genericCpu(comptime arch: Target.Cpu.Arch) Target.Cpu { | 203 | /// If the fine-grained detection of CPU features via Win registry fails, |
| 302 | return .{ | 204 | /// we fallback to a generic CPU model but we override the feature set |
| | 205 | /// using `SharedUserData` contents. |
| | 206 | /// This is effectively what LLVM does for all ARM chips on Windows. |
| | 207 | fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu { |
| | 208 | var cpu = Target.Cpu{ |
| 303 | .arch = arch, | 209 | .arch = arch, |
| 304 | .model = Target.Cpu.Model.generic(arch), | 210 | .model = Target.Cpu.Model.generic(arch), |
| 305 | .features = Target.Cpu.Feature.Set.empty, | 211 | .features = Target.Cpu.Feature.Set.empty, |
| 306 | }; | 212 | }; |
| 307 | } | | |
| 308 | | 213 | |
| 309 | pub fn detectNativeCpuAndFeatures() ?Target.Cpu { | 214 | switch (arch) { |
| 310 | const current_arch = builtin.cpu.arch; | | |
| 311 | switch (current_arch) { | | |
| 312 | .aarch64, .aarch64_be, .aarch64_32 => { | 215 | .aarch64, .aarch64_be, .aarch64_32 => { |
| 313 | var cpu = cpu: { | | |
| 314 | var maybe_cpu = ArmCpuInfoParser.parse(current_arch) catch break :cpu genericCpu(current_arch); | | |
| 315 | break :cpu maybe_cpu orelse genericCpu(current_arch); | | |
| 316 | }; | | |
| 317 | | | |
| 318 | const Feature = Target.aarch64.Feature; | 216 | const Feature = Target.aarch64.Feature; |
| 319 | | 217 | |
| 320 | // Override any features that are either present or absent | 218 | // Override any features that are either present or absent |
| 321 | if (IsProcessorFeaturePresent(PF.ARM_NEON_INSTRUCTIONS_AVAILABLE)) { | 219 | setFeature(Feature, &cpu, .neon, IsProcessorFeaturePresent(PF.ARM_NEON_INSTRUCTIONS_AVAILABLE)); |
| 322 | cpu.features.addFeature(@enumToInt(Feature.neon)); | 220 | setFeature(Feature, &cpu, .crc, IsProcessorFeaturePresent(PF.ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)); |
| 323 | } else { | 221 | setFeature(Feature, &cpu, .crypto, IsProcessorFeaturePresent(PF.ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)); |
| 324 | cpu.features.removeFeature(@enumToInt(Feature.neon)); | 222 | setFeature(Feature, &cpu, .lse, IsProcessorFeaturePresent(PF.ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)); |
| 325 | } | 223 | setFeature(Feature, &cpu, .dotprod, IsProcessorFeaturePresent(PF.ARM_V82_DP_INSTRUCTIONS_AVAILABLE)); |
| 326 | | 224 | setFeature(Feature, &cpu, .jsconv, IsProcessorFeaturePresent(PF.ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)); |
| 327 | if (IsProcessorFeaturePresent(PF.ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)) { | 225 | }, |
| 328 | cpu.features.addFeature(@enumToInt(Feature.crc)); | 226 | else => {}, |
| 329 | } else { | 227 | } |
| 330 | cpu.features.removeFeature(@enumToInt(Feature.crc)); | | |
| 331 | } | | |
| 332 | | 228 | |
| 333 | if (IsProcessorFeaturePresent(PF.ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)) { | 229 | return cpu; |
| 334 | cpu.features.addFeature(@enumToInt(Feature.crypto)); | 230 | } |
| 335 | } else { | | |
| 336 | cpu.features.removeFeature(@enumToInt(Feature.crypto)); | | |
| 337 | } | | |
| 338 | | 231 | |
| 339 | if (IsProcessorFeaturePresent(PF.ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)) { | 232 | pub fn detectNativeCpuAndFeatures() ?Target.Cpu { |
| 340 | cpu.features.addFeature(@enumToInt(Feature.lse)); | 233 | const current_arch = builtin.cpu.arch; |
| 341 | } else { | 234 | const cpu: ?Target.Cpu = switch (current_arch) { |
| 342 | cpu.features.removeFeature(@enumToInt(Feature.lse)); | 235 | .aarch64, .aarch64_be, .aarch64_32 => blk: { |
| 343 | } | 236 | var cores: [128]Target.Cpu = undefined; |
| | 237 | const core_count = getCpuCount(); |
| 344 | | 238 | |
| 345 | if (IsProcessorFeaturePresent(PF.ARM_V82_DP_INSTRUCTIONS_AVAILABLE)) { | 239 | if (core_count > cores.len) break :blk null; |
| 346 | cpu.features.addFeature(@enumToInt(Feature.dotprod)); | | |
| 347 | } else { | | |
| 348 | cpu.features.removeFeature(@enumToInt(Feature.dotprod)); | | |
| 349 | } | | |
| 350 | | 240 | |
| 351 | if (IsProcessorFeaturePresent(PF.ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)) { | 241 | var i: usize = 0; |
| 352 | cpu.features.addFeature(@enumToInt(Feature.jsconv)); | 242 | while (i < core_count) : (i += 1) { |
| 353 | } else { | 243 | // Backing datastore |
| 354 | cpu.features.removeFeature(@enumToInt(Feature.jsconv)); | 244 | var registers: [12]u64 = undefined; |
| | 245 | |
| | 246 | // Registry key to system ID register mapping |
| | 247 | // CP 4000 -> MIDR_EL1 |
| | 248 | // CP 4020 -> ID_AA64PFR0_EL1 |
| | 249 | // CP 4021 -> ID_AA64PFR1_EL1 |
| | 250 | // CP 4028 -> ID_AA64DFR0_EL1 |
| | 251 | // CP 4029 -> ID_AA64DFR1_EL1 |
| | 252 | // CP 402C -> ID_AA64AFR0_EL1 |
| | 253 | // CP 402D -> ID_AA64AFR1_EL1 |
| | 254 | // CP 4030 -> ID_AA64ISAR0_EL1 |
| | 255 | // CP 4031 -> ID_AA64ISAR1_EL1 |
| | 256 | // CP 4038 -> ID_AA64MMFR0_EL1 |
| | 257 | // CP 4039 -> ID_AA64MMFR1_EL1 |
| | 258 | // CP 403A -> ID_AA64MMFR2_EL1 |
| | 259 | getCpuInfoFromRegistry(i, .{ |
| | 260 | .{ .key = "CP 4000", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[0]) }, |
| | 261 | .{ .key = "CP 4020", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[1]) }, |
| | 262 | .{ .key = "CP 4021", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[2]) }, |
| | 263 | .{ .key = "CP 4028", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[3]) }, |
| | 264 | .{ .key = "CP 4029", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[4]) }, |
| | 265 | .{ .key = "CP 402C", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[5]) }, |
| | 266 | .{ .key = "CP 402D", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[6]) }, |
| | 267 | .{ .key = "CP 4030", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[7]) }, |
| | 268 | .{ .key = "CP 4031", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[8]) }, |
| | 269 | .{ .key = "CP 4038", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[9]) }, |
| | 270 | .{ .key = "CP 4039", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[10]) }, |
| | 271 | .{ .key = "CP 403A", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[11]) }, |
| | 272 | }) catch break :blk null; |
| | 273 | |
| | 274 | cores[i] = @import("arm.zig").aarch64.detectNativeCpuAndFeatures(current_arch, registers) orelse |
| | 275 | break :blk null; |
| 355 | } | 276 | } |
| 356 | | 277 | |
| 357 | return cpu; | 278 | // Pick the first core, usually LITTLE in big.LITTLE architecture. |
| | 279 | break :blk cores[0]; |
| 358 | }, | 280 | }, |
| 359 | else => {}, | 281 | else => null, |
| 360 | } | 282 | }; |
| | 283 | return cpu orelse genericCpuAndNativeFeatures(current_arch); |
| 361 | } | 284 | } |