Skip to content

Commit ec92a53

Browse files
lavenzgfacebook-github-bot
authored andcommitted
Add support of allocating different sizes of storage in StorageProvider (facebook#1504)
Summary: Pull Request resolved: facebook#1504 Large segment needs to be backed by a large storage size. StorageProvider currently always allocate fixed size of storage determined by HERMESVM_LOG_HEAP_SEGMENT_SIZE. This diffs adds support of allocating larger storage with below changes: 1. `newStorage()` and `deleteStorage()` takes additional `sz` parameter. 2. For `MallocStorageProvider` and `VMAllocateStorageProvider`, simply change the previous fixed storage size to passed in `sz`. 3. For `ContiguousVAStorageProvider`, use a BitVector to manage allocations and deallocations. This can be improved later if we observe fragmentations. The support of enabling different sizes of heap segment will be added later. Differential Revision: D61676721
1 parent 2e7e6d1 commit ec92a53

8 files changed

+253
-132
lines changed

include/hermes/VM/HeapRuntime.h

+4-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class HeapRuntime {
2222
public:
2323
~HeapRuntime() {
2424
runtime_->~RT();
25-
sp_->deleteStorage(runtime_);
25+
sp_->deleteStorage(runtime_, kHeapRuntimeStorageSize);
2626
}
2727

2828
/// Allocate a segment and create an aliased shared_ptr that points to the
@@ -36,17 +36,16 @@ class HeapRuntime {
3636

3737
private:
3838
HeapRuntime(std::shared_ptr<StorageProvider> sp) : sp_{std::move(sp)} {
39-
auto ptrOrError = sp_->newStorage("hermes-rt");
39+
auto ptrOrError = sp_->newStorage(kHeapRuntimeStorageSize, "hermes-rt");
4040
if (!ptrOrError)
4141
hermes_fatal("Cannot initialize Runtime storage.", ptrOrError.getError());
42-
static_assert(
43-
sizeof(RT) < FixedSizeHeapSegment::storageSize(),
44-
"Segments too small.");
42+
static_assert(sizeof(RT) < kHeapRuntimeStorageSize, "Segments too small.");
4543
runtime_ = static_cast<RT *>(*ptrOrError);
4644
}
4745

4846
std::shared_ptr<StorageProvider> sp_;
4947
RT *runtime_;
48+
static constexpr size_t kHeapRuntimeStorageSize = FixedSizeHeapSegment::kSize;
5049
};
5150
} // namespace vm
5251
} // namespace hermes

include/hermes/VM/LimitedStorageProvider.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ class LimitedStorageProvider final : public StorageProvider {
2929
: delegate_(std::move(provider)), limit_(limit) {}
3030

3131
protected:
32-
llvh::ErrorOr<void *> newStorageImpl(const char *name) override;
32+
llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) override;
3333

34-
void deleteStorageImpl(void *storage) override;
34+
void deleteStorageImpl(void *storage, size_t sz) override;
3535
};
3636

3737
} // namespace vm

include/hermes/VM/StorageProvider.h

+15-15
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,17 @@ class StorageProvider {
3737

3838
/// @}
3939

40-
/// Create a new segment memory space.
41-
llvh::ErrorOr<void *> newStorage() {
42-
return newStorage(nullptr);
43-
}
44-
/// Create a new segment memory space and give this memory the name \p name.
45-
/// \return A pointer to a block of memory that has
46-
/// FixedSizeHeapSegment::storageSize() bytes, and is aligned on
47-
/// FixedSizeHeapSegment::storageSize().
48-
llvh::ErrorOr<void *> newStorage(const char *name);
40+
/// \return A pointer to a block of memory that has \p sz bytes, and is
41+
/// aligned on AlignedHeapSegment::kSegmentUnitSize. Note that \p sz must
42+
/// be non-zero and equals to a multiple of
43+
/// AlignedHeapSegment::kSegmentUnitSize.
44+
llvh::ErrorOr<void *> newStorage(size_t sz, const char *name = nullptr);
4945

5046
/// Delete the given segment's memory space, and make it available for re-use.
51-
/// \post Nothing in the range [storage, storage +
52-
/// FixedSizeHeapSegment::storageSize()) is valid memory to be read or
53-
/// written.
54-
void deleteStorage(void *storage);
47+
/// Note that \p sz must be the same as used to allocating \p storage.
48+
/// \post Nothing in the range [storage, storage + sz) is valid memory to be
49+
/// read or written.
50+
void deleteStorage(void *storage, size_t sz);
5551

5652
/// The number of storages this provider has allocated in its lifetime.
5753
size_t numSucceededAllocs() const;
@@ -68,8 +64,12 @@ class StorageProvider {
6864
size_t numLiveAllocs() const;
6965

7066
protected:
71-
virtual llvh::ErrorOr<void *> newStorageImpl(const char *name) = 0;
72-
virtual void deleteStorageImpl(void *storage) = 0;
67+
/// \pre \p sz is non-zero and equals to a multiple of
68+
/// AlignedHeapSegment::kSegmentUnitSize.
69+
virtual llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) = 0;
70+
/// \pre \p sz is non-zero and equals to a multiple of
71+
/// AlignedHeapSegment::kSegmentUnitSize.
72+
virtual void deleteStorageImpl(void *storage, size_t sz) = 0;
7373

7474
private:
7575
size_t numSucceededAllocs_{0};

lib/VM/LimitedStorageProvider.cpp

+8-6
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@
1313
namespace hermes {
1414
namespace vm {
1515

16-
llvh::ErrorOr<void *> LimitedStorageProvider::newStorageImpl(const char *name) {
16+
llvh::ErrorOr<void *> LimitedStorageProvider::newStorageImpl(
17+
size_t sz,
18+
const char *name) {
1719
if (limit_ < FixedSizeHeapSegment::storageSize()) {
1820
return make_error_code(OOMError::TestVMLimitReached);
1921
}
20-
limit_ -= FixedSizeHeapSegment::storageSize();
21-
return delegate_->newStorage(name);
22+
limit_ -= sz;
23+
return delegate_->newStorage(sz, name);
2224
}
2325

24-
void LimitedStorageProvider::deleteStorageImpl(void *storage) {
26+
void LimitedStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
2527
if (!storage) {
2628
return;
2729
}
28-
delegate_->deleteStorage(storage);
29-
limit_ += FixedSizeHeapSegment::storageSize();
30+
delegate_->deleteStorage(storage, sz);
31+
limit_ += sz;
3032
}
3133

3234
} // namespace vm

lib/VM/StorageProvider.cpp

+106-52
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77

88
#include "hermes/VM/StorageProvider.h"
99

10+
#include "hermes/ADT/BitArray.h"
1011
#include "hermes/Support/CheckedMalloc.h"
1112
#include "hermes/Support/Compiler.h"
1213
#include "hermes/Support/OSCompat.h"
1314
#include "hermes/VM/AlignedHeapSegment.h"
1415

16+
#include "llvh/ADT/BitVector.h"
1517
#include "llvh/ADT/DenseMap.h"
1618
#include "llvh/Support/ErrorHandling.h"
1719
#include "llvh/Support/MathExtras.h"
@@ -55,14 +57,17 @@ namespace vm {
5557

5658
namespace {
5759

60+
/// Minimum segment storage size. Any larger segment size should be a multiple
61+
/// of it.
62+
constexpr auto kSegmentUnitSize = AlignedHeapSegment::kSegmentUnitSize;
63+
5864
bool isAligned(void *p) {
59-
return (reinterpret_cast<uintptr_t>(p) &
60-
(FixedSizeHeapSegment::storageSize() - 1)) == 0;
65+
return (reinterpret_cast<uintptr_t>(p) & (kSegmentUnitSize - 1)) == 0;
6166
}
6267

6368
char *alignAlloc(void *p) {
64-
return reinterpret_cast<char *>(llvh::alignTo(
65-
reinterpret_cast<uintptr_t>(p), FixedSizeHeapSegment::storageSize()));
69+
return reinterpret_cast<char *>(
70+
llvh::alignTo(reinterpret_cast<uintptr_t>(p), kSegmentUnitSize));
6671
}
6772

6873
void *getMmapHint() {
@@ -78,68 +83,108 @@ void *getMmapHint() {
7883

7984
class VMAllocateStorageProvider final : public StorageProvider {
8085
public:
81-
llvh::ErrorOr<void *> newStorageImpl(const char *name) override;
82-
void deleteStorageImpl(void *storage) override;
86+
llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) override;
87+
void deleteStorageImpl(void *storage, size_t sz) override;
8388
};
8489

8590
class ContiguousVAStorageProvider final : public StorageProvider {
8691
public:
8792
ContiguousVAStorageProvider(size_t size)
88-
: size_(llvh::alignTo<FixedSizeHeapSegment::storageSize()>(size)) {
89-
auto result = oscompat::vm_reserve_aligned(
90-
size_, FixedSizeHeapSegment::storageSize(), getMmapHint());
93+
: size_(llvh::alignTo<kSegmentUnitSize>(size)),
94+
statusBits_(size_ / kSegmentUnitSize) {
95+
auto result =
96+
oscompat::vm_reserve_aligned(size_, kSegmentUnitSize, getMmapHint());
9197
if (!result)
9298
hermes_fatal("Contiguous storage allocation failed.", result.getError());
93-
level_ = start_ = static_cast<char *>(*result);
99+
start_ = static_cast<char *>(*result);
94100
oscompat::vm_name(start_, size_, kFreeRegionName);
95101
}
96102
~ContiguousVAStorageProvider() override {
97103
oscompat::vm_release_aligned(start_, size_);
98104
}
99105

100-
llvh::ErrorOr<void *> newStorageImpl(const char *name) override {
106+
llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) override {
107+
// No available space to use.
108+
if (LLVM_UNLIKELY(firstFreeBit_ == -1)) {
109+
return make_error_code(OOMError::MaxStorageReached);
110+
}
111+
112+
assert(
113+
statusBits_.find_first_unset() == firstFreeBit_ &&
114+
"firstFreeBit_ should always be the first unset bit");
115+
101116
void *storage;
102-
if (!freelist_.empty()) {
103-
storage = freelist_.back();
104-
freelist_.pop_back();
105-
} else if (level_ < start_ + size_) {
106-
storage =
107-
std::exchange(level_, level_ + FixedSizeHeapSegment::storageSize());
108-
} else {
117+
int numUnits = sz / kSegmentUnitSize;
118+
int nextUsedBit = statusBits_.find_next(firstFreeBit_);
119+
int curFreeBit = firstFreeBit_;
120+
// Search for a large enough continuous bit range.
121+
while (nextUsedBit != -1 && (nextUsedBit - curFreeBit < numUnits)) {
122+
curFreeBit = statusBits_.find_next_unset(nextUsedBit);
123+
if (curFreeBit == -1) {
124+
return make_error_code(OOMError::MaxStorageReached);
125+
}
126+
nextUsedBit = statusBits_.find_next(curFreeBit);
127+
}
128+
// nextUsedBit could be -1, so check if there is enough space left.
129+
if (nextUsedBit == -1 && curFreeBit + numUnits > (int)statusBits_.size()) {
109130
return make_error_code(OOMError::MaxStorageReached);
110131
}
111-
auto res =
112-
oscompat::vm_commit(storage, FixedSizeHeapSegment::storageSize());
132+
133+
storage = start_ + curFreeBit * kSegmentUnitSize;
134+
statusBits_.set(curFreeBit, curFreeBit + numUnits);
135+
// Reset it to the new leftmost free bit.
136+
// If curFreeBit != firstFreeBit_, it means the current firstFreeBit_ is
137+
// still unset, and is certainly leftmost free bit.
138+
if (curFreeBit == firstFreeBit_) {
139+
// Subtracted by 1 since curFreeBit + numUnits might be the end.
140+
firstFreeBit_ = statusBits_.find_next_unset(curFreeBit + numUnits - 1);
141+
}
142+
143+
auto res = oscompat::vm_commit(storage, sz);
113144
if (res) {
114-
oscompat::vm_name(storage, FixedSizeHeapSegment::storageSize(), name);
145+
oscompat::vm_name(storage, sz, name);
115146
}
116147
return res;
117148
}
118149

119-
void deleteStorageImpl(void *storage) override {
150+
void deleteStorageImpl(void *storage, size_t sz) override {
120151
assert(
121-
!llvh::alignmentAdjustment(
122-
storage, FixedSizeHeapSegment::storageSize()) &&
152+
!llvh::alignmentAdjustment(storage, kSegmentUnitSize) &&
123153
"Storage not aligned");
124-
assert(storage >= start_ && storage < level_ && "Storage not in region");
125-
oscompat::vm_name(
126-
storage, FixedSizeHeapSegment::storageSize(), kFreeRegionName);
127-
oscompat::vm_uncommit(storage, FixedSizeHeapSegment::storageSize());
128-
freelist_.push_back(storage);
154+
assert(
155+
storage >= start_ && storage < start_ + size_ &&
156+
"Storage not in region");
157+
oscompat::vm_name(storage, sz, kFreeRegionName);
158+
oscompat::vm_uncommit(storage, sz);
159+
size_t numUnits = sz / kSegmentUnitSize;
160+
// Reset all bits for this storage.
161+
int startIndex = (static_cast<char *>(storage) - start_) / kSegmentUnitSize;
162+
statusBits_.reset(startIndex, startIndex + numUnits);
163+
if (startIndex < firstFreeBit_)
164+
firstFreeBit_ = startIndex;
129165
}
130166

131167
private:
132168
static constexpr const char *kFreeRegionName = "hermes-free-heap";
133169
size_t size_;
134170
char *start_;
135-
char *level_;
136-
llvh::SmallVector<void *, 0> freelist_;
171+
/// First free bit in \c statusBits_. We always make new allocation from the
172+
/// leftmost free bit, based on heuristics:
173+
/// 1. Usually the reserved address space is not full.
174+
/// 2. Storage with size kSegmentUnitSize is allocated and deleted more
175+
/// frequently than larger storage.
176+
/// 3. Likely small storage will find space available from leftmost free bit,
177+
/// leaving enough space at the right side for large storage.
178+
int firstFreeBit_{0};
179+
/// One bit for each kSegmentUnitSize space in the entire reserved virtual
180+
/// address space. A bit is set if the corresponding space is used.
181+
llvh::BitVector statusBits_;
137182
};
138183

139184
class MallocStorageProvider final : public StorageProvider {
140185
public:
141-
llvh::ErrorOr<void *> newStorageImpl(const char *name) override;
142-
void deleteStorageImpl(void *storage) override;
186+
llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) override;
187+
void deleteStorageImpl(void *storage, size_t sz) override;
143188

144189
private:
145190
/// Map aligned starts to actual starts for freeing.
@@ -149,46 +194,48 @@ class MallocStorageProvider final : public StorageProvider {
149194
};
150195

151196
llvh::ErrorOr<void *> VMAllocateStorageProvider::newStorageImpl(
197+
size_t sz,
152198
const char *name) {
153-
assert(FixedSizeHeapSegment::storageSize() % oscompat::page_size() == 0);
199+
assert(kSegmentUnitSize % oscompat::page_size() == 0);
154200
// Allocate the space, hoping it will be the correct alignment.
155-
auto result = oscompat::vm_allocate_aligned(
156-
FixedSizeHeapSegment::storageSize(),
157-
FixedSizeHeapSegment::storageSize(),
158-
getMmapHint());
201+
auto result =
202+
oscompat::vm_allocate_aligned(sz, kSegmentUnitSize, getMmapHint());
159203
if (!result) {
160204
return result;
161205
}
162206
void *mem = *result;
163207
assert(isAligned(mem));
164208
(void)&isAligned;
165-
#ifdef HERMESVM_ALLOW_HUGE_PAGES
166-
oscompat::vm_hugepage(mem, FixedSizeHeapSegment::storageSize());
167-
#endif
168-
209+
oscompat::vm_hugepage(mem, sz);
169210
// Name the memory region on platforms that support naming.
170-
oscompat::vm_name(mem, FixedSizeHeapSegment::storageSize(), name);
211+
oscompat::vm_name(mem, sz, name);
171212
return mem;
172213
}
173214

174-
void VMAllocateStorageProvider::deleteStorageImpl(void *storage) {
215+
void VMAllocateStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
175216
if (!storage) {
176217
return;
177218
}
178-
oscompat::vm_free_aligned(storage, FixedSizeHeapSegment::storageSize());
219+
oscompat::vm_free_aligned(storage, sz);
179220
}
180221

181-
llvh::ErrorOr<void *> MallocStorageProvider::newStorageImpl(const char *name) {
222+
llvh::ErrorOr<void *> MallocStorageProvider::newStorageImpl(
223+
size_t sz,
224+
const char *name) {
182225
// name is unused, can't name malloc memory.
183226
(void)name;
184-
void *mem = checkedMalloc2(FixedSizeHeapSegment::storageSize(), 2u);
227+
// Allocate size of sz + kSegmentUnitSize so that we could get an address
228+
// aligned to kSegmentUnitSize.
229+
void *mem = checkedMalloc2(/*count*/ 1u, sz + kSegmentUnitSize);
185230
void *lowLim = alignAlloc(mem);
186231
assert(isAligned(lowLim) && "New storage should be aligned");
187232
lowLimToAllocHandle_[lowLim] = mem;
188233
return lowLim;
189234
}
190235

191-
void MallocStorageProvider::deleteStorageImpl(void *storage) {
236+
void MallocStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
237+
// free() does not need the memory size.
238+
(void)sz;
192239
if (!storage) {
193240
return;
194241
}
@@ -218,8 +265,11 @@ std::unique_ptr<StorageProvider> StorageProvider::mallocProvider() {
218265
return std::unique_ptr<StorageProvider>(new MallocStorageProvider);
219266
}
220267

221-
llvh::ErrorOr<void *> StorageProvider::newStorage(const char *name) {
222-
auto res = newStorageImpl(name);
268+
llvh::ErrorOr<void *> StorageProvider::newStorage(size_t sz, const char *name) {
269+
assert(
270+
sz && (sz % kSegmentUnitSize == 0) &&
271+
"Allocated storage size must be multiples of kSegmentUnitSize");
272+
auto res = newStorageImpl(sz, name);
223273

224274
if (res) {
225275
numSucceededAllocs_++;
@@ -230,13 +280,17 @@ llvh::ErrorOr<void *> StorageProvider::newStorage(const char *name) {
230280
return res;
231281
}
232282

233-
void StorageProvider::deleteStorage(void *storage) {
283+
void StorageProvider::deleteStorage(void *storage, size_t sz) {
234284
if (!storage) {
235285
return;
236286
}
237287

288+
assert(
289+
sz && (sz % kSegmentUnitSize == 0) &&
290+
"Allocated storage size must be multiples of kSegmentUnitSize");
291+
238292
numDeletedAllocs_++;
239-
deleteStorageImpl(storage);
293+
return deleteStorageImpl(storage, sz);
240294
}
241295

242296
llvh::ErrorOr<std::pair<void *, size_t>>

0 commit comments

Comments
 (0)