Skip to content

FEXCore: Adds a lock-free atomic bitset that supports contiguous range allocations - #5793

Merged
lioncash merged 3 commits into
FEX-Emu:mainfrom
Sonicadvance1:193
Aug 10, 2026
Merged

FEXCore: Adds a lock-free atomic bitset that supports contiguous range allocations#5793
lioncash merged 3 commits into
FEX-Emu:mainfrom
Sonicadvance1:193

Conversation

@Sonicadvance1

Copy link
Copy Markdown
Member

This thing is a bit intense, so some requirements from the start:

  • It needs to be lock-free and thread-safe
  • It needs to support contiguous range allocations
  • It needs to support allocations larger than a single atomic word

These requirements kind of fly in the face of most bitset allocators
where they will support some parts of these requirements, or just throw
a mutex in front of the whole thing.

Some implementation details:

  • If allocating only 1-bit, trivial and always succeeds if there is space
  • If allocating <= 64-bit, then always succeeds if there is at least
    those many contiguous bits within a single atomic word
    • Allocation can fail if there are cross-word contiguous bits of the
      size available
    • Introduces some sparsity
  • If allocating > 64-bits then it falls down the longer scan path.
    • Searches for contiguous bits of free space between multiple atomic
      words.
    • If found, will attempt to allocate tracking which bits were allocated
    • If allocation fails, unwind bits already acquired and continue
      scanning

Some downsides to this implementation:

  • Allocations can fail if sparsity builds up
  • Heavily contended allocations can be worse than a lock
    • If larger than atomic word allocations are in flight.
  • Unwinding larger than word allocations and continuing scanning adds
    overhead, a lock would have won at that point.
  • A small bit of false sharing where an atomic word is read without
    acquire semantics for scanning can technically overlook some
    allocations that no longer exist.
  • Slower than a linear allocator, but that's not unexpected.

Most of these downsides are okay for our use case, which is code buffer
allocations with the ability to do partial invalidation. If the atomic
bitset fails to fit an allocation, we can throw away the code buffer
like we currently do.

The bitmap allocator that uses this lock-free atomic bitset is still
in-flight but this is one complex container that can land independently.

@Sonicadvance1
Sonicadvance1 force-pushed the 193 branch 5 times, most recently from a42f8fc to f2b4754 Compare August 3, 2026 23:55
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 7, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 7, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 7, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 7, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 8, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 8, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.

@lioncash lioncash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As far as I can tell, this looks good, aside from a few nits. Lot to wrap around on a first read

return allocate_inside_word(count, last_allocation_track.get_last_allocation(), words_to_track);
}

// TODO: Always scans from beginning to end.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this intended to be done in this PR, or a follow-up?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Planning on a follow up there. Needs some investigation in to if scanning from the start is better at removing sparsity for large allocations, or if we can just eat it.

Comment thread FEXCore/Source/Utils/atomic_bitset.h Outdated
Useful for removing integer division instructions when we know the
source value is aligned to be power of two. As integer division is quite
slow, we want to use this when possible.
…e allocations

This thing is a bit intense, so some requirements from the start:
- It needs to be lock-free and thread-safe
- It needs to support contiguous range allocations
- It needs to support allocations larger than a single atomic word

These requirements kind of fly in the face of most bitset allocators
where they will support some parts of these requirements, or just throw
a mutex in front of the whole thing.

Some implementation details:
- If allocating only 1-bit, trivial and always succeeds if there is space
- If allocating <= 64-bit, then always succeeds if there is at least
  those many contiguous bits within a single atomic word
  - Allocation can fail if there are cross-word contiguous bits of the
    size available
  - Introduces some sparsity
- If allocating > 64-bits then it falls down the longer scan path.
  - Searches for contiguous bits of free space between multiple atomic
    words.
  - If found, will attempt to allocate tracking which bits were allocated
  - If allocation fails, unwind bits already acquired and continue
    scanning

Some downsides to this implementation:
- Allocations can fail if sparsity builds up
- Heavily contended allocations can be worse than a lock
  - If larger than atomic word allocations are in flight.
- Unwinding larger than word allocations and continuing scanning adds
  overhead, a lock would have won at that point.
- A small bit of false sharing where an atomic word is read without
  acquire semantics for scanning can technically overlook some
  allocations that no longer exist.
- Slower than a linear allocator, but that's not unexpected.

Most of these downsides are okay for our use case, which is code buffer
allocations with the ability to do partial invalidation. If the atomic
bitset fails to fit an allocation, we can throw away the code buffer
like we currently do.

The bitmap allocator that uses this lock-free atomic bitset is still
in-flight but this is one complex container that can land independently.
Hammers the API in a couple of ways to make sure it works.
@lioncash
lioncash merged commit adea3e4 into FEX-Emu:main Aug 10, 2026
13 checks passed
@Sonicadvance1
Sonicadvance1 deleted the 193 branch August 10, 2026 21:24
Sonicadvance1 added a commit to Sonicadvance1/FEX that referenced this pull request Aug 10, 2026
On top of FEX-Emu#5793

This is tailored towards our needs for our JIT and eventually replacing
the linear allocator. Allowing us to reallocate memory for code blocks
that have been invalidated, letting us keep a single code buffer around
for longer and using less memory overall.

In particular, high-invalidation games that ship anti-tamper tend to
emit millions of ~128-byte blocks in just a handful of minutes which
causes our current linear allocator to consume gigabytes very quickly.
This will allow us to more aggressively reuse the allocation space and
reduce the memory load in those situations.

There's some additional resize tuning that needs some work whence it is
in situ which doesn't need to be done now.
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.

2 participants