From 6acf628a7b245b7d0cc2a04a06a708c70355def5 Mon Sep 17 00:00:00 2001 From: win7-7 Date: Thu, 15 Jan 2026 21:28:02 +0200 Subject: [PATCH] Enable nursery strings fixes from 67. Still crashes. Enable nursery strings fixes from 67. Still crashes. --- js/src/builtin/TestingFunctions.cpp | 6 +-- js/src/gc/Allocator.cpp | 5 +- js/src/gc/Marking.cpp | 1 - js/src/gc/Nursery.cpp | 11 ++-- js/src/gc/Nursery.h | 2 + js/src/jit/CodeGenerator.cpp | 81 ++++++++++++++--------------- js/src/jit/Ion.cpp | 4 +- js/src/jit/JitCompartment.h | 24 +++++++-- js/src/jit/VMFunctions.cpp | 7 +-- js/src/jit/VMFunctions.h | 9 +++- js/src/jscompartment.cpp | 2 +- js/src/vm/HelperThreads.cpp | 2 +- js/src/vm/String.cpp | 2 + 13 files changed, 92 insertions(+), 64 deletions(-) diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index e368a630b2..6d2a2e6a47 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1159,15 +1159,15 @@ NewRope(JSContext* cx, unsigned argc, Value* vp) heap = js::gc::TenuredHeap; } - JSString* left = args[0].toString(); - JSString* right = args[1].toString(); + RootedString left(cx, args[0].toString()); + RootedString right(cx, args[1].toString()); size_t length = JS_GetStringLength(left) + JS_GetStringLength(right); if (length > JSString::MAX_LENGTH) { JS_ReportErrorASCII(cx, "rope length exceeds maximum string length"); return false; } - Rooted str(cx, JSRope::new_(cx, left, right, length, heap)); + Rooted str(cx, JSRope::new_(cx, left, right, length, heap)); if (!str) return false; diff --git a/js/src/gc/Allocator.cpp b/js/src/gc/Allocator.cpp index 395c39adaa..de8e98a2a8 100644 --- a/js/src/gc/Allocator.cpp +++ b/js/src/gc/Allocator.cpp @@ -147,8 +147,9 @@ GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize, AllocKind kind) if (allowGC && !cx->suppressGC) { cx->runtime()->gc.minorGC(JS::gcreason::OUT_OF_NURSERY); - // Exceeding gcMaxBytes while tenuring can disable the Nursery. - if (cx->nursery().isEnabled()) + // Exceeding gcMaxBytes while tenuring can disable the Nursery, and + // other heuristics can disable nursery strings for this zone. + if (cx->nursery().isEnabled() && cx->zone()->allocNurseryStrings) return static_cast(cx->nursery().allocateString(cx, cx->zone(), thingSize, kind)); } return nullptr; diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 201d64d8d5..511d2b073b 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -2053,7 +2053,6 @@ inline JSRope* MarkStack::TaggedPtr::asTempRope() const { MOZ_ASSERT(tag() == TempRopeTag); - MOZ_ASSERT(ptr()->isTenured()); MOZ_ASSERT(ptr()->is()); return static_cast(ptr()); } diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 1d1330b99d..14c1528a1a 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -683,8 +683,10 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) TenureCountCache tenureCounts; previousGC.reason = JS::gcreason::NO_REASON; + mozilla::Maybe session; if (!isEmpty()) { - doCollection(reason, tenureCounts); + session.emplace(rt, JS::HeapState::MinorCollecting); + doCollection(reason, session.ref(), tenureCounts); } else { previousGC.nurseryUsedBytes = 0; previousGC.nurseryCapacity = spaceToEnd(maxChunkCount()); @@ -721,6 +723,7 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) } for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) { if (shouldPretenure && zone->allocNurseryStrings && zone->tenuredStrings >= 30 * 1000) { + MOZ_ASSERT(session.isSome(), "discarding JIT code must be in an AutoTraceSession"); JSRuntime::AutoProhibitActiveContextChange apacc(rt); CancelOffThreadIonCompile(zone); bool preserving = zone->isPreservingCode(); @@ -730,7 +733,7 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) { if (jit::JitCompartment* jitComp = c->jitCompartment()) { jitComp->discardStubs(); - jitComp->stringsCanBeInNursery = false; + jitComp->setStringsCanBeInNursery(false); } } zone->allocNurseryStrings = false; @@ -751,6 +754,7 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) disable(); endProfile(ProfileKey::Total); + session.reset(); // End the minor GC session, if running one. minorGcCount_++; int64_t totalTime = profileTimes_[ProfileKey::Total]; @@ -785,9 +789,10 @@ js::Nursery::collect(JSRuntime* rt, JS::gcreason::Reason reason) void js::Nursery::doCollection(JS::gcreason::Reason reason, + AutoTraceSession& session, TenureCountCache& tenureCounts) { - AutoTraceSession session(rt, JS::HeapState::MinorCollecting); + JSRuntime* rt = runtime(); AutoSetThreadIsPerformingGC performingGC; AutoDisableProxyCheck disableStrictProxyChecking(rt); mozilla::DebugOnly oomUnsafeRegion; diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index a8f77f2e37..5301cfb858 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -55,6 +55,7 @@ class HeapSlot; namespace gc { class AutoMaybeStartBackgroundAllocation; +class AutoTraceSession; struct Cell; class MinorCollectionTracer; class RelocationOverlay; @@ -509,6 +510,7 @@ class Nursery void* allocate(size_t size); void doCollection(JS::gcreason::Reason reason, + gc::AutoTraceSession& sesssion, gc::TenureCountCache& tenureCounts); /* diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 4f7b22a09d..cbd5ec173e 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1079,14 +1079,8 @@ EmitPostWriteBarrierS(MacroAssembler& masm, masm.bind(&exit); } -enum class FieldToBarrier { - REGEXP_PENDING_INPUT, - REGEXP_MATCHES_INPUT, - DEPENDENT_STRING_BASE -}; - static void -EmitStoreBufferMutation(MacroAssembler& masm, Register holder, FieldToBarrier field, +EmitStoreBufferMutation(MacroAssembler& masm, Register holder, size_t offset, Register buffer, LiveGeneralRegisterSet& liveVolatiles, void (*fun)(js::gc::StoreBuffer*, js::gc::Cell**)) @@ -1105,19 +1099,7 @@ EmitStoreBufferMutation(MacroAssembler& masm, Register holder, FieldToBarrier fi regs.takeUnchecked(holder); Register addrReg = regs.takeAny(); - switch (field) { - case FieldToBarrier::REGEXP_PENDING_INPUT: - masm.computeEffectiveAddress(Address(holder, RegExpStatics::offsetOfPendingInput()), addrReg); - break; - - case FieldToBarrier::REGEXP_MATCHES_INPUT: - masm.computeEffectiveAddress(Address(holder, RegExpStatics::offsetOfMatchesInput()), addrReg); - break; - - case FieldToBarrier::DEPENDENT_STRING_BASE: - masm.leaNewDependentStringBase(holder, addrReg); - break; - } + masm.computeEffectiveAddress(Address(holder, offset), addrReg); bool needExtraReg = !regs.empty(); if (needExtraReg) { @@ -1140,7 +1122,7 @@ EmitStoreBufferMutation(MacroAssembler& masm, Register holder, FieldToBarrier fi // Warning: this function modifies prev and next. static void EmitPostWriteBarrierS(MacroAssembler& masm, - Register string, FieldToBarrier field, + Register holder, size_t offset, Register prev, Register next, LiveGeneralRegisterSet& liveVolatiles) { @@ -1160,7 +1142,7 @@ EmitPostWriteBarrierS(MacroAssembler& masm, // buffer->putCell(cellp) masm.bind(&putCell); - EmitStoreBufferMutation(masm, string, field, storebuffer, liveVolatiles, + EmitStoreBufferMutation(masm, holder, offset, storebuffer, liveVolatiles, JSString::addCellAddressToStoreBuffer); masm.jump(&exit); @@ -1169,7 +1151,7 @@ EmitPostWriteBarrierS(MacroAssembler& masm, masm.branchPtr(Assembler::Equal, prev, ImmWord(0), &exit); masm.loadStoreBuffer(prev, storebuffer); masm.branchPtr(Assembler::Equal, storebuffer, ImmWord(0), &exit); - EmitStoreBufferMutation(masm, string, field, storebuffer, liveVolatiles, + EmitStoreBufferMutation(masm, holder, offset, storebuffer, liveVolatiles, JSString::removeCellAddressFromStoreBuffer); masm.bind(&exit); @@ -1216,6 +1198,7 @@ PrepareAndExecuteRegExp(JSContext* cx, MacroAssembler& masm, Register regexp, Re Register temp1, Register temp2, Register temp3, size_t inputOutputDataStartOffset, RegExpShared::CompilationMode mode, + bool stringsCanBeInNursery, Label* notFound, Label* failure) { size_t matchPairsStartOffset = inputOutputDataStartOffset + sizeof(irregexp::InputOutputData); @@ -1393,22 +1376,26 @@ PrepareAndExecuteRegExp(JSContext* cx, MacroAssembler& masm, Register regexp, Re masm.patchableCallPreBarrier(matchesInputAddress, MIRType::String); masm.patchableCallPreBarrier(lazySourceAddress, MIRType::String); + if (stringsCanBeInNursery) { + // Writing into RegExpStatics tenured memory; must post-barrier. + if (temp1.volatile_()) + volatileRegs.add(temp1); - if (temp1.volatile_()) - volatileRegs.add(temp1); + masm.loadPtr(pendingInputAddress, temp2); + masm.storePtr(input, pendingInputAddress); + masm.movePtr(input, temp3); + EmitPostWriteBarrierS(masm, temp1, RegExpStatics::offsetOfPendingInput(), + temp2 /* prev */, temp3 /* next */, volatileRegs); - // Writing into RegExpStatics tenured memory; must post-barrier. - masm.loadPtr(pendingInputAddress, temp2); - masm.storePtr(input, pendingInputAddress); - masm.movePtr(input, temp3); - EmitPostWriteBarrierS(masm, temp1, FieldToBarrier::REGEXP_PENDING_INPUT, - temp2 /* prev */, temp3 /* next */, volatileRegs); - - masm.loadPtr(matchesInputAddress, temp2); - masm.storePtr(input, matchesInputAddress); - masm.movePtr(input, temp3); - EmitPostWriteBarrierS(masm, temp1, FieldToBarrier::REGEXP_MATCHES_INPUT, - temp2 /* prev */, temp3 /* next */, volatileRegs); + masm.loadPtr(matchesInputAddress, temp2); + masm.storePtr(input, matchesInputAddress); + masm.movePtr(input, temp3); + EmitPostWriteBarrierS(masm, temp1, RegExpStatics::offsetOfMatchesInput(), + temp2 /* prev */, temp3 /* next */, volatileRegs); + } else { + masm.storePtr(input, pendingInputAddress); + masm.storePtr(input, matchesInputAddress); + } masm.storePtr(lastIndex, Address(temp1, RegExpStatics::offsetOfLazyIndex())); masm.store32(Imm32(1), Address(temp1, RegExpStatics::offsetOfPendingLazyEvaluation())); @@ -1449,6 +1436,7 @@ public: // Caller should call generateFallback after masm.ret(), to generate // fallback path. void generate(MacroAssembler& masm, const JSAtomState& names, + CompileRuntime* runtime, bool latin1, Register string, Register base, Register temp1, Register temp2, BaseIndex startIndexAddress, BaseIndex limitIndexAddress, @@ -1461,6 +1449,7 @@ public: void CreateDependentString::generate(MacroAssembler& masm, const JSAtomState& names, + CompileRuntime* runtime, bool latin1, Register string, Register base, Register temp1, Register temp2, BaseIndex startIndexAddress, BaseIndex limitIndexAddress, @@ -1715,7 +1704,7 @@ JitCompartment::generateRegExpMatcherStub(JSContext* cx) Label notFound, oolEntry; if (!PrepareAndExecuteRegExp(cx, masm, regexp, input, lastIndex, temp1, temp2, temp5, inputOutputDataStartOffset, - RegExpShared::Normal, ¬Found, &oolEntry)) + RegExpShared::Normal, stringsCanBeInNursery, ¬Found, &oolEntry)) { return nullptr; } @@ -1813,7 +1802,9 @@ JitCompartment::generateRegExpMatcherStub(JSContext* cx) Label isUndefined, storeDone; masm.branch32(Assembler::LessThan, stringIndexAddress, Imm32(0), &isUndefined); - depStr[isLatin].generate(masm, cx->names(), isLatin, temp3, input, temp4, temp5, + depStr[isLatin].generate(masm, cx->names(), + CompileRuntime::get(cx->runtime()), + isLatin, temp3, input, temp4, temp5, stringIndexAddress, stringLimitAddress, stringsCanBeInNursery, failure); masm.storeValue(JSVAL_TYPE_STRING, temp3, stringAddress); @@ -2030,7 +2021,8 @@ JitCompartment::generateRegExpSearcherStub(JSContext* cx) Label notFound, oolEntry; if (!PrepareAndExecuteRegExp(cx, masm, regexp, input, lastIndex, temp1, temp2, temp3, inputOutputDataStartOffset, - RegExpShared::Normal, ¬Found, &oolEntry)) + RegExpShared::Normal, stringsCanBeInNursery, + ¬Found, &oolEntry)) { return nullptr; } @@ -2181,7 +2173,8 @@ JitCompartment::generateRegExpTesterStub(JSContext* cx) Label notFound, oolEntry; if (!PrepareAndExecuteRegExp(cx, masm, regexp, input, lastIndex, temp1, temp2, temp3, 0, - RegExpShared::MatchOnly, ¬Found, &oolEntry)) + RegExpShared::MatchOnly, stringsCanBeInNursery, + ¬Found, &oolEntry)) { return nullptr; } @@ -3768,11 +3761,13 @@ EmitPostWriteBarrier(MacroAssembler& masm, Register objreg, JSObject* maybeConst Register runtimereg = regs.takeAny(); masm.mov(ImmPtr(GetJitContext()->runtime), runtimereg); - void (*fun)(JSRuntime*, JSObject*) = isGlobal ? PostGlobalWriteBarrier : PostWriteBarrier; masm.setupUnalignedABICall(regs.takeAny()); masm.passABIArg(runtimereg); masm.passABIArg(objreg); - masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, fun)); + if (isGlobal) + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostGlobalWriteBarrier)); + else + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); masm.bind(&exit); } diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 06a9b67168..9c3e86879f 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -439,7 +439,7 @@ JitCompartment::~JitCompartment() } bool -JitCompartment::initialize(JSContext* cx) +JitCompartment::initialize(JSContext* cx, bool zoneHasNurseryStrings) { stubCodes_ = cx->new_(cx->runtime()); if (!stubCodes_) @@ -450,7 +450,7 @@ JitCompartment::initialize(JSContext* cx) return false; } - stringsCanBeInNursery = cx->nursery().canAllocateStrings(); + setStringsCanBeInNursery(zoneHasNurseryStrings); return true; } diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index cc85141943..c8a956245b 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -552,6 +552,8 @@ class JitCompartment JitCode* regExpSearcherStub_; JitCode* regExpTesterStub_; + bool stringsCanBeInNursery; + JitCode* generateStringConcatStub(JSContext* cx); JitCode* generateRegExpMatcherStub(JSContext* cx); JitCode* generateRegExpSearcherStub(JSContext* cx); @@ -619,7 +621,7 @@ class JitCompartment JitCompartment(); ~JitCompartment(); - MOZ_MUST_USE bool initialize(JSContext* cx); + [[nodiscard]] bool initialize(JSContext* cx, bool zoneHasNurseryStrings); // Initialize code stubs only used by Ion, not Baseline. MOZ_MUST_USE bool ensureIonStubsExist(JSContext* cx); @@ -638,6 +640,24 @@ class JitCompartment regExpTesterStub_ = nullptr; } + bool hasStubs() const { + // Check each stub individually + if (stringConcatStub_) + return true; + if (regExpMatcherStub_) + return true; + if (regExpSearcherStub_) + return true; + if (regExpTesterStub_) + return true; + return false; + } + + void setStringsCanBeInNursery(bool allow) { + MOZ_ASSERT(!hasStubs()); + stringsCanBeInNursery = allow; + } + JitCode* regExpMatcherStubNoBarrier() const { return regExpMatcherStub_; } @@ -672,8 +692,6 @@ class JitCompartment } size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; - - bool stringsCanBeInNursery; }; // Called from JSCompartment::discardJitCode(). diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 54344a8b3d..47eaad3592 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -596,10 +596,11 @@ GetDynamicName(JSContext* cx, JSObject* envChain, JSString* str, Value* vp) } void -PostWriteBarrier(JSRuntime* rt, JSObject* obj) +PostWriteBarrier(JSRuntime* rt, js::gc::Cell* cell) { - MOZ_ASSERT(!IsInsideNursery(obj)); - rt->gc.storeBuffer.putWholeCell(obj); + JS::AutoCheckCannotGC nogc; + MOZ_ASSERT(!IsInsideNursery(cell)); + rt->gc.storeBuffer().putWholeCell(cell); } static const size_t MAX_WHOLE_CELL_BUFFER_SIZE = 4096; diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index b134c5df05..c47247e463 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -22,6 +22,12 @@ class InlineTypedObject; class GeneratorObject; class TypedArrayObject; +namespace gc { + +struct Cell; + +} + namespace jit { enum DataType { @@ -653,8 +659,7 @@ CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHa void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp); -void PostWriteBarrier(JSRuntime* rt, JSObject* obj); -void PostWriteElementBarrier(JSRuntime* rt, JSObject* obj, int32_t index); +void PostWriteBarrier(JSRuntime* rt, js::gc::Cell* cell); void PostGlobalWriteBarrier(JSRuntime* rt, JSObject* obj); uint32_t GetIndexFromString(JSString* str); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index d7afc1e097..1139898b0e 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -200,7 +200,7 @@ JSCompartment::ensureJitCompartmentExists(JSContext* cx) if (!jitCompartment_) return false; - if (!jitCompartment_->initialize(cx)) { + if (!jitCompartment_->initialize(cx, zone()->allocNurseryStrings)) { js_delete(jitCompartment_); jitCompartment_ = nullptr; return false; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index a53e7776f5..227d3b42c8 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -173,7 +173,7 @@ CompiledScriptMatches(CompilationSelector selector, JSScript* target) bool match(JSScript* script) { return script == builder_->script(); } bool match(JSCompartment* comp) { return comp == builder_->script()->compartment(); } - bool match(Zone* zone) { return zone == builder_->script()->zone(); } + bool match(Zone* zone) { return zone == builder_->script()->zoneFromAnyThread(); } bool match(JSRuntime* runtime) { return runtime == builder_->script()->runtimeFromAnyThread(); } bool match(AllCompilations all) { return true; } bool match(ZonesInState zbs) { diff --git a/js/src/vm/String.cpp b/js/src/vm/String.cpp index 9ade5e302a..f5d3d9eb9c 100644 --- a/js/src/vm/String.cpp +++ b/js/src/vm/String.cpp @@ -13,6 +13,7 @@ #include "mozilla/TypeTraits.h" #include "mozilla/Unused.h" +#include "gc/GCInternals.h" #include "gc/Marking.h" #include "gc/Nursery.h" #include "js/UbiNode.h" @@ -198,6 +199,7 @@ JSString::dumpRepresentationHeader(FILE* fp, int indent, const char* subclass) c if (flags & HAS_BASE_BIT) fputs(" HAS_BASE", fp); if (flags & INLINE_CHARS_BIT) fputs(" INLINE_CHARS", fp); if (flags & NON_ATOM_BIT) fputs(" NON_ATOM", fp); + else fputs(" (ATOM)", fp); if (isPermanentAtom()) fputs(" PERMANENT", fp); if (flags & LATIN1_CHARS_BIT) fputs(" LATIN1", fp); if (!isTenured()) fputs(" NURSERY", fp);