Skip to content

Commit 3c5b15c

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 7e6ae3e commit 3c5b15c

8 files changed

+248
-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 equal 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 equal 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

+101-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,103 @@ 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+
firstFreeBit_ = statusBits_.find_next_unset(firstFreeBit_);
137+
138+
auto res = oscompat::vm_commit(storage, sz);
113139
if (res) {
114-
oscompat::vm_name(storage, FixedSizeHeapSegment::storageSize(), name);
140+
oscompat::vm_name(storage, sz, name);
115141
}
116142
return res;
117143
}
118144

119-
void deleteStorageImpl(void *storage) override {
145+
void deleteStorageImpl(void *storage, size_t sz) override {
120146
assert(
121-
!llvh::alignmentAdjustment(
122-
storage, FixedSizeHeapSegment::storageSize()) &&
147+
!llvh::alignmentAdjustment(storage, kSegmentUnitSize) &&
123148
"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);
149+
assert(
150+
storage >= start_ && storage < start_ + size_ &&
151+
"Storage not in region");
152+
oscompat::vm_name(storage, sz, kFreeRegionName);
153+
oscompat::vm_uncommit(storage, sz);
154+
size_t numUnits = sz / kSegmentUnitSize;
155+
// Reset all bits for this storage.
156+
int startIndex = (static_cast<char *>(storage) - start_) / kSegmentUnitSize;
157+
statusBits_.reset(startIndex, startIndex + numUnits);
158+
if (startIndex < firstFreeBit_)
159+
firstFreeBit_ = startIndex;
129160
}
130161

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

139179
class MallocStorageProvider final : public StorageProvider {
140180
public:
141-
llvh::ErrorOr<void *> newStorageImpl(const char *name) override;
142-
void deleteStorageImpl(void *storage) override;
181+
llvh::ErrorOr<void *> newStorageImpl(size_t sz, const char *name) override;
182+
void deleteStorageImpl(void *storage, size_t sz) override;
143183

144184
private:
145185
/// Map aligned starts to actual starts for freeing.
@@ -149,46 +189,48 @@ class MallocStorageProvider final : public StorageProvider {
149189
};
150190

151191
llvh::ErrorOr<void *> VMAllocateStorageProvider::newStorageImpl(
192+
size_t sz,
152193
const char *name) {
153-
assert(FixedSizeHeapSegment::storageSize() % oscompat::page_size() == 0);
194+
assert(kSegmentUnitSize % oscompat::page_size() == 0);
154195
// 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());
196+
auto result =
197+
oscompat::vm_allocate_aligned(sz, kSegmentUnitSize, getMmapHint());
159198
if (!result) {
160199
return result;
161200
}
162201
void *mem = *result;
163202
assert(isAligned(mem));
164203
(void)&isAligned;
165-
#ifdef HERMESVM_ALLOW_HUGE_PAGES
166-
oscompat::vm_hugepage(mem, FixedSizeHeapSegment::storageSize());
167-
#endif
168-
204+
oscompat::vm_hugepage(mem, sz);
169205
// Name the memory region on platforms that support naming.
170-
oscompat::vm_name(mem, FixedSizeHeapSegment::storageSize(), name);
206+
oscompat::vm_name(mem, sz, name);
171207
return mem;
172208
}
173209

174-
void VMAllocateStorageProvider::deleteStorageImpl(void *storage) {
210+
void VMAllocateStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
175211
if (!storage) {
176212
return;
177213
}
178-
oscompat::vm_free_aligned(storage, FixedSizeHeapSegment::storageSize());
214+
oscompat::vm_free_aligned(storage, sz);
179215
}
180216

181-
llvh::ErrorOr<void *> MallocStorageProvider::newStorageImpl(const char *name) {
217+
llvh::ErrorOr<void *> MallocStorageProvider::newStorageImpl(
218+
size_t sz,
219+
const char *name) {
182220
// name is unused, can't name malloc memory.
183221
(void)name;
184-
void *mem = checkedMalloc2(FixedSizeHeapSegment::storageSize(), 2u);
222+
// Allocate size of sz + kSegmentUnitSize so that we could get an address
223+
// aligned to kSegmentUnitSize.
224+
void *mem = checkedMalloc2(/*count*/ 1u, sz + kSegmentUnitSize);
185225
void *lowLim = alignAlloc(mem);
186226
assert(isAligned(lowLim) && "New storage should be aligned");
187227
lowLimToAllocHandle_[lowLim] = mem;
188228
return lowLim;
189229
}
190230

191-
void MallocStorageProvider::deleteStorageImpl(void *storage) {
231+
void MallocStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
232+
// free() does not need the memory size.
233+
(void)sz;
192234
if (!storage) {
193235
return;
194236
}
@@ -218,8 +260,11 @@ std::unique_ptr<StorageProvider> StorageProvider::mallocProvider() {
218260
return std::unique_ptr<StorageProvider>(new MallocStorageProvider);
219261
}
220262

221-
llvh::ErrorOr<void *> StorageProvider::newStorage(const char *name) {
222-
auto res = newStorageImpl(name);
263+
llvh::ErrorOr<void *> StorageProvider::newStorage(size_t sz, const char *name) {
264+
assert(
265+
sz && (sz % kSegmentUnitSize == 0) &&
266+
"Allocated storage size must be multiples of kSegmentUnitSize");
267+
auto res = newStorageImpl(sz, name);
223268

224269
if (res) {
225270
numSucceededAllocs_++;
@@ -230,13 +275,17 @@ llvh::ErrorOr<void *> StorageProvider::newStorage(const char *name) {
230275
return res;
231276
}
232277

233-
void StorageProvider::deleteStorage(void *storage) {
278+
void StorageProvider::deleteStorage(void *storage, size_t sz) {
234279
if (!storage) {
235280
return;
236281
}
237282

283+
assert(
284+
sz && (sz % kSegmentUnitSize == 0) &&
285+
"Allocated storage size must be multiples of kSegmentUnitSize");
286+
238287
numDeletedAllocs_++;
239-
deleteStorageImpl(storage);
288+
return deleteStorageImpl(storage, sz);
240289
}
241290

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

0 commit comments

Comments
 (0)