package platform import ( "os" "runtime" "strings" "github.com/klauspost/cpuid/v2" "gitgud.io/mike/mpv-manager/pkg/log" ) func detectCPU() CPUInfo { info := CPUInfo{ Features: []string{}, } if runtime.GOOS == "windows" { info = detectCPUWindows() } else if runtime.GOOS == "linux" { info = detectCPULinux() } else if runtime.GOOS == "darwin" { info = detectCPUDarwin() } if runtime.GOARCH == "arm64" && !containsFeature(info.Features, "arm64") && !containsFeature(info.Features, "aarch64") { // /proc/cpuinfo may expose only architecture extension names (or no // Features line). Seed the process architecture so an arm64 host can // never fall through to the x86-64-v2 display default. info.Features = append(info.Features, "arm64") } info.SupportsAVX2 = checkAVX2(info.Features) info.SupportsAVX512 = checkAVX512(info.Features) info.SupportsNEON = checkNEON(info.Features) // Normalize vendor name for consistent color coding and display info.VendorDisplay = getVendorDisplay(info.Vendor) info.Vendor = info.VendorDisplay // Artifact selection uses the complete baseline, including OS-enabled AVX // state, rather than a single feature from the display/proc feature list. if info.ArchitectureLevel == "" { if runtime.GOARCH == "arm64" { info.ArchitectureLevel = "arm64" } else if runtime.GOARCH == "amd64" { info.ArchitectureLevel = x64ArchitectureLevel(cpuid.CPU) } else { info.ArchitectureLevel = runtime.GOARCH } } return info } func detectCPULinux() CPUInfo { info := CPUInfo{} if data, err := os.ReadFile("/proc/cpuinfo"); err == nil { content := string(data) lines := strings.Split(content, "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "vendor_id") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { info.Vendor = strings.TrimSpace(parts[1]) } } else if strings.HasPrefix(line, "model name") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { info.Model = strings.TrimSpace(parts[1]) } } else if strings.HasPrefix(line, "flags") || strings.HasPrefix(line, "Features") { parts := strings.SplitN(line, ":", 2) if len(parts) == 2 { features := strings.Fields(strings.TrimSpace(parts[1])) info.Features = features } } } } else { log.Warn("Failed to read /proc/cpuinfo: " + err.Error()) } return info } func detectCPUDarwin() CPUInfo { return detectCPUDarwinWithRunner(systemProbeRunner{}) } func detectCPUDarwinWithRunner(runner probeRunner) CPUInfo { info := CPUInfo{ Vendor: "Apple", } if output, err := runner.Run(newProbe("sysctl", quickProbeTimeout, "-n", "machdep.cpu.brand_string")); err == nil { info.Model = strings.TrimSpace(string(output)) } else if runtime.GOARCH == "arm64" { info.Model = "Apple Silicon" } else { info.Model = "Intel" } // Set features based on architecture (ALWAYS, not just when sysctl fails) if runtime.GOARCH == "arm64" { info.Features = []string{"arm64", "neon"} } else { // Intel Mac - try to get actual features, or use reasonable defaults info.Features = []string{"x86-64", "sse", "sse2", "sse3", "ssse3", "sse4_1", "sse4_2"} // Could also check for AVX2 via sysctl hw.optional.avx2_0 } return info } func checkAVX2(features []string) bool { for _, feature := range features { if strings.Contains(strings.ToLower(feature), "avx2") { return true } } return false } func checkAVX512(features []string) bool { for _, feature := range features { if strings.Contains(strings.ToLower(feature), "avx512") { return true } } return false } func checkNEON(features []string) bool { for _, feature := range features { feature = strings.ToLower(feature) if strings.Contains(feature, "neon") || feature == "asimd" { return true } } return false } func containsFeature(features []string, want string) bool { for _, feature := range features { if strings.EqualFold(feature, want) { return true } } return false } func getVendorDisplay(vendor string) string { switch vendor { case "GenuineIntel": return "Intel" case "AuthenticAMD": return "AMD" case "nvidia": return "NVIDIA" case "amd": return "AMD" case "intel": return "Intel" case "apple": return "Apple" default: return vendor } } func x64ArchitectureLevel(cpu cpuid.CPUInfo) string { switch cpu.X64Level() { case 4: return "x86-64-v4" case 3: return "x86-64-v3" case 2: return "x86-64-v2" default: return "x86-64" } } // SupportsX86_64_v3 requires the full detected v3 baseline (v4 includes v3). // Unknown or incomplete CPU observations select the conservative build. func (p *Platform) SupportsX86_64_v3() bool { return (p.Arch == "amd64" || p.Arch == "x86_64") && (p.CPUInfo.ArchitectureLevel == "x86-64-v3" || p.CPUInfo.ArchitectureLevel == "x86-64-v4") }