mirror of
https://github.com/libretro/parallel-ps2.git
synced 2026-11-09 17:28:25 -10:00
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.
60 lines
1.7 KiB
C++
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;
|
|
}
|