diff --git a/JSTests/stress/stack-position-is-the-same-on-every-read.js b/JSTests/stress/stack-position-is-the-same-on-every-read.js new file mode 100644 index 0000000000000..17d38b3ce7049 --- /dev/null +++ b/JSTests/stress/stack-position-is-the-same-on-every-read.js @@ -0,0 +1,25 @@ +function shouldBe(actual, expected) { + if (actual !== expected) + throw new Error(`expected ${expected} but got ${actual}`); +} + +function makeStack(which) { + if (which === 0) return new Error("a").stack; + if (which === 1) + return new Error("b").stack; + const error = ( + new Error("c") + ); + return error.stack; +} + +function positionOfFirstFrame(stack) { + const match = /^makeStack@.*:(\d+):(\d+)$/m.exec(stack); + return `${match[1]}:${match[2]}`; +} + +const expected = ["7:38", "9:25", "11:18"]; +for (let i = 0; i < 100; i++) { + for (let which = 0; which < 3; which++) + shouldBe(positionOfFirstFrame(makeStack(which)), expected[which]); +} diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index 784fea3c81fbc..21dc1bb1d8ee2 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -2576,8 +2576,12 @@ void CodeBlock::removeExceptionHandlerForCallSite(DisposableCallSiteIndex callSi LineColumn CodeBlock::lineColumnForBytecodeIndex(BytecodeIndex bytecodeIndex) const { RELEASE_ASSERT(bytecodeIndex.offset() < instructions().size()); +#if USE(BUN_JSC_ADDITIONS) + unsigned divotInProvider = sourceOffset() + m_unlinkedCode->divotForBytecodeIndex(bytecodeIndex); +#else auto entry = m_unlinkedCode->expressionInfoForBytecodeIndex(bytecodeIndex); unsigned divotInProvider = sourceOffset() + entry.divot; +#endif return source().provider()->documentLineColumnForOffset(divotInProvider); } diff --git a/Source/JavaScriptCore/bytecode/ExpressionInfo.cpp b/Source/JavaScriptCore/bytecode/ExpressionInfo.cpp index 65522c791d885..b8448573ce7fc 100644 --- a/Source/JavaScriptCore/bytecode/ExpressionInfo.cpp +++ b/Source/JavaScriptCore/bytecode/ExpressionInfo.cpp @@ -890,6 +890,19 @@ auto ExpressionInfo::entryForInstPC(InstPC instPC) -> Entry return decoder.entry(); } +#if USE(BUN_JSC_ADDITIONS) +unsigned ExpressionInfo::divotForInstPC(InstPC instPC) +{ + auto iter = m_cachedDivots.find(instPC); + if (iter != m_cachedDivots.end()) + return iter->value; + + unsigned divot = entryForInstPC(instPC).divot; + m_cachedDivots.add(instPC, divot); + return divot; +} +#endif + template void ExpressionInfo::print(PrintStream& out, FieldID fieldID, unsigned value) { diff --git a/Source/JavaScriptCore/bytecode/ExpressionInfo.h b/Source/JavaScriptCore/bytecode/ExpressionInfo.h index d839a8a320dcb..ccbc48f29c030 100644 --- a/Source/JavaScriptCore/bytecode/ExpressionInfo.h +++ b/Source/JavaScriptCore/bytecode/ExpressionInfo.h @@ -183,6 +183,12 @@ class ExpressionInfo { Entry NODELETE entryForInstPC(InstPC); +#if USE(BUN_JSC_ADDITIONS) + // entryForInstPC() decodes from the start of the chapter on every call, and a stack trace + // asks for the same instructions again. This keeps the divot of each instruction it was asked for. + unsigned divotForInstPC(InstPC); +#endif + bool isEmpty() const { return !m_numberOfEncodedInfo; }; size_t NODELETE byteSize() const; // owned by this object size_t NODELETE byteSizeForGCPacing() const; // what a generated (non-borrowed) one this size would own @@ -312,7 +318,11 @@ class ExpressionInfo { static constexpr unsigned numberOfWordsBetweenChapters = 10000; +#if USE(BUN_JSC_ADDITIONS) + using DivotMap = UncheckedKeyHashMap, WTF::UnsignedWithZeroKeyHashTraits>; + DivotMap m_cachedDivots; +#endif unsigned m_numberOfChapters; unsigned m_numberOfEncodedInfo; unsigned m_numberOfEncodedInfoExtensions; diff --git a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp index fcb9469ab93d0..a266c33008743 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp @@ -187,6 +187,13 @@ ExpressionInfo::Entry UnlinkedCodeBlock::expressionInfoForBytecodeIndex(Bytecode return expressionInfo().entryForInstPC(bytecodeIndex.offset()); } +#if USE(BUN_JSC_ADDITIONS) +unsigned UnlinkedCodeBlock::divotForBytecodeIndex(BytecodeIndex bytecodeIndex) +{ + return expressionInfo().divotForInstPC(bytecodeIndex.offset()); +} +#endif + #ifndef NDEBUG static void dumpExpressionInfoDetails(size_t index, const JSInstructionStream& instructionStream, unsigned instructionOffset, unsigned divot, unsigned startOffset, unsigned endOffset) { diff --git a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h index 384757482bcc6..206201308fe62 100644 --- a/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h +++ b/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h @@ -183,7 +183,7 @@ class UnlinkedCodeBlock : public JSCell { bool hasExpressionInfo() { return !expressionInfo().isEmpty(); } // Null while the expression info is still in the cache payload. For a caller that cannot take m_lock or allocate // (a sampling hook inside malloc): unlike expressionInfo() it never decodes. On the result use entryForInstPC(), - // which does neither; lineColumnForInstPC() fills a cache. + // which does neither; divotForInstPC() fills a cache. ExpressionInfo* expressionInfoIfDecoded() const { return m_expressionInfo.get(); } bool hasCheckpoints() const { return m_hasCheckpoints; } @@ -291,6 +291,9 @@ class UnlinkedCodeBlock : public JSCell { bool hasRareData() const { return m_rareData.get(); } ExpressionInfo::Entry expressionInfoForBytecodeIndex(BytecodeIndex); +#if USE(BUN_JSC_ADDITIONS) + unsigned divotForBytecodeIndex(BytecodeIndex); +#endif bool typeProfilerExpressionInfoForBytecodeOffset(unsigned bytecodeOffset, unsigned& startDivot, unsigned& endDivot); diff --git a/Source/JavaScriptCore/parser/SourceProvider.h b/Source/JavaScriptCore/parser/SourceProvider.h index 8e3305dadb56e..d7a2e805ce4f8 100644 --- a/Source/JavaScriptCore/parser/SourceProvider.h +++ b/Source/JavaScriptCore/parser/SourceProvider.h @@ -124,6 +124,9 @@ class JS_EXPORT_PRIVATE SourceProvider : public ThreadSafeRefCounted shifts every line of its document, but shifts the column only on its first // line, since later lines begin where their own line begins. LineColumn documentLineColumnForOffset(unsigned offset) { - auto info = positionInfoForOffset(offset); + auto info = lineAndColumnInfoForOffset(offset); return { m_startPosition.m_line.oneBasedInt() + info.line0Based, info.line0Based ? info.column0Based + 1 : m_startPosition.m_column.oneBasedInt() + info.column0Based, @@ -202,7 +218,7 @@ class JS_EXPORT_PRIVATE SourceProvider : public ThreadSafeRefCounted