constpool.cpp 13.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
// [AsmJit]
// Complete x86/x64 JIT and Remote Assembler for C++.
//
// [License]
// Zlib - See LICENSE.md file in the package.

// [Export]
#define ASMJIT_EXPORTS

10
// [Dependencies]
11
#include "../base/constpool.h"
12
13
14
#include "../base/utils.h"

#include <algorithm>
15
16

// [Api-Begin]
17
#include "../asmjit_apibegin.h"
18
19
20
21
22
23
24
25

namespace asmjit {

// Binary tree code is based on Julienne Walker's "Andersson Binary Trees"
// article and implementation. However, only three operations are implemented -
// get, insert and traverse.

// ============================================================================
26
// [asmjit::ConstPool::Tree - Ops]
27
28
29
30
31
// ============================================================================

//! \internal
//!
//! Remove left horizontal links.
32
33
static ASMJIT_INLINE ConstPool::Node* ConstPoolTree_skewNode(ConstPool::Node* node) noexcept {
  ConstPool::Node* link = node->_link[0];
34
35
  uint32_t level = node->_level;

36
  if (level != 0 && link && link->_level == level) {
37
38
39
40
41
42
43
44
45
46
47
48
    node->_link[0] = link->_link[1];
    link->_link[1] = node;

    node = link;
  }

  return node;
}

//! \internal
//!
//! Remove consecutive horizontal links.
49
50
static ASMJIT_INLINE ConstPool::Node* ConstPoolTree_splitNode(ConstPool::Node* node) noexcept {
  ConstPool::Node* link = node->_link[1];
51
52
  uint32_t level = node->_level;

53
  if (level != 0 && link && link->_link[1] && link->_link[1]->_level == level) {
54
55
56
57
58
59
60
61
62
63
    node->_link[1] = link->_link[0];
    link->_link[0] = node;

    node = link;
    node->_level++;
  }

  return node;
}

64
65
ConstPool::Node* ConstPool::Tree::get(const void* data) noexcept {
  ConstPool::Node* node = _root;
66
67
  size_t dataSize = _dataSize;

68
  while (node) {
69
70
71
72
73
74
    int c = ::memcmp(node->getData(), data, dataSize);
    if (c == 0)
      return node;
    node = node->_link[c < 0];
  }

75
  return nullptr;
76
77
}

78
void ConstPool::Tree::put(ConstPool::Node* newNode) noexcept {
79
80
  size_t dataSize = _dataSize;
  _length++;
81
82

  if (!_root) {
83
84
85
86
    _root = newNode;
    return;
  }

87
88
  ConstPool::Node* node = _root;
  ConstPool::Node* stack[kHeightLimit];
89
90
91
92
93
94
95
96
97

  unsigned int top = 0;
  unsigned int dir;

  // Find a spot and save the stack.
  for (;;) {
    stack[top++] = node;
    dir = ::memcmp(node->getData(), newNode->getData(), dataSize) < 0;

98
99
    ConstPool::Node* link = node->_link[dir];
    if (!link) break;
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

    node = link;
  }

  // Link and rebalance.
  node->_link[dir] = newNode;

  while (top > 0) {
    // Which child?
    node = stack[--top];

    if (top != 0) {
      dir = stack[top - 1]->_link[1] == node;
    }

    node = ConstPoolTree_skewNode(node);
    node = ConstPoolTree_splitNode(node);

    // Fix the parent.
    if (top != 0)
      stack[top - 1]->_link[dir] = node;
    else
      _root = node;
  }
}

// ============================================================================
// [asmjit::ConstPool - Construction / Destruction]
// ============================================================================

130
131
ConstPool::ConstPool(Zone* zone) noexcept { reset(zone); }
ConstPool::~ConstPool() noexcept {}
132
133
134
135
136

// ============================================================================
// [asmjit::ConstPool - Reset]
// ============================================================================

137
138
139
140
void ConstPool::reset(Zone* zone) noexcept {
  _zone = zone;

  size_t dataSize = 1;
141
142
  for (size_t i = 0; i < ASMJIT_ARRAY_SIZE(_tree); i++) {
    _tree[i].reset();
143
144
145
    _tree[i].setDataSize(dataSize);
    _gaps[i] = nullptr;
    dataSize <<= 1;
146
147
  }

148
  _gapPool = nullptr;
149
150
151
152
153
154
155
156
  _size = 0;
  _alignment = 0;
}

// ============================================================================
// [asmjit::ConstPool - Ops]
// ============================================================================

157
158
159
static ASMJIT_INLINE ConstPool::Gap* ConstPool_allocGap(ConstPool* self) noexcept {
  ConstPool::Gap* gap = self->_gapPool;
  if (!gap) return self->_zone->allocT<ConstPool::Gap>();
160
161
162
163
164

  self->_gapPool = gap->_next;
  return gap;
}

165
static ASMJIT_INLINE void ConstPool_freeGap(ConstPool* self,  ConstPool::Gap* gap) noexcept {
166
167
168
169
  gap->_next = self->_gapPool;
  self->_gapPool = gap;
}

170
static void ConstPool_addGap(ConstPool* self, size_t offset, size_t length) noexcept {
171
172
173
174
175
176
177
  ASMJIT_ASSERT(length > 0);

  while (length > 0) {
    size_t gapIndex;
    size_t gapLength;

      gapIndex = ConstPool::kIndex16;
178
    if (length >= 16 && Utils::isAligned<size_t>(offset, 16)) {
179
180
      gapLength = 16;
    }
181
    else if (length >= 8 && Utils::isAligned<size_t>(offset, 8)) {
182
183
184
      gapIndex = ConstPool::kIndex8;
      gapLength = 8;
    }
185
    else if (length >= 4 && Utils::isAligned<size_t>(offset, 4)) {
186
187
188
      gapIndex = ConstPool::kIndex4;
      gapLength = 4;
    }
189
    else if (length >= 2 && Utils::isAligned<size_t>(offset, 2)) {
190
191
192
193
194
195
196
197
198
199
200
      gapIndex = ConstPool::kIndex2;
      gapLength = 2;
    }
    else {
      gapIndex = ConstPool::kIndex1;
      gapLength = 1;
    }

    // We don't have to check for errors here, if this failed nothing really
    // happened (just the gap won't be visible) and it will fail again at
    // place where checking will cause kErrorNoHeapMemory.
201
202
    ConstPool::Gap* gap = ConstPool_allocGap(self);
    if (!gap) return;
203
204
205
206
207
208
209
210
211
212
213
214

    gap->_next = self->_gaps[gapIndex];
    self->_gaps[gapIndex] = gap;

    gap->_offset = offset;
    gap->_length = gapLength;

    offset += gapLength;
    length -= gapLength;
  }
}

215
Error ConstPool::add(const void* data, size_t size, size_t& dstOffset) noexcept {
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
  size_t treeIndex;

  if (size == 32)
    treeIndex = kIndex32;
  else if (size == 16)
    treeIndex = kIndex16;
  else if (size == 8)
    treeIndex = kIndex8;
  else if (size == 4)
    treeIndex = kIndex4;
  else if (size == 2)
    treeIndex = kIndex2;
  else if (size == 1)
    treeIndex = kIndex1;
  else
231
    return DebugUtils::errored(kErrorInvalidArgument);
232

233
234
  ConstPool::Node* node = _tree[treeIndex].get(data);
  if (node) {
235
236
237
238
239
240
241
242
243
244
    dstOffset = node->_offset;
    return kErrorOk;
  }

  // Before incrementing the current offset try if there is a gap that can
  // be used for the requested data.
  size_t offset = ~static_cast<size_t>(0);
  size_t gapIndex = treeIndex;

  while (gapIndex != kIndexCount - 1) {
245
    ConstPool::Gap* gap = _gaps[treeIndex];
246
247

    // Check if there is a gap.
248
    if (gap) {
249
250
251
252
253
254
255
256
      size_t gapOffset = gap->_offset;
      size_t gapLength = gap->_length;

      // Destroy the gap for now.
      _gaps[treeIndex] = gap->_next;
      ConstPool_freeGap(this, gap);

      offset = gapOffset;
257
      ASMJIT_ASSERT(Utils::isAligned<size_t>(offset, size));
258
259
260
261
262
263
264
265
266
267
268
269

      gapLength -= size;
      if (gapLength > 0)
        ConstPool_addGap(this, gapOffset, gapLength);
    }

    gapIndex++;
  }

  if (offset == ~static_cast<size_t>(0)) {
    // Get how many bytes have to be skipped so the address is aligned accordingly
    // to the 'size'.
270
    size_t diff = Utils::alignDiff<size_t>(_size, size);
271

272
273
274
    if (diff != 0) {
      ConstPool_addGap(this, _size, diff);
      _size += diff;
275
276
277
278
279
280
281
    }

    offset = _size;
    _size += size;
  }

  // Add the initial node to the right index.
282
283
  node = ConstPool::Tree::_newNode(_zone, data, size, offset, false);
  if (!node) return DebugUtils::errored(kErrorNoHeapMemory);
284
285

  _tree[treeIndex].put(node);
286
  _alignment = std::max<size_t>(_alignment, size);
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303

  dstOffset = offset;

  // Now create a bunch of shared constants that are based on the data pattern.
  // We stop at size 4, it probably doesn't make sense to split constants down
  // to 1 byte.
  size_t pCount = 1;
  while (size > 4) {
    size >>= 1;
    pCount <<= 1;

    ASMJIT_ASSERT(treeIndex != 0);
    treeIndex--;

    const uint8_t* pData = static_cast<const uint8_t*>(data);
    for (size_t i = 0; i < pCount; i++, pData += size) {
      node = _tree[treeIndex].get(pData);
304
      if (node) continue;
305

306
      node = ConstPool::Tree::_newNode(_zone, pData, size, offset + (i * size), true);
307
308
309
310
311
312
313
314
315
316
317
318
      _tree[treeIndex].put(node);
    }
  }

  return kErrorOk;
}

// ============================================================================
// [asmjit::ConstPool - Reset]
// ============================================================================

struct ConstPoolFill {
319
  ASMJIT_INLINE ConstPoolFill(uint8_t* dst, size_t dataSize) noexcept :
320
321
322
    _dst(dst),
    _dataSize(dataSize) {}

323
  ASMJIT_INLINE void visit(const ConstPool::Node* node) noexcept {
324
325
326
327
328
329
330
331
    if (!node->_shared)
      ::memcpy(_dst + node->_offset, node->getData(), _dataSize);
  }

  uint8_t* _dst;
  size_t _dataSize;
};

332
void ConstPool::fill(void* dst) const noexcept {
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
  // Clears possible gaps, asmjit should never emit garbage to the output.
  ::memset(dst, 0, _size);

  ConstPoolFill filler(static_cast<uint8_t*>(dst), 1);
  for (size_t i = 0; i < ASMJIT_ARRAY_SIZE(_tree); i++) {
    _tree[i].iterate(filler);
    filler._dataSize <<= 1;
  }
}

// ============================================================================
// [asmjit::ConstPool - Test]
// ============================================================================

#if defined(ASMJIT_TEST)
UNIT(base_constpool) {
349
  Zone zone(32384 - Zone::kZoneOverhead);
350
351
352
353
354
355
356
357
358
359
360
361
  ConstPool pool(&zone);

  uint32_t i;
  uint32_t kCount = 1000000;

  INFO("Adding %u constants to the pool.", kCount);
  {
    size_t prevOffset;
    size_t curOffset;
    uint64_t c = ASMJIT_UINT64_C(0x0101010101010101);

    EXPECT(pool.add(&c, 8, prevOffset) == kErrorOk,
362
      "pool.add() - Returned error");
363
    EXPECT(prevOffset == 0,
364
      "pool.add() - First constant should have zero offset");
365
366
367
368

    for (i = 1; i < kCount; i++) {
      c++;
      EXPECT(pool.add(&c, 8, curOffset) == kErrorOk,
369
        "pool.add() - Returned error");
370
      EXPECT(prevOffset + 8 == curOffset,
371
        "pool.add() - Returned incorrect curOffset");
372
      EXPECT(pool.getSize() == (i + 1) * 8,
373
        "pool.getSize() - Reported incorrect size");
374
375
376
377
      prevOffset = curOffset;
    }

    EXPECT(pool.getAlignment() == 8,
378
      "pool.getAlignment() - Expected 8-byte alignment");
379
380
381
382
383
384
385
386
387
  }

  INFO("Retrieving %u constants from the pool.", kCount);
  {
    uint64_t c = ASMJIT_UINT64_C(0x0101010101010101);

    for (i = 0; i < kCount; i++) {
      size_t offset;
      EXPECT(pool.add(&c, 8, offset) == kErrorOk,
388
        "pool.add() - Returned error");
389
      EXPECT(offset == i * 8,
390
        "pool.add() - Should have reused constant");
391
392
393
394
      c++;
    }
  }

395
  INFO("Checking if the constants were split into 4-byte patterns");
396
397
398
399
400
  {
    uint32_t c = 0x01010101;
    for (i = 0; i < kCount; i++) {
      size_t offset;
      EXPECT(pool.add(&c, 4, offset) == kErrorOk,
401
        "pool.add() - Returned error");
402
      EXPECT(offset == i * 8,
403
        "pool.add() - Should reuse existing constant");
404
405
406
407
      c++;
    }
  }

408
  INFO("Adding 2 byte constant to misalign the current offset");
409
410
411
412
413
  {
    uint16_t c = 0xFFFF;
    size_t offset;

    EXPECT(pool.add(&c, 2, offset) == kErrorOk,
414
      "pool.add() - Returned error");
415
    EXPECT(offset == kCount * 8,
416
      "pool.add() - Didn't return expected position");
417
    EXPECT(pool.getAlignment() == 8,
418
      "pool.getAlignment() - Expected 8-byte alignment");
419
420
  }

421
  INFO("Adding 8 byte constant to check if pool gets aligned again");
422
423
424
425
426
  {
    uint64_t c = ASMJIT_UINT64_C(0xFFFFFFFFFFFFFFFF);
    size_t offset;

    EXPECT(pool.add(&c, 8, offset) == kErrorOk,
427
      "pool.add() - Returned error");
428
    EXPECT(offset == kCount * 8 + 8,
429
      "pool.add() - Didn't return aligned offset");
430
431
  }

432
  INFO("Adding 2 byte constant to verify the gap is filled");
433
434
435
436
437
  {
    uint16_t c = 0xFFFE;
    size_t offset;

    EXPECT(pool.add(&c, 2, offset) == kErrorOk,
438
      "pool.add() - Returned error");
439
    EXPECT(offset == kCount * 8 + 2,
440
      "pool.add() - Didn't fill the gap");
441
    EXPECT(pool.getAlignment() == 8,
442
      "pool.getAlignment() - Expected 8-byte alignment");
443
444
  }

445
  INFO("Checking reset functionality");
446
  {
447
448
    pool.reset(&zone);
    zone.reset();
449
450

    EXPECT(pool.getSize() == 0,
451
      "pool.getSize() - Expected pool size to be zero");
452
    EXPECT(pool.getAlignment() == 0,
453
      "pool.getSize() - Expected pool alignment to be zero");
454
455
  }

456
  INFO("Checking pool alignment when combined constants are added");
457
458
459
460
461
462
463
  {
    uint8_t bytes[32] = { 0 };
    size_t offset;

    pool.add(bytes, 1, offset);

    EXPECT(pool.getSize() == 1,
464
      "pool.getSize() - Expected pool size to be 1 byte");
465
    EXPECT(pool.getAlignment() == 1,
466
      "pool.getSize() - Expected pool alignment to be 1 byte");
467
    EXPECT(offset == 0,
468
      "pool.getSize() - Expected offset returned to be zero");
469
470
471
472

    pool.add(bytes, 2, offset);

    EXPECT(pool.getSize() == 4,
473
      "pool.getSize() - Expected pool size to be 4 bytes");
474
    EXPECT(pool.getAlignment() == 2,
475
      "pool.getSize() - Expected pool alignment to be 2 bytes");
476
    EXPECT(offset == 2,
477
      "pool.getSize() - Expected offset returned to be 2");
478
479
480
481

    pool.add(bytes, 4, offset);

    EXPECT(pool.getSize() == 8,
482
      "pool.getSize() - Expected pool size to be 8 bytes");
483
    EXPECT(pool.getAlignment() == 4,
484
      "pool.getSize() - Expected pool alignment to be 4 bytes");
485
    EXPECT(offset == 4,
486
      "pool.getSize() - Expected offset returned to be 4");
487
488
489
490

    pool.add(bytes, 4, offset);

    EXPECT(pool.getSize() == 8,
491
      "pool.getSize() - Expected pool size to be 8 bytes");
492
    EXPECT(pool.getAlignment() == 4,
493
      "pool.getSize() - Expected pool alignment to be 4 bytes");
494
    EXPECT(offset == 4,
495
      "pool.getSize() - Expected offset returned to be 8");
496
497
498

    pool.add(bytes, 32, offset);
    EXPECT(pool.getSize() == 64,
499
      "pool.getSize() - Expected pool size to be 64 bytes");
500
    EXPECT(pool.getAlignment() == 32,
501
      "pool.getSize() - Expected pool alignment to be 32 bytes");
502
    EXPECT(offset == 32,
503
      "pool.getSize() - Expected offset returned to be 32");
504
505
506
507
508
509
510
  }
}
#endif // ASMJIT_TEST

} // asmjit namespace

// [Api-End]
511
#include "../asmjit_apiend.h"