Skip to content

Commit e0cd6e0

Browse files
Riley Dulinfacebook-github-bot
authored andcommitted
Add write barriers to all places where an array length is shortened
Summary: In Hades, a write barrier is needed when a pointer becomes unreachable. This primarily happens when the GC has a different set of things that might be marked. Currently, the only way that can happen is if an array's length is reduced. Whenever that can occur, call a new API on GCHermesValue: `unreachableWriteBarrier`, to alert the GC of the change. Reviewed By: davedets Differential Revision: D23362654 fbshipit-source-id: 4125f899774336e60aafd7282310aa7d25388a2b
1 parent fecb9ff commit e0cd6e0

8 files changed

Lines changed: 115 additions & 36 deletions

File tree

include/hermes/VM/ArrayStorage.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,14 +156,10 @@ class ArrayStorage final
156156
const size_type sz = size();
157157
assert(sz > 0 && "Can't pop from empty ArrayStorage");
158158
HermesValue val = data()[sz - 1];
159-
#ifdef HERMESVM_GC_HADES
160159
// In Hades, a snapshot write barrier must be executed on the value that is
161160
// conceptually being changed to null. The write doesn't need to occur, but
162161
// it is the only correct way to use the write barrier.
163-
data()[sz - 1].set(HermesValue::encodeEmptyValue(), &runtime->getHeap());
164-
#else
165-
(void)runtime;
166-
#endif
162+
data()[sz - 1].unreachableWriteBarrier(&runtime->getHeap());
167163
// The background thread can't mutate size, so we don't need fetch_sub here.
168164
// Relaxed is fine, because the GC doesn't care about the order of seeing
169165
// the length and the individual elements, as long as illegal HermesValues

include/hermes/VM/HadesGC.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ class HadesGC final : public GCBase {
138138
void constructorWriteBarrier(void *loc, HermesValue value);
139139
void constructorWriteBarrier(void *loc, void *value);
140140

141+
void snapshotWriteBarrier(GCHermesValue *loc);
142+
void snapshotWriteBarrierRange(GCHermesValue *start, uint32_t numHVs);
143+
141144
void weakRefReadBarrier(void *value);
142145
void weakRefReadBarrier(HermesValue value);
143146

@@ -552,11 +555,12 @@ class HadesGC final : public GCBase {
552555
void scanDirtyCards(EvacAcceptor &acceptor);
553556

554557
/// Common logic for doing the Snapshot At The Beginning (SATB) write barrier.
555-
void snapshotWriteBarrier(GCCell *oldValue);
558+
void snapshotWriteBarrierInternal(GCCell *oldValue);
556559

557560
/// Common logic for doing the Snapshot At The Beginning (SATB) write barrier.
558-
/// Forwards to \c snapshotWriteBarrier(GCCell *) if oldValue is a pointer.
559-
void snapshotWriteBarrier(HermesValue oldValue);
561+
/// Forwards to \c snapshotWriteBarrierInternal(GCCell *) if oldValue is a
562+
/// pointer.
563+
void snapshotWriteBarrierInternal(HermesValue oldValue);
560564

561565
/// Common logic for doing the generational write barrier for detecting
562566
/// pointers into YG.

include/hermes/VM/HermesValue-inline.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ void GCHermesValue::setNonPtr(HermesValue hv, GC *gc) {
7171
setNoBarrier(hv);
7272
}
7373

74+
void GCHermesValue::unreachableWriteBarrier(GC *gc) {
75+
#ifdef HERMESVM_GC_HADES
76+
// Hades needs a snapshot barrier executed when something becomes unreachable.
77+
gc->snapshotWriteBarrier(this);
78+
#endif
79+
}
80+
7481
/*static*/
7582
template <typename InputIt>
7683
inline void
@@ -183,6 +190,15 @@ inline OutputIt GCHermesValue::copy_backward(
183190
return result;
184191
}
185192

193+
inline void GCHermesValue::rangeUnreachableWriteBarrier(
194+
GCHermesValue *first,
195+
GCHermesValue *last,
196+
GC *gc) {
197+
#ifdef HERMESVM_GC_HADES
198+
gc->snapshotWriteBarrierRange(first, last - first);
199+
#endif
200+
}
201+
186202
inline void GCHermesValue::copyToPinned(
187203
const GCHermesValue *first,
188204
const GCHermesValue *last,

include/hermes/VM/HermesValue.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,13 @@ class GCHermesValue : public HermesValue {
541541
/// Some GCs still need to do a write barrier though, so pass a GC parameter.
542542
inline void setNonPtr(HermesValue hv, GC *gc);
543543

544+
/// Force a write barrier to occur on this value, as if the value was being
545+
/// set to null. This should be used when a value is becoming unreachable by
546+
/// the GC, without having anything written to its memory.
547+
/// NOTE: This barrier is typically used when a variable-sized object's length
548+
/// decreases.
549+
inline void unreachableWriteBarrier(GC *gc);
550+
544551
/// Fills a region of GCHermesValues defined by [\p first, \p last) with the
545552
/// value \p fill. If the fill value is an object pointer, must
546553
/// provide a non-null \p gc argument, to perform write barriers.
@@ -572,6 +579,13 @@ class GCHermesValue : public HermesValue {
572579
static inline OutputIt
573580
copy_backward(InputIt first, InputIt last, OutputIt result, GC *gc);
574581

582+
/// Same as \c unreachableWriteBarrier, but for a range of values all becoming
583+
/// unreachable.
584+
static inline void rangeUnreachableWriteBarrier(
585+
GCHermesValue *first,
586+
GCHermesValue *last,
587+
GC *gc);
588+
575589
/// Copies a range of values to a non-heap location, e.g., the JS stack.
576590
static inline void copyToPinned(
577591
const GCHermesValue *first,

include/hermes/VM/SegmentedArray.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ class SegmentedArray final
8585
/// It is the caller's responsibility to ensure that the newly used portion
8686
/// will contain valid values before they are accessed (including accesses
8787
/// by the GC).
88+
/// \pre This cannot be called if kConcurrentGC is true, since the GC might
89+
/// read uninitialized memory even if the mutator wouldn't.
8890
void setLengthWithoutFilling(uint32_t newLength) {
91+
assert(!kConcurrentGC && "Cannot avoid filling for a concurrent GC");
8992
assert(newLength <= kMaxLength && "Cannot set length to more than size");
9093
length_.store(newLength, std::memory_order_release);
9194
}

lib/VM/ArrayStorage.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,12 @@ void ArrayStorage::resizeWithinCapacity(
169169
HermesValue::encodeEmptyValue(),
170170
&runtime->getHeap());
171171
} else if (newSize < sz) {
172-
#ifdef HERMESVM_GC_HADES
173172
// Execute write barriers on elements about to be conceptually changed to
174173
// null.
175174
// This also means if an array is refilled, it can treat the memory here
176175
// as uninitialized safely.
177-
GCHermesValue::fill(
178-
self->data() + newSize,
179-
self->data() + sz,
180-
HermesValue::encodeEmptyValue(),
181-
&runtime->getHeap());
182-
#endif
176+
GCHermesValue::rangeUnreachableWriteBarrier(
177+
self->data() + newSize, self->data() + sz, &runtime->getHeap());
183178
}
184179
self->size_.store(newSize, std::memory_order_release);
185180
}
@@ -191,6 +186,8 @@ ExecutionStatus ArrayStorage::shift(
191186
size_type toFirst,
192187
size_type toLast) {
193188
assert(toLast <= maxElements() && "size overflows 32-bit storage");
189+
assert(toFirst <= toLast && "First must be before last");
190+
assert(fromFirst <= selfHandle->size() && "fromFirst must be before size");
194191

195192
// If we don't need to expand the capacity.
196193
if (toLast <= selfHandle->capacity_) {
@@ -229,6 +226,13 @@ ExecutionStatus ArrayStorage::shift(
229226
HermesValue::encodeEmptyValue(),
230227
&runtime->getHeap());
231228
}
229+
if (toLast < self->size()) {
230+
// Some elements are becoming unreachable, let the GC know.
231+
GCHermesValue::rangeUnreachableWriteBarrier(
232+
self->data() + toLast,
233+
self->data() + self->size(),
234+
&runtime->getHeap());
235+
}
232236
self->size_.store(toLast, std::memory_order_release);
233237
return ExecutionStatus::RETURNED;
234238
}

lib/VM/SegmentedArray.cpp

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,13 @@ void SegmentedArray::Segment::setLength(Runtime *runtime, uint32_t newLength) {
8181
data_ + newLength,
8282
HermesValue::encodeEmptyValue(),
8383
&runtime->getHeap());
84+
length_.store(newLength, std::memory_order_release);
85+
} else if (newLength < len) {
86+
// If length is decreasing a write barrier needs to be done.
87+
GCHermesValue::rangeUnreachableWriteBarrier(
88+
data_ + newLength, data_ + len, &runtime->getHeap());
89+
length_.store(newLength, std::memory_order_release);
8490
}
85-
// If length is decreasing nothing special needs to be done.
86-
setLengthWithoutFilling(newLength);
8791
}
8892

8993
VTable SegmentedArray::vt(
@@ -332,8 +336,9 @@ ExecutionStatus SegmentedArray::growLeft(
332336
return ExecutionStatus::EXCEPTION;
333337
}
334338
PseudoHandle<SegmentedArray> newSegmentedArray = std::move(*arrRes);
335-
// Don't fill with empty values, most will be copied in.
336-
newSegmentedArray = increaseSize</*Fill*/ false>(
339+
// If it's not a concurrent GC, don't fill with empty values, most will be
340+
// copied in.
341+
newSegmentedArray = increaseSize</*Fill*/ kConcurrentGC>(
337342
runtime, std::move(newSegmentedArray), newSize);
338343
// Fill the beginning of the new array with empty values.
339344
GCHermesValue::uninitialized_fill(
@@ -389,6 +394,9 @@ void SegmentedArray::increaseSizeWithinCapacity(
389394
Runtime *runtime,
390395
size_type amount,
391396
bool fill) {
397+
assert(
398+
!kConcurrentGC ||
399+
fill && "If kConcurrentGC is true, fill must also be true");
392400
// This function has the same logic as increaseSize, but removes some
393401
// complexity from avoiding dealing with alllocations.
394402
const auto empty = HermesValue::encodeEmptyValue();
@@ -435,6 +443,9 @@ PseudoHandle<SegmentedArray> SegmentedArray::increaseSize(
435443
Runtime *runtime,
436444
PseudoHandle<SegmentedArray> self,
437445
size_type amount) {
446+
assert(
447+
!kConcurrentGC ||
448+
Fill && "If kConcurrentGC is true, fill must also be true");
438449
const auto empty = HermesValue::encodeEmptyValue();
439450
const auto currSize = self->size();
440451
const auto finalSize = currSize + amount;
@@ -530,18 +541,26 @@ PseudoHandle<SegmentedArray> SegmentedArray::increaseSize(
530541
}
531542

532543
void SegmentedArray::decreaseSize(Runtime *runtime, size_type amount) {
533-
assert(amount <= size() && "Cannot decrease size past zero");
534-
const auto finalSize = size() - amount;
535-
if (finalSize <= kValueToSegmentThreshold) {
536-
// Just adjust the field and exit, no segments to compress.
537-
numSlotsUsed_.store(finalSize, std::memory_order_release);
538-
return;
544+
const auto initialSize = size();
545+
const auto initialNumSlots = numSlotsUsed_.load(std::memory_order_relaxed);
546+
assert(amount <= initialSize && "Cannot decrease size past zero");
547+
const auto finalSize = initialSize - amount;
548+
const auto finalNumSlots = numSlotsForCapacity(finalSize);
549+
assert(
550+
finalNumSlots <= initialNumSlots &&
551+
"Should not be increasing the number of slots");
552+
if (finalSize > kValueToSegmentThreshold) {
553+
// Set the new last used segment's length to be the leftover.
554+
segmentAt(toSegment(finalSize - 1))
555+
->setLength(runtime, toInterior(finalSize - 1) + 1);
539556
}
540-
// Set the new last used segment's length to be the leftover.
541-
segmentAt(toSegment(finalSize - 1))
542-
->setLength(runtime, toInterior(finalSize - 1) + 1);
543-
numSlotsUsed_.store(
544-
numSlotsForCapacity(finalSize), std::memory_order_release);
557+
// Before shrinking, do a snapshot write barrier for the elements being
558+
// removed.
559+
GCHermesValue::rangeUnreachableWriteBarrier(
560+
inlineStorage() + finalNumSlots,
561+
inlineStorage() + initialNumSlots,
562+
&runtime->getHeap());
563+
numSlotsUsed_.store(finalNumSlots, std::memory_order_release);
545564
}
546565

547566
gcheapsize_t SegmentedArray::_trimSizeCallback(const GCCell *cell) {

lib/VM/gcs/HadesGC.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,7 +1240,7 @@ void HadesGC::writeBarrier(void *loc, HermesValue value) {
12401240
return;
12411241
}
12421242
if (concurrentPhase_.load(std::memory_order_acquire) == Phase::Mark) {
1243-
snapshotWriteBarrier(*static_cast<HermesValue *>(loc));
1243+
snapshotWriteBarrierInternal(*static_cast<HermesValue *>(loc));
12441244
}
12451245
if (!value.isPointer()) {
12461246
return;
@@ -1264,7 +1264,7 @@ void HadesGC::writeBarrier(void *loc, void *value) {
12641264
#else
12651265
GCCell *const oldValue = static_cast<GCCell *>(oldValueStorage);
12661266
#endif
1267-
snapshotWriteBarrier(oldValue);
1267+
snapshotWriteBarrierInternal(oldValue);
12681268
}
12691269
// Always do the non-snapshot write barrier in order for YG to be able to
12701270
// scan cards.
@@ -1294,7 +1294,30 @@ void HadesGC::constructorWriteBarrier(void *loc, void *value) {
12941294
generationalWriteBarrier(loc, value);
12951295
}
12961296

1297-
void HadesGC::snapshotWriteBarrier(GCCell *oldValue) {
1297+
void HadesGC::snapshotWriteBarrier(GCHermesValue *loc) {
1298+
if (inYoungGen(loc)) {
1299+
// A pointer that lives in YG never needs any write barriers.
1300+
return;
1301+
}
1302+
if (concurrentPhase_.load(std::memory_order_acquire) == Phase::Mark) {
1303+
snapshotWriteBarrierInternal(*loc);
1304+
}
1305+
}
1306+
1307+
void HadesGC::snapshotWriteBarrierRange(GCHermesValue *start, uint32_t numHVs) {
1308+
if (inYoungGen(start)) {
1309+
// A pointer that lives in YG never needs any write barriers.
1310+
return;
1311+
}
1312+
if (concurrentPhase_.load(std::memory_order_acquire) != Phase::Mark) {
1313+
return;
1314+
}
1315+
for (uint32_t i = 0; i < numHVs; ++i) {
1316+
snapshotWriteBarrierInternal(start[i]);
1317+
}
1318+
}
1319+
1320+
void HadesGC::snapshotWriteBarrierInternal(GCCell *oldValue) {
12981321
assert(
12991322
(!oldValue || oldValue->isValid()) &&
13001323
"Invalid cell encountered in snapshotWriteBarrier");
@@ -1306,9 +1329,9 @@ void HadesGC::snapshotWriteBarrier(GCCell *oldValue) {
13061329
}
13071330
}
13081331

1309-
void HadesGC::snapshotWriteBarrier(HermesValue oldValue) {
1332+
void HadesGC::snapshotWriteBarrierInternal(HermesValue oldValue) {
13101333
if (oldValue.isPointer()) {
1311-
snapshotWriteBarrier(static_cast<GCCell *>(oldValue.getPointer()));
1334+
snapshotWriteBarrierInternal(static_cast<GCCell *>(oldValue.getPointer()));
13121335
}
13131336
}
13141337

@@ -1339,7 +1362,7 @@ void HadesGC::weakRefReadBarrier(void *value) {
13391362
return;
13401363
case Phase::Mark:
13411364
// Treat the value read from the weak reference as live.
1342-
snapshotWriteBarrier(static_cast<GCCell *>(value));
1365+
snapshotWriteBarrierInternal(static_cast<GCCell *>(value));
13431366
return;
13441367
}
13451368
llvm_unreachable("All phases should be handled");

0 commit comments

Comments
 (0)