Skip to content
Open
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
25 changes: 16 additions & 9 deletions system/lib/emmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,21 @@ static_assert(alignof(max_align_t) == 8, "max_align_t must be correct");

// Dynamic memory is subdivided into regions, in the format

// <size:uint32_t> ..... <size:uint32_t> | <size:uint32_t> ..... <size:uint32_t> | <size:uint32_t> ..... <size:uint32_t> | .....
// <size:size_t> ..... <size:size_t> | <size:size_t> ..... <size:size_t> | <size:size_t> ..... <size:size_t> | .....

// That is, at the bottom and top end of each memory region, the size of that region is stored. That allows traversing the
// memory regions backwards and forwards. Because each allocation must be at least a multiple of 4 bytes, the lowest two bits of
// each size field is unused. Free regions are distinguished from used regions by having the FREE_REGION_FLAG bit present
// in the size field. I.e. for free regions, the size field is odd, and for used regions, the size field reads even.
#define FREE_REGION_FLAG 0x1u
#define FREE_REGION_FLAG ((size_t)1)

// Attempts to malloc() more than this many bytes would cause an overflow when calculating the size of a region,
// therefore allocations larger than this are short-circuited immediately on entry.
#ifdef __wasm64__
#define MAX_ALLOC_SIZE 0xFFFFFFFFFFull
#else
#define MAX_ALLOC_SIZE 0xFFFFFFC7u
#endif
Comment thread
sbc100 marked this conversation as resolved.
Resolved

// A free region has the following structure:
// <size:size_t> <prevptr> <nextptr> ... <size:size_t>
Expand All @@ -119,7 +123,7 @@ typedef struct Region {
// list of all RootRegions in the program, starting with `listOfAllRegions`
// below.
typedef struct RootRegion {
uint32_t size;
size_t size;
struct RootRegion *next;
uint8_t* endPtr;
} RootRegion;
Expand All @@ -139,9 +143,9 @@ static volatile uint8_t multithreadingLock = 0;
#endif

#define IS_POWER_OF_2(val) (((val) & ((val)-1)) == 0)
#define ALIGN_UP(ptr, alignment) ((uint8_t*)((((uintptr_t)(ptr)) + ((alignment)-1)) & ~((alignment)-1)))
#define ALIGN_DOWN(ptr, alignment) ((uint8_t*)(((uintptr_t)(ptr)) & ~((alignment)-1)))
#define HAS_ALIGNMENT(ptr, alignment) ((((uintptr_t)(ptr)) & ((alignment)-1)) == 0)
#define ALIGN_UP(ptr, alignment) ((uint8_t*)((((uintptr_t)(ptr)) + ((alignment)-1)) & ~((uintptr_t)(alignment)-1)))
#define ALIGN_DOWN(ptr, alignment) ((uint8_t*)(((uintptr_t)(ptr)) & ~((uintptr_t)(alignment)-1)))
#define HAS_ALIGNMENT(ptr, alignment) ((((uintptr_t)(ptr)) & ((uintptr_t)(alignment)-1)) == 0)

static_assert(IS_POWER_OF_2(MALLOC_ALIGNMENT), "MALLOC_ALIGNMENT must be a power of two value!");
static_assert(MALLOC_ALIGNMENT >= 4, "Smallest possible MALLOC_ALIGNMENT if 4!");
Expand Down Expand Up @@ -244,11 +248,12 @@ an allocation size to the bucket index that should be looked at. The buckets are
static_assert(NUM_FREE_BUCKETS == 64, "Following function is tailored specifically for NUM_FREE_BUCKETS == 64 case");
static int compute_free_list_bucket(size_t allocSize) {
if (allocSize < 128) return (allocSize >> 3) - 1;
if (allocSize >= (1U << 27)) return NUM_FREE_BUCKETS - 1;
int clz = __builtin_clz(allocSize);
int bucketIndex =
(clz > 19)
? 110 - (clz<<2) + ((allocSize >> (29-clz)) ^ 4)
: MIN( 71 - (clz<<1) + ((allocSize >> (30-clz)) ^ 2), NUM_FREE_BUCKETS-1);
? 110 - (clz<<2) + ((allocSize >> (29-clz)) ^ 4)
: 71 - (clz<<1) + ((allocSize >> (30-clz)) ^ 2);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks like code size neutral change, lgtm. (There might be a way to squeeze out the if() with a bit trick, but might not make much difference)


assert(bucketIndex >= 0);
assert(bucketIndex < NUM_FREE_BUCKETS);
Expand Down Expand Up @@ -1406,7 +1411,9 @@ size_t emmalloc_compute_free_dynamic_memory_fragmentation_map(size_t freeMemoryS
freeRegion = freeRegion->next) {
++numFreeMemoryRegions;
size_t freeDynamicMemory = freeRegion->size - REGION_HEADER_SIZE;
if (freeDynamicMemory > 0) {
if (freeDynamicMemory >= (1ull << 31)) {
++freeMemorySizeMap[31];
} else if (freeDynamicMemory > 0) {
++freeMemorySizeMap[31-__builtin_clz(freeDynamicMemory)];
} else {
++freeMemorySizeMap[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"a.html.gz": 352,
"a.js": 4638,
"a.js.gz": 2377,
"a.wasm": 11214,
"a.wasm": 11217,
"a.wasm.gz": 6085,
"total": 16361,
"total": 16364,
"total_gz": 8814
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"a.html.gz": 367,
"a.js": 7455,
"a.js.gz": 3399,
"a.wasm": 7201,
"a.wasm.gz": 3305,
"total": 15196,
"total_gz": 7071
"a.wasm": 7213,
"a.wasm.gz": 3302,
"total": 15208,
"total_gz": 7068
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"a.html.gz": 367,
"a.js": 5343,
"a.js.gz": 2500,
"a.wasm": 5817,
"a.wasm.gz": 2736,
"total": 11700,
"total_gz": 5603
"a.wasm": 5829,
"a.wasm.gz": 2731,
"total": 11712,
"total_gz": 5598
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"a.html.gz": 352,
"a.js": 923,
"a.js.gz": 582,
"a.wasm": 2661,
"a.wasm": 2670,
"a.wasm.gz": 1479,
"total": 4093,
"total": 4102,
"total_gz": 2413
}
4 changes: 4 additions & 0 deletions test/core/test_emmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// found in the LICENSE file.

#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
Expand All @@ -30,6 +31,9 @@ void stage(const char* name) {

void basics() {
stage("basics");
stage("allocate too large");
assert(malloc(SIZE_MAX) == NULL);
assert(malloc((size_t)-50) == NULL);
stage("allocate 0");
void* ptr = malloc(0);
assert(ptr != 0);
Expand Down
69 changes: 69 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -6958,6 +6958,75 @@ def test_failing_growth_wasm64(self):
''')
self.do_runf('test.c', 'done\n', cflags=['-sGLOBAL_BASE=2Gb', '-sTOTAL_MEMORY=4Gb', '-sMAXIMUM_MEMORY=5Gb', '-sALLOW_MEMORY_GROWTH', '-m64'])

@requires_wasm64
@parameterized({
'': ([],),
'emmalloc': (['-sMALLOC=emmalloc'],),
'emmalloc_memvalidate': (['-sMALLOC=emmalloc-memvalidate', '-DEMMALLOC_MEMVALIDATE'],),
'mimalloc': (['-sMALLOC=mimalloc'],),
})
def test_malloc_large_wasm64(self, args):
create_file('test.c', r'''
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <emscripten/emmalloc.h>
#include <emscripten/heap.h>

#define GiB ((size_t)1 << 30)
#define MiB ((size_t)1 << 20)

int main() {
// Overflowing allocation sizes must return NULL.
assert(malloc(SIZE_MAX) == NULL);
assert(malloc((size_t)-100) == NULL);

// Allocate and free single blocks of 4 GiB and 5 GiB.
const size_t sizes[] = { 4 * GiB, 5 * GiB };
for (int i = 0; i < 2; ++i) {
size_t size = sizes[i];
uint8_t* p = (uint8_t*)malloc(size);
assert(p != NULL);
p[0] = 0x5a;
p[size - 1] = 0xa5;
assert(p[0] == 0x5a && p[size - 1] == 0xa5);
free(p);
}

// Allocate 10 blocks of 512 MiB (5 GiB total), free them so they merge into
// a single >4 GiB free region, and allocate them again without growing the heap.
enum { N = 10 };
void* blocks[N];
for (int i = 0; i < N; ++i) {
blocks[i] = malloc(512 * MiB);
assert(blocks[i] != NULL);
memset(blocks[i], 1, 4096);
}
size_t heap_after_first = emscripten_get_heap_size();
for (int i = 0; i < N; ++i) {
free(blocks[i]);
}
#ifdef EMMALLOC_MEMVALIDATE
assert(emmalloc_validate_memory_regions() == 0);
emmalloc_dump_free_dynamic_memory_fragmentation_map();
#endif
for (int i = 0; i < N; ++i) {
blocks[i] = malloc(512 * MiB);
assert(blocks[i] != NULL);
}
assert(emscripten_get_heap_size() == heap_after_first);
for (int i = 0; i < N; ++i) {
free(blocks[i]);
}

printf("done\n");
return 0;
}
''')
self.do_runf('test.c', 'done\n', cflags=['-m64', '-sALLOW_MEMORY_GROWTH', '-sMAXIMUM_MEMORY=16GB', '-sABORTING_MALLOC=0'] + args)

def test_libcxx_minimal(self):
create_file('vector.cpp', r'''
#include <vector>
Expand Down
Loading