mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 09:25:05 -04:00
cb812c1fd0
use -mcpu=native on arm64 targets
232 lines
8.1 KiB
C++
232 lines
8.1 KiB
C++
// Host ISA guard. Every other product target builds with -march=x86-64-v3, so a pre-Haswell
|
|
// Intel or pre-Excavator AMD machine would otherwise die on an illegal-instruction fault with no
|
|
// explanation. This TU alone skips that flag (own CMake object library, excluded from unity
|
|
// build/PCH) and runs from a priority-101 C initializer, ahead of every C++ dynamic initializer
|
|
// and thus the first AVX2 code that could execute. Keep it free of anything that could pull in
|
|
// vectorized code: no iostreams, no std::string, no runtime-wide headers.
|
|
//
|
|
// x86-64-v3 is an x86-specific optional-feature baseline (AVX2/BMI2/FMA and friends are not
|
|
// guaranteed present on every x86_64 chip); nothing here applies on AArch64, where ASIMD/NEON is
|
|
// mandatory in the base architecture and PublicProducts.cmake never applies an -march=x86-64-v3
|
|
// equivalent flag to begin with. That branch below is a no-op stub, not a port of this check.
|
|
|
|
#if defined(__x86_64__)
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
|
|
#include <cpuid.h>
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
void HostCpuId(unsigned leaf, unsigned subleaf, unsigned regs[4]) {
|
|
unsigned eax = 0, ebx = 0, ecx = 0, edx = 0;
|
|
__cpuid_count(leaf, subleaf, eax, ebx, ecx, edx);
|
|
regs[0] = eax;
|
|
regs[1] = ebx;
|
|
regs[2] = ecx;
|
|
regs[3] = edx;
|
|
}
|
|
|
|
unsigned HostCpuIdMaxLeaf(unsigned base) {
|
|
unsigned regs[4] = {0, 0, 0, 0};
|
|
HostCpuId(base, 0, regs);
|
|
return regs[0];
|
|
}
|
|
|
|
// XGETBV through inline assembly rather than the _xgetbv intrinsic: the GNU
|
|
// driver gates that intrinsic behind -mxsave, which this file is specifically
|
|
// compiled without. Only reachable once CPUID has reported OSXSAVE.
|
|
uint64_t ReadXcr0() {
|
|
unsigned eax = 0, edx = 0;
|
|
__asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0));
|
|
return (static_cast<uint64_t>(edx) << 32) | eax;
|
|
}
|
|
|
|
struct CpuFeature {
|
|
const char* name;
|
|
unsigned leaf;
|
|
unsigned subleaf;
|
|
unsigned reg; // index into the eax/ebx/ecx/edx array filled by HostCpuId
|
|
unsigned bit;
|
|
bool isOsXsave;
|
|
};
|
|
|
|
// Everything x86-64-v3 implies, which includes all of x86-64-v2. Spelled out so
|
|
// the error message can name the exact instruction sets the machine lacks
|
|
// rather than only "AVX2", which is merely the best known member of the set.
|
|
constexpr CpuFeature kRequiredFeatures[] = {
|
|
{"SSE3", 1, 0, 2, 0, false},
|
|
{"SSSE3", 1, 0, 2, 9, false},
|
|
{"FMA", 1, 0, 2, 12, false},
|
|
{"CMPXCHG16B", 1, 0, 2, 13, false},
|
|
{"SSE4.1", 1, 0, 2, 19, false},
|
|
{"SSE4.2", 1, 0, 2, 20, false},
|
|
{"MOVBE", 1, 0, 2, 22, false},
|
|
{"POPCNT", 1, 0, 2, 23, false},
|
|
{"OSXSAVE", 1, 0, 2, 27, true},
|
|
{"AVX", 1, 0, 2, 28, false},
|
|
{"F16C", 1, 0, 2, 29, false},
|
|
{"BMI1", 7, 0, 1, 3, false},
|
|
{"AVX2", 7, 0, 1, 5, false},
|
|
{"BMI2", 7, 0, 1, 8, false},
|
|
{"LAHF-SAHF", 0x80000001u, 0, 2, 0, false},
|
|
{"LZCNT", 0x80000001u, 0, 2, 5, false},
|
|
};
|
|
|
|
// Fixed-capacity text accumulation: no allocation, no exceptions, nothing that
|
|
// could route through code this file is trying to stay ahead of.
|
|
struct TextBuffer {
|
|
char data[1024] = {};
|
|
size_t used = 0;
|
|
|
|
void Append(const char* text) {
|
|
if (text == nullptr) {
|
|
return;
|
|
}
|
|
while (*text != '\0' && used + 1 < sizeof(data)) {
|
|
data[used++] = *text++;
|
|
}
|
|
data[used] = '\0';
|
|
}
|
|
};
|
|
|
|
// True when the host can run this build. Otherwise `missing` holds the absent
|
|
// feature names, comma separated.
|
|
bool CollectMissingBaselineFeatures(TextBuffer& missing) {
|
|
const unsigned maxBasic = HostCpuIdMaxLeaf(0);
|
|
const unsigned maxExtended = HostCpuIdMaxLeaf(0x80000000u);
|
|
|
|
bool ok = true;
|
|
bool haveOsXsave = false;
|
|
|
|
for (const CpuFeature& feature : kRequiredFeatures) {
|
|
const bool leafAvailable = (feature.leaf & 0x80000000u) != 0
|
|
? feature.leaf <= maxExtended
|
|
: feature.leaf <= maxBasic;
|
|
|
|
bool present = false;
|
|
if (leafAvailable) {
|
|
unsigned regs[4] = {0, 0, 0, 0};
|
|
HostCpuId(feature.leaf, feature.subleaf, regs);
|
|
present = (regs[feature.reg] & (1u << feature.bit)) != 0;
|
|
}
|
|
|
|
if (present) {
|
|
haveOsXsave = haveOsXsave || feature.isOsXsave;
|
|
continue;
|
|
}
|
|
|
|
if (!ok) {
|
|
missing.Append(", ");
|
|
}
|
|
missing.Append(feature.name);
|
|
ok = false;
|
|
}
|
|
|
|
// CPUID reporting AVX is not sufficient: the OS also has to have enabled
|
|
// XMM and YMM state saving or every VEX-encoded instruction faults. This is
|
|
// the same guard a compiler's own runtime feature dispatch applies.
|
|
if (ok && haveOsXsave) {
|
|
constexpr uint64_t kXmmAndYmmState = 0x6u;
|
|
if ((ReadXcr0() & kXmmAndYmmState) != kXmmAndYmmState) {
|
|
missing.Append("operating system support for AVX register state (XCR0 YMM bits)");
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
// stdio is NOT usable from a .CRT$XIC initializer - the UCRT has not stood it
|
|
// up yet, and fprintf(stderr, ...) faults there. Verified on this toolchain:
|
|
// WriteFile on the raw standard-error handle and MessageBoxA both work, printf
|
|
// does not. Anything added to this reporting path has to respect that.
|
|
//
|
|
// The POSIX path runs from an __attribute__((constructor)) instead, ahead of libc's own startup
|
|
// guarantees; ::write() on the raw fd is the same kind of allocation-free, libc-init-independent
|
|
// primitive as WriteFile is on Windows, so the same restriction is honored here.
|
|
void WriteStdErrEarly(const char* text) {
|
|
#if defined(_WIN32)
|
|
const HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE);
|
|
if (handle == nullptr || handle == INVALID_HANDLE_VALUE) {
|
|
return;
|
|
}
|
|
size_t length = 0;
|
|
while (text[length] != '\0') {
|
|
++length;
|
|
}
|
|
DWORD written = 0;
|
|
::WriteFile(handle, text, static_cast<DWORD>(length), &written, nullptr);
|
|
#else
|
|
size_t length = 0;
|
|
while (text[length] != '\0') {
|
|
++length;
|
|
}
|
|
(void)::write(STDERR_FILENO, text, length);
|
|
#endif
|
|
}
|
|
|
|
[[noreturn]] void ReportUnsupportedCpu(const char* missing) {
|
|
TextBuffer message;
|
|
message.Append(
|
|
"This build needs a processor that supports AVX2 and the rest of the "
|
|
"x86-64-v3 instruction set.\n\nMissing on this machine: ");
|
|
message.Append(missing);
|
|
message.Append(
|
|
"\n\nx86-64-v3 covers Intel Core processors from Haswell (4th "
|
|
"generation, 2013) onward and AMD processors from Excavator (2015) onward.");
|
|
|
|
// The tag matches RT_TAG_RUNTIME in runtime_log.h. It is spelled out here
|
|
// because this translation unit must not include runtime-wide headers (see
|
|
// the file comment): runtime_log.h pulls in <iostream> and memory.h, and
|
|
// this code runs before any C++ dynamic initializer.
|
|
WriteStdErrEarly("[runtime] ");
|
|
WriteStdErrEarly(message.data);
|
|
WriteStdErrEarly("\n");
|
|
|
|
#if defined(_WIN32)
|
|
::MessageBoxA(nullptr, message.data, "WiiCompiled - Unsupported Processor",
|
|
MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_TASKMODAL);
|
|
// Leave through the OS rather than exit(): the C++ dynamic initializers
|
|
// have not run yet, so there is no constructed program state to unwind and
|
|
// the teardown path itself lives in AVX2 translation units.
|
|
::ExitProcess(1u);
|
|
#else
|
|
// Same reasoning as the Windows path above: no C++ dynamic initializer has run yet, so
|
|
// _exit() (skips atexit/global destructors, unlike exit()) is the correct way out.
|
|
::_exit(1);
|
|
#endif
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" int MkwHostCpuBaselineInit() {
|
|
TextBuffer missing;
|
|
if (!CollectMissingBaselineFeatures(missing)) {
|
|
ReportUnsupportedCpu(missing.data);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Priorities 0-100 are reserved for the implementation; 101 is the earliest a
|
|
// user constructor can request, which puts this ahead of every default-priority
|
|
// constructor in the image.
|
|
__attribute__((constructor(101))) static void MkwHostCpuBaselineCtor() {
|
|
MkwHostCpuBaselineInit();
|
|
}
|
|
|
|
#else // !defined(__x86_64__)
|
|
|
|
extern "C" int MkwHostCpuBaselineInit() {
|
|
return 0;
|
|
}
|
|
|
|
#endif // defined(__x86_64__)
|