| ... | ... | @@ -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 | | }; |
| 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; |
| 58 | 63 | |
| 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 { |
| 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_name = 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_name)), |
| 131 | .Name = key_buf[0..key_len :0], |
| 130 | 132 | .EntryContext = ctx, |
| 131 | 133 | .DefaultType = REG.NONE, |
| 132 | 134 | .DefaultData = null, |
| ... | ... | @@ -154,14 +156,15 @@ fn getCpuInfoFromRegistry( |
| 154 | 156 | ); |
| 155 | 157 | switch (res) { |
| 156 | 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 | 160 | REG.SZ, |
| 159 | 161 | REG.EXPAND_SZ, |
| 160 | 162 | REG.MULTI_SZ, |
| 161 | 163 | => { |
| 164 | var buf = @field(args, field.name).value_buf; |
| 162 | 165 | const entry = @ptrCast(*align(1) const std.os.windows.UNICODE_STRING, table[i + 1].EntryContext); |
| 163 | | const len = try std.unicode.utf16leToUtf8(out_buf[i][0..], entry.Buffer[0 .. entry.Length / 2]); |
| 164 | | out_buf[i][len] = 0; |
| 166 | const len = try std.unicode.utf16leToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]); |
| 167 | buf[len] = 0; |
| 165 | 168 | }, |
| 166 | 169 | |
| 167 | 170 | REG.DWORD, |
| ... | ... | @@ -169,12 +172,12 @@ fn getCpuInfoFromRegistry( |
| 169 | 172 | REG.QWORD, |
| 170 | 173 | => { |
| 171 | 174 | const entry = @ptrCast([*]align(1) const u8, table[i + 1].EntryContext); |
| 172 | | switch (pair.value) { |
| 175 | switch (@field(args, field.name).value_type) { |
| 173 | 176 | REG.DWORD, REG.DWORD_BIG_ENDIAN => { |
| 174 | | 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]); |
| 175 | 178 | }, |
| 176 | 179 | REG.QWORD => { |
| 177 | | 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]); |
| 178 | 181 | }, |
| 179 | 182 | else => unreachable, |
| 180 | 183 | } |
| ... | ... | @@ -197,7 +200,7 @@ fn getCpuCount() usize { |
| 197 | 200 | return std.os.windows.peb().NumberOfProcessors; |
| 198 | 201 | } |
| 199 | 202 | |
| 200 | | const ArmCpuInfoImpl = struct { |
| 203 | const ArmCpuInfoParser = struct { |
| 201 | 204 | cores: [4]CoreInfo = undefined, |
| 202 | 205 | core_no: usize = 0, |
| 203 | 206 | have_fields: usize = 0, |
| ... | ... | @@ -205,38 +208,26 @@ const ArmCpuInfoImpl = struct { |
| 205 | 208 | const CoreInfo = @import("arm.zig").CoreInfo; |
| 206 | 209 | const cpu_models = @import("arm.zig").cpu_models; |
| 207 | 210 | |
| 208 | | const Data = struct { |
| 209 | | cp_4000: []const u8, |
| 210 | | identifier: []const u8, |
| 211 | | }; |
| 212 | | |
| 213 | | fn parseDataHook(self: *ArmCpuInfoImpl, data: Data) !void { |
| 211 | fn parseFeaturesFromRegisters(self: *ArmCpuInfoParser, registers: [12]u64) !void { |
| 214 | 212 | const info = &self.cores[self.core_no]; |
| 215 | 213 | info.* = .{}; |
| 216 | 214 | |
| 217 | | // CPU part |
| 218 | | info.part = mem.readIntLittle(u16, data.cp_4000[0..2]) >> 4; |
| 219 | | self.have_fields += 1; |
| 220 | | |
| 221 | | // CPU implementer |
| 222 | | info.implementer = data.cp_4000[3]; |
| 223 | | self.have_fields += 1; |
| 224 | | |
| 225 | | var tokens = mem.tokenize(u8, data.identifier, " "); |
| 226 | | while (tokens.next()) |token| { |
| 227 | | if (mem.eql(u8, "Family", token)) { |
| 228 | | // CPU architecture |
| 229 | | const family = tokens.next() orelse continue; |
| 230 | | info.architecture = try std.fmt.parseInt(u8, family, 10); |
| 231 | | self.have_fields += 1; |
| 232 | | break; |
| 233 | | } |
| 234 | | } else return; |
| 215 | for (registers) |register| { |
| 216 | std.log.warn("{x}", .{register}); |
| 217 | } |
| 218 | |
| 219 | // // CPU part |
| 220 | // info.part = mem.readIntLittle(u16, data.cp_4000[0..2]) >> 4; |
| 221 | // self.have_fields += 1; |
| 222 | |
| 223 | // // CPU implementer |
| 224 | // info.implementer = data.cp_4000[3]; |
| 225 | // self.have_fields += 1; |
| 235 | 226 | |
| 236 | | self.addOne(); |
| 227 | // self.addOne(); |
| 237 | 228 | } |
| 238 | 229 | |
| 239 | | fn addOne(self: *ArmCpuInfoImpl) void { |
| 230 | fn addOne(self: *ArmCpuInfoParser) void { |
| 240 | 231 | if (self.have_fields == 3 and self.core_no < self.cores.len) { |
| 241 | 232 | if (self.core_no > 0) { |
| 242 | 233 | // Deduplicate the core info. |
| ... | ... | @@ -249,7 +240,7 @@ const ArmCpuInfoImpl = struct { |
| 249 | 240 | } |
| 250 | 241 | } |
| 251 | 242 | |
| 252 | | fn finalize(self: ArmCpuInfoImpl, arch: Target.Cpu.Arch) ?Target.Cpu { |
| 243 | fn finalize(self: ArmCpuInfoParser, arch: Target.Cpu.Arch) ?Target.Cpu { |
| 253 | 244 | if (self.core_no == 0) return null; |
| 254 | 245 | |
| 255 | 246 | const is_64bit = switch (arch) { |
| ... | ... | @@ -271,36 +262,49 @@ const ArmCpuInfoImpl = struct { |
| 271 | 262 | .features = model.features, |
| 272 | 263 | }; |
| 273 | 264 | } |
| 274 | | }; |
| 275 | | |
| 276 | | const ArmCpuInfoParser = CpuInfoParser(ArmCpuInfoImpl); |
| 277 | | |
| 278 | | fn CpuInfoParser(comptime impl: anytype) type { |
| 279 | | return struct { |
| 280 | | fn parse(arch: Target.Cpu.Arch) !?Target.Cpu { |
| 281 | | var obj: impl = .{}; |
| 282 | | var out_buf: [2][max_value_len]u8 = undefined; |
| 283 | 265 | |
| 284 | | var i: usize = 0; |
| 285 | | while (i < getCpuCount()) : (i += 1) { |
| 286 | | try getCpuInfoFromRegistry(i, 2, .{ |
| 287 | | .{ .key = "CP 4000", .value = REG.QWORD }, |
| 288 | | .{ .key = "Identifier", .value = REG.SZ }, |
| 289 | | }, &out_buf); |
| 290 | | |
| 291 | | const cp_4000 = out_buf[0][0..8]; |
| 292 | | const identifier = mem.sliceTo(out_buf[1][0..], 0); |
| 293 | | |
| 294 | | try obj.parseDataHook(.{ |
| 295 | | .cp_4000 = cp_4000, |
| 296 | | .identifier = identifier, |
| 297 | | }); |
| 298 | | } |
| 299 | | |
| 300 | | return obj.finalize(arch); |
| 266 | fn parse(arch: Target.Cpu.Arch) !?Target.Cpu { |
| 267 | var obj: ArmCpuInfoParser = .{}; |
| 268 | |
| 269 | // Backing datastore |
| 270 | var registers: [12]u64 = undefined; |
| 271 | |
| 272 | var i: usize = 0; |
| 273 | while (i < getCpuCount()) : (i += 1) { |
| 274 | // Registry key to system ID register mapping |
| 275 | // CP 4000 -> MIDR_EL1 |
| 276 | // CP 4020 -> ID_AA64PFR0_EL1 |
| 277 | // CP 4021 -> ID_AA64PFR1_EL1 |
| 278 | // CP 4028 -> ID_AA64DFR0_EL1 |
| 279 | // CP 4029 -> ID_AA64DFR1_EL1 |
| 280 | // CP 402C -> ID_AA64AFR0_EL1 |
| 281 | // CP 402D -> ID_AA64AFR1_EL1 |
| 282 | // CP 4030 -> ID_AA64ISAR0_EL1 |
| 283 | // CP 4031 -> ID_AA64ISAR1_EL1 |
| 284 | // CP 4038 -> ID_AA64MMFR0_EL1 |
| 285 | // CP 4039 -> ID_AA64MMFR1_EL1 |
| 286 | // CP 403A -> ID_AA64MMFR2_EL1 |
| 287 | try getCpuInfoFromRegistry(i, .{ |
| 288 | .{ .key = "CP 4000", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[0]) }, |
| 289 | .{ .key = "CP 4020", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[1]) }, |
| 290 | .{ .key = "CP 4021", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[2]) }, |
| 291 | .{ .key = "CP 4028", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[3]) }, |
| 292 | .{ .key = "CP 4029", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[4]) }, |
| 293 | .{ .key = "CP 402C", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[5]) }, |
| 294 | .{ .key = "CP 402D", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[6]) }, |
| 295 | .{ .key = "CP 4030", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[7]) }, |
| 296 | .{ .key = "CP 4031", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[8]) }, |
| 297 | .{ .key = "CP 4038", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[9]) }, |
| 298 | .{ .key = "CP 4039", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[10]) }, |
| 299 | .{ .key = "CP 403A", .value_type = REG.QWORD, .value_buf = @ptrCast(*[8]u8, &registers[11]) }, |
| 300 | }); |
| 301 | |
| 302 | try obj.parseFeaturesFromRegisters(registers); |
| 301 | 303 | } |
| 302 | | }; |
| 303 | | } |
| 304 | |
| 305 | return obj.finalize(arch); |
| 306 | } |
| 307 | }; |
| 304 | 308 | |
| 305 | 309 | /// If the fine-grained detection of CPU features via Win registry fails, |
| 306 | 310 | /// we fallback to a generic CPU model but we override the feature set |
| ... | ... | @@ -333,10 +337,9 @@ fn genericCpuAndNativeFeatures(arch: Target.Cpu.Arch) Target.Cpu { |
| 333 | 337 | |
| 334 | 338 | pub fn detectNativeCpuAndFeatures() ?Target.Cpu { |
| 335 | 339 | const current_arch = builtin.cpu.arch; |
| 336 | | switch (current_arch) { |
| 337 | | .aarch64, .aarch64_be, .aarch64_32 => { |
| 338 | | return ArmCpuInfoParser.parse(current_arch) catch genericCpuAndNativeFeatures(current_arch); |
| 339 | | }, |
| 340 | | else => return null, |
| 341 | | } |
| 340 | const cpu: ?Target.Cpu = switch (current_arch) { |
| 341 | .aarch64, .aarch64_be, .aarch64_32 => ArmCpuInfoParser.parse(current_arch) catch null, |
| 342 | else => null, |
| 343 | }; |
| 344 | return cpu orelse genericCpuAndNativeFeatures(current_arch); |
| 342 | 345 | } |