"причина ошибки: выделение через IntervalND::opera...
Created on: April 21, 2026
Answered using GPT-5.4 Thinking by Chat01
Created on: April 21, 2026
Answered using GPT-5.4 Thinking by Chat01
"причина ошибки: выделение через IntervalND::operator new (slab), а освобождение через IntervalND::operator delete (который, судя по стеку, пытается использовать другой механизм освобождения или не находит блок в slab). Это приводит к SLAB_FREE_IGNORED и повреждению служебных структур slab (видно по дампам SLAB_DUMP с некорректными head/tail).
🚩 Самые показательные строки для отправки копилоту
Отправьте копилоту несколько полных блоков (по 3–4 строки подряд), содержащих пару SLAB_ALLOC → SLAB_FREE_IGNORED для одного и того же указателя, плюс ближайший SLAB_DUMP для того же указателя. Вот конкретные примеры из вашего лога:
Пара 1 (ptr = 0000024D56789880)
text
2026-04-21 21:50:26 [TID=8040] SLAB_ALLOC ptr=0000024D56789880 size=64 bt=00007FFCB83CFC17[agp_log_event+0x223 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:65)],00007FFCB83BAC7B[IntervalND::operator new+0x83 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:502)],00007FFCB83D5B2D[agp_run_branch_mpi+0x5649 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:1152)],00007FFCB83F3AE1[AGP_Manip2D+0xd25 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:1309)],...
2026-04-21 21:50:26 [TID=8040] SLAB_FREE_IGNORED ptr=0000024D56789880 size=0 bt=00007FFCB83CFC17[agp_log_event+0x223 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:65)],00007FFCB83BADCF[IntervalND::operator delete+0x7b (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:520)],00007FFCB83D6589[agp_run_branch_mpi+0x60a5 (E:\TEST_AGP\TEST_FUNC\TEST_FUNC.cpp:11a1)],...
2026-04-21 21:50:26 [TID=8040] SLAB_DUMP head=8707003f8b07003f5acfef41e5d21142 tail=8b07800000000000a9562c3e0b000202 ptr=0000024D56789880 size=0 bt=...
Пара 2 (ptr = 0000024D567898C0)
text
2026-04-21 21:50:26 [TID=8040] SLAB_ALLOC ptr=0000024D567898C0 size=64 bt=... IntervalND::operator new+0x83 (TEST_FUNC.cpp:502) ...
2026-04-21 21:50:26 [TID=8040] SLAB_FREE_IGNORED ptr=0000024D567898C0 size=0 bt=... IntervalND::operator delete+0x7b (TEST_FUNC.cpp:520) ...
2026-04-21 21:50:26 [TID=8040] SLAB_DUMP head=8b07003f8f07003fe5d21142b7638643 tail=8f07800000000000a9562c3e0b000202 ptr=0000024D567898C0 ...
Пара 3 (ptr = 0000024D56789980)
text
2026-04-21 21:50:26 [TID=8040] SLAB_ALLOC ptr=0000024D56789980 size=64 bt=... IntervalND::operator new+0x83 ...
2026-04-21 21:50:26 [TID=8040] SLAB_FREE_IGNORED ptr=0000024D56789980 size=0 bt=... IntervalND::operator delete+0x7b ...
2026-04-21 21:50:26 [TID=8040] SLAB_DUMP head=8b07003f8d07003fe5d211427681df42 tail=8d07800000000000f813173e0b000202 ptr=0000024D56789980 ...
Пара 4 (ptr = 0000024D56789440 – первая в логе)
text
2026-04-21 21:50:26 [TID=8040] SLAB_FREE_IGNORED ptr=0000024D56789440 size=0 bt=... IntervalND::operator delete+0x7b ...
2026-04-21 21:50:26 [TID=8040] SLAB_DUMP head=fc07003fff07003f65ee0b440b4f1c43 tail=ff07800000000000f813173e0b000202 ptr=0000024D56789440 ...
(Хотя здесь нет предшествующего SLAB_ALLOC в этом фрагменте, он явно был ранее выделен через slab, и его освобождение тоже не распознано.)
🔍 Что ещё важно отметить для копилота
Все операции в одном потоке (TID=8040), значит проблема не в cross‑thread free, а в логической ошибке кода.
Класс IntervalND:
Выделение через operator new (строка 502 в TEST_FUNC.cpp) – вероятно, перегруженный оператор, использующий slab‑аллокатор.
Освобождение через operator delete (строка 520) – этот оператор, судя по стеку, не может найти блок в slab (поэтому size=0 и дамп показывает испорченные заголовки).
Стеки STORE_Q: между выделениями и освобождениями идут записи STORE_Q (копирование состояний), что может указывать на копирование указателей без правильной семантики владения.
CO_TASK_ALLOC в конце – это выделение через CoTaskMemAlloc, которое может происходить уже после того, как куча повреждена предыдущими некорректными освобождениями.
Отправьте эти строки копилоту – по ним он точно определит, что IntervalND::operator delete вызывает неправильную функцию освобождения (вероятно, глобальный ::operator delete или free вместо специального освобождения slab).
" - актуальные логи о проблеме, и: "struct Slab final {
char* const base;
char* current;
char* const end;
text__forceinline Slab(void* memory, size_t usable) noexcept : base(static_cast<char*>(memory)), current(base), end(base + (usable & ~static_cast<size_t>(63u))) { } __forceinline void reset() noexcept { current = base; }
};
static constexpr size_t AGP_INTERVAL_SLAB_BYTES = 67108864u;
static tbb::enumerable_thread_specific<Slab*> tls( noexcept {
void* memory = _aligned_malloc(AGP_INTERVAL_SLAB_BYTES, 64u);
Slab* slab = static_cast<Slab*>(_aligned_malloc(sizeof(Slab), 64u));
new (slab) Slab(memory, AGP_INTERVAL_SLAB_BYTES);
textchar* p = slab->base; while (p < slab->end) { *p = 0; p += 4096u; } return slab; });
static __forceinline void AgpResetIntervalSlabs() noexcept {
for (auto it = tls.begin(); it != tls.end(); ++it) {
Slab* s = *it;
if (s != nullptr) s->reset();
}
}
static __declspec(noalias) __forceinline
float agp_nextup01(float x) noexcept
{
unsigned u = reinterpret_cast<unsigned>(&x);
if (u == 0x3f800000u) return x;
if ((u & 0x7fffffffU) == 0u) {
const unsigned one = 1u;
return reinterpret_cast<const float>(&one);
}
++u;
return reinterpret_cast<float>(&u);
}
static __declspec(noalias) __forceinline
float agp_nextdown01(float x) noexcept
{
unsigned u = reinterpret_cast<unsigned>(&x);
if ((u & 0x7fffffffU) == 0u) return x;
--u;
return reinterpret_cast<float>(&u);
}
static __declspec(noalias) __forceinline
bool agp_force_strict_interior(float x1, float x2, float& t) noexcept
{
const float lo = agp_nextup01(x1);
const float hi = agp_nextdown01(x2);
textif (!(lo < hi)) return false; if (!(t > lo)) t = lo; else if (!(t < hi)) t = hi; return true;
}
struct IntervalND final {
float x1, x2, y1, y2, delta_y, ordinate_factor, N_factor, quadratic_term, M, R;
unsigned long long i1, i2;
float diam;
textunion { struct { unsigned short span_level; unsigned char idx1; unsigned char idx2; }; unsigned span_pack; }; static __declspec(noalias) __forceinline void* operator new(size_t sz) noexcept { Slab* s = tls.local(); const size_t chunk = (sz + 63u) & ~static_cast<size_t>(63u); if (s->current + chunk <= s->end) { char* r = s->current; s->current += chunk; return r; } return _aligned_malloc(chunk, 64u); } static __declspec(noalias) __forceinline void operator delete(void* p) noexcept { if (!p) return; Slab* s = tls.local(); const char* pc = static_cast<const char*>(p); if (pc < s->base || pc >= s->end) { _aligned_free(p); } } __declspec(noalias) __forceinline IntervalND(float _x1, float _x2, float _y1, float _y2, unsigned _idx1, unsigned _idx2) noexcept : x1(_x1) , x2(_x2) , y1(_y1) , y2(_y2) , delta_y(fmaf(_y2, 1.0f, -_y1)) , ordinate_factor(fmaf(fmaf(-y1, 1.0f, -y2), 2.0f, 0.0f)) , N_factor(0.0f) , quadratic_term(0.0f) , M(0.0f) , R(0.0f) , i1(0ull) , i2(0ull) , diam(0.0f) , span_pack((static_cast<unsigned>(_idx2 & 0xFFu) << 24) | (static_cast<unsigned>(_idx1 & 0xFFu) << 16)) { } __declspec(noalias) __forceinline void compute_span_level(const struct MortonND& map) noexcept; __declspec(noalias) __forceinline void set_metric(float d_alpha) noexcept { N_factor = d_alpha; if (idx1 == idx2) { const float inv = 1.0f / N_factor; quadratic_term = fmaf(inv, fmaf(delta_y, delta_y, 0.0f), 0.0f); M = fmaf(inv, fabsf(delta_y), 0.0f); } else { quadratic_term = 0.0f; M = 0.0f; } } __declspec(noalias) __forceinline void ChangeCharacteristic(float _m) noexcept { if (idx1 == idx2) R = fmaf(1.0f / _m, quadratic_term, fmaf(_m, N_factor, ordinate_factor)); else if (idx1 < idx2) R = fmaf(2.0f * _m, N_factor, -4.0f * y2); else R = fmaf(2.0f * _m, N_factor, -4.0f * y1); }
};
static_assert(sizeof(IntervalND) == 64);
using IntervalPtrVec =
std::vector<IntervalND*, boost::alignment::aligned_allocator<IntervalND*, 32u>>;
using IntervalHeap =
std::vector<IntervalND*, boost::alignment::aligned_allocator<IntervalND*, 32u>>;
static __declspec(noalias) __forceinline
bool ComparePtrND(const IntervalND* a, const IntervalND* b) noexcept
{
return a->R < b->R;
}
static __declspec(noalias) __forceinline
void heap_sift_up(IntervalHeap& H, size_t pos) noexcept
{
IntervalND* v = H[pos];
while (pos > 0u) {
const size_t parent = (pos - 1u) >> 1u;
if (!ComparePtrND(H[parent], v)) break;
H[pos] = H[parent];
pos = parent;
}
H[pos] = v;
}
static __declspec(noalias) __forceinline
void heap_sift_down(IntervalHeap& H, size_t pos) noexcept
{
const size_t n = H.size();
IntervalND* v = H[pos];
size_t child = pos * 2u + 1u;
textwhile (child < n) { size_t best = child; const size_t right = child + 1u; if (right < n && ComparePtrND(H[best], H[right])) best = right; if (!ComparePtrND(v, H[best])) break; H[pos] = H[best]; pos = best; child = pos * 2u + 1u; } H[pos] = v;
}
static __declspec(noalias) __forceinline
void heap_fix_at(IntervalHeap& H, size_t pos) noexcept
{
if (pos > 0u && ComparePtrND(H[(pos - 1u) >> 1u], H[pos]))
heap_sift_up(H, pos);
else
heap_sift_down(H, pos);
}
static __declspec(noalias) __forceinline
void heap_erase_at(IntervalHeap& H, size_t pos) noexcept
{
const size_t last = H.size() - 1u;
if (pos != last) {
H[pos] = H[last];
H.pop_back();
heap_fix_at(H, pos);
}
else {
H.pop_back();
}
}
static __declspec(noalias) __forceinline
void AgpFillIntervalWire(IntervalWire& w, const IntervalND* I) noexcept
{
w.x1 = I->x1;
w.x2 = I->x2;
w.y1 = I->y1;
w.y2 = I->y2;
w.N_factor = I->N_factor;
w.quadratic_term = I->quadratic_term;
w.M = I->M;
w.diam = I->diam;
w.i1 = I->i1;
w.i2 = I->i2;
w.span_pack = I->span_pack;
}
static __declspec(noalias) __forceinline
IntervalND* AgpCreateIntervalFromWire(const IntervalWire& w) noexcept
{
IntervalND* I = new IntervalND(w.x1, w.x2, w.y1, w.y2, 0u, 0u);
I->i1 = w.i1;
I->i2 = w.i2;
I->diam = w.diam;
I->N_factor = w.N_factor;
I->quadratic_term = w.quadratic_term;
I->M = w.M;
I->span_pack = w.span_pack;
return I;
}
static __declspec(noalias) __forceinline
void heap_make(IntervalHeap& H) noexcept
{
std::make_heap(H.begin(), H.end(), ComparePtrND);
}
static __declspec(noalias) __forceinline
void heap_push(IntervalHeap& H, IntervalND* p) noexcept
{
H.emplace_back(p);
heap_sift_up(H, H.size() - 1u);
}
static __declspec(noalias) __forceinline
IntervalND* heap_pop_front(IntervalHeap& H) noexcept
{
IntervalND* top = H[0];
const size_t last = H.size() - 1u;
if (last != 0u) {
H[0] = H[last];
H.pop_back();
heap_sift_down(H, 0u);
}
else {
H.pop_back();
}
return top;
}
static __declspec(noalias) __forceinline
void RecomputeR_ConstM_Mixed_ND(IntervalND** __restrict data, size_t sz, float m) noexcept
{
size_t i = 0u;
const size_t limit = sz & ~3ull;
while (i < limit) {
data[i + 0u]->ChangeCharacteristic(m);
data[i + 1u]->ChangeCharacteristic(m);
data[i + 2u]->ChangeCharacteristic(m);
data[i + 3u]->ChangeCharacteristic(m);
i += 4u;
}
while (i < sz) {
data[i]->ChangeCharacteristic(m);
++i;
}
}
static __declspec(noalias) __forceinline
void RecomputeR_AffineM_Mixed_ND(IntervalND** __restrict data, size_t sz, float GF, float alpha) noexcept
{
size_t i = 0u;
const size_t limit = sz & ~3ull;
textwhile (i < limit) { IntervalND* I0 = data[i + 0u]; IntervalND* I1 = data[i + 1u]; IntervalND* I2 = data[i + 2u]; IntervalND* I3 = data[i + 3u]; I0->ChangeCharacteristic(fmaf(GF, I0->N_factor, fmaf(alpha, I0->M, 0.0f))); I1->ChangeCharacteristic(fmaf(GF, I1->N_factor, fmaf(alpha, I1->M, 0.0f))); I2->ChangeCharacteristic(fmaf(GF, I2->N_factor, fmaf(alpha, I2->M, 0.0f))); I3->ChangeCharacteristic(fmaf(GF, I3->N_factor, fmaf(alpha, I3->M, 0.0f))); i += 4u; } while (i < sz) { IntervalND* I = data[i]; I->ChangeCharacteristic(fmaf(GF, I->N_factor, fmaf(alpha, I->M, 0.0f))); ++i; }
}
static __declspec(noalias) __forceinline
void RecomputeR_ConstM_AVX2_ND(IntervalND* __restrict const* arr, size_t n, float m) noexcept
{
__declspec(align(32)) const __m256 vm = _mm256_set1_ps(m), v2 = _mm256_set1_ps(2.0f);
__m256 vinvm = _mm256_rcp_ps(vm);
vinvm = _mm256_mul_ps(vinvm, _mm256_fnmadd_ps(vm, vinvm, v2));
textsize_t i = 0u, limit = n & ~7ull; alignas(32) float q[8], nf[8], od[8], out[8]; while (i < limit) { int k = 0; while (k < 8) { const IntervalND* p = arr[i + static_cast<size_t>(k)]; q[k] = p->quadratic_term; nf[k] = p->N_factor; od[k] = p->ordinate_factor; ++k; } __m256 vq = _mm256_load_ps(q); __m256 vnf = _mm256_load_ps(nf); __m256 vod = _mm256_load_ps(od); __m256 t = _mm256_fmadd_ps(vm, vnf, vod); __m256 res = _mm256_fmadd_ps(vq, vinvm, t); _mm256_store_ps(out, res); k = 0; while (k < 8) { arr[i + static_cast<size_t>(k)]->R = out[k]; ++k; } i += 8u; } while (i < n) { arr[i]->ChangeCharacteristic(m); ++i; }
}
static __declspec(noalias) __forceinline
void RecomputeR_AffineM_AVX2_ND(IntervalND* __restrict const* arr, size_t n, float GF, float alpha) noexcept
{
__declspec(align(32)) const __m256 vGF = _mm256_set1_ps(GF), va = _mm256_set1_ps(alpha), v2 = _mm256_set1_ps(2.0f);
size_t i = 0u, limit = n & ~7ull;
alignas(32) float ln[8], Mv[8], q[8], nf[8], od[8], out[8];
textwhile (i < limit) { int k = 0; while (k < 8) { const IntervalND* p = arr[i + static_cast<size_t>(k)]; ln[k] = fmaf(-p->x1, 1.0f, p->x2); Mv[k] = p->M; q[k] = p->quadratic_term; nf[k] = p->N_factor; od[k] = p->ordinate_factor; ++k; } __m256 vln = _mm256_load_ps(ln); __m256 vM = _mm256_load_ps(Mv); __m256 vq = _mm256_load_ps(q); __m256 vnf = _mm256_load_ps(nf); __m256 vod = _mm256_load_ps(od); __m256 vm = _mm256_fmadd_ps(vGF, vln, _mm256_mul_ps(va, vM)); __m256 vinvm = _mm256_rcp_ps(vm); vinvm = _mm256_mul_ps(vinvm, _mm256_fnmadd_ps(vm, vinvm, v2)); __m256 t = _mm256_fmadd_ps(vm, vnf, vod); __m256 res = _mm256_fmadd_ps(vq, vinvm, t); _mm256_store_ps(out, res); k = 0; while (k < 8) { arr[i + static_cast<size_t>(k)]->R = out[k]; ++k; } i += 8u; } while (i < n) { const IntervalND* p = arr[i]; const float mi = fmaf(GF, fmaf(-p->x1, 1.0f, p->x2), fmaf(p->M, alpha, 0.0f)); arr[i]->R = fmaf(1.0f / mi, p->quadratic_term, fmaf(mi, p->N_factor, p->ordinate_factor)); ++i; }
}
__declspec(noalias) __forceinline
void IntervalND::compute_span_level(const MortonND& map) noexcept
{
unsigned s = 0u;
int d = 0;
while (d < map.dim) {
const unsigned long long varying = (i1 ^ i2) & map.pextMask[d];
s += static_cast<unsigned>(_mm_popcnt_u64(varying));
++d;
}
texts += static_cast<unsigned>((map.levels - map.chunk_bits[0]) * map.dim); if (s > 11u) s = 11u; span_level = static_cast<unsigned short>(s);
}
__declspec(align(32)) struct TransitionGeomSample final {
alignas(32) float q[AGP_MAX_FULL_DIM]{};
float x = 0.0f, y = 0.0f, clearance = FLT_MAX;
};" - это то что связано со структурой интервал сейчас - теперь давай думать о том что мы можем с этим сделать и как решить проблему - давай сделаем так что искоренить сам корень чтобы работа слаб аллокатора и кастомный выделений и освобождений памяти под него была корректной - какие пути решения ты видишь
Да, давай разберём это как инженерно, а не “подлатать симптомы”.
По коду видно, что у тебя IntervalND сейчас живёт в bump/slab allocator с такой моделью:
operator new:
_aligned_malloc.operator delete:
_aligned_free;Это допустимая схема, но только если строго соблюдены 3 инварианта:
reset()-ится, пока жив хотя бы один объект из него;И вот именно здесь у тебя архитектурная слабость.
operator delete смотрит только в tls.local()Вот это место:
cppstatic __declspec(noalias) __forceinline void operator delete(void* p) noexcept { if (!p) return; Slab* s = tls.local(); const char* pc = static_cast<const char*>(p); if (pc < s->base || pc >= s->end) { _aligned_free(p); } }
Логика тут такая: “если указатель не из моего текущего slab, значит он heap/fallback, освобожу _aligned_free”.
Это неэквивалентно “если указатель не из slab вообще”.
Если объект был выделен:
tls.local() в момент delete вернул другой slab instance,то указатель:
s,И тогда код ошибочно делает:
cpp_aligned_free(p);
на память, которая вообще не была выделена через _aligned_malloc для этого объекта.
Это уже прямой корень heap/slab corruption.
Даже если в логах один TID=8040, это ещё не гарантирует, что tls.local() всегда указывает на тот же slab-экземпляр, из которого объект был получен. Для enumerable_thread_specific это обычно так в пределах потока, но архитектурно код всё равно хрупкий: delete определяет происхождение объекта по текущему состоянию потока, а не по факту выделения.
AgpResetIntervalSlabs() может убивать ещё живые объектыВот это ещё более критично:
cppstatic __forceinline void AgpResetIntervalSlabs() noexcept { for (auto it = tls.begin(); it != tls.end(); ++it) { Slab* s = *it; if (s != nullptr) s->reset(); } }
reset() просто откатывает bump pointer:
cppcurrent = base;
То есть вся память slab считается “свободной”, без проверки живых объектов.
Если где-то ещё остались:
IntervalHeap,IntervalPtrVec,IntervalWire -> IntervalND*,то после reset() ты получаешь reuse той же памяти поверх живых объектов. А затем:
Это очень хорошо совпадает с тем, что ты описал про STORE_Q и перенос состояний.
Сейчас delete пытается угадать, откуда пришёл p, по диапазону адресов. Это плохой контракт.
У корректного кастомного аллокатора должно быть одно из двух:
delete.Сейчас у тебя ни то ни другое не доведено до железобетона.
Если говорить прямо: корень проблемы не в том, что slab allocator “плохо освобождает”, а в том, что модель владения памятью у IntervalND нестрогая.
Симптомы указывают на смесь из двух проблем:
IntervalND*, выданные из slab, продолжают жить за пределами ожидаемой “эпохи” slab;operator delete принимает решение об освобождении на основании tls.local(), а это ненадёжно как источник истины.Ниже от самого надёжного к наименее инвазивному.
delete для slab-объектовЭто лучший архитектурный вариант.
Сделать IntervalND arena-allocated object:
AgpResetIntervalSlabs() память возвращается целиком;delete p для slab-объектов вообще.Fallback-объекты тоже лучше убрать, либо выделять их из отдельного arena block list.
_aligned_free на slab-указателях;IntervalND вообще исчезает как класс проблем.Вариант А:
delete для IntervalND;AgpMakeInterval(...);AgpResetIntervalSlabs() в конце эпохи.Например:
cppstruct IntervalND final { // ... private: static void operator delete(void*) = delete; static void operator delete(void*, std::size_t) = delete; };
И наружу:
cpptemplate<class... Args> static __forceinline IntervalND* AgpMakeInterval(Args&&... args) noexcept { void* mem = IntervalArenaAlloc(sizeof(IntervalND), alignof(IntervalND)); return new (mem) IntervalND(std::forward<Args>(args)...); }
Если IntervalND тривиально разрушаем или деструктор не нужен. По твоему коду это именно так.
delete, но сделать явную маркировку происхождения блокаЭто лучший компромисс, если delete нужен.
Каждому выделению предшествует небольшой header, где пишется:
slab или heap);Тогда operator delete не угадывает, а точно знает, что делать.
cppenum class IntervalAllocKind : uint32_t { Slab = 0x534C4142, // 'SLAB' Heap = 0x48454150 // 'HEAP' }; struct alignas(16) IntervalAllocHeader { uint32_t magic; uint32_t kind; Slab* owner; uint32_t requested_size; uint32_t reserved; };
cppstatic void* operator new(size_t sz) noexcept { constexpr size_t A = 64; const size_t total = (sizeof(IntervalAllocHeader) + sz + (A - 1)) & ~(A - 1); Slab* s = tls.local(); char* raw = nullptr; if (s->current + total <= s->end) { raw = s->current; s->current += total; auto* h = reinterpret_cast<IntervalAllocHeader*>(raw); h->magic = 0xDEADC0DEu; h->kind = static_cast<uint32_t>(IntervalAllocKind::Slab); h->owner = s; h->requested_size = static_cast<uint32_t>(sz); return raw + sizeof(IntervalAllocHeader); } raw = static_cast<char*>(_aligned_malloc(total, A)); if (!raw) return nullptr; auto* h = reinterpret_cast<IntervalAllocHeader*>(raw); h->magic = 0xDEADC0DEu; h->kind = static_cast<uint32_t>(IntervalAllocKind::Heap); h->owner = nullptr; h->requested_size = static_cast<uint32_t>(sz); return raw + sizeof(IntervalAllocHeader); }
cppstatic void operator delete(void* p) noexcept { if (!p) return; char* raw = static_cast<char*>(p) - sizeof(IntervalAllocHeader); auto* h = reinterpret_cast<IntervalAllocHeader*>(raw); if (h->magic != 0xDEADC0DEu) { __debugbreak(); // или лог fatal return; } if (h->kind == static_cast<uint32_t>(IntervalAllocKind::Heap)) { _aligned_free(raw); return; } // slab block: intentionally no-op }
tls.local();_aligned_free;sizeof(IntervalND)==64 сломается, если ты хочешь header “рядом” с объектом и прежнюю адресную модель;Это, на мой взгляд, наиболее практичное исправление, если ты хочешь минимально трогать остальной код.
Это более “allocator-grade” путь.
Не класть header перед объектом, а держать внешний реестр:
Тогда delete проверяет реестр, а не tls.local().
Например:
std::pmr::unordered_set<void*> fallback_ptrs;Работать будет, но сложнее, и нужно синхронизировать доступ. Для локального класса IntervalND header проще и надёжнее.
Это самый чистый дизайн, если готов чуть сильнее переписать.
Вообще убрать глобальный tls из IntervalND, и сделать так:
cppstruct IntervalArena { Slab* slab; IntervalND* create(...); void reset(); };
И все функции, создающие интервалы, получают IntervalArena&.
Самая надёжная стратегия:
delete не нужен по архитектуреСделать IntervalND strictly arena-only:
delete;_aligned_malloc на каждый объект;IntervalND*.Это почти идеал.
delete всё-таки нуженСделать явный allocation header и добавить полный набор delete-overloads:
cppstatic void operator delete(void*) noexcept; static void operator delete(void*, std::size_t) noexcept; static void operator delete(void*, std::align_val_t) noexcept; static void operator delete(void*, std::size_t, std::align_val_t) noexcept;
Все они должны идти в один и тот же внутренний deallocate(p).
Это убирает риск, что компилятор/CRT в части сценариев уйдёт не туда.
tls.local() в deleteВот это надо выкинуть как сам принцип:
cppSlab* s = tls.local(); if (pc < s->base || pc >= s->end) { _aligned_free(p); }
Потому что delete должен определять происхождение блока по самому блоку, а не по текущему состоянию потока.
Перед AgpResetIntervalSlabs() надо уметь ответить: живы ли ещё объекты?
Хотя бы в debug:
Например:
cppstruct Slab final { char* const base; char* current; char* const end; uint32_t generation = 0; uint32_t live_count = 0; void reset() noexcept { ++generation; current = base; live_count = 0; } };
И при slab allocation:
cpp++s->live_count;
А при delete slab-object:
cpp--owner->live_count; // если вообще допускается поштучный delete
И перед reset в debug:
cppAGP_ASSERT(s->live_count == 0);
Если у тебя модель “пообъектный delete не нужен”, тогда live_count можно использовать только как детектор утечек эпохи.
Очень полезно для ловли испорченных указателей:
cppuintptr_t off = static_cast<uintptr_t>(pc - s->base); AGP_ASSERT((off % 64u) == 0);
Хотя лучше проверять это не через текущий s, а через owner/header.
Даже если кажется, что не нужен — добавь.
cppstatic void operator delete(void* p, std::size_t) noexcept { operator delete(p); }
И аналоги для aligned delete, если хочешь закрыть тему полностью.
Если выбирать один главный:
reset() вызывается, пока старые IntervalND* ещё где-то живутПотому что твоя модель slab — это “allocate-many, free-all-at-once”. А остальной код по контейнерам с сырыми указателями выглядит так, что у объектов может быть более длинная жизнь, чем одна фаза.
Особенно настораживают:
IntervalHeap и IntervalPtrVec хранят сырые IntervalND*;AgpCreateIntervalFromWire, который заново материализует объект из wire;STORE_Q, про который ты уже упомянул как про копирование состояний;Это очень похоже на смешение двух моделей:
Их одновременно держать на сырых указателях очень опасно.
Я бы делал так по шагам.
Убрать зависимость delete от tls.local() и ввести header.
Это быстро даст стабилизацию и позволит точно отличать:
Добавить:
Например, если slab allocation хранит owner_generation, то при обращении/delete можно ловить use-after-reset.
Нужно выбрать одно:
IntervalND — short-lived arena objects
и тогда:
IntervalND — individually managed objects
и тогда:
Но второй вариант для такого объекта обычно не стоит сложности. Для IntervalND лучше arena model.
Ниже концепция, которая, скорее всего, искоренит проблему.
operator new/delete из IntervalNDВообще.
IntervalArenacppstruct IntervalArena { Slab* slab; void* allocate(size_t sz, size_t align = 64) noexcept; void reset() noexcept; };
cpptemplate<class... Args> IntervalND* MakeInterval(IntervalArena& arena, Args&&... args) noexcept { void* mem = arena.allocate(sizeof(IntervalND), 64); return new (mem) IntervalND(std::forward<Args>(args)...); }
delete IntervalND*Совсем.
IntervalND* очищаются до arena.reset()И это становится контрактом системы.
Это убирает саму возможность mismatch alloc/free.
new/delete, вот конкретный safe patchВот что я бы сделал минимально.
cppenum : uint32_t { AGP_INTERVAL_MAGIC = 0xA61F4E2Du, AGP_KIND_SLAB = 0x534C4142u, AGP_KIND_HEAP = 0x48454150u }; struct alignas(16) IntervalHdr { uint32_t magic; uint32_t kind; Slab* owner; uint32_t size; }; static constexpr size_t AGP_INTERVAL_ALIGN = 64u; struct IntervalND final { // поля... static __declspec(noalias) __forceinline void* operator new(size_t sz) noexcept { const size_t total = (sizeof(IntervalHdr) + sz + (AGP_INTERVAL_ALIGN - 1u)) & ~static_cast<size_t>(AGP_INTERVAL_ALIGN - 1u); Slab* s = tls.local(); char* raw = nullptr; if (s->current + total <= s->end) { raw = s->current; s->current += total; auto* h = reinterpret_cast<IntervalHdr*>(raw); h->magic = AGP_INTERVAL_MAGIC; h->kind = AGP_KIND_SLAB; h->owner = s; h->size = static_cast<uint32_t>(sz); return raw + sizeof(IntervalHdr); } raw = static_cast<char*>(_aligned_malloc(total, AGP_INTERVAL_ALIGN)); if (!raw) return nullptr; auto* h = reinterpret_cast<IntervalHdr*>(raw); h->magic = AGP_INTERVAL_MAGIC; h->kind = AGP_KIND_HEAP; h->owner = nullptr; h->size = static_cast<uint32_t>(sz); return raw + sizeof(IntervalHdr); } static __declspec(noalias) __forceinline void operator delete(void* p) noexcept { if (!p) return; char* raw = static_cast<char*>(p) - sizeof(IntervalHdr); auto* h = reinterpret_cast<IntervalHdr*>(raw); if (h->magic != AGP_INTERVAL_MAGIC) { __debugbreak(); return; } if (h->kind == AGP_KIND_HEAP) { _aligned_free(raw); return; } // slab allocation: no-op } static __declspec(noalias) __forceinline void operator delete(void* p, size_t) noexcept { operator delete(p); } };
Это уже на порядок безопаснее текущего решения.
Корень проблемы не в slab как таковом, а в том, что IntervalND сейчас живёт сразу в двух несовместимых моделях управления памятью — arena lifetime и индивидуальный delete — при этом delete пытается определить происхождение блока по tls.local(), что концептуально ненадёжно.
Мой совет:
IntervalND в чистую arena-модель без delete;tls.local() из delete;AgpResetIntervalSlabs() не вызывается до полной очистки всех контейнеров с IntervalND*.Я могу следующим сообщением дать тебе уже готовый, аккуратно оформленный патч в стиле твоего файла: либо arena-only redesign, либо минимальный safe-patch с header + all delete overloads.