aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/gopkg.in/olebedev/go-duktape.v3/duk_alloc_pool.h
blob: 286ec0166a9819fec468250114ef58ddf421a9f1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#if !defined(DUK_ALLOC_POOL_H_INCLUDED)
#define DUK_ALLOC_POOL_H_INCLUDED

#include "duktape.h"

/* 32-bit (big endian) marker used at the end of pool entries so that wasted
 * space can be detected.  Waste tracking must be enabled explicitly.
 */
#if defined(DUK_ALLOC_POOL_TRACK_WASTE)
#define DUK_ALLOC_POOL_WASTE_MARKER  0xedcb2345UL
#endif

/* Pointer compression with ROM strings/objects:
 *
 * For now, use DUK_USE_ROM_OBJECTS to signal the need for compressed ROM
 * pointers.  DUK_USE_ROM_PTRCOMP_FIRST is provided for the ROM pointer
 * compression range minimum to avoid duplication in user code.
 */
#if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16)
#define DUK_ALLOC_POOL_ROMPTR_COMPRESSION
#define DUK_ALLOC_POOL_ROMPTR_FIRST DUK_USE_ROM_PTRCOMP_FIRST

/* This extern declaration is provided by duktape.h, array provided by duktape.c.
 * Because duk_config.h may include this file (to get the inline functions) we
 * need to forward declare this also here.
 */
extern const void * const duk_rom_compressed_pointers[];
#endif

/* Pool configuration for a certain block size. */
typedef struct {
    unsigned int size;  /* must be divisible by 4 and >= sizeof(void *) */
    unsigned int a;     /* bytes (not count) to allocate: a*t + b, t is an arbitrary scale parameter */
    unsigned int b;
} duk_pool_config;

/* Freelist entry, must fit into the smallest block size. */
struct duk_pool_free;
typedef struct duk_pool_free duk_pool_free;
struct duk_pool_free {
    duk_pool_free *next;
};

/* Pool state for a certain block size. */
typedef struct {
    duk_pool_free *first;
    char *alloc_end;
    unsigned int size;
    unsigned int count;
#if defined(DUK_ALLOC_POOL_TRACK_HIGHWATER)
    unsigned int hwm_used_count;
#endif
} duk_pool_state;

/* Statistics for a certain pool. */
typedef struct {
    size_t used_count;
    size_t used_bytes;
    size_t free_count;
    size_t free_bytes;
    size_t waste_bytes;
    size_t hwm_used_count;
} duk_pool_stats;

/* Top level state for all pools.  Pointer to this struct is used as the allocator
 * userdata pointer.
 */
typedef struct {
    int num_pools;
    duk_pool_state *states;
#if defined(DUK_ALLOC_POOL_TRACK_HIGHWATER)
    size_t hwm_used_bytes;
    size_t hwm_waste_bytes;
#endif
} duk_pool_global;

/* Statistics for the entire set of pools. */
typedef struct {
    size_t used_bytes;
    size_t free_bytes;
    size_t waste_bytes;
    size_t hwm_used_bytes;
    size_t hwm_waste_bytes;
} duk_pool_global_stats;

/* Initialize a pool allocator, arguments:
 *   - buffer and size: continuous region to use for pool, must align to 4
 *   - config: configuration for pools in ascending block size
 *   - state: state for pools, matches config order
 *   - num_pools: number of entries in 'config' and 'state'
 *   - global: global state structure
 *
 * The 'config', 'state', and 'global' pointers must be valid beyond the init
 * call, as long as the pool is used.
 *
 * Returns a void pointer to be used as userdata for the allocator functions.
 * Concretely the return value will be "(void *) global", i.e. the global
 * state struct.  If pool init fails, the return value will be NULL.
 */
void *duk_alloc_pool_init(char *buffer,
                          size_t size,
                          const duk_pool_config *configs,
                          duk_pool_state *states,
                          int num_pools,
                          duk_pool_global *global);

/* Duktape allocation providers.  Typing matches Duktape requirements. */
void *duk_alloc_pool(void *udata, duk_size_t size);
void *duk_realloc_pool(void *udata, void *ptr, duk_size_t size);
void duk_free_pool(void *udata, void *ptr);

/* Stats. */
void duk_alloc_pool_get_pool_stats(duk_pool_state *s, duk_pool_stats *res);
void duk_alloc_pool_get_global_stats(duk_pool_global *g, duk_pool_global_stats *res);

/* Duktape pointer compression global state (assumes single pool). */
#if defined(DUK_USE_ROM_OBJECTS) && defined(DUK_USE_HEAPPTR16)
extern const void *duk_alloc_pool_romptr_low;
extern const void *duk_alloc_pool_romptr_high;
duk_uint16_t duk_alloc_pool_enc16_rom(void *ptr);
#endif
#if defined(DUK_USE_HEAPPTR16)
extern void *duk_alloc_pool_ptrcomp_base;
#endif

#if 0
duk_uint16_t duk_alloc_pool_enc16(void *ptr);
void *duk_alloc_pool_dec16(duk_uint16_t val);
#endif

/* Inlined pointer compression functions.  Gcc and clang -Os won't in
 * practice inline these without an "always inline" attribute because it's
 * more size efficient (by a few kB) to use explicit calls instead.  Having
 * these defined inline here allows performance optimized builds to inline
 * pointer compression operations.
 *
 * Pointer compression assumes there's a single globally registered memory
 * pool which makes pointer compression more efficient.  This would be easy
 * to fix by adding a userdata pointer to the compression functions and
 * plumbing the heap userdata from the compression/decompression macros.
 */

/* DUK_ALWAYS_INLINE is not a public API symbol so it may go away in even a
 * minor update.  But it's pragmatic for this extra because it handles many
 * compilers via duk_config.h detection.  Check that the macro exists so that
 * if it's gone, we can still compile.
 */
#if defined(DUK_ALWAYS_INLINE)
#define DUK__ALLOC_POOL_ALWAYS_INLINE DUK_ALWAYS_INLINE
#else
#define DUK__ALLOC_POOL_ALWAYS_INLINE /* nop */
#endif

#if defined(DUK_USE_HEAPPTR16)
static DUK__ALLOC_POOL_ALWAYS_INLINE duk_uint16_t duk_alloc_pool_enc16(void *ptr) {
    if (ptr == NULL) {
        /* With 'return 0' gcc and clang -Os generate inefficient code.
         * For example, gcc -Os generates:
         *
         *   0804911d <duk_alloc_pool_enc16>:
         *    804911d:       55                      push   %ebp
         *    804911e:       85 c0                   test   %eax,%eax
         *    8049120:       89 e5                   mov    %esp,%ebp
         *    8049122:       74 0b                   je     804912f <duk_alloc_pool_enc16+0x12>
         *    8049124:       2b 05 e4 90 07 08       sub    0x80790e4,%eax
         *    804912a:       c1 e8 02                shr    $0x2,%eax
         *    804912d:       eb 02                   jmp    8049131 <duk_alloc_pool_enc16+0x14>
         *    804912f:       31 c0                   xor    %eax,%eax
         *    8049131:       5d                      pop    %ebp
         *    8049132:       c3                      ret
         *
         * The NULL path checks %eax for zero; if it is zero, a zero
         * is unnecessarily loaded into %eax again.  The non-zero path
         * has an unnecessary jump as a side effect of this.
         *
         * Using 'return (duk_uint16_t) (intptr_t) ptr;' generates similarly
         * inefficient code; not sure how to make the result better.
         */
        return 0;
    }
#if defined(DUK_ALLOC_POOL_ROMPTR_COMPRESSION)
    if (ptr >= duk_alloc_pool_romptr_low && ptr <= duk_alloc_pool_romptr_high) {
        /* This is complex enough now to need a separate function. */
        return duk_alloc_pool_enc16_rom(ptr);
    }
#endif
    return (duk_uint16_t) (((size_t) ((char *) ptr - (char *) duk_alloc_pool_ptrcomp_base)) >> 2);
}

static DUK__ALLOC_POOL_ALWAYS_INLINE void *duk_alloc_pool_dec16(duk_uint16_t val) {
    if (val == 0) {
        /* As with enc16 the gcc and clang -Os output is inefficient,
         * e.g. gcc -Os:
         *
         *   08049133 <duk_alloc_pool_dec16>:
         *    8049133:       55                      push   %ebp
         *    8049134:       66 85 c0                test   %ax,%ax
         *    8049137:       89 e5                   mov    %esp,%ebp
         *    8049139:       74 0e                   je     8049149 <duk_alloc_pool_dec16+0x16>
         *    804913b:       8b 15 e4 90 07 08       mov    0x80790e4,%edx
         *    8049141:       0f b7 c0                movzwl %ax,%eax
         *    8049144:       8d 04 82                lea    (%edx,%eax,4),%eax
         *    8049147:       eb 02                   jmp    804914b <duk_alloc_pool_dec16+0x18>
         *    8049149:       31 c0                   xor    %eax,%eax
         *    804914b:       5d                      pop    %ebp
         *    804914c:       c3                      ret
         */
        return NULL;
    }
#if defined(DUK_ALLOC_POOL_ROMPTR_COMPRESSION)
    if (val >= DUK_ALLOC_POOL_ROMPTR_FIRST) {
        /* This is a blind lookup, could check index validity.
         * Duktape should never decompress a pointer which would
         * be out-of-bounds here.
         */
        return (void *) (intptr_t) (duk_rom_compressed_pointers[val - DUK_ALLOC_POOL_ROMPTR_FIRST]);
    }
#endif
    return (void *) ((char *) duk_alloc_pool_ptrcomp_base + (((size_t) val) << 2));
}
#endif

#endif  /* DUK_ALLOC_POOL_H_INCLUDED */