Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit 384fb3d

Browse files
committed
Bug 1706694 - When flattening ropes, store the parent pointer in the left child field rather than overwriting the cell header r=jandem
This moves the part that sets the charater data pointer to finish_node as this also shares this field. We pass the parent pointer (and flags) into first_visit_node and set these after we've extracted the left child pointer. Differential Revision: https://phabricator.services.mozilla.com/D113316
1 parent b90eab4 commit 384fb3d

File tree

4 files changed

+54
-64
lines changed

4 files changed

+54
-64
lines changed

js/src/gc/Cell.h

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,9 @@ struct Cell {
142142
// compacting GC and is now a RelocationOverlay.
143143
static constexpr uintptr_t FORWARD_BIT = Bit(0);
144144

145-
// Indicates whether the cell header has been temporarily replaced by calling
146-
// setTemporaryGCUnsafeData(). This is currently only used during rope
147-
// flattening.
148-
static constexpr uintptr_t TEMP_DATA_BIT = Bit(1);
149-
150-
// For use by derived cell classes. This is currently only used during rope
151-
// flattening.
152-
static constexpr uintptr_t USER_BIT = Bit(2);
145+
// Bits 1 and 2 are reserved for future use by the GC.
153146

154147
bool isForwarded() const { return header_ & FORWARD_BIT; }
155-
bool hasTempHeaderData() const { return header_ & TEMP_DATA_BIT; }
156148
uintptr_t flags() const { return header_ & RESERVED_MASK; }
157149

158150
MOZ_ALWAYS_INLINE bool isTenured() const { return !IsInsideNursery(this); }
@@ -649,23 +641,6 @@ class alignas(gc::CellAlignBytes) CellWithLengthAndFlags : public Cell {
649641
#endif
650642
}
651643

652-
// Subclasses can store temporary data in the flags word. This is not GC safe
653-
// and users must ensure flags/length are never checked (including by asserts)
654-
// while this data is stored. Use of this method is strongly discouraged!
655-
void setTemporaryGCUnsafeData(uintptr_t data) {
656-
MOZ_ASSERT((data & TEMP_DATA_BIT) == 0);
657-
header_ = data | TEMP_DATA_BIT;
658-
}
659-
660-
// To get back the data, values to safely re-initialize clobbered length and
661-
// flags must be provided.
662-
uintptr_t unsetTemporaryGCUnsafeData(uint32_t len, uint32_t flags) {
663-
MOZ_ASSERT(hasTempHeaderData());
664-
uintptr_t data = header_;
665-
setHeaderLengthAndFlags(len, flags);
666-
return data & ~TEMP_DATA_BIT;
667-
}
668-
669644
public:
670645
// Returns the offset of header_. JIT code should use offsetOfFlags
671646
// below.

js/src/gc/Marking.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4158,7 +4158,6 @@ void BarrierTracer::performBarrier(JS::GCCellPtr cell) {
41584158
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()));
41594159
MOZ_ASSERT(!runtime()->gc.isBackgroundMarking());
41604160
MOZ_ASSERT(!cell.asCell()->isForwarded());
4161-
MOZ_ASSERT(!cell.asCell()->hasTempHeaderData());
41624161

41634162
// Mark the cell here to prevent us recording it again.
41644163
if (!cell.asCell()->asTenured().markIfUnmarked()) {
@@ -4215,7 +4214,7 @@ void GCMarker::traceBarrieredCell(JS::GCCellPtr cell) {
42154214
MOZ_ASSERT(thing->isMarkedBlack());
42164215

42174216
if constexpr (std::is_same_v<decltype(thing), JSString*>) {
4218-
if (thing->isBeingFlattened()) {
4217+
if (thing->isRope() && thing->asRope().isBeingFlattened()) {
42194218
// This string is an interior node of a rope that is currently being
42204219
// flattened. The flattening process invokes the barrier on all nodes in
42214220
// the tree, so interior nodes need not be traversed.

js/src/vm/StringType.cpp

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "mozilla/ArrayUtils.h"
1010
#include "mozilla/Casting.h"
11+
#include "mozilla/DebugOnly.h"
1112
#include "mozilla/EndianUtils.h"
1213
#include "mozilla/HashFunctions.h"
1314
#include "mozilla/Latin1.h"
@@ -723,11 +724,6 @@ JSLinearString* JSRope::flattenInternal(JSRope* root) {
723724
size_t wholeCapacity;
724725
CharT* wholeChars;
725726

726-
// JSString::setFlattenData() is used to store a tagged pointer to the parent
727-
// node. These flags indicates what to do when we return to the parent.
728-
static constexpr uintptr_t Flag_FinishNode = 0;
729-
static constexpr uintptr_t Flag_VisitRightChild = Cell::USER_BIT;
730-
731727
AutoCheckCannotGC nogc;
732728

733729
Nursery& nursery = root->runtimeFromMainThread()->gc.nursery();
@@ -796,19 +792,30 @@ JSLinearString* JSRope::flattenInternal(JSRope* root) {
796792
JSString* str = root;
797793
CharT* pos = wholeChars;
798794

795+
JSString* parent = nullptr;
796+
uint32_t parentFlag = 0;
797+
799798
first_visit_node : {
799+
MOZ_ASSERT_IF(str != root, parent && parentFlag);
800+
MOZ_ASSERT(!str->asRope().isBeingFlattened());
801+
802+
ropeBarrierDuringFlattening<usingBarrier>(str);
803+
804+
JSString& left = *str->d.s.u2.left;
805+
str->d.s.u2.parent = parent;
806+
str->setFlagBit(parentFlag);
807+
parent = nullptr;
808+
parentFlag = 0;
809+
800810
if (reuseLeftmostBuffer && str == leftmostRope) {
801811
// Left child has already been overwritten.
802812
pos += leftmostChildLength;
803813
goto visit_right_child;
804814
}
805-
806-
JSString& left = *str->d.s.u2.left;
807-
ropeBarrierDuringFlattening<usingBarrier>(str);
808-
str->setNonInlineChars(pos);
809815
if (left.isRope()) {
810816
/* Return to this node when 'left' done, then goto visit_right_child. */
811-
left.setFlattenData(str, Flag_VisitRightChild);
817+
parent = str;
818+
parentFlag = FLATTEN_VISIT_RIGHT;
812819
str = &left;
813820
goto first_visit_node;
814821
}
@@ -820,7 +827,8 @@ visit_right_child : {
820827
JSString& right = *str->d.s.u3.right;
821828
if (right.isRope()) {
822829
/* Return to this node when 'right' done, then goto finish_node. */
823-
right.setFlattenData(str, Flag_FinishNode);
830+
parent = str;
831+
parentFlag = FLATTEN_FINISH_NODE;
824832
str = &right;
825833
goto first_visit_node;
826834
}
@@ -833,11 +841,19 @@ finish_node : {
833841
goto finish_root;
834842
}
835843

836-
JSString* parent;
837-
uintptr_t flattenFlags;
838-
uint32_t len = pos - str->nonInlineCharsRaw<CharT>();
839-
parent = str->unsetFlattenData(
840-
len, StringFlagsForCharType<CharT>(INIT_DEPENDENT_FLAGS), &flattenFlags);
844+
MOZ_ASSERT(pos >= wholeChars);
845+
CharT* chars = pos - str->length();
846+
JSString* strParent = str->d.s.u2.parent;
847+
str->setNonInlineChars(chars);
848+
849+
MOZ_ASSERT(str->asRope().isBeingFlattened());
850+
mozilla::DebugOnly<bool> visitRight = str->flags() & FLATTEN_VISIT_RIGHT;
851+
bool finishNode = str->flags() & FLATTEN_FINISH_NODE;
852+
MOZ_ASSERT(visitRight != finishNode);
853+
854+
// This also clears the flags related to flattening.
855+
str->setLengthAndFlags(str->length(),
856+
StringFlagsForCharType<CharT>(INIT_DEPENDENT_FLAGS));
841857
str->d.s.u3.base = (JSLinearString*)root; /* will be true on exit */
842858

843859
// Every interior (rope) node in the rope's tree will be visited during
@@ -852,12 +868,12 @@ finish_node : {
852868
root->storeBuffer()->putWholeCell(str);
853869
}
854870

855-
str = parent;
856-
if (flattenFlags == Flag_VisitRightChild) {
857-
goto visit_right_child;
871+
str = strParent;
872+
if (finishNode) {
873+
goto finish_node;
858874
}
859-
MOZ_ASSERT(flattenFlags == Flag_FinishNode);
860-
goto finish_node;
875+
MOZ_ASSERT(visitRight);
876+
goto visit_right_child;
861877
}
862878

863879
finish_root:
@@ -881,6 +897,8 @@ template <JSRope::UsingBarrier usingBarrier>
881897
inline void JSRope::ropeBarrierDuringFlattening(JSString* str) {
882898
// |str| is a rope but its flags maybe have been overwritten by temporary data
883899
// at this point.
900+
MOZ_ASSERT(str->isRope());
901+
MOZ_ASSERT(!str->asRope().isBeingFlattened());
884902
if constexpr (usingBarrier) {
885903
gc::PreWriteBarrierDuringFlattening(str->d.s.u2.left);
886904
gc::PreWriteBarrierDuringFlattening(str->d.s.u3.right);

js/src/vm/StringType.h

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ class JSString : public js::gc::CellWithLengthAndFlags {
219219
const char16_t* nonInlineCharsTwoByte; /* JSLinearString, except
220220
JS(Fat)InlineString */
221221
JSString* left; /* JSRope */
222+
JSString* parent; /* Used in flattening */
222223
} u2;
223224
union {
224225
JSLinearString* base; /* JSDependentString */
@@ -333,6 +334,13 @@ class JSString : public js::gc::CellWithLengthAndFlags {
333334
// clearing this bit.
334335
static const uint32_t IN_STRING_TO_ATOM_CACHE = js::Bit(13);
335336

337+
// Flags used during rope flattening that indicate what action to perform when
338+
// returning to the rope's parent rope.
339+
static const uint32_t FLATTEN_VISIT_RIGHT = js::Bit(14);
340+
static const uint32_t FLATTEN_FINISH_NODE = js::Bit(15);
341+
static const uint32_t FLATTEN_MASK =
342+
FLATTEN_VISIT_RIGHT | FLATTEN_FINISH_NODE;
343+
336344
static const uint32_t MAX_LENGTH = JS::MaxStringLength;
337345

338346
static const JS::Latin1Char MAX_LATIN1_CHAR = 0xff;
@@ -422,19 +430,6 @@ class JSString : public js::gc::CellWithLengthAndFlags {
422430
#endif
423431
}
424432

425-
void setFlattenData(JSString* parent, uintptr_t flags) {
426-
MOZ_ASSERT((uintptr_t(parent) & RESERVED_MASK) == 0);
427-
MOZ_ASSERT((flags & ~RESERVED_MASK) == 0);
428-
setTemporaryGCUnsafeData(uintptr_t(parent) | flags);
429-
}
430-
431-
JSString* unsetFlattenData(uint32_t len, uint32_t newFlags,
432-
uintptr_t* oldFlagsOut) {
433-
uintptr_t data = unsetTemporaryGCUnsafeData(len, newFlags);
434-
*oldFlagsOut = data & RESERVED_MASK;
435-
return reinterpret_cast<JSString*>(data & ~RESERVED_MASK);
436-
}
437-
438433
// Get correct non-inline chars enum arm for given type
439434
template <typename CharT>
440435
MOZ_ALWAYS_INLINE const CharT* nonInlineCharsRaw() const;
@@ -615,8 +610,6 @@ class JSString : public js::gc::CellWithLengthAndFlags {
615610
mozilla::Maybe<mozilla::Tuple<size_t, size_t>> encodeUTF8Partial(
616611
const JS::AutoRequireNoGC& nogc, mozilla::Span<char> buffer) const;
617612

618-
bool isBeingFlattened() const { return hasTempHeaderData(); }
619-
620613
private:
621614
// To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
622615
// to call the method below.
@@ -758,8 +751,13 @@ class JSRope : public JSString {
758751
// Returns the same value as if this were a linear string being hashed.
759752
[[nodiscard]] bool hash(uint32_t* outhHash) const;
760753

754+
// The process of flattening a rope temporarily overwrites the left pointer of
755+
// interior nodes in the rope DAG with the parent pointer.
756+
bool isBeingFlattened() const { return flags() & FLATTEN_MASK; }
757+
761758
JSString* leftChild() const {
762759
MOZ_ASSERT(isRope());
760+
MOZ_ASSERT(!isBeingFlattened()); // Flattening overwrites this field.
763761
return d.s.u2.left;
764762
}
765763

0 commit comments

Comments
 (0)