AK: Adopt mimalloc v2 as main allocator

Use mimalloc for Ladybird-owned allocations without overriding malloc().
Route kmalloc(), kcalloc(), krealloc(), and kfree() through mimalloc,
and put the embedded Rust crates on the same allocator via a shared
shim in AK/kmalloc.cpp.

This also lets us drop kfree_sized(), since it no longer used its size
argument. StringData, Utf16StringData, JS object storage, Rust error
strings, and the CoreAudio playback helpers can all free their AK-backed
storage with plain kfree().

Sanitizer builds still use the system allocator. LeakSanitizer does not
reliably trace references stored in mimalloc-managed AK containers, so
static caches and other long-lived roots can look leaked. Pass the old
size into the Rust realloc shim so aligned fallback reallocations can
move posix_memalign-backed blocks safely.

Static builds still need a little linker help. macOS app binaries need
the Rust allocator entry points forced in from liblagom-ak.a, while
static ELF links can pull in identical allocator shim definitions from
multiple Rust staticlibs. Keep the Apple -u flags and allow those
duplicate shim symbols for LibJS and LibRegex links on Linux and BSD.
This commit is contained in:
Andreas Kling
2026-04-03 17:06:58 +02:00
committed by Andreas Kling
parent 648ececa62
commit b23aa38546
Notes: github-actions[bot] 2026-04-08 07:58:49 +00:00
27 changed files with 349 additions and 53 deletions

View File

@@ -50,7 +50,7 @@ public:
Bitmap& operator=(Bitmap&& other)
{
if (this != &other) {
kfree_sized(m_data, size_in_bytes());
kfree(m_data);
m_data = exchange(other.m_data, nullptr);
m_size = exchange(other.m_size, 0);
}
@@ -60,7 +60,7 @@ public:
~Bitmap()
{
if (m_is_owning) {
kfree_sized(m_data, size_in_bytes());
kfree(m_data);
}
m_data = nullptr;
}
@@ -96,7 +96,7 @@ public:
__builtin_memcpy(m_data, previous_data, previous_size_bytes);
if ((previous_size % 8) != 0)
set_range(previous_size, 8 - previous_size % 8, default_value);
kfree_sized(previous_data, previous_size_bytes);
kfree(previous_data);
}
}

View File

@@ -87,7 +87,7 @@ public:
munmap((void*)chunk, m_chunk_size);
#endif
} else {
kfree_sized((void*)chunk, m_chunk_size);
kfree((void*)chunk);
}
});
}

View File

@@ -43,7 +43,7 @@ public:
{
if (this != &other) {
if (!m_inline)
kfree_sized(m_outline_buffer, m_outline_capacity);
kfree(m_outline_buffer);
move_from(move(other));
}
return *this;
@@ -176,7 +176,7 @@ public:
void clear()
{
if (!m_inline) {
kfree_sized(m_outline_buffer, m_outline_capacity);
kfree(m_outline_buffer);
m_inline = true;
}
m_size = 0;
@@ -353,10 +353,9 @@ private:
{
// m_inline_buffer and m_outline_buffer are part of a union, so save the pointer
auto* outline_buffer = m_outline_buffer;
auto outline_capacity = m_outline_capacity;
if (!may_discard_existing_data)
__builtin_memcpy(m_inline_buffer, outline_buffer, size);
kfree_sized(outline_buffer, outline_capacity);
kfree(outline_buffer);
m_inline = true;
}
@@ -377,7 +376,7 @@ private:
__builtin_memcpy(new_buffer, data(), m_size);
} else if (m_outline_buffer) {
__builtin_memcpy(new_buffer, m_outline_buffer, min(new_capacity, m_outline_capacity));
kfree_sized(m_outline_buffer, m_outline_capacity);
kfree(m_outline_buffer);
}
m_outline_buffer = new_buffer;

View File

@@ -30,7 +30,7 @@ public:
void operator delete(void* ptr)
{
kfree_sized(ptr, allocation_size_for_stringimpl(static_cast<ByteStringImpl*>(ptr)->m_length));
kfree(ptr);
}
static ByteStringImpl& the_empty_stringimpl();

View File

@@ -77,12 +77,22 @@ target_link_libraries(AK PRIVATE FastFloat::fast_float)
find_package(fmt CONFIG REQUIRED)
target_link_libraries(AK PRIVATE fmt::fmt)
set(MIMALLOC_VISIBILITY PRIVATE)
if (NOT BUILD_SHARED_LIBS)
set(MIMALLOC_VISIBILITY PUBLIC)
endif()
target_link_libraries(AK ${MIMALLOC_VISIBILITY} mimalloc)
# FIXME: Make this generic for all imported shared library dependencies and apply globally
if (BUILD_SHARED_LIBS AND NOT CMAKE_SKIP_INSTALL_RULES AND NOT "${VCPKG_INSTALLED_DIR}" STREQUAL "")
install(IMPORTED_RUNTIME_ARTIFACTS simdutf::simdutf
LIBRARY COMPONENT Lagom_Runtime NAMELINK_COMPONENT Lagom_Development
FRAMEWORK COMPONENT Lagom_Runtime
)
install(IMPORTED_RUNTIME_ARTIFACTS mimalloc
LIBRARY COMPONENT Lagom_Runtime NAMELINK_COMPONENT Lagom_Development
FRAMEWORK COMPONENT Lagom_Runtime
)
endif()
if (WIN32)
@@ -108,6 +118,14 @@ elseif (APPLE)
set(ASSERTION_HANDLER_VISIBILITY INTERFACE)
endif()
target_link_options(AK ${ASSERTION_HANDLER_VISIBILITY} LINKER:-U,_ak_assertion_handler)
if (NOT BUILD_SHARED_LIBS)
target_link_options(AK INTERFACE
LINKER:-u,_ladybird_rust_alloc
LINKER:-u,_ladybird_rust_alloc_zeroed
LINKER:-u,_ladybird_rust_dealloc
LINKER:-u,_ladybird_rust_realloc
)
endif()
endif()
# Manually install AK headers

View File

@@ -99,7 +99,7 @@ public:
return;
for (size_t i = 0; i < m_size; ++i)
m_elements[i].~T();
kfree_sized(m_elements, storage_allocation_size(m_size));
kfree(m_elements);
m_elements = nullptr;
}

View File

@@ -190,7 +190,7 @@ public:
}
}
kfree_sized(m_buckets, size_in_bytes(capacity()));
kfree(m_buckets);
}
HashTable(HashTable const& other)
@@ -622,7 +622,6 @@ private:
VERIFY(new_capacity >= size());
auto* old_buckets = m_buckets;
auto old_buckets_size = size_in_bytes(capacity());
Iterator old_iter = begin();
auto* new_buckets = kcalloc(1, size_in_bytes(new_capacity));
@@ -644,7 +643,7 @@ private:
it->~T();
}
kfree_sized(old_buckets, old_buckets_size);
kfree(old_buckets);
return {};
}
void rehash(size_t new_capacity)

View File

@@ -10,6 +10,7 @@
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
#include <AK/StringBuilder.h>
#include <AK/kmalloc.h>
namespace AK::Detail {
@@ -26,7 +27,7 @@ public:
VERIFY(byte_count);
auto capacity = allocation_size_for_string_data(byte_count);
void* slot = malloc(capacity);
void* slot = kmalloc(capacity);
if (!slot)
return Error::from_errno(ENOMEM);
@@ -52,7 +53,7 @@ public:
VERIFY(byte_count > MAX_SHORT_STRING_BYTE_COUNT);
auto capacity = sizeof(StringData) + sizeof(StringData::SubstringData);
void* slot = malloc(capacity);
void* slot = kmalloc(capacity);
if (!slot)
return Error::from_errno(ENOMEM);
@@ -66,7 +67,7 @@ public:
void operator delete(void* ptr)
{
free(ptr);
kfree(ptr);
}
~StringData()

View File

@@ -20,11 +20,9 @@ namespace AK::Detail {
NonnullRefPtr<Utf16StringData> Utf16StringData::create_uninitialized(StorageType storage_type, size_t code_unit_length)
{
auto allocation_size = storage_type == Utf16StringData::StorageType::ASCII
? sizeof(Utf16StringData) + (sizeof(char) * code_unit_length)
: sizeof(Utf16StringData) + (sizeof(char16_t) * code_unit_length);
auto allocation_size = allocation_size_for_string_data(storage_type == Utf16StringData::StorageType::ASCII, code_unit_length);
void* slot = malloc(allocation_size);
void* slot = kmalloc(allocation_size);
VERIFY(slot);
return adopt_ref(*new (slot) Utf16StringData(storage_type, code_unit_length));

View File

@@ -13,6 +13,7 @@
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Utf16View.h>
#include <AK/kmalloc.h>
namespace AK::Detail {
@@ -52,7 +53,7 @@ public:
void operator delete(void* ptr)
{
free(ptr);
kfree(ptr);
}
[[nodiscard]] ALWAYS_INLINE bool operator==(Utf16StringData const& other) const
@@ -130,6 +131,13 @@ private:
template<typename ViewType>
static NonnullRefPtr<Utf16StringData> create_from_code_point_iterable(ViewType const&);
[[nodiscard]] static constexpr size_t allocation_size_for_string_data(bool has_ascii_storage, size_t code_unit_length)
{
return has_ascii_storage
? sizeof(Utf16StringData) + (sizeof(char) * code_unit_length)
: sizeof(Utf16StringData) + (sizeof(char16_t) * code_unit_length);
}
[[nodiscard]] size_t calculate_code_point_length() const;
// We store whether this string has ASCII or UTF-16 storage by setting the most significant bit of m_length_in_code_units

View File

@@ -481,7 +481,7 @@ public:
{
clear_with_capacity();
if (m_metadata.outline_buffer) {
kfree_sized(m_metadata.outline_buffer, m_capacity * sizeof(StorageType));
kfree(m_metadata.outline_buffer);
m_metadata.outline_buffer = nullptr;
}
reset_capacity();
@@ -862,7 +862,7 @@ public:
}
}
if (m_metadata.outline_buffer)
kfree_sized(m_metadata.outline_buffer, m_capacity * sizeof(StorageType));
kfree(m_metadata.outline_buffer);
m_metadata.outline_buffer = new_buffer;
m_capacity = new_capacity;
update_metadata(); // We have *some* space, we just allocated it.

View File

@@ -68,4 +68,162 @@ nothrow_t const nothrow;
}
#else
# include <cstddef>
# include <cstring>
# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
// LeakSanitizer does not reliably trace references stored in mimalloc-managed
// AK containers, so sanitizer builds fall back to the system allocator.
# define AK_USE_SYSTEM_ALLOCATOR_INSTRUMENTED 1
# else
# include <mimalloc.h>
# endif
static bool allocation_needs_explicit_alignment(size_t alignment)
{
return alignment > alignof(std::max_align_t);
}
# ifdef AK_USE_SYSTEM_ALLOCATOR_INSTRUMENTED
static void* aligned_alloc_with_system_allocator(size_t size, size_t alignment, bool zeroed)
{
void* ptr = nullptr;
auto actual_size = size == 0 ? static_cast<size_t>(1) : size;
if (auto result = posix_memalign(&ptr, alignment, actual_size); result != 0)
return nullptr;
if (zeroed)
__builtin_memset(ptr, 0, actual_size);
return ptr;
}
void* ak_kcalloc(size_t count, size_t size)
{
return calloc(count, size);
}
void* ak_kmalloc(size_t size)
{
return malloc(size);
}
void* ak_krealloc(void* ptr, size_t size)
{
return realloc(ptr, size);
}
size_t ak_kmalloc_good_size(size_t size)
{
return size;
}
void ak_kfree(void* ptr)
{
free(ptr);
}
extern "C" {
void* ladybird_rust_alloc(size_t size, size_t alignment);
void* ladybird_rust_alloc_zeroed(size_t size, size_t alignment);
void ladybird_rust_dealloc(void* ptr, size_t alignment);
void* ladybird_rust_realloc(void* ptr, size_t old_size, size_t new_size, size_t alignment);
}
extern "C" void* ladybird_rust_alloc(size_t size, size_t alignment)
{
if (allocation_needs_explicit_alignment(alignment))
return aligned_alloc_with_system_allocator(size, alignment, false);
return malloc(size);
}
extern "C" void* ladybird_rust_alloc_zeroed(size_t size, size_t alignment)
{
if (allocation_needs_explicit_alignment(alignment))
return aligned_alloc_with_system_allocator(size, alignment, true);
return calloc(1, size);
}
extern "C" void ladybird_rust_dealloc(void* ptr, size_t)
{
free(ptr);
}
extern "C" void* ladybird_rust_realloc(void* ptr, size_t old_size, size_t new_size, size_t alignment)
{
if (!allocation_needs_explicit_alignment(alignment))
return realloc(ptr, new_size);
auto* new_ptr = aligned_alloc_with_system_allocator(new_size, alignment, false);
if (!new_ptr)
return nullptr;
if (ptr)
__builtin_memcpy(new_ptr, ptr, old_size < new_size ? old_size : new_size);
free(ptr);
return new_ptr;
}
# else
void* ak_kcalloc(size_t count, size_t size)
{
return mi_calloc(count, size);
}
void* ak_kmalloc(size_t size)
{
return mi_malloc(size);
}
void* ak_krealloc(void* ptr, size_t size)
{
return mi_realloc(ptr, size);
}
size_t ak_kmalloc_good_size(size_t size)
{
return mi_good_size(size);
}
void ak_kfree(void* ptr)
{
mi_free(ptr);
}
extern "C" {
void* ladybird_rust_alloc(size_t size, size_t alignment);
void* ladybird_rust_alloc_zeroed(size_t size, size_t alignment);
void ladybird_rust_dealloc(void* ptr, size_t alignment);
void* ladybird_rust_realloc(void* ptr, size_t old_size, size_t new_size, size_t alignment);
}
extern "C" void* ladybird_rust_alloc(size_t size, size_t alignment)
{
if (allocation_needs_explicit_alignment(alignment))
return mi_malloc_aligned(size, alignment);
return mi_malloc(size);
}
extern "C" void* ladybird_rust_alloc_zeroed(size_t size, size_t alignment)
{
if (allocation_needs_explicit_alignment(alignment))
return mi_zalloc_aligned(size, alignment);
return mi_zalloc(size);
}
extern "C" void ladybird_rust_dealloc(void* ptr, size_t)
{
mi_free(ptr);
}
extern "C" void* ladybird_rust_realloc(void* ptr, size_t, size_t new_size, size_t alignment)
{
if (allocation_needs_explicit_alignment(alignment))
return mi_realloc_aligned(ptr, new_size, alignment);
return mi_realloc(ptr, new_size);
}
# endif
#endif

View File

@@ -12,25 +12,43 @@
#include <new>
#include <stdlib.h>
#define kcalloc calloc
#define kmalloc malloc
#define kmalloc_good_size malloc_good_size
#if defined(AK_OS_SERENITY)
# define kcalloc calloc
# define kfree free
# define kmalloc malloc
# define krealloc realloc
# define kmalloc_good_size malloc_good_size
#else
[[nodiscard]] void* ak_kcalloc(size_t count, size_t size);
void ak_kfree(void* ptr);
[[nodiscard]] void* ak_kmalloc(size_t size);
[[nodiscard]] void* ak_krealloc(void* ptr, size_t size);
[[nodiscard]] size_t ak_kmalloc_good_size(size_t size);
inline void kfree_sized(void* ptr, size_t)
[[nodiscard]] inline void* kcalloc(size_t count, size_t size)
{
free(ptr);
return ak_kcalloc(count, size);
}
#ifndef AK_OS_SERENITY
# include <AK/Types.h>
# ifndef AK_OS_MACOS
extern "C" {
inline size_t malloc_good_size(size_t size) { return size; }
inline void kfree(void* ptr)
{
ak_kfree(ptr);
}
[[nodiscard]] inline void* kmalloc(size_t size)
{
return ak_kmalloc(size);
}
[[nodiscard]] inline void* krealloc(void* ptr, size_t size)
{
return ak_krealloc(ptr, size);
}
[[nodiscard]] inline size_t kmalloc_good_size(size_t size)
{
return ak_kmalloc_good_size(size);
}
# else
# include <malloc/malloc.h>
# endif
#endif
using std::nothrow;