Skip to content

Fix O(n) backward scan and data corruption#15

Merged
jserv merged 1 commit intomasterfrom
backward-scan
Feb 8, 2026
Merged

Fix O(n) backward scan and data corruption#15
jserv merged 1 commit intomasterfrom
backward-scan

Conversation

@jserv
Copy link
Copy Markdown
Collaborator

@jserv jserv commented Feb 8, 2026

The backward scan in arena_append_pool had two bugs:

  1. Data corruption: writing new_free_block->prev overwrites the last sizeof(void *) bytes of the previous allocated block's payload, since prev field of block N physically overlaps block N-1's payload tail by TLSF's block layout.
  2. WCET violation: the scan iterated O(pool_size / ALIGN_SIZE) times (up to 65,536 on a 512 KB pool), violating the O(1) guarantee.

The scan was unnecessary: when !last_block, the previous block is guaranteed allocated (block_is_prev_free(old_sentinel) was false), so BLOCK_BIT_PREV_FREE must be clear. The prev pointer is never read when prev_free is clear (block_prev() asserts block_is_prev_free() first).


Summary by cubic

Fixes data corruption and worst‑case O(n) behavior in arena_append_pool by removing the backward scan and not writing new_free_block->prev when the previous block is allocated. Restores O(1) operation and keeps adjacent allocated block payloads intact.

  • Bug Fixes
    • Removed backward scan and relied on BLOCK_BIT_PREV_FREE being clear when !last_block, restoring O(1) WCET.
    • Stopped writing new_free_block->prev because it overlaps the previous block’s payload; the field is only read when prev_free is set.

Written for commit ee93c80. Summary will update on new commits.

The backward scan in arena_append_pool had two bugs:
1. Data corruption: writing new_free_block->prev overwrites the last
   sizeof(void *) bytes of the previous allocated block's payload, since
   prev field of block N physically overlaps block N-1's payload tail by
   TLSF's block layout.
2. WCET violation: the scan iterated O(pool_size / ALIGN_SIZE) times
   (up to 65,536 on a 512 KB pool), violating the O(1) guarantee.

The scan was unnecessary: when !last_block, the previous block is
guaranteed allocated (block_is_prev_free(old_sentinel) was false), so
BLOCK_BIT_PREV_FREE must be clear. The prev pointer is never read when
prev_free is clear (block_prev() asserts block_is_prev_free() first).
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 8, 2026

WCET Results (x86-64)

TLSF WCET Analysis
==================
Timer:      cycles
Cache:      hot
Pool:       4194304 bytes (4.0 MB)
Iterations: 5000 (warmup: 500)
Sizes:      16 64 256 1024 4096 bytes

--- malloc_worst (small alloc from single huge block) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         73         98         98         98        123        171       90.0       11.7
      64         73         98         98         98        171      52945      100.4      747.6
     256         73         98         98         98         98      72373      104.1     1022.3
    1024         73         98         98        123        196        441       93.4       12.6
    4096         73         98         98        123        196        490       98.8       10.0

--- malloc_best (exact bin hit, no split) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         49         74         74         98         98        147       75.2        8.1
      64         49         73         74         74         74        147       71.0        7.8
     256         49         73         74         98         98        147       73.3        6.6
    1024         49         74         98         98         98         98       76.2        7.7
    4096         49         73         74         74         74         74       70.8        7.6

--- free_worst (sandwiched between two free blocks) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         73         74         98         98         98         98       77.0        8.6
      64         73         74         98         98        147        172       77.1        9.0
     256         73         74         98         98         98         98       76.9        8.4
    1024         49         74         98         98         98         98       76.8        8.8
    4096         49         74         98         98        147        172       77.4        9.6

--- free_best (no merge (used neighbors)) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         49         73         74         74         74         98       64.1       11.9
      64         49         73         74         74         74         74       64.1       11.9
     256         49         73         74         74         74      20163       67.9      284.5
    1024         49         73         74         74         74        123       64.1       12.0
    4096         49         49         74         74         74         74       58.8       12.0

--- worst/best ratio (p99) ---
    size     malloc       free
      16      1.00x      1.32x
      64      1.00x      1.32x
     256      1.00x      1.32x
    1024      1.32x      1.32x
    4096      1.00x      1.32x

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Feb 8, 2026

WCET Results (arm64)

TLSF WCET Analysis
==================
Timer:      ticks
Cache:      hot
Pool:       4194304 bytes (4.0 MB)
Iterations: 5000 (warmup: 500)
Sizes:      16 64 256 1024 4096 bytes

--- malloc_worst (small alloc from single huge block) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         24         32         32         40         40      19216       35.2      271.3
      64         24         32         32         40         40         48       31.0        2.9
     256         24         32         32         40         40         40       31.5        2.4
    1024         24         32         32         32         40         48       30.6        3.1
    4096         24         32         32         32         40       4416       31.2       62.1

--- malloc_best (exact bin hit, no split) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         16         24         32         32         32         32       25.3        2.9
      64         16         24         32         32         32         40       25.3        3.0
     256         16         24         32         32         32         40       25.3        3.0
    1024         16         24         32         32         32         32       25.4        3.0
    4096         16         24         32         32         32         32       25.3        2.9

--- free_worst (sandwiched between two free blocks) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         16         24         32         32         32       2776       27.0       39.1
      64         16         24         32         32         32         32       25.8        3.3
     256         16         24         32         32         32         32       26.1        3.5
    1024         16         24         32         32         32         32       25.3        3.1
    4096         16         24         32         32         32         40       25.6        3.3

--- free_best (no merge (used neighbors)) ---
    size        min        p50        p90        p99      p99.9        max       mean     stddev
      16         16         24         24         24         24         24       20.3        4.0
      64          8         24         24         24         24         32       20.4        4.0
     256          8         24         24         24         24         32       20.4        4.0
    1024          8         24         24         24         24         24       20.4        4.0
    4096          8         24         24         24         24         24       20.4        4.0

--- worst/best ratio (p99) ---
    size     malloc       free
      16      1.25x      1.33x
      64      1.00x      1.33x
     256      1.25x      1.33x
    1024      1.25x      1.33x
    4096      1.00x      1.33x

@jserv jserv merged commit 982cab7 into master Feb 8, 2026
10 checks passed
@jserv jserv deleted the backward-scan branch February 8, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant