shim: launder game-address reads past -Werror=array-bounds in ai_orders

A fresh cross-build directory caught what a reused one had been hiding: gcc reasons
about a reinterpret_cast from uintptr_t as an object of its own when the read is
inlined at a constant offset. Same barrier idiom watchpoints.cpp already uses for
the TIB reads.
This commit is contained in:
alex 2026-09-08 21:50:01 -04:00
parent 68a9fc824d
commit 0c6d0a3573

View file

@ -51,11 +51,22 @@ inline bool Readable(std::uintptr_t p, std::size_t n) {
return p != 0 && !IsBadReadPtr(reinterpret_cast<void*>(p), n); return p != 0 && !IsBadReadPtr(reinterpret_cast<void*>(p), n);
} }
// A game address is an integer we were handed, not a pointer into any object gcc can see. When one
// of these is inlined at a site with a constant offset the optimizer reasons about it as an object
// of its own and trips -Werror=array-bounds. Launder it through a register the same way
// `watchpoints.cpp` reads the TIB: the barrier costs nothing and makes the provenance opaque.
template <typename T>
inline volatile T* Opaque(std::uintptr_t p) {
auto* q = reinterpret_cast<volatile T*>(p);
asm volatile("" : "+r"(q));
return q;
}
inline std::uint32_t U32(std::uintptr_t p) { inline std::uint32_t U32(std::uintptr_t p) {
return Readable(p, 4) ? *reinterpret_cast<volatile std::uint32_t*>(p) : 0u; return Readable(p, 4) ? *Opaque<std::uint32_t>(p) : 0u;
} }
inline std::uint8_t U8(std::uintptr_t p) { inline std::uint8_t U8(std::uintptr_t p) {
return Readable(p, 1) ? *reinterpret_cast<volatile std::uint8_t*>(p) : 0u; return Readable(p, 1) ? *Opaque<std::uint8_t>(p) : 0u;
} }
inline float F32(std::uintptr_t p) { inline float F32(std::uintptr_t p) {
const std::uint32_t v = U32(p); const std::uint32_t v = U32(p);