| ... | ... | @@ -51,23 +51,22 @@ pub fn detectRuntimeVersion() WindowsVersion { |
| 51 | 51 | // https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-element-size-limits |
| 52 | 52 | const max_value_len = 2048; |
| 53 | 53 | |
| 54 | | const RegistryPair = struct { |
| 55 | | key: []const u8, |
| 56 | | value: std.os.windows.ULONG, |
| 57 | | }; |
| 58 | | |
| 59 | | fn getCpuInfoFromRegistry( |
| 60 | | core: usize, |
| 61 | | comptime pairs_num: comptime_int, |
| 62 | | comptime pairs: [pairs_num]RegistryPair, |
| 63 | | out_buf: *[pairs_num][max_value_len]u8, |
| 64 | | ) !void { |
| 54 | fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { |
| 55 | const ArgsType = @TypeOf(args); |
| 56 | const args_type_info = @typeInfo(ArgsType); |
| 57 | |
| 58 | if (args_type_info != .Struct) { |
| 59 | @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType)); |
| 60 | } |
| 61 | |
| 62 | const fields_info = args_type_info.Struct.fields; |
| 63 | |
| 65 | 64 | // Originally, I wanted to issue a single call with a more complex table structure such that we |
| 66 | 65 | // would sequentially visit each CPU#d subkey in the registry and pull the value of interest into |
| 67 | 66 | // a buffer, however, NT seems to be expecting a single buffer per each table meaning we would |
| 68 | 67 | // end up pulling only the last CPU core info, overwriting everything else. |
| 69 | 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 | 70 | var table: [table_size + 1]std.os.windows.RTL_QUERY_REGISTRY_TABLE = undefined; |
| 72 | 71 | |
| 73 | 72 | const topkey = std.unicode.utf8ToUtf16LeStringLiteral("\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor"); |
| ... | ... | @@ -90,9 +89,9 @@ fn getCpuInfoFromRegistry( |
| 90 | 89 | .DefaultLength = 0, |
| 91 | 90 | }; |
| 92 | 91 | |
| 93 | | inline for (pairs) |pair, i| { |
| 92 | inline for (fields_info) |field, i| { |
| 94 | 93 | const ctx: *anyopaque = blk: { |
| 95 | | switch (pair.value) { |
| 94 | switch (@field(args, field.name).value_type) { |
| 96 | 95 | REG.SZ, |
| 97 | 96 | REG.EXPAND_SZ, |
| 98 | 97 | REG.MULTI_SZ, |
| ... | ... | @@ -121,12 +120,15 @@ fn getCpuInfoFromRegistry( |
| 121 | 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 | 128 | table[i + 1] = .{ |
| 127 | 129 | .QueryRoutine = null, |
| 128 | 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 | 132 | .EntryContext = ctx, |
| 131 | 133 | .DefaultType = REG.NONE, |
| 132 | 134 | .DefaultData = null, |
| ... | ... | @@ -154,16 +156,15 @@ fn getCpuInfoFromRegistry( |
| 154 | 156 | ); |
| 155 | 157 | switch (res) { |
| 156 | 158 | .SUCCESS => { |
| 157 | | inline for (pairs) |pair, i| switch (pair.value) { |
| 158 | | REG.NONE => unreachable, |
| 159 | | |
| 159 | inline for (fields_info) |field, i| switch (@field(args, field.name).value_type) { |
| 160 | 160 | REG.SZ, |
| 161 | 161 | REG.EXPAND_SZ, |
| 162 | 162 | REG.MULTI_SZ, |
| 163 | 163 | => { |
| 164 | var buf = @field(args, field.name).value_buf; |
| 164 | 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 | | out_buf[i][len] = 0; |
| 166 | const len = try std.unicode.utf16leToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); |
| 167 | buf[len] = 0; |
| 167 | 168 | }, |
| 168 | 169 | |
| 169 | 170 | REG.DWORD, |
| ... | ... | @@ -171,12 +172,12 @@ fn getCpuInfoFromRegistry( |
| 171 | 172 | REG.QWORD, |
| 172 | 173 | => { |
| 173 | 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 | 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 | 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 | 182 | else => unreachable, |
| 182 | 183 | } |
| ... | ... | @@ -189,173 +190,95 @@ fn getCpuInfoFromRegistry( |
| 189 | 190 | } |
| 190 | 191 | } |
| 191 | 192 | |
| 192 | | fn getCpuCount() usize { |
| 193 | | return std.os.windows.peb().NumberOfProcessors; |
| 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); |
| 193 | fn setFeature(comptime Feature: type, cpu: *Target.Cpu, feature: Feature, enabled: bool) void { |
| 194 | const idx = @as(Target.Cpu.Feature.Set.Index, @enumToInt(feature)); |
| 273 | 195 | |
| 274 | | fn CpuInfoParser(comptime impl: anytype) type { |
| 275 | | return struct { |
| 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 | | } |
| 196 | if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx); |
| 197 | } |
| 295 | 198 | |
| 296 | | return obj.finalize(arch); |
| 297 | | } |
| 298 | | }; |
| 199 | fn getCpuCount() usize { |
| 200 | return std.os.windows.peb().NumberOfProcessors; |
| 299 | 201 | } |
| 300 | 202 | |
| 301 | | fn genericCpu(comptime arch: Target.Cpu.Arch) Target.Cpu { |
| 302 | | return .{ |
| 203 | /// If the fine-grained detection of CPU features via Win registry fails, |
| 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 | 209 | .arch = arch, |
| 304 | 210 | .model = Target.Cpu.Model.generic(arch), |
| 305 | 211 | .features = Target.Cpu.Feature.Set.empty, |
| 306 | 212 | }; |
| 307 | | } |
| 308 | 213 | |
| 309 | | pub fn detectNativeCpuAndFeatures() ?Target.Cpu { |
| 310 | | const current_arch = builtin.cpu.arch; |
| 311 | | switch (current_arch) { |
| 214 | switch (arch) { |
| 312 | 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 | 216 | const Feature = Target.aarch64.Feature; |
| 319 | 217 | |
| 320 | 218 | // Override any features that are either present or absent |
| 321 | | if (IsProcessorFeaturePresent(PF.ARM_NEON_INSTRUCTIONS_AVAILABLE)) { |
| 322 | | cpu.features.addFeature(@enumToInt(Feature.neon)); |
| 323 | | } else { |
| 324 | | cpu.features.removeFeature(@enumToInt(Feature.neon)); |
| 325 | | } |
| 326 | | |
| 327 | | if (IsProcessorFeaturePresent(PF.ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)) { |
| 328 | | cpu.features.addFeature(@enumToInt(Feature.crc)); |
| 329 | | } else { |
| 330 | | cpu.features.removeFeature(@enumToInt(Feature.crc)); |
| 331 | | } |
| 219 | setFeature(Feature, &cpu, .neon, IsProcessorFeaturePresent(PF.ARM_NEON_INSTRUCTIONS_AVAILABLE)); |
| 220 | setFeature(Feature, &cpu, .crc, IsProcessorFeaturePresent(PF.ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE)); |
| 221 | setFeature(Feature, &cpu, .crypto, IsProcessorFeaturePresent(PF.ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)); |
| 222 | setFeature(Feature, &cpu, .lse, IsProcessorFeaturePresent(PF.ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)); |
| 223 | setFeature(Feature, &cpu, .dotprod, IsProcessorFeaturePresent(PF.ARM_V82_DP_INSTRUCTIONS_AVAILABLE)); |
| 224 | setFeature(Feature, &cpu, .jsconv, IsProcessorFeaturePresent(PF.ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)); |
| 225 | }, |
| 226 | else => {}, |
| 227 | } |
| 332 | 228 | |
| 333 | | if (IsProcessorFeaturePresent(PF.ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE)) { |
| 334 | | cpu.features.addFeature(@enumToInt(Feature.crypto)); |
| 335 | | } else { |
| 336 | | cpu.features.removeFeature(@enumToInt(Feature.crypto)); |
| 337 | | } |
| 229 | return cpu; |
| 230 | } |
| 338 | 231 | |
| 339 | | if (IsProcessorFeaturePresent(PF.ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)) { |
| 340 | | cpu.features.addFeature(@enumToInt(Feature.lse)); |
| 341 | | } else { |
| 342 | | cpu.features.removeFeature(@enumToInt(Feature.lse)); |
| 343 | | } |
| 232 | pub fn detectNativeCpuAndFeatures() ?Target.Cpu { |
| 233 | const current_arch = builtin.cpu.arch; |
| 234 | const cpu: ?Target.Cpu = switch (current_arch) { |
| 235 | .aarch64, .aarch64_be, .aarch64_32 => blk: { |
| 236 | var cores: [128]Target.Cpu = undefined; |
| 237 | const core_count = getCpuCount(); |
| 344 | 238 | |
| 345 | | if (IsProcessorFeaturePresent(PF.ARM_V82_DP_INSTRUCTIONS_AVAILABLE)) { |
| 346 | | cpu.features.addFeature(@enumToInt(Feature.dotprod)); |
| 347 | | } else { |
| 348 | | cpu.features.removeFeature(@enumToInt(Feature.dotprod)); |
| 349 | | } |
| 239 | if (core_count > cores.len) break :blk null; |
| 350 | 240 | |
| 351 | | if (IsProcessorFeaturePresent(PF.ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE)) { |
| 352 | | cpu.features.addFeature(@enumToInt(Feature.jsconv)); |
| 353 | | } else { |
| 354 | | cpu.features.removeFeature(@enumToInt(Feature.jsconv)); |
| 241 | var i: usize = 0; |
| 242 | while (i < core_count) : (i += 1) { |
| 243 | // Backing datastore |
| 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 => {}, |
| 360 | | } |
| 281 | else => null, |
| 282 | }; |
| 283 | return cpu orelse genericCpuAndNativeFeatures(current_arch); |
| 361 | 284 | } |