Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make smallbuddy handle larger requests correctly #556

Merged
merged 4 commits into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/snmalloc/aal/aal_cheri.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ namespace snmalloc
}

void* pb = __builtin_cheri_bounds_set_exact(a.unsafe_ptr(), size);

SNMALLOC_ASSERT(
__builtin_cheri_tag_get(pb) && "capptr_bound exactness failed.");

return CapPtr<T, BOut>::unsafe_from(static_cast<T*>(pb));
}

Expand Down
8 changes: 6 additions & 2 deletions src/snmalloc/backend/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace snmalloc
* does not avail itself of this degree of freedom.
*/
template<typename T>
static capptr::Arena<void>
static capptr::Alloc<void>
alloc_meta_data(LocalState* local_state, size_t size)
{
capptr::Arena<void> p;
Expand All @@ -61,9 +61,13 @@ namespace snmalloc
}

if (p == nullptr)
{
errno = ENOMEM;
return nullptr;
}

return p;
return capptr_to_user_address_control(
Aal::capptr_bound<void, capptr::bounds::AllocFull>(p, size));
}

/**
Expand Down
30 changes: 18 additions & 12 deletions src/snmalloc/backend_helpers/smallbuddyrange.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,22 @@ namespace snmalloc
base,
length,
[this](CapPtr<void, ChunkBounds> base, size_t align, bool) {
CapPtr<void, ChunkBounds> overflow =
buddy_small
.add_block(
base.template as_reinterpret<FreeChunk<ChunkBounds>>(), align)
.template as_reinterpret<void>();
if (overflow != nullptr)
parent.dealloc_range(overflow, bits::one_at_bit(MIN_CHUNK_BITS));
if (align < MIN_CHUNK_SIZE)
{
CapPtr<void, ChunkBounds> overflow =
buddy_small
.add_block(
base.template as_reinterpret<FreeChunk<ChunkBounds>>(),
align)
.template as_reinterpret<void>();
if (overflow != nullptr)
parent.dealloc_range(
overflow, bits::one_at_bit(MIN_CHUNK_BITS));
}
else
{
parent.dealloc_range(base, align);
}
});
}

Expand All @@ -204,7 +213,8 @@ namespace snmalloc

CapPtr<void, ChunkBounds> alloc_range(size_t size)
{
SNMALLOC_ASSERT(size < MIN_CHUNK_SIZE);
if (size >= MIN_CHUNK_SIZE)
return parent.alloc_range(size);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I recall us going back and forth on this previously, with the idea that the caller should skip the SmallBuddy ranges if they needed something larger. This fallback requires that the return type of the SmallBuddy match that of its parent, which might not, in general, be true: while a LargeBuddy "must" operate on CapPtrs intended to be large enough to include whole PageMap MetaEntrys worth of space, as it uses those for its metadata, the SmallBuddy, which uses in-band metadata, could have more tightly bounded types.

I suppose we're not actually taking advantage of that right now, tho'... but is there reason not to ask for the Range's parent in the caller instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

So ultimately we need a branch somewhere in alloc_meta_data. This would need to determine how large the request is and then which range to send it too. The code in SmallBuddyRange::alloc_with_leftover, returns the slop at the end of the allocation. This could be useful for large meta data allocations as well as small, so might want to be duplicated, though not essential.

Copy link
Member Author

Choose a reason for hiding this comment

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

Following Teams call, I have added a test to ensure future changes don't break the Verona use case.

We will leave this design as is, but will revisit after @nwf-msr investigates a fine granularity of permissions.


auto result = buddy_small.remove_block(size);
if (result != nullptr)
Expand All @@ -218,8 +228,6 @@ namespace snmalloc

CapPtr<void, ChunkBounds> alloc_range_with_leftover(size_t size)
{
SNMALLOC_ASSERT(size < MIN_CHUNK_SIZE);

auto rsize = bits::next_pow2(size);

auto result = alloc_range(rsize);
Expand All @@ -236,8 +244,6 @@ namespace snmalloc

void dealloc_range(CapPtr<void, ChunkBounds> base, size_t size)
{
SNMALLOC_ASSERT(size < MIN_CHUNK_SIZE);

add_range(base, size);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/snmalloc/mem/backend_concept.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ namespace snmalloc
{
Backend::template alloc_meta_data<void*>(local_state, size)
}
->ConceptSame<capptr::Arena<void>>;
->ConceptSame<capptr::Alloc<void>>;
}
&&requires(
LocalState& local_state,
Expand Down
7 changes: 2 additions & 5 deletions src/snmalloc/mem/pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,8 @@ namespace snmalloc
Config::Pal::error("Failed to initialise thread local allocator.");
}

p = capptr_to_user_address_control(
Aal::capptr_bound<T, capptr::bounds::AllocFull>(
capptr::Arena<T>::unsafe_from(new (raw.unsafe_ptr())
T(std::forward<Args>(args)...)),
sizeof(T)));
p = capptr::Alloc<T>::unsafe_from(new (raw.unsafe_ptr())
T(std::forward<Args>(args)...));

FlagLock f(pool.lock);
p->list_next = pool.list;
Expand Down
39 changes: 39 additions & 0 deletions src/test/func/pool/pool.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <array>
#include <iostream>
#include <snmalloc/snmalloc.h>
#include <test/opt.h>
#include <test/setup.h>
Expand All @@ -24,6 +26,23 @@ struct PoolBEntry : Pooled<PoolBEntry>

using PoolB = Pool<PoolBEntry, Alloc::Config>;

struct PoolLargeEntry : Pooled<PoolLargeEntry>
{
std::array<int, 2'000'000> payload;

PoolLargeEntry()
{
printf(".");
fflush(stdout);
payload[0] = 1;
printf("first %d\n", payload[0]);
payload[1'999'999] = 1;
printf("last %d\n", payload[1'999'999]);
};
};

using PoolLarge = Pool<PoolLargeEntry, Alloc::Config>;

void test_alloc()
{
auto ptr = PoolA::acquire();
Expand Down Expand Up @@ -116,6 +135,18 @@ void test_iterator()
PoolA::release(after_iteration_ptr);
}

void test_large()
{
printf(".");
fflush(stdout);
PoolLargeEntry* p = PoolLarge::acquire();
printf(".");
fflush(stdout);
PoolLarge::release(p);
printf(".");
fflush(stdout);
}

int main(int argc, char** argv)
{
setup();
Expand All @@ -128,10 +159,18 @@ int main(int argc, char** argv)
#endif

test_alloc();
std::cout << "test_alloc passed" << std::endl;
test_constructor();
std::cout << "test_constructor passed" << std::endl;
test_alloc_many();
std::cout << "test_alloc_many passed" << std::endl;
test_double_alloc();
std::cout << "test_double_alloc passed" << std::endl;
test_different_alloc();
std::cout << "test_different_alloc passed" << std::endl;
test_iterator();
std::cout << "test_iterator passed" << std::endl;
test_large();
std::cout << "test_large passed" << std::endl;
return 0;
}