diff --git a/Utilities/JITLLVM.cpp b/Utilities/JITLLVM.cpp index dccca9ffd6..feb3421836 100644 --- a/Utilities/JITLLVM.cpp +++ b/Utilities/JITLLVM.cpp @@ -1053,6 +1053,14 @@ const char * fallback_cpu_detection() return s_result.c_str(); #else + // Prefer a concrete host -mcpu (e.g. apple-m2) so LLVM uses the right scheduling model. + // MIDR_EL1 is exposed via sysfs on Linux; see aarch64::get_cpu_llvm_name(). + static const std::string s_mcpu = aarch64::get_cpu_llvm_name(); + if (!s_mcpu.empty()) + { + return s_mcpu.c_str(); + } + // TODO: Read the data from /proc/cpuinfo. ARM CPU registers are not accessible from usermode. // This will be a pain when supporting snapdragon on windows but we'll cross that bridge when we get there. // Require at least armv8-2a. Older chips are going to be useless anyway. diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.cpp b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.cpp index e2468655a8..7350aa7926 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.cpp +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.cpp @@ -83,6 +83,8 @@ namespace aarch64 { 0x61, 0x25, "armv8.5-a", "M1 Pro", "Icestorm" }, { 0x61, 0x32, "armv8.5-a", "M2", "Avalanche" }, { 0x61, 0x33, "armv8.5-a", "M2", "Blizzard" }, + { 0x61, 0x38, "armv8.5-a+fp16+bf16+i8mm+dotprod", "M2 Pro/Max", "Avalanche" }, + { 0x61, 0x39, "armv8.5-a+fp16+bf16+i8mm+dotprod", "M2 Pro/Max", "Blizzard" }, // QUALCOMM { 0x51, 0x01, "armv8.5-a", "Snapdragon", "X-Elite" }, @@ -136,7 +138,9 @@ namespace aarch64 #endif } - std::string get_cpu_name() + // Resolve the representative CPU part entry for this machine (the "lowest" core in a + // big.LITTLE layout), or nullptr if the layout can't be fully identified. + static const cpu_entry_t* get_cpu_part_info() { std::map core_layout; for (u32 i = 0; i < std::thread::hardware_concurrency(); ++i) @@ -152,10 +156,10 @@ namespace aarch64 if (core_layout.empty()) { - return {}; + return nullptr; } - const cpu_entry_t* lowest_part_info = nullptr; + const cpu_entry_t* lowest_part_info = nullptr; for (const auto& [midr, count] : core_layout) { const auto implementer_id = (midr >> 24) & 0xff; @@ -164,7 +168,7 @@ namespace aarch64 const auto part_info = find_cpu_part(implementer_id, part_id); if (!part_info) { - return {}; + return nullptr; } if (lowest_part_info == nullptr || lowest_part_info > part_info) @@ -173,7 +177,13 @@ namespace aarch64 } } - return lowest_part_info ? lowest_part_info->name : ""; + return lowest_part_info; + } + + std::string get_cpu_name() + { + const auto part_info = get_cpu_part_info(); + return part_info ? part_info->name : ""; } std::string get_cpu_brand() @@ -239,6 +249,27 @@ namespace aarch64 result += suffix; return result; } + + std::string get_cpu_llvm_name() + { + const auto part_info = get_cpu_part_info(); + + // Only Apple silicon (implementer 0x61) gets a hand-picked -mcpu for now. The family + // field holds the SoC generation ("M2", "M2 Pro/Max", ...) whose first token maps + // directly onto the LLVM "apple-mN" targets (and auto-extends to future generations). + if (!part_info || part_info->vendor != 0x61 || !part_info->family || !part_info->family[0]) + { + return {}; + } + + const auto tokens = fmt::split_sv(part_info->family, {" "}); + if (tokens.empty()) + { + return {}; + } + + return fmt::format("apple-%s", fmt::to_lower(tokens.front())); + } #else static std::string sysctl_s(const std::string_view& variable_name) { @@ -297,5 +328,28 @@ namespace aarch64 return fmt::format("%s (%lluP+%lluE)", brand, pcores, ecores); } + + std::string get_cpu_llvm_name() + { + // machdep.cpu.brand_string on Apple silicon is "Apple ", e.g. "Apple M2 Max", + // "Apple M1 Pro", "Apple M4". The generation token after the "Apple " vendor prefix maps + // directly onto the LLVM "apple-mN"/"apple-aN" targets. + const auto brand = sysctl_s("machdep.cpu.brand_string"); + + std::string_view family = brand; + if (!family.starts_with("Apple ")) + { + return {}; + } + family.remove_prefix(6); + + const auto tokens = fmt::split_sv(family, {" "}); + if (tokens.empty()) + { + return {}; + } + + return fmt::format("apple-%s", fmt::to_lower(tokens.front())); + } #endif } diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h index 2ce4fa68b3..d0b525e5bf 100644 --- a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Common.h @@ -39,4 +39,8 @@ namespace aarch64 std::string get_cpu_name(); std::string get_cpu_brand(); + + // Returns a concrete LLVM -mcpu name for the host (e.g. "apple-m2") or "" if unknown. + // Used to give the JIT the correct scheduling model instead of a generic fallback. + std::string get_cpu_llvm_name(); }