Skip to content

Commit 0b87239

Browse files
committedJan 7, 2025
Bug 1934856 - Part 2: Use buffer allocator for JSObject elements r=sfink,jandem
Differential Revision: https://phabricator.services.mozilla.com/D230956
1 parent 51df71c commit 0b87239

11 files changed

+63
-95
lines changed
 

‎js/src/gc/GCEnum.h

-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ enum class GCAbortReason {
108108
#define JS_FOR_EACH_INTERNAL_MEMORY_USE(_) \
109109
_(ArrayBufferContents) \
110110
_(StringContents) \
111-
_(ObjectElements) \
112111
_(ScriptPrivateData) \
113112
_(MapObjectData) \
114113
_(BigIntDigits) \

‎js/src/gc/Marking.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,11 @@ scan_obj: {
16711671
BufferAllocator::MarkTenuredAlloc(slots);
16721672
}
16731673

1674+
if (nobj->hasDynamicElements()) {
1675+
void* elements = nobj->getUnshiftedElementsHeader();
1676+
BufferAllocator::MarkTenuredAlloc(elements);
1677+
}
1678+
16741679
if (!nobj->hasEmptyElements()) {
16751680
base = nobj->getDenseElements();
16761681
kind = SlotsOrElementsKind::Elements;

‎js/src/gc/Tenuring.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -930,8 +930,7 @@ size_t js::gc::TenuringTracer::moveElements(NativeObject* dst,
930930
/* TODO Bug 874151: Prefer to put element data inline if we have space. */
931931

932932
Nursery::WasBufferMoved result =
933-
nursery().maybeMoveNurseryOrMallocBufferOnPromotion(
934-
&unshiftedHeader, dst, allocSize, MemoryUse::ObjectElements);
933+
nursery().maybeMoveBufferOnPromotion(&unshiftedHeader, dst, allocSize);
935934
if (result == Nursery::BufferNotMoved) {
936935
return 0;
937936
}

‎js/src/jit-test/lib/pretenure.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const is64bit = getBuildConfiguration("pointer-byte-size") === 8;
66
const nurseryCount = is64bit ? 25000 : 50000;
77

88
// Count of objects that will exceed the tenured heap collection threshold.
9-
const tenuredCount = is64bit ? 300000 : 600000;
9+
const tenuredCount = is64bit ? 400000 : 800000;
1010

1111
function setupPretenureTest() {
1212
// The test requires that baseline is enabled and is not bypassed with
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Shrink a large dense elements allocation of ~2MB to half its size.
2+
const sizeMB = 2;
3+
const elementCount = (sizeMB * 1024 * 1024) / 8;
4+
const array = new Array(elementCount);
5+
for (let i = 0; i < elementCount; i++) {
6+
array[i] = i;
7+
}
8+
array.length = elementCount / 2;

‎js/src/jit-test/tests/gc/pretenured-operations.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ check(() => { return Object(); });
3535

3636
// Array construction.
3737
check(() => { return Array(); });
38-
check(() => { return Array(100); });
38+
check(() => { return Array(150); });
3939
check(() => { return new Array(); });
40-
check(() => { return new Array(100); });
40+
check(() => { return new Array(150); });
4141

4242
// DOM Allocations
4343
let fdo = new FakeDOMObject();

‎js/src/jit-test/tests/heap-analysis/byteSize-of-object.js

+14-14
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4 }), s(48, 56));
3636
assertEq(tByteSize({ w: 1, x: 2, y: 3, z:4, a: 5 }), s(80, 88));
3737

3838
// Try objects with only indexed properties.
39-
assertEq(tByteSize({ 0:0 }), s(80, 88));
40-
assertEq(tByteSize({ 0:0, 1:1 }), s(80, 88));
41-
assertEq(tByteSize({ 0:0, 1:1, 2:2 }), s(96, 104));
42-
assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3 }), s(96, 104));
43-
assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
39+
assertEq(tByteSize({ 0:0 }), s(88, 96));
40+
assertEq(tByteSize({ 0:0, 1:1 }), s(88, 96));
41+
assertEq(tByteSize({ 0:0, 1:1, 2:2 }), s(104, 112));
42+
assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3 }), s(104, 112));
43+
assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3, 4:4 }), s(136, 144));
4444

4545
// Mix indexed and named properties, exploring each combination of the size
4646
// classes above.
@@ -49,15 +49,15 @@ assertEq(tByteSize({ 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
4949
// changes above: for example, with one named property, the objects with three
5050
// and five indexed properties are in different size classes; but with three
5151
// named properties, there's no break there.
52-
assertEq(tByteSize({ w:1, 0:0 }), s(80, 88));
53-
assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2 }), s(96, 104));
54-
assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
55-
assertEq(tByteSize({ w:1, x:2, y:3, 0:0 }), s(96, 104));
56-
assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2 }), s(128, 136));
57-
assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(144, 152));
58-
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0 }), s(128, 136));
59-
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2 }), s(128, 136));
60-
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(176, 184));
52+
assertEq(tByteSize({ w:1, 0:0 }), s(88, 96));
53+
assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2 }), s(104, 112));
54+
assertEq(tByteSize({ w:1, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(136, 144));
55+
assertEq(tByteSize({ w:1, x:2, y:3, 0:0 }), s(104, 112));
56+
assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2 }), s(136, 144));
57+
assertEq(tByteSize({ w:1, x:2, y:3, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(136, 144));
58+
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0 }), s(136, 144));
59+
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2 }), s(136, 144));
60+
assertEq(tByteSize({ w:1, x:2, y:3, z:4, a:6, 0:0, 1:1, 2:2, 3:3, 4:4 }), s(168, 176));
6161

6262
// Check various lengths of array.
6363
assertEq(tByteSize([]), s(80, 88));

‎js/src/vm/JSObject-inl.h

-12
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,6 @@ inline void JSObject::finalize(JS::GCContext* gcx) {
105105
if (clasp->hasFinalize()) {
106106
clasp->doFinalize(gcx, this);
107107
}
108-
109-
if (!objShape->isNative()) {
110-
return;
111-
}
112-
113-
js::NativeObject* nobj = &as<js::NativeObject>();
114-
if (nobj->hasDynamicElements()) {
115-
js::ObjectElements* elements = nobj->getElementsHeader();
116-
size_t size = elements->numAllocatedElements() * sizeof(js::HeapSlot);
117-
gcx->free_(this, nobj->getUnshiftedElementsHeader(), size,
118-
js::MemoryUse::ObjectElements);
119-
}
120108
}
121109

122110
inline bool JSObject::isQualifiedVarObj() const {

‎js/src/vm/JSObject.cpp

+13-20
Original file line numberDiff line numberDiff line change
@@ -1035,21 +1035,17 @@ bool NativeObject::prepareForSwap(JSContext* cx,
10351035
size_t count = elements->numAllocatedElements();
10361036
size_t size = count * sizeof(HeapSlot);
10371037

1038-
if (isTenured()) {
1039-
RemoveCellMemory(this, size, MemoryUse::ObjectElements);
1040-
} else if (cx->nursery().isInside(allocatedElements)) {
1038+
if (ChunkPtrIsInsideNursery(allocatedElements)) {
10411039
// Move nursery allocated elements in case they end up in a tenured
10421040
// object.
1043-
ObjectElements* newElements =
1044-
reinterpret_cast<ObjectElements*>(js_pod_malloc<HeapSlot>(count));
1041+
ObjectElements* newElements = static_cast<ObjectElements*>(
1042+
AllocBuffer(cx->zone(), size, !isTenured()));
10451043
if (!newElements) {
10461044
return false;
10471045
}
10481046

10491047
memmove(newElements, elements, size);
10501048
elements_ = newElements->elements();
1051-
} else {
1052-
cx->nursery().removeMallocedBuffer(allocatedElements, size);
10531049
}
10541050
MOZ_ASSERT(hasDynamicElements());
10551051
}
@@ -1098,17 +1094,8 @@ bool NativeObject::fixupAfterSwap(JSContext* cx, Handle<NativeObject*> obj,
10981094
obj->initSlotUnchecked(i, slotValues[i]);
10991095
}
11001096

1101-
if (obj->hasDynamicElements()) {
1102-
ObjectElements* elements = obj->getElementsHeader();
1103-
void* allocatedElements = obj->getUnshiftedElementsHeader();
1104-
MOZ_ASSERT(!cx->nursery().isInside(allocatedElements));
1105-
size_t size = elements->numAllocatedElements() * sizeof(HeapSlot);
1106-
if (obj->isTenured()) {
1107-
AddCellMemory(obj, size, MemoryUse::ObjectElements);
1108-
} else if (!cx->nursery().registerMallocedBuffer(allocatedElements, size)) {
1109-
return false;
1110-
}
1111-
}
1097+
MOZ_ASSERT_IF(obj->hasDynamicElements(),
1098+
gc::IsBufferAlloc(obj->getUnshiftedElementsHeader()));
11121099

11131100
return true;
11141101
}
@@ -1299,7 +1286,6 @@ void JSObject::swap(JSContext* cx, HandleObject a, HandleObject b,
12991286
js_memcpy(a, b, size);
13001287
js_memcpy(b, tmp, size);
13011288

1302-
zone->swapCellMemory(a, b, MemoryUse::ObjectElements);
13031289
zone->swapCellMemory(a, b, MemoryUse::ProxyExternalValueArray);
13041290

13051291
if (aIsProxyWithInlineValues) {
@@ -3319,14 +3305,16 @@ js::gc::AllocKind JSObject::allocKindForTenure(
33193305
void JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
33203306
JS::ClassInfo* info,
33213307
JS::RuntimeSizes* runtimeSizes) {
3308+
// TODO: These will eventually count as GC heap memory.
33223309
if (is<NativeObject>() && as<NativeObject>().hasDynamicSlots()) {
33233310
info->objectsMallocHeapSlots +=
33243311
gc::GetAllocSize(as<NativeObject>().getSlotsHeader());
33253312
}
33263313

33273314
if (is<NativeObject>() && as<NativeObject>().hasDynamicElements()) {
33283315
void* allocatedElements = as<NativeObject>().getUnshiftedElementsHeader();
3329-
info->objectsMallocHeapElementsNormal += mallocSizeOf(allocatedElements);
3316+
info->objectsMallocHeapElementsNormal +=
3317+
gc::GetAllocSize(allocatedElements);
33303318
}
33313319

33323320
// Other things may be measured in the future if DMD indicates it is
@@ -3448,6 +3436,11 @@ void JSObject::traceChildren(JSTracer* trc) {
34483436
}
34493437
}
34503438

3439+
if (nobj->hasDynamicElements()) {
3440+
TraceEdgeToBuffer(trc, nobj, nobj->getUnshiftedElementsHeader(),
3441+
"objectDynamicElements allocation");
3442+
}
3443+
34513444
TraceRange(trc, nobj->getDenseInitializedLength(),
34523445
nobj->getDenseElements().begin(), "objectElements");
34533446
}

‎js/src/vm/NativeObject.cpp

+18-41
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
806806
const uint32_t Mebi = 1 << 20;
807807
if (reqAllocated < Mebi) {
808808
uint32_t amount =
809-
mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
809+
gc::GetGoodPower2ElementCount(reqAllocated, sizeof(Value));
810810

811811
// If |amount| would be 2/3 or more of the array's length, adjust
812812
// it (up or down) to be equal to the array's length. This avoids
@@ -816,11 +816,18 @@ bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
816816
// opposed to the usual doubling.
817817
uint32_t goodCapacity = amount - ObjectElements::VALUES_PER_HEADER;
818818
if (length >= reqCapacity && goodCapacity > (length / 3) * 2) {
819-
amount = length + ObjectElements::VALUES_PER_HEADER;
819+
amount = gc::GetGoodElementCount(
820+
length + ObjectElements::VALUES_PER_HEADER, sizeof(Value));
820821
}
821822

822-
if (amount < ELEMENT_CAPACITY_MIN) {
823-
amount = ELEMENT_CAPACITY_MIN;
823+
const size_t AmountMin =
824+
ELEMENT_CAPACITY_MIN + ObjectElements::VALUES_PER_HEADER;
825+
826+
// Check this size doesn't waste any space in the allocation.
827+
MOZ_ASSERT(AmountMin == gc::GetGoodElementCount(AmountMin, sizeof(Value)));
828+
829+
if (amount < AmountMin) {
830+
amount = AmountMin;
824831
}
825832

826833
*goodAmount = amount;
@@ -857,7 +864,7 @@ bool NativeObject::goodElementsAllocationAmount(JSContext* cx,
857864
// Pick the first bucket that'll fit |reqAllocated|.
858865
for (uint32_t b : BigBuckets) {
859866
if (b >= reqAllocated) {
860-
*goodAmount = b;
867+
*goodAmount = gc::GetGoodElementCount(b, sizeof(Value));
861868
return true;
862869
}
863870
}
@@ -961,8 +968,8 @@ bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
961968
oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER + numShifted;
962969

963970
// Finally, try to resize the buffer.
964-
newHeaderSlots = ReallocNurseryOrMallocBuffer<HeapSlot>(
965-
cx, this, oldHeaderSlots, oldAllocated, newAllocated, js::MallocArena);
971+
newHeaderSlots = ReallocateCellBuffer<HeapSlot>(cx, this, oldHeaderSlots,
972+
oldAllocated, newAllocated);
966973
if (!newHeaderSlots) {
967974
return false; // If the resizing failed, then we leave elements at its
968975
// old size.
@@ -971,8 +978,7 @@ bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
971978
// If the object has fixed elements, then we always need to allocate a new
972979
// buffer, because if we've reached this code, then the requested capacity
973980
// is greater than the existing inline space available within the object
974-
newHeaderSlots =
975-
AllocNurseryOrMallocBuffer<HeapSlot>(cx, this, newAllocated);
981+
newHeaderSlots = AllocateCellBuffer<HeapSlot>(cx, this, newAllocated);
976982
if (!newHeaderSlots) {
977983
return false; // Leave elements at its old size.
978984
}
@@ -982,13 +988,6 @@ bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
982988
ObjectElements::VALUES_PER_HEADER + initlen + numShifted);
983989
}
984990

985-
// If the object already had dynamic elements, then we have to account
986-
// for freeing the old elements buffer.
987-
if (oldAllocated) {
988-
RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
989-
MemoryUse::ObjectElements);
990-
}
991-
992991
ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
993992
// Update the elements pointer to point to the new elements buffer.
994993
elements_ = newheader->elements() + numShifted;
@@ -1001,10 +1000,6 @@ bool NativeObject::growElements(JSContext* cx, uint32_t reqCapacity) {
10011000
// Poison the uninitialized portion of the new elements buffer.
10021001
Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
10031002

1004-
// Account for allocating the new elements buffer.
1005-
AddCellMemory(this, newAllocated * sizeof(HeapSlot),
1006-
MemoryUse::ObjectElements);
1007-
10081003
return true;
10091004
}
10101005

@@ -1044,22 +1039,16 @@ void NativeObject::shrinkElements(JSContext* cx, uint32_t reqCapacity) {
10441039

10451040
HeapSlot* oldHeaderSlots =
10461041
reinterpret_cast<HeapSlot*>(getUnshiftedElementsHeader());
1047-
HeapSlot* newHeaderSlots = ReallocNurseryOrMallocBuffer<HeapSlot>(
1048-
cx, this, oldHeaderSlots, oldAllocated, newAllocated, js::MallocArena);
1042+
HeapSlot* newHeaderSlots = ReallocateCellBuffer<HeapSlot>(
1043+
cx, this, oldHeaderSlots, oldAllocated, newAllocated);
10491044
if (!newHeaderSlots) {
10501045
cx->recoverFromOutOfMemory();
10511046
return; // Leave elements at its old size.
10521047
}
10531048

1054-
RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
1055-
MemoryUse::ObjectElements);
1056-
10571049
ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
10581050
elements_ = newheader->elements() + numShifted;
10591051
getElementsHeader()->capacity = newCapacity;
1060-
1061-
AddCellMemory(this, newAllocated * sizeof(HeapSlot),
1062-
MemoryUse::ObjectElements);
10631052
}
10641053

10651054
void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) {
@@ -1084,19 +1073,7 @@ void NativeObject::shrinkCapacityToInitializedLength(JSContext* cx) {
10841073

10851074
shrinkElements(cx, len);
10861075

1087-
header = getElementsHeader();
1088-
uint32_t oldAllocated = header->numAllocatedElements();
1089-
header->capacity = len;
1090-
1091-
// The size of the memory allocation hasn't changed but we lose the actual
1092-
// capacity information. Make the associated size match the updated capacity.
1093-
if (!hasFixedElements()) {
1094-
uint32_t newAllocated = header->numAllocatedElements();
1095-
RemoveCellMemory(this, oldAllocated * sizeof(HeapSlot),
1096-
MemoryUse::ObjectElements);
1097-
AddCellMemory(this, newAllocated * sizeof(HeapSlot),
1098-
MemoryUse::ObjectElements);
1099-
}
1076+
getElementsHeader()->capacity = len;
11001077
}
11011078

11021079
/* static */

‎js/src/vm/NativeObject.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -881,8 +881,7 @@ class NativeObject : public JSObject {
881881
/*
882882
* Minimum size for dynamically allocated elements in normal Objects.
883883
*/
884-
static const uint32_t ELEMENT_CAPACITY_MIN =
885-
8 - ObjectElements::VALUES_PER_HEADER;
884+
static const uint32_t ELEMENT_CAPACITY_MIN = 5;
886885

887886
HeapSlot* fixedSlots() const {
888887
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(NativeObject));

0 commit comments

Comments
 (0)
Please sign in to comment.