summaryrefslogtreecommitdiff
path: root/erts/emulator/asmjit/core/jitallocator.h
blob: 0b340df4ab3201f1eb7070809996243d836c453f (plain)
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// AsmJit - Machine code generation for C++
//
//  * Official AsmJit Home Page: https://asmjit.com
//  * Official Github Repository: https://github.com/asmjit/asmjit
//
// Copyright (c) 2008-2020 The AsmJit Authors
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would be
//    appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//    misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

#ifndef ASMJIT_CORE_JITALLOCATOR_H_INCLUDED
#define ASMJIT_CORE_JITALLOCATOR_H_INCLUDED

#include "../core/api-config.h"
#ifndef ASMJIT_NO_JIT

#include "../core/globals.h"
#include "../core/virtmem.h"

ASMJIT_BEGIN_NAMESPACE

//! \addtogroup asmjit_virtual_memory
//! \{

// ============================================================================
// [asmjit::JitAllocator]
// ============================================================================

//! A simple implementation of memory manager that uses `asmjit::VirtMem`
//! functions to manage virtual memory for JIT compiled code.
//!
//! Implementation notes:
//!
//! - Granularity of allocated blocks is different than granularity for a typical
//!   C malloc. In addition, the allocator can use several memory pools having a
//!   different granularity to minimize the maintenance overhead. Multiple pools
//!   feature requires `kFlagUseMultiplePools` flag to be set.
//!
//! - The allocator doesn't store any information in executable memory, instead,
//!   the implementation uses two bit-vectors to manage allocated memory of each
//!   allocator-block. The first bit-vector called 'used' is used to track used
//!   memory (where each bit represents memory size defined by granularity) and
//!   the second bit vector called 'stop' is used as a sentinel to mark where
//!   the allocated area ends.
//!
//! - Internally, the allocator also uses RB tree to keep track of all blocks
//!   across all pools. Each inserted block is added to the tree so it can be
//!   matched fast during `release()` and `shrink()`.
class JitAllocator {
public:
  ASMJIT_NONCOPYABLE(JitAllocator)

  //! Jit allocator options.
  enum Options : uint32_t {
    //! Enables the use of an anonymous memory-mapped memory that is mapped into
    //! two buffers having a different pointer. The first buffer has read and
    //! execute permissions and the second buffer has read+write permissions.
    //!
    //! See \ref VirtMem::allocDualMapping() for more details about this feature.
    kOptionUseDualMapping = 0x00000001u,

    //! Enables the use of multiple pools with increasing granularity instead of
    //! a single pool. This flag would enable 3 internal pools in total having
    //! 64, 128, and 256 bytes granularity.
    //!
    //! This feature is only recommended for users that generate a lot of code
    //! and would like to minimize the overhead of `JitAllocator` itself by
    //! having blocks of different allocation granularities. Using this feature
    //! only for few allocations won't pay off as the allocator may need to
    //! create more blocks initially before it can take the advantage of
    //! variable block granularity.
    kOptionUseMultiplePools = 0x00000002u,

    //! Always fill reserved memory by a fill-pattern.
    //!
    //! Causes a new block to be cleared by the fill pattern and freshly
    //! released memory to be cleared before making it ready for another use.
    kOptionFillUnusedMemory = 0x00000004u,

    //! When this flag is set the allocator would immediately release unused
    //! blocks during `release()` or `reset()`. When this flag is not set the
    //! allocator would keep one empty block in each pool to prevent excessive
    //! virtual memory allocations and deallocations in border cases, which
    //! involve constantly allocating and deallocating a single block caused
    //! by repetitive calling `alloc()` and `release()` when the allocator has
    //! either no blocks or have all blocks fully occupied.
    kOptionImmediateRelease = 0x00000008u,

    //! Use a custom fill pattern, must be combined with `kFlagFillUnusedMemory`.
    kOptionCustomFillPattern = 0x10000000u
  };

  struct Impl {
    //! Allocator options, see \ref JitAllocator::Options.
    uint32_t options;
    //! Base block size (0 if the allocator is not initialized).
    uint32_t blockSize;
    //! Base granularity (0 if the allocator is not initialized).
    uint32_t granularity;
    //! A pattern that is used to fill unused memory if secure mode is enabled.
    uint32_t fillPattern;
  };

  //! Allocator implementation (private).
  Impl* _impl;

  //! \name Construction & Destruction
  //! \{

  //! Parameters that can be passed to `JitAllocator` constructor.
  //!
  //! Use it like this:
  //!
  //! ```
  //! // Zero initialize (zero means the default value) and change what you need.
  //! JitAllocator::CreateParams params {};
  //! params.blockSize = 1024 * 1024;
  //!
  //! // Create the allocator.
  //! JitAllocator allocator(&params);
  //! ```
  struct CreateParams {
    //! Allocator options, see \ref JitAllocator::Options.
    //!
    //! No options are used by default.
    uint32_t options;

    //! Base size of a single block in bytes (default 64kB).
    //!
    //! \remarks Block size must be equal or greater to page size and must be
    //! power of 2. If the input is not valid then the default block size will
    //! be used instead.
    uint32_t blockSize;

    //! Base granularity (and also natural alignment) of allocations in bytes
    //! (default 64).
    //!
    //! Since the `JitAllocator` uses bit-arrays to mark used memory the
    //! granularity also specifies how many bytes correspond to a single bit in
    //! such bit-array. Higher granularity means more waste of virtual memory
    //! (as it increases the natural alignment), but smaller bit-arrays as less
    //! bits would be required per a single block.
    uint32_t granularity;

    //! Patter to use to fill unused memory.
    //!
    //! Only used if \ref kOptionCustomFillPattern is set.
    uint32_t fillPattern;

    // Reset the content of `CreateParams`.
    inline void reset() noexcept { memset(this, 0, sizeof(*this)); }
  };

  //! Creates a `JitAllocator` instance.
  explicit ASMJIT_API JitAllocator(const CreateParams* params = nullptr) noexcept;
  //! Destroys the `JitAllocator` instance and release all blocks held.
  ASMJIT_API ~JitAllocator() noexcept;

  inline bool isInitialized() const noexcept { return _impl->blockSize == 0; }

  //! Free all allocated memory - makes all pointers returned by `alloc()` invalid.
  //!
  //! \remarks This function is not thread-safe as it's designed to be used when
  //! nobody else is using allocator. The reason is that there is no point of
  //1 calling `reset()` when the allocator is still in use.
  ASMJIT_API void reset(uint32_t resetPolicy = Globals::kResetSoft) noexcept;

  //! \}

  //! \name Accessors
  //! \{

  //! Returns allocator options, see `Flags`.
  inline uint32_t options() const noexcept { return _impl->options; }
  //! Tests whether the allocator has the given `option` set.
  inline bool hasOption(uint32_t option) const noexcept { return (_impl->options & option) != 0; }

  //! Returns a base block size (a minimum size of block that the allocator would allocate).
  inline uint32_t blockSize() const noexcept { return _impl->blockSize; }
  //! Returns granularity of the allocator.
  inline uint32_t granularity() const noexcept { return _impl->granularity; }
  //! Returns pattern that is used to fill unused memory if `kFlagUseFillPattern` is set.
  inline uint32_t fillPattern() const noexcept { return _impl->fillPattern; }

  //! \}

  //! \name Alloc & Release
  //! \{

  //! Allocates `size` bytes of virtual memory.
  //!
  //! When the function is successful it stores two pointers in `rxPtrOut` and `rwPtrOut`.
  //! The pointers will be different only if `kOptionUseDualMapping` was used to setup the
  //! allocator (in that case the `rxPtrOut` would point to a Read+Execute region and
  //! `rwPtrOut` would point to a Read+Write region of the same memory-mapped block.
  //!
  //! \remarks This function is thread-safe.
  ASMJIT_API Error alloc(void** rxPtrOut, void** rwPtrOut, size_t size) noexcept;

  //! Releases a memory returned by `alloc()`.
  //!
  //! \remarks This function is thread-safe.
  ASMJIT_API Error release(void* rxPtr) noexcept;

  //! Frees extra memory allocated with `p` by restricting it to `newSize` size.
  //!
  //! \remarks This function is thread-safe.
  ASMJIT_API Error shrink(void* rxPtr, size_t newSize) noexcept;

  //! Queries information about an allocated memory block that contains the given `rxPtr`.
  //!
  //! The function returns `kErrorOk` when `rxPtr` is matched and fills `rxPtrOut`, `rwPtrOut`, and `sizeOut` output
  //! arguments. The returned `rxPtrOut` and `rwPtrOut` pointers point to the beginning of the block, and `sizeOut`
  //! describes the total amount of bytes this allocation uses - the `sizeOut` will always be aligned to the allocation
  //! granularity, so for example if an allocation was 1 byte and the size granularity is 64, the returned `sizeOut`
  //! will be 64 bytes, because that's what the allocator sees.
  ASMJIT_API Error query(void* rxPtr, void** rxPtrOut, void** rwPtrOut, size_t* sizeOut) const noexcept;

  //! \}

  //! \name Statistics
  //! \{

  //! Statistics about `JitAllocator`.
  struct Statistics {
    //! Number of blocks `JitAllocator` maintains.
    size_t _blockCount;
    //! Number of active allocations.
    size_t _allocationCount;
    //! How many bytes are currently used / allocated.
    size_t _usedSize;
    //! How many bytes are currently reserved by the allocator.
    size_t _reservedSize;
    //! Allocation overhead (in bytes) required to maintain all blocks.
    size_t _overheadSize;

    inline void reset() noexcept {
      _blockCount = 0;
      _usedSize = 0;
      _reservedSize = 0;
      _overheadSize = 0;
    }

    //! Returns count of blocks managed by `JitAllocator` at the moment.
    inline size_t blockCount() const noexcept { return _blockCount; }

    //! Returns the number of active allocations.
    inline size_t allocationCount() const noexcept { return _allocationCount; }

    //! Returns how many bytes are currently used.
    inline size_t usedSize() const noexcept { return _usedSize; }
    //! Returns the number of bytes unused by the allocator at the moment.
    inline size_t unusedSize() const noexcept { return _reservedSize - _usedSize; }
    //! Returns the total number of bytes bytes reserved by the allocator (sum of sizes of all blocks).
    inline size_t reservedSize() const noexcept { return _reservedSize; }
    //! Returns the number of bytes the allocator needs to manage the allocated memory.
    inline size_t overheadSize() const noexcept { return _overheadSize; }

    inline double usedSizeAsPercent() const noexcept {
      return (double(usedSize()) / (double(reservedSize()) + 1e-16)) * 100.0;
    }

    inline double unusedSizeAsPercent() const noexcept {
      return (double(unusedSize()) / (double(reservedSize()) + 1e-16)) * 100.0;
    }

    inline double overheadSizeAsPercent() const noexcept {
      return (double(overheadSize()) / (double(reservedSize()) + 1e-16)) * 100.0;
    }
  };

  //! Returns JIT allocator statistics.
  //!
  //! \remarks This function is thread-safe.
  ASMJIT_API Statistics statistics() const noexcept;

  //! \}
};

//! \}

ASMJIT_END_NAMESPACE

#endif
#endif