Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions JSTests/stress/stack-position-is-the-same-on-every-read.js
Original file line number Diff line number Diff line change
@@ -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]);
}
4 changes: 4 additions & 0 deletions Source/JavaScriptCore/bytecode/CodeBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
13 changes: 13 additions & 0 deletions Source/JavaScriptCore/bytecode/ExpressionInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned bitCount>
void ExpressionInfo::print(PrintStream& out, FieldID fieldID, unsigned value)
{
Expand Down
10 changes: 10 additions & 0 deletions Source/JavaScriptCore/bytecode/ExpressionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -312,7 +318,11 @@ class ExpressionInfo {

static constexpr unsigned numberOfWordsBetweenChapters = 10000;

#if USE(BUN_JSC_ADDITIONS)
using DivotMap = UncheckedKeyHashMap<InstPC, unsigned, WTF::IntHash<InstPC>, WTF::UnsignedWithZeroKeyHashTraits<InstPC>>;

DivotMap m_cachedDivots;
#endif
unsigned m_numberOfChapters;
unsigned m_numberOfEncodedInfo;
unsigned m_numberOfEncodedInfoExtensions;
Expand Down
7 changes: 7 additions & 0 deletions Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
5 changes: 4 additions & 1 deletion Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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);

Expand Down
20 changes: 18 additions & 2 deletions Source/JavaScriptCore/parser/SourceProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ class JS_EXPORT_PRIVATE SourceProvider : public ThreadSafeRefCounted<SourceProvi
#if USE(BUN_JSC_ADDITIONS)
JS_EXPORT_PRIVATE virtual size_t memoryCost() const { return 0; }
JS_EXPORT_PRIVATE virtual void didGenerateUnlinkedCodeBlock(VM&, const SourceCodeKey&, UnlinkedCodeBlock*) const { }
// A provider that can find a line and a column without the line start table overrides this.
// Both are zero-based and in the provider's own text. Any thread can call it. False means: use the table.
JS_EXPORT_PRIVATE virtual bool lineAndColumnForOffset(unsigned /* offset */, unsigned& /* line0Based */, unsigned& /* column0Based */) { return false; }
#endif

StringView getRange(int start, int end) const LIFETIME_BOUND
Expand Down Expand Up @@ -189,11 +192,24 @@ class JS_EXPORT_PRIVATE SourceProvider : public ThreadSafeRefCounted<SourceProvi
return m_lineStartTable.offsetForPosition(source(), line0Based, column0Based);
}

#if USE(BUN_JSC_ADDITIONS)
// positionInfoForOffset() for a caller that reads the line and the column only.
LineStartTable::PositionInfo lineAndColumnInfoForOffset(unsigned offset)
{
LineStartTable::PositionInfo info;
if (lineAndColumnForOffset(offset, info.line0Based, info.column0Based))
return info;
return positionInfoForOffset(offset);
}
#else
LineStartTable::PositionInfo lineAndColumnInfoForOffset(unsigned offset) { return positionInfoForOffset(offset); }
#endif

// An inline <script> 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,
Expand All @@ -202,7 +218,7 @@ class JS_EXPORT_PRIVATE SourceProvider : public ThreadSafeRefCounted<SourceProvi

LineColumn documentZeroBasedLineColumnForOffset(unsigned offset)
{
auto info = positionInfoForOffset(offset);
auto info = lineAndColumnInfoForOffset(offset);
return {
m_startPosition.m_line.zeroBasedInt() + info.line0Based,
info.line0Based ? info.column0Based : m_startPosition.m_column.zeroBasedInt() + info.column0Based,
Expand Down
Loading