Skip to content

Commit fa697a5

Browse files
lavenzgfacebook-github-bot
authored andcommitted
Allow allocating segment larger than 4MB in StorageProvider
Differential Revision: D61676721
1 parent c9c0777 commit fa697a5

8 files changed

+239
-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("hermes-rt", kHeapRuntimeStorageSize);
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(const char *name, size_t sz) 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

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

3838
/// @}
3939

40-
/// Create a new segment memory space.
41-
llvh::ErrorOr<void *> newStorage() {
42-
return newStorage(nullptr);
40+
/// Create a new segment memory space with given size \p sz.
41+
llvh::ErrorOr<void *> newStorage(size_t sz) {
42+
return newStorage(nullptr, sz);
4343
}
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);
44+
/// \return A pointer to a block of memory that has \p sz bytes, and is
45+
/// aligned on AlignedHeapSegment::kSegmentUnitSize. Note that \p sz must
46+
/// be non-zero and equals to a multiple of
47+
/// AlignedHeapSegment::kSegmentUnitSize.
48+
llvh::ErrorOr<void *> newStorage(const char *name, size_t sz);
4949

5050
/// 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);
51+
/// Note that \p sz must be the same as used to allocating \p storage.
52+
/// \post Nothing in the range [storage, storage + sz) is valid memory to be
53+
/// read or written.
54+
void deleteStorage(void *storage, size_t sz);
5555

5656
/// The number of storages this provider has allocated in its lifetime.
5757
size_t numSucceededAllocs() const;
@@ -68,8 +68,8 @@ class StorageProvider {
6868
size_t numLiveAllocs() const;
6969

7070
protected:
71-
virtual llvh::ErrorOr<void *> newStorageImpl(const char *name) = 0;
72-
virtual void deleteStorageImpl(void *storage) = 0;
71+
virtual llvh::ErrorOr<void *> newStorageImpl(const char *name, size_t sz) = 0;
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+
const char *name,
18+
size_t sz) {
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(name, sz);
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

+96-53
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+
static constexpr size_t 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(const char *name, size_t sz) 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(const char *name, size_t sz) 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_first_unset();
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(const char *name, size_t sz) 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,46 @@ class MallocStorageProvider final : public StorageProvider {
149189
};
150190

151191
llvh::ErrorOr<void *> VMAllocateStorageProvider::newStorageImpl(
152-
const char *name) {
153-
assert(FixedSizeHeapSegment::storageSize() % oscompat::page_size() == 0);
192+
const char *name,
193+
size_t sz) {
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+
const char *name,
219+
size_t sz) {
182220
// name is unused, can't name malloc memory.
183221
(void)name;
184-
void *mem = checkedMalloc2(FixedSizeHeapSegment::storageSize(), 2u);
222+
void *mem = checkedMalloc2(sz, 2u);
185223
void *lowLim = alignAlloc(mem);
186224
assert(isAligned(lowLim) && "New storage should be aligned");
187225
lowLimToAllocHandle_[lowLim] = mem;
188226
return lowLim;
189227
}
190228

191-
void MallocStorageProvider::deleteStorageImpl(void *storage) {
229+
void MallocStorageProvider::deleteStorageImpl(void *storage, size_t sz) {
230+
// free() does not need the memory size.
231+
(void)sz;
192232
if (!storage) {
193233
return;
194234
}
@@ -218,8 +258,11 @@ std::unique_ptr<StorageProvider> StorageProvider::mallocProvider() {
218258
return std::unique_ptr<StorageProvider>(new MallocStorageProvider);
219259
}
220260

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

224267
if (res) {
225268
numSucceededAllocs_++;
@@ -230,13 +273,13 @@ llvh::ErrorOr<void *> StorageProvider::newStorage(const char *name) {
230273
return res;
231274
}
232275

233-
void StorageProvider::deleteStorage(void *storage) {
276+
void StorageProvider::deleteStorage(void *storage, size_t sz) {
234277
if (!storage) {
235278
return;
236279
}
237280

238281
numDeletedAllocs_++;
239-
deleteStorageImpl(storage);
282+
return deleteStorageImpl(storage, sz);
240283
}
241284

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

lib/VM/gcs/AlignedHeapSegment.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ llvh::ErrorOr<FixedSizeHeapSegment> FixedSizeHeapSegment::create(
5252
llvh::ErrorOr<FixedSizeHeapSegment> FixedSizeHeapSegment::create(
5353
StorageProvider *provider,
5454
const char *name) {
55-
auto result = provider->newStorage(name);
55+
auto result = provider->newStorage(name, storageSize());
5656
if (!result) {
5757
return result.getError();
5858
}
@@ -106,7 +106,7 @@ FixedSizeHeapSegment::~FixedSizeHeapSegment() {
106106
__asan_unpoison_memory_region(start(), end() - start());
107107

108108
if (provider_) {
109-
provider_->deleteStorage(lowLim_);
109+
provider_->deleteStorage(lowLim_, storageSize());
110110
}
111111
}
112112

0 commit comments

Comments
 (0)