Flexible alignment - #50
ilyachastikov wants to merge 36 commits into
Conversation
| tlsf_free(&t, p); | ||
| tlsf_free(&t, z); | ||
| tlsf_free(&t, q); | ||
| tlsf_free(&t, h); |
There was a problem hiding this comment.
h is never declared or allocated anywhere in this snippet, so the example does not compile. The table below gains a tlsf_acalloc row that the snippet never shows, so this was probably meant to be paired with a void *h = tlsf_acalloc(&t, 4, 64, 64); above the frees.
There was a problem hiding this comment.
tlsf_acalloc for h is present in this example at line 117
| assert(p_new != NULL); | ||
| assert(((uintptr_t) p_new % target_align) == 0 && | ||
| "tlsf_arealloc lost alignment"); | ||
| assert(p_new != p_align && "tlsf_arealloc didn't relocate the block"); |
There was a problem hiding this comment.
This assert depends on where the linker puts pool. Falling back to target_align = 128 only helps when p_align is 64 byte aligned but not 128 byte aligned; when the static pool lands on a 128 byte boundary the pointer satisfies both, tlsf_arealloc grows in place, and p_new == p_align trips the assert. Make the layout deterministic: allocate padding so p_align is known to be misaligned for target_align, and assert that before the call.
| memset(p, id & 0xFF, sz); | ||
| tlsf_thread_free(&ts, p); | ||
| size_t new_sz = (size_t) (TLSF_RAND(&seed) % 512) + 1; | ||
| void *q = tlsf_thread_arealloc(&ts, p, align, new_sz); |
There was a problem hiding this comment.
The stress loop frees q without looking at it, so tlsf_thread_arealloc passes here as long as it returns non-NULL: lost alignment or a corrupted payload goes undetected. Assert ((uintptr_t) q % align) == 0 and check that the first min(sz, new_sz) bytes still hold id & 0xFF before freeing.
|
|
| block_merge_next(t, block); | ||
| ASAN_UNPOISON(block_payload(block), block_size(block)); | ||
| block_set_prev_free(block_next(block), false); | ||
| } else { |
There was a problem hiding this comment.
Nothing in the suite reaches this branch. It needs a block already aligned for align whose block_next is in use, and every aligned grow in tests/test.c has a free tail behind it, while arealloc_test relocates through the misalignment branch instead. Add a case that allocates an aligned block, allocates a barrier right after it, then grows the first one, and confirm the case fails against a wrong copy length before trusting it.
| if (!ptr) | ||
| return tlsf_thread_aalloc(ts, align, size); | ||
|
|
||
| if (!size) { |
There was a problem hiding this comment.
Same ordering as tlsf_arealloc: a zero size frees ptr before align is looked at, so an invalid align with size == 0 returns NULL with the block already released, against the contract stated on this function that a failure leaves @ptr allocated and intact. Validate align first.
| size_t old_size; | ||
| TLSF_LOCK_ACQUIRE(&ts->arenas[idx].lock); | ||
| old_size = tlsf_usable_size(ptr); | ||
| if (!align || (align & (align - 1))) { |
There was a problem hiding this comment.
This check runs after arena_find and after the arena lock is taken, so an invalid align pays a lock acquire and release before it is rejected. It is also narrower than the one in tlsf_arealloc, which additionally rejects align > TLSF_MAX_SIZE: for such an align the in-arena call returns NULL, this function reads that as arena exhaustion, and falls through to tlsf_thread_aalloc, which try-locks every remaining arena before failing anyway. Hoist !align || (align & (align - 1)) || align > TLSF_MAX_SIZE to the top of the function and drop this copy from inside the lock.
| #define TLSF_INIT_STATIC TLSF_INIT | ||
| #endif | ||
|
|
||
| /* Native alignment for target paltform architecture */ |
There was a problem hiding this comment.
Spelling in text that ships in the public header: paltform for platform here, alloctions for allocations on the macro block below and again in tlsf_thread.h, upto for up to in the tlsf_arealloc doc comment, and falls back o allocating in the tlsf_thread_arealloc doc comment.
| /* Native alignment for target paltform architecture */ | |
| /* Native alignment for target platform architecture */ |
| defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) | ||
| #define TLSF_ARCH_ALIGNMENT 16 | ||
| #else | ||
| #define TLSF_ARCH_ALIGNMENT (_TLSF_SIZE_WIDTH >> 2) |
There was a problem hiding this comment.
This fallback is 2 * sizeof(size_t), 16 on 64-bit and 8 on 32-bit, not the sizeof(void *) that tests/bench.c used before the definition moved here, so the benchmark's alignment changes on any target that misses every SIMD test above. It matches what a hosted malloc guarantees, so it looks deliberate, but the comment reads as the architecture's natural alignment and the value is twice that. On a 32-bit target without SIMD it also pushes every TLSF_NATIVE_AALLOC past the align <= ALIGN_SIZE fast path in tlsf_aalloc and into a left trim plus an extra header. Say which of the two is intended.
| uint8_t *p_align = (uint8_t *) tlsf_aalloc(&t, 16, initial_size); | ||
| assert(p_align != NULL && "Allocation failed"); | ||
| assert(((uintptr_t) p_align % target_align) != 0 && | ||
| "Test setup error: p_align is accidentally aligned"); |
There was a problem hiding this comment.
This aborts the suite when the setup happens to land aligned, rather than reporting a defect in tlsf_arealloc, and its premise rests on TLSF_GCC_ALIGN actually expanding. On a compiler that is neither MSVC nor GCC-like, all three alignment macros expand to nothing, raw_pool gets whatever the linker picks, and whether p_align lands off a 128-byte boundary is luck. Compute the offset into the pool so the misalignment is forced instead of asserted.
| * there is no space available thus tlsf_aalloc will return NULL */ | ||
| void *p_fail = tlsf_arealloc(&t, p_oom, 16, 2048); | ||
| assert(p_fail == NULL); | ||
| assert(p_oom != NULL); |
There was a problem hiding this comment.
p_oom is a local that the failing call cannot change, so this asserts nothing about the block surviving. An implementation that freed or scribbled on the old block before returning NULL passes here and surfaces later as a double free. Fill p_oom with a canary before the call, verify it after, then free it once and call tlsf_check. The allocation above is also allowed to fail (the if (p_filler) guard says so), and if it does the pool still has room and p_fail comes back non-NULL, so assert p_filler as well.
There was a problem hiding this comment.
2 issues found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/test.c">
<violation number="1" location="tests/test.c:2069">
P2: When either setup allocation fails, `memset(p_adjavail, ...)` dereferences NULL and aborts the test before validating `tlsf_arealloc`. Assert both setup pointers are non-NULL before writing to them, or skip the branch when setup cannot be established.</violation>
<violation number="2" location="tests/test.c:2083">
P2: When either setup allocation fails, `memset(p_else, ...)` dereferences NULL and the test aborts before exercising the relocation branch. Assert both setup pointers are non-NULL before writing to them.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| /* adjust > avail test branch (else sub-branch) */ | ||
| void *p_else = tlsf_aalloc(&t, 32, 32); | ||
| void *p_else_barrier = tlsf_aalloc(&t, 32, 32); | ||
| memset(p_else, 0xAA, 32); |
There was a problem hiding this comment.
P2: When either setup allocation fails, memset(p_else, ...) dereferences NULL and the test aborts before exercising the relocation branch. Assert both setup pointers are non-NULL before writing to them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test.c, line 2083:
<comment>When either setup allocation fails, `memset(p_else, ...)` dereferences NULL and the test aborts before exercising the relocation branch. Assert both setup pointers are non-NULL before writing to them.</comment>
<file context>
@@ -2063,6 +2063,34 @@ static void arealloc_test(void)
+ /* adjust > avail test branch (else sub-branch) */
+ void *p_else = tlsf_aalloc(&t, 32, 32);
+ void *p_else_barrier = tlsf_aalloc(&t, 32, 32);
+ memset(p_else, 0xAA, 32);
+ void *p_else_test = tlsf_arealloc(&t, p_else, 32, 128);
+ assert(p_else_test != NULL);
</file context>
| memset(p_else, 0xAA, 32); | |
| assert(p_else != NULL); | |
| assert(p_else_barrier != NULL); | |
| memset(p_else, 0xAA, 32); |
| /* adjust > avail test branch (if sub-branch) */ | ||
| void *p_adjavail = tlsf_aalloc(&t, 32, 32); | ||
| void *p_adjavail_barrier = tlsf_aalloc(&t, 32, 32); | ||
| memset(p_adjavail, 0xBB, 32); |
There was a problem hiding this comment.
P2: When either setup allocation fails, memset(p_adjavail, ...) dereferences NULL and aborts the test before validating tlsf_arealloc. Assert both setup pointers are non-NULL before writing to them, or skip the branch when setup cannot be established.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test.c, line 2069:
<comment>When either setup allocation fails, `memset(p_adjavail, ...)` dereferences NULL and aborts the test before validating `tlsf_arealloc`. Assert both setup pointers are non-NULL before writing to them, or skip the branch when setup cannot be established.</comment>
<file context>
@@ -2063,6 +2063,34 @@ static void arealloc_test(void)
+ /* adjust > avail test branch (if sub-branch) */
+ void *p_adjavail = tlsf_aalloc(&t, 32, 32);
+ void *p_adjavail_barrier = tlsf_aalloc(&t, 32, 32);
+ memset(p_adjavail, 0xBB, 32);
+ void *p_adjavail_test = tlsf_arealloc(&t, p_adjavail, 32, 64);
+ assert(p_adjavail_test != NULL);
</file context>
| memset(p_adjavail, 0xBB, 32); | |
| assert(p_adjavail != NULL); | |
| assert(p_adjavail_barrier != NULL); | |
| memset(p_adjavail, 0xBB, 32); |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/test.c">
<violation number="1" location="tests/test.c:2068">
P3: The whole if-sub-branch test now depends on this allocation succeeding: if it returns NULL, `tlsf_free(NULL)` is a no-op, no hole is created, `tlsf_arealloc` falls through to the relocate path, and `assert(p_adjavail_test == p_adjavail)` at line 2074 aborts the suite with no indication that the failure was setup, not allocator behavior. Assert the setup allocation like `p_align`/`barrier` do above, so the dependency is documented and the abort message is meaningful.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| /* adjust > avail test branch (if sub-branch) */ | ||
| void *p_adjavail = tlsf_aalloc(&t, 32, 32); | ||
| void *p_if_free_space = tlsf_aalloc(&t, 32, 64); |
There was a problem hiding this comment.
P3: The whole if-sub-branch test now depends on this allocation succeeding: if it returns NULL, tlsf_free(NULL) is a no-op, no hole is created, tlsf_arealloc falls through to the relocate path, and assert(p_adjavail_test == p_adjavail) at line 2074 aborts the suite with no indication that the failure was setup, not allocator behavior. Assert the setup allocation like p_align/barrier do above, so the dependency is documented and the abort message is meaningful.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test.c, line 2068:
<comment>The whole if-sub-branch test now depends on this allocation succeeding: if it returns NULL, `tlsf_free(NULL)` is a no-op, no hole is created, `tlsf_arealloc` falls through to the relocate path, and `assert(p_adjavail_test == p_adjavail)` at line 2074 aborts the suite with no indication that the failure was setup, not allocator behavior. Assert the setup allocation like `p_align`/`barrier` do above, so the dependency is documented and the abort message is meaningful.</comment>
<file context>
@@ -2065,7 +2065,9 @@ static void arealloc_test(void)
/* adjust > avail test branch (if sub-branch) */
void *p_adjavail = tlsf_aalloc(&t, 32, 32);
+ void *p_if_free_space = tlsf_aalloc(&t, 32, 64);
void *p_adjavail_barrier = tlsf_aalloc(&t, 32, 32);
+ tlsf_free(&t, p_if_free_space);
</file context>
| void *p_if_free_space = tlsf_aalloc(&t, 32, 64); | |
| void *p_if_free_space = tlsf_aalloc(&t, 32, 64); | |
| assert(p_if_free_space != NULL && "Test setup failed: cannot create free space"); |
All mallocs for 64 bit targets in modern compilers have 16 byte alignment as default. It is done this way because all 64-bit processors have SIMD extensions and compiles use them. In other words, using 8-byte alignment would reduce performance.
But TLSF has 8-byte default alignment in 64-bit mode. There is no possibility to control default alignment. But aalloc has no partner functions for calloc and realloc.
I.e. I think is too late to change behavior of tlsf_malloc.
This PR adds such functions.
These functions are:
Also this PR adds auxiliary macros for native architecture alignment alloctions:
I think that way with using of macros is more flexible cause different modules can be compiled for different architecture targets.
I.e. now user can get correct alignment with using of these macros. And there is no need to change behavior of tlsf_malloc.
Summary by cubic
Adds aligned
callocandreallocAPIs to TLSF, plus alignedreallocfor the thread-safe wrapper. Previously, aligned allocation had nocallocorreallocpartners; callers can now allocate zeroed aligned arrays and resize allocations without changing the allocator’s default alignment.Behavior notes
TLSF_ARCH_ALIGNMENTintlsf.hand adds native-alignment convenience macros.tlsf_arealloctreats NULL as allocation and zero size as free; invalid alignment returns NULL without modifying the allocation.tlsf_thread_areallocvalidates alignment, tries the owning arena first, then falls back to another arena.realloc, while in-place growth merges only forward.tlsf_acallocrejects multiplication overflow and returns a minimum-sized allocation for a zero total size.Written for commit c7ec9fe. Summary will update on new commits.