Files
LibretroAdmin 49e511a864 GS: the palette's CLUT is aligned, so comparing two of them is not a fault
The startup crash in every game, and it was never a dangling pointer.

0xFFFFFFFFFFFFFFFF is not an address. It is what Windows puts in an
access violation record when the fault was a general protection fault,
and the classic way to take one of those is an aligned SSE load on an
address that is not aligned. That is what both traces were:
PaletteKeyEqual calls GSVector4i::compare64, which casts its arguments
to GSVector4i* and indexes them, so both CLUTs have to be 16-byte
aligned.

They were, until I moved the CLUT into the Palette (da4d6052a). The
allocation it replaced was memalign_alloc(64, ...); inside the object
the array landed at offset eight, behind m_alive and m_refs, and every
comparison of two CLUTs faulted. First paletted texture, every game.

alignas(GS_OBJECT_POOL_ALIGN) on the array, and the pool's slots are
aligned to that too: sixty-four, which is the cache line and what the
allocations these pools replaced asked for. The three fallback paths
that go to the allocator when a pool is full ask for the same.

This is also the answer to the earlier heap corruption, and I had it
backwards for three rounds: the free that reported it was a symptom, the
searches for a double free found nothing because there was not one, and
the FastList guard and the erase-before-delete fix - both correct on
their own - fixed things that were not this. A general protection fault
does not look like a bad pointer, and I read the -1 as one.

tests/gsalign: a CLUT inside a pooled object, aligned, compared with an
aligned SSE load, sixty-four objects out of a pool. Built without the
alignas it fails on every object, which is the check that matters.

GSTextureCache.cpp, GSHwHack.cpp, GSRendererHW.cpp,
GSTextureReplacements.cpp and GS.cpp compile with the Makefile's flags;
gsalign, gspool and gshash pass, plain and under ASan/UBSan.
2026-09-20 02:13:10 +02:00

60 lines
1.7 KiB
C++

/* What the crash was: a CLUT inside a pooled object, compared with an
* aligned SSE load. */
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <emmintrin.h>
#include "GSObjectPool.h"
static int fails;
#define CHECK(c, m) do { if (!(c)) { printf(" FAIL: %s\n", m); fails++; } } while (0)
/* GSVector4i::compare64 in miniature: cast and index, which is an
* aligned load. */
static bool compare64(const void* a, const void* b, size_t size)
{
const __m128i* x = (const __m128i*)a;
const __m128i* y = (const __m128i*)b;
size >>= 4;
for (size_t i = 0; i < size; i++)
{
__m128i v = _mm_cmpeq_epi32(_mm_load_si128(x + i), _mm_load_si128(y + i));
if (_mm_movemask_epi8(v) != 0xffff)
return false;
}
return true;
}
struct Palette
{
uint32_t m_alive;
uint32_t m_refs;
alignas(GS_OBJECT_POOL_ALIGN) uint32_t m_clut[256];
uint16_t m_pal;
};
int main()
{
gs_object_pool_t pool;
int i;
memset(&pool, 0, sizeof(pool));
CHECK(alignof(Palette) >= 16, "the object is aligned enough for an SSE load");
CHECK((offsetof(Palette, m_clut) % 16) == 0, "the CLUT is aligned inside it");
gs_object_pool_init(&pool, sizeof(Palette), 64);
for (i = 0; i < 64; i++)
{
Palette* p = (Palette*)gs_object_pool_take(&pool, sizeof(Palette));
CHECK(p != NULL, "slot");
CHECK(((uintptr_t)p % alignof(Palette)) == 0, "slot meets the object's alignment");
CHECK(((uintptr_t)p->m_clut % 16) == 0, "so the CLUT is 16-byte aligned");
memset(p->m_clut, i, sizeof(p->m_clut));
/* The comparison that faulted. */
CHECK(compare64(p->m_clut, p->m_clut, 1024), "compare a CLUT against itself");
}
gs_object_pool_free(&pool);
printf(fails ? "align: FAILED (%d)\n" : "align: ok\n", fails);
return fails != 0;
}