Skip to content

Commit 7632a41

Browse files
committed
Add tracked arena allocator
Available under -DZEND_TRACK_ARENA_ALLOC. This will use the system allocator combined with arena checkpointing & release semantics and allows analyzing arena usage under asan/valgrind. I've sacrificed the duplicate arena implementation in mysqlnd, as the integration with mysqlnd alloc is not worth the code duplication to me.
1 parent 645ca71 commit 7632a41

File tree

2 files changed

+117
-83
lines changed

2 files changed

+117
-83
lines changed

Zend/zend_arena.h

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
#include "zend.h"
2323

24+
#ifndef ZEND_TRACK_ARENA_ALLOC
25+
2426
typedef struct _zend_arena zend_arena;
2527

2628
struct _zend_arena {
@@ -48,8 +50,6 @@ static zend_always_inline void zend_arena_destroy(zend_arena *arena)
4850
} while (arena);
4951
}
5052

51-
#define ZEND_ARENA_ALIGNMENT 8U
52-
5353
static zend_always_inline void* zend_arena_alloc(zend_arena **arena_ptr, size_t size)
5454
{
5555
zend_arena *arena = *arena_ptr;
@@ -121,4 +121,104 @@ static zend_always_inline zend_bool zend_arena_contains(zend_arena *arena, void
121121
return 0;
122122
}
123123

124+
#else
125+
126+
/* Use normal allocations and keep track of them for mass-freeing.
127+
* This is intended for use with asan/valgrind. */
128+
129+
typedef struct _zend_arena zend_arena;
130+
131+
struct _zend_arena {
132+
void **ptr;
133+
void **end;
134+
struct _zend_arena *prev;
135+
void *ptrs[0];
136+
};
137+
138+
#define ZEND_TRACKED_ARENA_SIZE 1000
139+
140+
static zend_always_inline zend_arena *zend_arena_create(size_t _size)
141+
{
142+
zend_arena *arena = (zend_arena*) emalloc(
143+
sizeof(zend_arena) + sizeof(void *) * ZEND_TRACKED_ARENA_SIZE);
144+
arena->ptr = &arena->ptrs[0];
145+
arena->end = &arena->ptrs[ZEND_TRACKED_ARENA_SIZE];
146+
arena->prev = NULL;
147+
return arena;
148+
}
149+
150+
static zend_always_inline void zend_arena_destroy(zend_arena *arena)
151+
{
152+
do {
153+
zend_arena *prev = arena->prev;
154+
void **ptr;
155+
for (ptr = arena->ptrs; ptr < arena->ptr; ptr++) {
156+
efree(*ptr);
157+
}
158+
efree(arena);
159+
arena = prev;
160+
} while (arena);
161+
}
162+
163+
static zend_always_inline void *zend_arena_alloc(zend_arena **arena_ptr, size_t size)
164+
{
165+
zend_arena *arena = *arena_ptr;
166+
if (arena->ptr == arena->end) {
167+
*arena_ptr = zend_arena_create(0);
168+
(*arena_ptr)->prev = arena;
169+
arena = *arena_ptr;
170+
}
171+
172+
return *arena->ptr++ = emalloc(size);
173+
}
174+
175+
static zend_always_inline void* zend_arena_calloc(zend_arena **arena_ptr, size_t count, size_t unit_size)
176+
{
177+
int overflow;
178+
size_t size;
179+
void *ret;
180+
181+
size = zend_safe_address(unit_size, count, 0, &overflow);
182+
if (UNEXPECTED(overflow)) {
183+
zend_error(E_ERROR, "Possible integer overflow in zend_arena_calloc() (%zu * %zu)", unit_size, count);
184+
}
185+
ret = zend_arena_alloc(arena_ptr, size);
186+
memset(ret, 0, size);
187+
return ret;
188+
}
189+
190+
static zend_always_inline void* zend_arena_checkpoint(zend_arena *arena)
191+
{
192+
return arena->ptr;
193+
}
194+
195+
static zend_always_inline void zend_arena_release(zend_arena **arena_ptr, void *checkpoint)
196+
{
197+
while (1) {
198+
zend_arena *arena = *arena_ptr;
199+
zend_arena *prev = arena->prev;
200+
while (1) {
201+
if (arena->ptr == (void **) checkpoint) {
202+
return;
203+
}
204+
if (arena->ptr == arena->ptrs) {
205+
break;
206+
}
207+
arena->ptr--;
208+
efree(*arena->ptr);
209+
}
210+
efree(arena);
211+
*arena_ptr = prev;
212+
ZEND_ASSERT(*arena_ptr);
213+
}
214+
}
215+
216+
static zend_always_inline zend_bool zend_arena_contains(zend_arena *arena, void *ptr)
217+
{
218+
/* TODO: Dummy */
219+
return 1;
220+
}
221+
222+
#endif
223+
124224
#endif /* _ZEND_ARENA_H_ */

ext/mysqlnd/mysqlnd_block_alloc.c

Lines changed: 15 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -24,83 +24,13 @@
2424
#include "mysqlnd_debug.h"
2525
#include "mysqlnd_priv.h"
2626

27-
28-
/* {{{ mysqlnd_arena_create */
29-
static zend_always_inline zend_arena* mysqlnd_arena_create(size_t size)
30-
{
31-
zend_arena *arena = (zend_arena*)mnd_emalloc(size);
32-
33-
arena->ptr = (char*) arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena));
34-
arena->end = (char*) arena + size;
35-
arena->prev = NULL;
36-
return arena;
37-
}
38-
/* }}} */
39-
40-
/* {{{ mysqlnd_arena_destroy */
41-
static zend_always_inline void mysqlnd_arena_destroy(zend_arena *arena)
42-
{
43-
do {
44-
zend_arena *prev = arena->prev;
45-
mnd_efree(arena);
46-
arena = prev;
47-
} while (arena);
48-
}
49-
/* }}} */
50-
51-
/* {{{ mysqlnd_arena_alloc */
52-
static zend_always_inline void* mysqlnd_arena_alloc(zend_arena **arena_ptr, size_t size)
53-
{
54-
zend_arena *arena = *arena_ptr;
55-
char *ptr = arena->ptr;
56-
57-
size = ZEND_MM_ALIGNED_SIZE(size);
58-
59-
if (EXPECTED(size <= (size_t)(arena->end - ptr))) {
60-
arena->ptr = ptr + size;
61-
} else {
62-
size_t arena_size =
63-
UNEXPECTED((size + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena))) > (size_t)(arena->end - (char*) arena)) ?
64-
(size + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena))) :
65-
(size_t)(arena->end - (char*) arena);
66-
zend_arena *new_arena = (zend_arena*)mnd_emalloc(arena_size);
67-
68-
ptr = (char*) new_arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena));
69-
new_arena->ptr = (char*) new_arena + ZEND_MM_ALIGNED_SIZE(sizeof(zend_arena)) + size;
70-
new_arena->end = (char*) new_arena + arena_size;
71-
new_arena->prev = arena;
72-
*arena_ptr = new_arena;
73-
}
74-
75-
return (void*) ptr;
76-
}
77-
/* }}} */
78-
79-
static zend_always_inline void* mysqlnd_arena_checkpoint(zend_arena *arena)
80-
{
81-
return arena->ptr;
82-
}
83-
84-
static zend_always_inline void mysqlnd_arena_release(zend_arena **arena_ptr, void *checkpoint)
85-
{
86-
zend_arena *arena = *arena_ptr;
87-
88-
while (UNEXPECTED((char*)checkpoint > arena->end) ||
89-
UNEXPECTED((char*)checkpoint <= (char*)arena)) {
90-
zend_arena *prev = arena->prev;
91-
mnd_efree(arena);
92-
*arena_ptr = arena = prev;
93-
}
94-
ZEND_ASSERT((char*)checkpoint > (char*)arena && (char*)checkpoint <= arena->end);
95-
arena->ptr = (char*)checkpoint;
96-
}
97-
9827
/* {{{ mysqlnd_mempool_free_chunk */
9928
static void
10029
mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr)
10130
{
10231
DBG_ENTER("mysqlnd_mempool_free_chunk");
10332
/* Try to back-off and guess if this is the last block allocated */
33+
#ifndef ZEND_TRACK_ARENA_ALLOC
10434
if (ptr == pool->last) {
10535
/*
10636
This was the last allocation. Lucky us, we can free
@@ -109,6 +39,7 @@ mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr)
10939
pool->arena->ptr = (char*)ptr;
11040
pool->last = NULL;
11141
}
42+
#endif
11243
DBG_VOID_RETURN;
11344
}
11445
/* }}} */
@@ -120,6 +51,7 @@ mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr, size_t old_
12051
{
12152
DBG_ENTER("mysqlnd_mempool_resize_chunk");
12253

54+
#ifndef ZEND_TRACK_ARENA_ALLOC
12355
/* Try to back-off and guess if this is the last block allocated */
12456
if (ptr == pool->last
12557
&& (ZEND_MM_ALIGNED_SIZE(size) <= ((char*)pool->arena->end - (char*)ptr))) {
@@ -128,11 +60,13 @@ mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL * pool, void * ptr, size_t old_
12860
a bit of memory from the pool. Next time we will return from the same ptr.
12961
*/
13062
pool->arena->ptr = (char*)ptr + ZEND_MM_ALIGNED_SIZE(size);
131-
} else {
132-
void *new_ptr = mysqlnd_arena_alloc(&pool->arena, size);
133-
memcpy(new_ptr, ptr, MIN(old_size, size));
134-
pool->last = ptr = new_ptr;
63+
DBG_RETURN(ptr);
13564
}
65+
#endif
66+
67+
void *new_ptr = zend_arena_alloc(&pool->arena, size);
68+
memcpy(new_ptr, ptr, MIN(old_size, size));
69+
pool->last = ptr = new_ptr;
13670
DBG_RETURN(ptr);
13771
}
13872
/* }}} */
@@ -145,7 +79,7 @@ mysqlnd_mempool_get_chunk(MYSQLND_MEMORY_POOL * pool, size_t size)
14579
void *ptr = NULL;
14680
DBG_ENTER("mysqlnd_mempool_get_chunk");
14781

148-
ptr = mysqlnd_arena_alloc(&pool->arena, size);
82+
ptr = zend_arena_alloc(&pool->arena, size);
14983
pool->last = ptr;
15084

15185
DBG_RETURN(ptr);
@@ -161,8 +95,8 @@ mysqlnd_mempool_create(size_t arena_size)
16195
MYSQLND_MEMORY_POOL * ret;
16296

16397
DBG_ENTER("mysqlnd_mempool_create");
164-
arena = mysqlnd_arena_create(MAX(arena_size, sizeof(zend_arena)));
165-
ret = mysqlnd_arena_alloc(&arena, sizeof(MYSQLND_MEMORY_POOL));
98+
arena = zend_arena_create(MAX(arena_size, sizeof(zend_arena)));
99+
ret = zend_arena_alloc(&arena, sizeof(MYSQLND_MEMORY_POOL));
166100
ret->arena = arena;
167101
ret->last = NULL;
168102
ret->checkpoint = NULL;
@@ -180,7 +114,7 @@ mysqlnd_mempool_destroy(MYSQLND_MEMORY_POOL * pool)
180114
{
181115
DBG_ENTER("mysqlnd_mempool_destroy");
182116
/* mnd_free will reference LOCK_access and might crash, depending on the caller...*/
183-
mysqlnd_arena_destroy(pool->arena);
117+
zend_arena_destroy(pool->arena);
184118
DBG_VOID_RETURN;
185119
}
186120
/* }}} */
@@ -190,7 +124,7 @@ PHPAPI void
190124
mysqlnd_mempool_save_state(MYSQLND_MEMORY_POOL * pool)
191125
{
192126
DBG_ENTER("mysqlnd_mempool_save_state");
193-
pool->checkpoint = mysqlnd_arena_checkpoint(pool->arena);
127+
pool->checkpoint = zend_arena_checkpoint(pool->arena);
194128
DBG_VOID_RETURN;
195129
}
196130
/* }}} */
@@ -201,7 +135,7 @@ mysqlnd_mempool_restore_state(MYSQLND_MEMORY_POOL * pool)
201135
{
202136
DBG_ENTER("mysqlnd_mempool_restore_state");
203137
if (pool->checkpoint) {
204-
mysqlnd_arena_release(&pool->arena, pool->checkpoint);
138+
zend_arena_release(&pool->arena, pool->checkpoint);
205139
pool->last = NULL;
206140
pool->checkpoint = NULL;
207141
}

0 commit comments

Comments
 (0)