forked from facebook/hermes
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDictPropertyMap.cpp
More file actions
387 lines (317 loc) · 12.8 KB
/
DictPropertyMap.cpp
File metadata and controls
387 lines (317 loc) · 12.8 KB
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#define DEBUG_TYPE "vm"
#include "hermes/VM/DictPropertyMap.h"
#include "hermes/Support/Statistic.h"
HERMES_SLOW_STATISTIC(NumDictLookups, "Number of dictionary lookups");
HERMES_SLOW_STATISTIC(NumExtraHashProbes, "Number of extra hash probes");
namespace hermes {
namespace vm {
struct DictPropertyMap::detail {
/// The upper bound of the search when trying to find the maximum capacity
/// of this object, given GC::maxAllocationSize().
/// It was chosen to be a value that is certain to not fit into an allocation;
/// at the same time we want to make it smaller, so/ we have arbitrarily
/// chosen to divide the max allocation size by two, which is still guaranteed
/// not to fit.
static constexpr uint32_t kSearchUpperBound = GC::maxAllocationSize() / 2;
static_assert(
!DictPropertyMap::constWouldFitAllocation(kSearchUpperBound),
"kSearchUpperBound should not fit into an allocation");
/// The maximum capacity of DictPropertyMap, given GC::maxAllocationSize().
static constexpr uint32_t kMaxCapacity =
DictPropertyMap::constFindMaxCapacity(0, kSearchUpperBound);
// Double-check that kMaxCapacity is reasonable.
static_assert(
DictPropertyMap::constApproxAllocSize64(kMaxCapacity) <=
GC::maxAllocationSize(),
"invalid kMaxCapacity");
// Ensure that it is safe to double capacities without checking for overflow
// until we exceed kMaxCapacity.
static_assert(
kMaxCapacity < (1u << 31),
"kMaxCapacity is unrealistically large");
static_assert(
DictPropertyMap::HashPair::canStore(kMaxCapacity),
"too few bits to store max possible descriptor index");
};
VTable DictPropertyMap::vt{CellKind::DictPropertyMapKind, 0};
void DictPropertyMapBuildMeta(const GCCell *cell, Metadata::Builder &mb) {
const auto *self = static_cast<const DictPropertyMap *>(cell);
mb.addArray<Metadata::ArrayData::ArrayType::Symbol>(
self->getDescriptorPairs(),
&self->numDescriptors_,
sizeof(DictPropertyMap::DescriptorPair));
}
DictPropertyMap::size_type DictPropertyMap::getMaxCapacity() {
return detail::kMaxCapacity;
}
CallResult<PseudoHandle<DictPropertyMap>> DictPropertyMap::create(
Runtime *runtime,
size_type capacity) {
if (LLVM_UNLIKELY(capacity > detail::kMaxCapacity)) {
return runtime->raiseRangeError(
TwineChar16("Property storage exceeds ") + detail::kMaxCapacity +
" properties");
}
size_type hashCapacity = calcHashCapacity(capacity);
void *mem = runtime->alloc</*fixedSize*/ false>(
allocationSize(capacity, hashCapacity));
return createPseudoHandle(
new (mem) DictPropertyMap(runtime, capacity, hashCapacity));
}
#ifdef HERMESVM_SERIALIZE
void DictPropertyMapSerialize(Serializer &s, const GCCell *cell) {
auto *self = vmcast<const DictPropertyMap>(cell);
s.writeInt<uint32_t>(self->descriptorCapacity_);
s.writeInt<uint32_t>(self->hashCapacity_);
s.writeInt<uint32_t>(self->numDescriptors_.load(std::memory_order_relaxed));
s.writeInt<uint32_t>(self->numProperties_);
s.writeInt<uint32_t>(self->deletedListHead_);
s.writeInt<uint32_t>(self->deletedListSize_);
// No pointer in any of the arrays. let's do a memcpy of the storage
// and write a size here for sanity check
size_t size = DictPropertyMap::allocationSize(
self->descriptorCapacity_, self->hashCapacity_) -
sizeof(DictPropertyMap);
s.writeInt<uint32_t>(size);
s.writeData(self->getDescriptorPairs(), size);
s.endObject(cell);
}
void DictPropertyMapDeserialize(Deserializer &d, CellKind kind) {
assert(kind == CellKind::DictPropertyMapKind && "Expected DictPropertyMap");
uint32_t descriptorCapacity = d.readInt<uint32_t>();
uint32_t hashCapacity = d.readInt<uint32_t>();
if (LLVM_UNLIKELY(
descriptorCapacity > DictPropertyMap::detail::kMaxCapacity)) {
hermes_fatal("deserialized descriptorCapacity exceeds limit");
}
#ifndef NDEBUG
assert(
DictPropertyMap::calcHashCapacity(descriptorCapacity) == hashCapacity &&
"deserialized hash capacity does not match with calculated hashCapacity");
#endif
void *mem = d.getRuntime()->alloc</*fixedSize*/ false>(
DictPropertyMap::allocationSize(descriptorCapacity, hashCapacity));
auto *cell = new (mem)
DictPropertyMap(d.getRuntime(), descriptorCapacity, hashCapacity);
cell->numDescriptors_.store(d.readInt<uint32_t>(), std::memory_order_release);
cell->numProperties_ = d.readInt<uint32_t>();
cell->deletedListHead_ = d.readInt<uint32_t>();
cell->deletedListSize_ = d.readInt<uint32_t>();
// Read the whole storage into the trailing objects.
size_t size = d.readInt<uint32_t>();
assert(
DictPropertyMap::allocationSize(
cell->descriptorCapacity_, cell->hashCapacity_) ==
size + sizeof(DictPropertyMap) &&
"allocation size doesn't match");
d.readData(cell->getDescriptorPairs(), size);
d.endObject(cell);
}
#endif
std::pair<bool, DictPropertyMap::HashPair *> DictPropertyMap::lookupEntryFor(
DictPropertyMap *self,
SymbolID symbolID) {
++NumDictLookups;
size_type const mask = self->hashCapacity_ - 1;
size_type index = hash(symbolID) & mask;
// Probing step.
size_type step = 1;
// Save the address of the start of the table to avoid recalculating it.
HashPair *const tableStart = self->getHashPairs();
// The first deleted entry we found.
HashPair *deleted = nullptr;
assert(symbolID.isValid() && "looking for an invalid SymbolID");
for (;;) {
HashPair *curEntry = tableStart + index;
if (curEntry->isValid()) {
if (self->isMatch(curEntry, symbolID))
return {true, curEntry};
} else if (curEntry->isEmpty()) {
// If we encountered an empty pair, the search is over - we failed.
// Return either this entry or a deleted one, if we encountered one.
return {false, deleted ? deleted : curEntry};
} else {
assert(curEntry->isDeleted() && "unexpected HashPair state");
// The first time we encounter a deleted entry, record it so we can
// potentially reuse it for insertion.
if (!deleted)
deleted = curEntry;
}
++NumExtraHashProbes;
index = (index + step) & mask;
++step;
}
}
ExecutionStatus DictPropertyMap::grow(
MutableHandle<DictPropertyMap> &selfHandleRef,
Runtime *runtime,
size_type newCapacity) {
auto res = create(runtime, newCapacity);
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
auto *newSelf = res->get();
auto *self = *selfHandleRef;
selfHandleRef = newSelf;
auto *dst = newSelf->getDescriptorPairs();
size_type count = 0;
for (auto *src = self->getDescriptorPairs(),
*e = src + self->numDescriptors_.load(std::memory_order_relaxed);
src != e;
++src) {
if (src->first.isInvalid())
continue;
auto key = src->first;
dst->first = key;
dst->second = src->second;
auto result = lookupEntryFor(newSelf, key);
assert(!result.first && "found duplicate entry while growing");
result.second->setDescIndex(count, key);
++dst;
++count;
}
assert(
count == self->numProperties_ && "numProperties mismatch when growing");
newSelf->numProperties_ = count;
// Transfer the deleted list to the new instance.
auto deletedIndex = self->deletedListHead_;
if (deletedIndex != END_OF_LIST) {
newSelf->deletedListHead_ = count;
newSelf->deletedListSize_ = self->deletedListSize_;
do {
const auto *src = self->getDescriptorPairs() + deletedIndex;
assert(
src->first == SymbolID::deleted() &&
"pair in the deleted list is not marked as deleted");
dst->first = SymbolID::deleted();
dst->second.slot = src->second.slot;
deletedIndex = getNextDeletedIndex(src);
setNextDeletedIndex(
dst, deletedIndex != END_OF_LIST ? count + 1 : END_OF_LIST);
++dst;
++count;
} while (deletedIndex != END_OF_LIST);
}
newSelf->numDescriptors_.store(count, std::memory_order_release);
assert(count <= newSelf->descriptorCapacity_);
return ExecutionStatus::RETURNED;
}
CallResult<std::pair<NamedPropertyDescriptor *, bool>>
DictPropertyMap::findOrAdd(
MutableHandle<DictPropertyMap> &selfHandleRef,
Runtime *runtime,
SymbolID id) {
auto *self = *selfHandleRef;
auto numDescriptors = self->numDescriptors_.load(std::memory_order_relaxed);
auto found = lookupEntryFor(self, id);
if (found.first) {
return std::make_pair(
&self->getDescriptorPairs()[found.second->getDescIndex()].second,
false);
}
// We want to grow the hash table if the number of occupied hash entries
// exceeds 75% of capacity or if the descriptor array is full. Since the
// capacity of the table is 4/3 of the capacity of the descriptor array, it is
// sufficient to only check for the latter.
if (numDescriptors == self->descriptorCapacity_) {
size_type newCapacity;
if (self->numProperties_ == self->descriptorCapacity_) {
// Double the new capacity, up to kMaxCapacity. However make sure that
// we try to allocate at least one extra property. If we are already
// exactly at kMaxCapacity, there is nothing we can do, so grow() will
// simply fail.
newCapacity = self->numProperties_ * 2;
if (newCapacity > detail::kMaxCapacity)
newCapacity =
std::max(toRValue(detail::kMaxCapacity), self->numProperties_ + 1);
} else {
// Calculate the new capacity to be exactly as much as we need to
// accomodate the deleted list plus one extra property. It it happens
// to exceed kMaxCapacity, there is nothing we can do, so grow() will
// raise an exception.
newCapacity = self->numProperties_ + 1 + self->deletedListSize_;
}
if (LLVM_UNLIKELY(
grow(selfHandleRef, runtime, newCapacity) ==
ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
self = *selfHandleRef;
numDescriptors = self->numDescriptors_.load(std::memory_order_relaxed);
found = lookupEntryFor(self, id);
}
++self->numProperties_;
if (found.second->isDeleted())
self->decDeletedHashCount();
found.second->setDescIndex(numDescriptors, id);
auto *descPair = self->getDescriptorPairs() + numDescriptors;
descPair->first = id;
self->numDescriptors_.fetch_add(1, std::memory_order_acq_rel);
return std::make_pair(&descPair->second, true);
}
void DictPropertyMap::erase(DictPropertyMap *self, PropertyPos pos) {
auto *hashPair = self->getHashPairs() + pos.hashPairIndex;
auto descIndex = hashPair->getDescIndex();
assert(
descIndex < self->numDescriptors_.load(std::memory_order_relaxed) &&
"descriptor index out of range");
auto *descPair = self->getDescriptorPairs() + descIndex;
assert(
descPair->first != SymbolID::empty() &&
"accessing deleted descriptor pair");
hashPair->setDeleted();
descPair->first = SymbolID::deleted();
// Add the descriptor to the deleted list.
setNextDeletedIndex(descPair, self->deletedListHead_);
self->deletedListHead_ = descIndex;
++self->deletedListSize_;
assert(self->numProperties_ != 0 && "num properties out of sync");
--self->numProperties_;
self->incDeletedHashCount();
}
SlotIndex DictPropertyMap::allocatePropertySlot(DictPropertyMap *self) {
// If there are no deleted properties, the number of properties corresponds
// exactly to the number of slots.
if (self->deletedListHead_ == END_OF_LIST)
return self->numProperties_;
auto *deletedPair = self->getDescriptorPairs() + self->deletedListHead_;
assert(
deletedPair->first == SymbolID::deleted() &&
"Head of deleted list is not deleted");
// Remove the first element from the deleted list.
self->deletedListHead_ = getNextDeletedIndex(deletedPair);
--self->deletedListSize_;
// Mark the pair as "invalid" instead of "deleted".
deletedPair->first = SymbolID::empty();
return deletedPair->second.slot;
}
void DictPropertyMap::dump() {
auto &OS = llvh::errs();
OS << "DictPropertyMap:" << getDebugAllocationId() << "\n";
OS << " HashPairs[" << hashCapacity_ << "]:\n";
for (unsigned i = 0; i < hashCapacity_; ++i) {
auto *pair = getHashPairs() + i;
if (pair->isValid()) {
OS << " " << pair->getDescIndex() << "\n";
} else if (pair->isEmpty()) {
OS << " (empty)\n";
} else {
assert(pair->isDeleted());
OS << " (deleted)\n";
}
}
OS << " Descriptors[" << descriptorCapacity_ << "]:\n";
for (unsigned i = 0; i < descriptorCapacity_; ++i) {
auto *pair = getDescriptorPairs() + i;
OS << " (" << pair->first << ", "
<< "(slot=" << pair->second.slot << "))\n";
}
}
} // namespace vm
} // namespace hermes