Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ExpressionInfo as a replacement for ExpressionRangeInfo. #22880

Conversation

MenloDorian
Copy link

@MenloDorian MenloDorian commented Jan 17, 2024

126d202

Introduce ExpressionInfo as a replacement for ExpressionRangeInfo.
https://bugs.webkit.org/show_bug.cgi?id=260324
rdar://problem/114357179

Reviewed by Justin Michaud.

Previously, ExpressionRangeInfo takes 24 bytes for every entry, plus potentially a FatPosition
entry for large line and column values.  It would also just clear divot and its start and end
offsets to 0 if they are too large.

The new ExpressionInfo compresses most expression info into just 4 bytes, with occasional
exceptions.  I estimate that this change may yield a 0.4% improvement on Membuster.

Here's how ExpressionInfo works (from a comment in ExpressionInfo.cpp):

    Since ExpressionInfo is only used to get source position info (e.g. line and column)
    for error messages / stacks and debugging, it need not be fast. However, empirical data
    shows that there is a lot of it on websites that are especially memory hungry. So, it
    behooves us to reduce this memory burden.

    ExpressionInfo is a data structure that contains:
       a. EncodedInfo entries.
          This is where the expression info data is really stored.

       b. Chapter marks in the list of EncodedInfo entries.
          This is just an optimization aid to speed up reconstruction of expression info
          from the EncodedInfo.

       c. A LineColumnMap cache.
          This is to speed up look up of LineColumn values we have looked up before.

    Encoding of EncodedInfo words
    =============================

      Extension: [   11111b | 1b |    offset:26                                ]
      AbsInstPC: [   11111b | 0b |     value:26                                ]
      MultiWide: [   11110b | 1b |     111b | numFields:5 | fields[6]:18       ] [ value:32 ] ...
        DuoWide: [   11110b | 1b | field2:3 | value2:10 | field1:3 | value1:10 ]
     SingleWide: [   11110b | 0b |  field:3 | value:23                         ]
   ExtensionEnd: [   11110b | 0b |     111b |     0:23                         ]
          Basic: [ instPC:5 |   divot:7 | start:6 |  end:6 | line:3 | column:5 ]

    Details of what these encodings are used for follows.

    EncodedInfo
    ===========
    Abstractly, each expression info defines 6 fields of unsigned type:
    1. InstPC: bytecodeIndex offset.
    2. Divot: the execution point in an expression.
    3. Start: the offset from the Divot to the start of the expression.
    4. End: the offset from the Divot to the end of the expression.
    5. Line: the line number at the Divot.
    6. Column: the column number at the Divot.

    Let's call this an expression info entry, represented by ExpressionInfo::Entry.

    However, we know that the delta values of these fields between consecutive entries tend to be
    small. So, instead of encoding an Entry with 24 bytes, we aim to encode the info in just 4 bytes
    in an EncodedInfo word. See the encoding of the Basic word above.

    UnlinkedCodeBlockGenerator::addExpressionInfo() triggers the addition of EncodedInfo.
    Each call of addExpressionInfo() may add 1 or more EncodedInfo. It can be more than 1 because
    the delta of some of the fields may exceed the capacity afforded them in the Basic word.

    To compensate for this, we introduce Wide Encodings of the EncodedInfo word.

    Wide Encodings
    ==============
    Wide Encodings specify additional delta to add to each field value. Wide Encodings must
    eventually be followed by a Basic word. The Basic word acts as the terminator for each
    expression info entry.

    The 3 types of Wide Encodings are:

    1. SingleWide: encodes a single field value with size singleValueBits (currently 23) bits.
    2. DuoWide: encodes 2 field values with size duoValueBits (currently 10) bits each.
    3. MultiWide: encodes up to 6 field values (because there are only 6 fields in an expression
       info record). The 1st word will be a header specifying the FieldIDs. Subsequent words are
       full 32-bit values for those respective fields.

    Extension Encodings
    ===================
    Some additional consideration: after UnlinkedCodeBlockGenerator::addExpressionInfo() is done,
    UnlinkedCodeBlockGenerator::applyModification() may insert, remove, add new bytecode due to
    generatorification. Hence, the InstPC the EncodedInfo defining each Entry needs to be adjusted
    accordingly. Similarly, we also need to adjust the Chapter marks in the EncodedInfo.

    Given that the EncodedInfo is a compressed form of the Entry fields, there may or may not be
    sufficient bits to encode the new InstPC. The straightforward implementation would be to
    insert some Wide Encodings to add additional delta for the InstPC. However, this will result
    in a lot of shifting of the EncodedInfo vector.

    For simplicity and performance (given these adjustments are more rare), rather than actually
    inserting additional EncodedInfo, we do an inplace replacement of the first EncodedInfo for the
    Entry with an Extension (see encodings above) with an offset to an extension section in the
    EncodedInfo vector (pass the end of the normal non-Extension info). This effectively behaves
    like a call to an extension routine to apply some additional EncodedInfo to adjust the InstPC
    as needed.

    The original first EncodedInfo for the Entry will be copied over as the first EncodedInfo in
    the extension section. The only exception to this is when the first EncodedInfo is a Basic
    word. By definition, the Basic word is the terminator i.e. it must be the last EncodedInfo in
    the sequence for each Entry.

    To terminate the extension island, we introduce an ExtensionEnd word that tells the decoder
    to resume decoding in the original EncodedInfo stream (effectively a return operation).
    This is needed if the replaced word is not a Basic word (which again is the terminator).
    If the replaced word is the Basic word, then we don't need to append an ExtensionEnd at the
    end of the extension and will execute the "return" operation.

    Miscellaneous details:
    1. Currently, the code expects that Extensions cannot be nested. There is no need to nest,
       and there are ASSERTs in the decoder to check for this. However, if we find a need in the
       future, this can certainly be changed.

    2. The inplace replacement works on the assumption that we can move a "complete" EncodedInfo
       over to the extension island. For all encodings, this is just a single word. The only
       exception is the MultiWide encoding, which are followed by N value words. Because the
       decoder expects these words to be contiguous, we move all the value words over too. The
       slots of the original value words will not be replaced by no-ops (SingleWide with 0).

    AbsInstPC and Chapters
    ======================
    One downside of this compressed encoding scheme is that every time we need the Entry value
    for a given InstPC, we need to decode from the start of the EncodedInfo vector to compute the
    cummulative value of all the deltas up to the Entry of interest. This can be slow if the
    EncodedInfo vector is large.

    To mitigate against this, we break the EncodedInfo regions into Chapters every
    numberOfWordsBetweenChapters (currently 10000) words or so. A Chapter is always started
    with an AbsInstPC (Absolute Instruction PC) word. Unlike other EncodedInfo words with defines
    delta values for fields to add, AbsInstPC defines an absolute value (currently 26 bits) for
    the InstPC to mark the start of the Chapter. If 26-bits isn't enough, we can always add
    SingleWide or MultiWide words to make up the difference of the InstPC for this Entry.

    Apart from resetting the cummulative InstPC to this new value, AbsInstPC also implicitly
    clears to 0 the cummulative delta for all the other fields. As a result, we can always jump
    to the start of a Chapter and start decoding from there (instead of starting from the start
    of the EncodedInfo vector). It limits the number of words that we need to decode to around
    numberOfWordsBetweenChapters.

    Now, all we need to know is which Chapter a given InstPC falls into.

    Finding Chapters
    ================
    For this, we have a vector of Chapter info, where each Chapter defines its starting InstPC
    and the EncodedInfo word index for the start of the first encoded Entry in that Chapter.

    The first Chapter always starts at InstPC 0 and EncodedInfo index 0. Hence, we do not emit
    a Chapter entry for the 1st Chapter. Hence, for most small functions that do not have
    more than numberOfWordsBetweenChapters EncodedInfo, there is no Chapter info in the
    ExpressionInfo.

    Additional Compression Details (of Basic word fields)
    =====================================================

    1. Column Reset on Line change

    When Line changes from the last Entry, we always reset the cummulative Column to 0. This makes
    sense because Column is relative to the start of the Line. Without this reset, the delta for
    Column may be some huge negative value (which hurts compression).

    2. Column same as Divot

    Column often has the same value as Divot. This is because a lot of websites use compacted
    and obfuscated JS code in a single line. Therefore, the column position is exactly the Divot
    position. So, the Basic word reserves the value sameAsDivotValue (currently 0b1111) as an
    indicator that the Column delta to apply is same as the Divot delta for this Entry. This
    reduces the number of Wide values we need to encode both if the Divot is large.

    3. Biased fields

    Divot, Line, and Column deltas are signed ints, not unsigned. This is because the evaluation
    of an expression isn't in sequential order. For example, consider this expression:

        foo(bar())
           ^   ^-------- Divot, Line, and Column for bar()
           `------------ Divot, Line, and Column for foo()

    The order of evaluation is first to evaluate the call to bar() followed by the call to foo().
    As a result, the Divot delta for the call to foo() is a negative value relative to the Divot
    for the call to bar(). Similarly, this is true for Line and Column.

    Since the delta values for these 3 fields are signed, instead of storing an unsigned value at
    their bitfield positions in the Basic word, we store a biased value: Divot + divotBias,
    Line + lineBias, and Column + columnBias. This maps the values into an unsigned, and makes it
    easy to do a bounds check against the max capacity of the bitfield.

    Similarly, ExpressionInfo::Diff which is used to track the delta for each field has signed int
    for these fields. This is in contrast to Entry and LineColumn where these fields are stored
    as unsigned.

    Backing Store and Shape
    =======================
    The ExpressionInfo and its backing store (with the exception of the contents of the
    LineColumnMap cache) is allocated as a contiguous slab. We first compute the size of the slab,
    then allocate it, and lastly use placement new to instantiate the ExpressionInfo.

    The shape of ExpressionInfo looks like this:

            ExpressionInfo: [ m_cachedLineColumns             ]
                            [ m_numberOfChapters              ]
                            [ m_numberOfEncodedInfo           ]
                            [ m_numberOfEncodedInfoExtensions ]
            Chapters Start: [ chapters()[0]                      ]
                            ...
                            [ chapters()[m_numberOfChapters - 1] ]
         EncodedInfo Start: [ encodedInfo()[0]                   ]
                            ...
                            [ encodedInfo()[m_numberOfEncodedInfo - 1] ]
          Extensions Start: [ encodedInfo()[m_numberOfEncodedInfo]     ]
                            ...
                            [ encodedInfo()[m_numberOfEncodedInfo + m_numberOfEncodedInfoExtensions - 1] ]
            Extensions End:

* JSTests/wasm/references/externref_table.js:
* JSTests/wasm/references/func_ref.js:
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly.imp.ref):
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly):
* JSTests/wasm/references/multitable.js:
* JSTests/wasm/references/table_misc.js:
(GetLocal.0.GetLocal.1.TableGrow.0.End.End.WebAssembly):
* JSTests/wasm/regress/unmatched-else.js:
(catch):
* Source/JavaScriptCore/CMakeLists.txt:
* Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj:
* Source/JavaScriptCore/Sources.txt:
* Source/JavaScriptCore/bytecode/BytecodeRewriter.h:
(JSC::BytecodeRewriter::forEachLabelPoint):
* Source/JavaScriptCore/bytecode/CodeBlock.cpp:
(JSC::CodeBlock::expressionInfoForBytecodeIndex const):
(JSC::CodeBlock::expressionRangeForBytecodeIndex const): Deleted.
* Source/JavaScriptCore/bytecode/CodeBlock.h:
* Source/JavaScriptCore/bytecode/ExpressionInfo.cpp: Added.
(JSC::ExpressionInfo::Diff::set):
(JSC::ExpressionInfo::Diff::reset):
(JSC::ExpressionInfo::EncodedInfo::isAbsInstPC const):
(JSC::ExpressionInfo::EncodedInfo::isExtension const):
(JSC::ExpressionInfo::Encoder::encodeAbsInstPC):
(JSC::ExpressionInfo::Encoder::encodeExtension):
(JSC::ExpressionInfo::Encoder::encodeExtensionEnd):
(JSC::ExpressionInfo::Encoder::encodeSingle):
(JSC::ExpressionInfo::Encoder::encodeDuo):
(JSC::ExpressionInfo::Encoder::encodeMultiHeader):
(JSC::ExpressionInfo::Encoder::encodeBasic):
(JSC::ExpressionInfo::Encoder::adjustInstPC):
(JSC::ExpressionInfo::Encoder::encode):
(JSC::ExpressionInfo::Encoder::fits):
(JSC::ExpressionInfo::Encoder::createExpressionInfo):
(JSC::ExpressionInfo::Decoder::Decoder):
(JSC::ExpressionInfo::Decoder::recacheInfo):
(JSC::ExpressionInfo::cast):
(JSC::ExpressionInfo::isSpecial):
(JSC::ExpressionInfo::isWideOrSpecial):
(JSC::ExpressionInfo::Decoder::decode):
(JSC::ExpressionInfo::createUninitialized):
(JSC::ExpressionInfo::ExpressionInfo):
(JSC::ExpressionInfo::byteSize const):
(JSC::ExpressionInfo::lineColumnForInstPC):
(JSC::ExpressionInfo::findChapterEncodedInfoJustBelow const):
(JSC::ExpressionInfo::entryForInstPC):
(JSC::ExpressionInfo::print):
(JSC::ExpressionInfo::dumpEncodedInfo):
(WTF::printInternal):
* Source/JavaScriptCore/bytecode/ExpressionInfo.h: Added.
(JSC::ExpressionInfo::Entry::reset):
(JSC::ExpressionInfo::Encoder::entry const):
(JSC::ExpressionInfo::Encoder::dumpEncodedInfo):
(JSC::ExpressionInfo::Decoder::currentInfo const):
(JSC::ExpressionInfo::Decoder::setNextInfo):
(JSC::ExpressionInfo::Decoder::entry const):
(JSC::ExpressionInfo::Decoder::setEntry):
(JSC::ExpressionInfo::Decoder::instPC const):
(JSC::ExpressionInfo::Decoder::divot const):
(JSC::ExpressionInfo::Decoder::startOffset const):
(JSC::ExpressionInfo::Decoder::endOffset const):
(JSC::ExpressionInfo::Decoder::lineColumn const):
(JSC::ExpressionInfo::Decoder::appendWide):
(JSC::ExpressionInfo::isEmpty const):
(JSC::ExpressionInfo::payloadSizeInBytes):
(JSC::ExpressionInfo::totalSizeInBytes):
(JSC::ExpressionInfo::chapters const):
(JSC::ExpressionInfo::encodedInfo const):
(JSC::ExpressionInfo::endEncodedInfo const):
(JSC::ExpressionInfo::endExtensionEncodedInfo const):
(JSC::ExpressionInfo::payloadSize const):
(JSC::ExpressionInfo::payload const):
* Source/JavaScriptCore/bytecode/ExpressionInfoInlines.h: Added.
(JSC::ExpressionInfo::Encoder::remap):
* Source/JavaScriptCore/bytecode/ExpressionRangeInfo.h: Removed.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp:
(JSC::UnlinkedCodeBlock::visitChildrenImpl):
(JSC::UnlinkedCodeBlock::RareData::sizeInBytes const):
(JSC::UnlinkedCodeBlock::lineColumnForBytecodeIndex):
(JSC::UnlinkedCodeBlock::expressionInfoForBytecodeIndex):
(JSC::dumpExpressionInfoDetails):
(JSC::UnlinkedCodeBlock::dumpExpressionInfo):
(JSC::UnlinkedCodeBlock::getLineAndColumn const): Deleted.
(JSC::dumpLineColumnEntry): Deleted.
(JSC::UnlinkedCodeBlock::dumpExpressionRangeInfo): Deleted.
(JSC::UnlinkedCodeBlock::expressionRangeForBytecodeIndex const): Deleted.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h:
(JSC::UnlinkedCodeBlock::hasExpressionInfo):
(JSC::UnlinkedCodeBlock::expressionInfo): Deleted.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.cpp:
(JSC::UnlinkedCodeBlockGenerator::addExpressionInfo):
(JSC::UnlinkedCodeBlockGenerator::finalize):
(JSC::UnlinkedCodeBlockGenerator::applyModification):
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h:
* Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h:
* Source/JavaScriptCore/runtime/CachedTypes.cpp:
(JSC::CachedArray::encode):
(JSC::CachedArray::decode const):
(JSC::CachedCodeBlockRareData::encode):
(JSC::CachedCodeBlockRareData::decode const):
(JSC::CachedExpressionInfo::encode):
(JSC::CachedExpressionInfo::decode const):
(JSC::CachedCodeBlock<CodeBlockType>::decode const):
(JSC::CachedCodeBlock<CodeBlockType>::encode):
* Source/JavaScriptCore/runtime/ErrorInstance.cpp:
(JSC::appendSourceToErrorMessage):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgent.cpp:
(JSC::FileBasedFuzzerAgent::getPredictionInternal):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.cpp:
(JSC::FileBasedFuzzerAgentBase::getPrediction):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.h:
* Source/JavaScriptCore/runtime/SamplingProfiler.cpp:
(JSC::SamplingProfiler::processUnverifiedStackTraces):
* Source/WTF/wtf/HashMap.h:
(WTF::Y>::byteSize const):
* Source/WTF/wtf/HashTable.h:
(WTF::HashTable::byteSize const):

Canonical link: https://commits.webkit.org/273233@main

5f1edbc

Misc iOS, tvOS & watchOS macOS Linux Windows
βœ… πŸ§ͺ style βœ… πŸ›  ios βœ… πŸ›  mac βœ… πŸ›  wpe βœ… πŸ›  wincairo
βœ… πŸ›  ios-sim βœ… πŸ›  mac-AS-debug βœ… πŸ§ͺ wpe-wk2
βœ… πŸ§ͺ webkitperl βœ… πŸ§ͺ ios-wk2 βœ… πŸ§ͺ api-mac βœ… πŸ§ͺ api-wpe
βœ… πŸ§ͺ ios-wk2-wpt βœ… πŸ§ͺ mac-wk1 βœ… πŸ›  gtk
βœ… πŸ›  πŸ§ͺ jsc βœ… πŸ§ͺ api-ios βœ… πŸ§ͺ mac-wk2 βœ… πŸ§ͺ gtk-wk2
βœ… πŸ›  πŸ§ͺ jsc-arm64 βœ… πŸ›  tv βœ… πŸ§ͺ mac-AS-debug-wk2 βœ… πŸ§ͺ api-gtk
βœ… πŸ›  tv-sim βœ… πŸ›  jsc-armv7
βœ… πŸ›  watch βœ… πŸ§ͺ jsc-armv7-tests
βœ… πŸ›  πŸ§ͺ unsafe-merge βœ… πŸ›  watch-sim

@MenloDorian MenloDorian requested a review from a team as a code owner January 17, 2024 22:47
@MenloDorian MenloDorian self-assigned this Jan 17, 2024
@MenloDorian MenloDorian added the JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues. label Jan 17, 2024
Copy link
Contributor

@justinmichaud justinmichaud left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rs=me

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 17, 2024
@MenloDorian MenloDorian removed the merging-blocked Applied to prevent a change from being merged label Jan 17, 2024
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 2b29080 to 179bf16 Compare January 17, 2024 23:58
@MenloDorian MenloDorian added skip-ews Applied to prevent a change from being run on EWS and removed skip-ews Applied to prevent a change from being run on EWS labels Jan 17, 2024
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 179bf16 to abe8c72 Compare January 18, 2024 00:06
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from abe8c72 to cc3538d Compare January 18, 2024 00:41
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from cc3538d to ea802f8 Compare January 18, 2024 00:50
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian removed the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from ea802f8 to 1d7cacf Compare January 18, 2024 01:40
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 1d7cacf to 8839eda Compare January 18, 2024 01:49
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian removed the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 8839eda to 32681c2 Compare January 18, 2024 23:08
@webkit-early-warning-system
Copy link
Collaborator

Starting EWS tests for 32681c2. Live statuses available at the PR page, #22880

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian removed the merging-blocked Applied to prevent a change from being merged label Jan 18, 2024
@MenloDorian MenloDorian force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 32681c2 to 5f1edbc Compare January 18, 2024 23:34
@MenloDorian MenloDorian added the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Jan 19, 2024
https://bugs.webkit.org/show_bug.cgi?id=260324
rdar://problem/114357179

Reviewed by Justin Michaud.

Previously, ExpressionRangeInfo takes 24 bytes for every entry, plus potentially a FatPosition
entry for large line and column values.  It would also just clear divot and its start and end
offsets to 0 if they are too large.

The new ExpressionInfo compresses most expression info into just 4 bytes, with occasional
exceptions.  I estimate that this change may yield a 0.4% improvement on Membuster.

Here's how ExpressionInfo works (from a comment in ExpressionInfo.cpp):

    Since ExpressionInfo is only used to get source position info (e.g. line and column)
    for error messages / stacks and debugging, it need not be fast. However, empirical data
    shows that there is a lot of it on websites that are especially memory hungry. So, it
    behooves us to reduce this memory burden.

    ExpressionInfo is a data structure that contains:
       a. EncodedInfo entries.
          This is where the expression info data is really stored.

       b. Chapter marks in the list of EncodedInfo entries.
          This is just an optimization aid to speed up reconstruction of expression info
          from the EncodedInfo.

       c. A LineColumnMap cache.
          This is to speed up look up of LineColumn values we have looked up before.

    Encoding of EncodedInfo words
    =============================

      Extension: [   11111b | 1b |    offset:26                                ]
      AbsInstPC: [   11111b | 0b |     value:26                                ]
      MultiWide: [   11110b | 1b |     111b | numFields:5 | fields[6]:18       ] [ value:32 ] ...
        DuoWide: [   11110b | 1b | field2:3 | value2:10 | field1:3 | value1:10 ]
     SingleWide: [   11110b | 0b |  field:3 | value:23                         ]
   ExtensionEnd: [   11110b | 0b |     111b |     0:23                         ]
          Basic: [ instPC:5 |   divot:7 | start:6 |  end:6 | line:3 | column:5 ]

    Details of what these encodings are used for follows.

    EncodedInfo
    ===========
    Abstractly, each expression info defines 6 fields of unsigned type:
    1. InstPC: bytecodeIndex offset.
    2. Divot: the execution point in an expression.
    3. Start: the offset from the Divot to the start of the expression.
    4. End: the offset from the Divot to the end of the expression.
    5. Line: the line number at the Divot.
    6. Column: the column number at the Divot.

    Let's call this an expression info entry, represented by ExpressionInfo::Entry.

    However, we know that the delta values of these fields between consecutive entries tend to be
    small. So, instead of encoding an Entry with 24 bytes, we aim to encode the info in just 4 bytes
    in an EncodedInfo word. See the encoding of the Basic word above.

    UnlinkedCodeBlockGenerator::addExpressionInfo() triggers the addition of EncodedInfo.
    Each call of addExpressionInfo() may add 1 or more EncodedInfo. It can be more than 1 because
    the delta of some of the fields may exceed the capacity afforded them in the Basic word.

    To compensate for this, we introduce Wide Encodings of the EncodedInfo word.

    Wide Encodings
    ==============
    Wide Encodings specify additional delta to add to each field value. Wide Encodings must
    eventually be followed by a Basic word. The Basic word acts as the terminator for each
    expression info entry.

    The 3 types of Wide Encodings are:

    1. SingleWide: encodes a single field value with size singleValueBits (currently 23) bits.
    2. DuoWide: encodes 2 field values with size duoValueBits (currently 10) bits each.
    3. MultiWide: encodes up to 6 field values (because there are only 6 fields in an expression
       info record). The 1st word will be a header specifying the FieldIDs. Subsequent words are
       full 32-bit values for those respective fields.

    Extension Encodings
    ===================
    Some additional consideration: after UnlinkedCodeBlockGenerator::addExpressionInfo() is done,
    UnlinkedCodeBlockGenerator::applyModification() may insert, remove, add new bytecode due to
    generatorification. Hence, the InstPC the EncodedInfo defining each Entry needs to be adjusted
    accordingly. Similarly, we also need to adjust the Chapter marks in the EncodedInfo.

    Given that the EncodedInfo is a compressed form of the Entry fields, there may or may not be
    sufficient bits to encode the new InstPC. The straightforward implementation would be to
    insert some Wide Encodings to add additional delta for the InstPC. However, this will result
    in a lot of shifting of the EncodedInfo vector.

    For simplicity and performance (given these adjustments are more rare), rather than actually
    inserting additional EncodedInfo, we do an inplace replacement of the first EncodedInfo for the
    Entry with an Extension (see encodings above) with an offset to an extension section in the
    EncodedInfo vector (pass the end of the normal non-Extension info). This effectively behaves
    like a call to an extension routine to apply some additional EncodedInfo to adjust the InstPC
    as needed.

    The original first EncodedInfo for the Entry will be copied over as the first EncodedInfo in
    the extension section. The only exception to this is when the first EncodedInfo is a Basic
    word. By definition, the Basic word is the terminator i.e. it must be the last EncodedInfo in
    the sequence for each Entry.

    To terminate the extension island, we introduce an ExtensionEnd word that tells the decoder
    to resume decoding in the original EncodedInfo stream (effectively a return operation).
    This is needed if the replaced word is not a Basic word (which again is the terminator).
    If the replaced word is the Basic word, then we don't need to append an ExtensionEnd at the
    end of the extension and will execute the "return" operation.

    Miscellaneous details:
    1. Currently, the code expects that Extensions cannot be nested. There is no need to nest,
       and there are ASSERTs in the decoder to check for this. However, if we find a need in the
       future, this can certainly be changed.

    2. The inplace replacement works on the assumption that we can move a "complete" EncodedInfo
       over to the extension island. For all encodings, this is just a single word. The only
       exception is the MultiWide encoding, which are followed by N value words. Because the
       decoder expects these words to be contiguous, we move all the value words over too. The
       slots of the original value words will not be replaced by no-ops (SingleWide with 0).

    AbsInstPC and Chapters
    ======================
    One downside of this compressed encoding scheme is that every time we need the Entry value
    for a given InstPC, we need to decode from the start of the EncodedInfo vector to compute the
    cummulative value of all the deltas up to the Entry of interest. This can be slow if the
    EncodedInfo vector is large.

    To mitigate against this, we break the EncodedInfo regions into Chapters every
    numberOfWordsBetweenChapters (currently 10000) words or so. A Chapter is always started
    with an AbsInstPC (Absolute Instruction PC) word. Unlike other EncodedInfo words with defines
    delta values for fields to add, AbsInstPC defines an absolute value (currently 26 bits) for
    the InstPC to mark the start of the Chapter. If 26-bits isn't enough, we can always add
    SingleWide or MultiWide words to make up the difference of the InstPC for this Entry.

    Apart from resetting the cummulative InstPC to this new value, AbsInstPC also implicitly
    clears to 0 the cummulative delta for all the other fields. As a result, we can always jump
    to the start of a Chapter and start decoding from there (instead of starting from the start
    of the EncodedInfo vector). It limits the number of words that we need to decode to around
    numberOfWordsBetweenChapters.

    Now, all we need to know is which Chapter a given InstPC falls into.

    Finding Chapters
    ================
    For this, we have a vector of Chapter info, where each Chapter defines its starting InstPC
    and the EncodedInfo word index for the start of the first encoded Entry in that Chapter.

    The first Chapter always starts at InstPC 0 and EncodedInfo index 0. Hence, we do not emit
    a Chapter entry for the 1st Chapter. Hence, for most small functions that do not have
    more than numberOfWordsBetweenChapters EncodedInfo, there is no Chapter info in the
    ExpressionInfo.

    Additional Compression Details (of Basic word fields)
    =====================================================

    1. Column Reset on Line change

    When Line changes from the last Entry, we always reset the cummulative Column to 0. This makes
    sense because Column is relative to the start of the Line. Without this reset, the delta for
    Column may be some huge negative value (which hurts compression).

    2. Column same as Divot

    Column often has the same value as Divot. This is because a lot of websites use compacted
    and obfuscated JS code in a single line. Therefore, the column position is exactly the Divot
    position. So, the Basic word reserves the value sameAsDivotValue (currently 0b1111) as an
    indicator that the Column delta to apply is same as the Divot delta for this Entry. This
    reduces the number of Wide values we need to encode both if the Divot is large.

    3. Biased fields

    Divot, Line, and Column deltas are signed ints, not unsigned. This is because the evaluation
    of an expression isn't in sequential order. For example, consider this expression:

        foo(bar())
           ^   ^-------- Divot, Line, and Column for bar()
           `------------ Divot, Line, and Column for foo()

    The order of evaluation is first to evaluate the call to bar() followed by the call to foo().
    As a result, the Divot delta for the call to foo() is a negative value relative to the Divot
    for the call to bar(). Similarly, this is true for Line and Column.

    Since the delta values for these 3 fields are signed, instead of storing an unsigned value at
    their bitfield positions in the Basic word, we store a biased value: Divot + divotBias,
    Line + lineBias, and Column + columnBias. This maps the values into an unsigned, and makes it
    easy to do a bounds check against the max capacity of the bitfield.

    Similarly, ExpressionInfo::Diff which is used to track the delta for each field has signed int
    for these fields. This is in contrast to Entry and LineColumn where these fields are stored
    as unsigned.

    Backing Store and Shape
    =======================
    The ExpressionInfo and its backing store (with the exception of the contents of the
    LineColumnMap cache) is allocated as a contiguous slab. We first compute the size of the slab,
    then allocate it, and lastly use placement new to instantiate the ExpressionInfo.

    The shape of ExpressionInfo looks like this:

            ExpressionInfo: [ m_cachedLineColumns             ]
                            [ m_numberOfChapters              ]
                            [ m_numberOfEncodedInfo           ]
                            [ m_numberOfEncodedInfoExtensions ]
            Chapters Start: [ chapters()[0]                      ]
                            ...
                            [ chapters()[m_numberOfChapters - 1] ]
         EncodedInfo Start: [ encodedInfo()[0]                   ]
                            ...
                            [ encodedInfo()[m_numberOfEncodedInfo - 1] ]
          Extensions Start: [ encodedInfo()[m_numberOfEncodedInfo]     ]
                            ...
                            [ encodedInfo()[m_numberOfEncodedInfo + m_numberOfEncodedInfoExtensions - 1] ]
            Extensions End:

* JSTests/wasm/references/externref_table.js:
* JSTests/wasm/references/func_ref.js:
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly.imp.ref):
(string_appeared_here.End.End.Function.End.Code.End.WebAssembly):
* JSTests/wasm/references/multitable.js:
* JSTests/wasm/references/table_misc.js:
(GetLocal.0.GetLocal.1.TableGrow.0.End.End.WebAssembly):
* JSTests/wasm/regress/unmatched-else.js:
(catch):
* Source/JavaScriptCore/CMakeLists.txt:
* Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj:
* Source/JavaScriptCore/Sources.txt:
* Source/JavaScriptCore/bytecode/BytecodeRewriter.h:
(JSC::BytecodeRewriter::forEachLabelPoint):
* Source/JavaScriptCore/bytecode/CodeBlock.cpp:
(JSC::CodeBlock::expressionInfoForBytecodeIndex const):
(JSC::CodeBlock::expressionRangeForBytecodeIndex const): Deleted.
* Source/JavaScriptCore/bytecode/CodeBlock.h:
* Source/JavaScriptCore/bytecode/ExpressionInfo.cpp: Added.
(JSC::ExpressionInfo::Diff::set):
(JSC::ExpressionInfo::Diff::reset):
(JSC::ExpressionInfo::EncodedInfo::isAbsInstPC const):
(JSC::ExpressionInfo::EncodedInfo::isExtension const):
(JSC::ExpressionInfo::Encoder::encodeAbsInstPC):
(JSC::ExpressionInfo::Encoder::encodeExtension):
(JSC::ExpressionInfo::Encoder::encodeExtensionEnd):
(JSC::ExpressionInfo::Encoder::encodeSingle):
(JSC::ExpressionInfo::Encoder::encodeDuo):
(JSC::ExpressionInfo::Encoder::encodeMultiHeader):
(JSC::ExpressionInfo::Encoder::encodeBasic):
(JSC::ExpressionInfo::Encoder::adjustInstPC):
(JSC::ExpressionInfo::Encoder::encode):
(JSC::ExpressionInfo::Encoder::fits):
(JSC::ExpressionInfo::Encoder::createExpressionInfo):
(JSC::ExpressionInfo::Decoder::Decoder):
(JSC::ExpressionInfo::Decoder::recacheInfo):
(JSC::ExpressionInfo::cast):
(JSC::ExpressionInfo::isSpecial):
(JSC::ExpressionInfo::isWideOrSpecial):
(JSC::ExpressionInfo::Decoder::decode):
(JSC::ExpressionInfo::createUninitialized):
(JSC::ExpressionInfo::ExpressionInfo):
(JSC::ExpressionInfo::byteSize const):
(JSC::ExpressionInfo::lineColumnForInstPC):
(JSC::ExpressionInfo::findChapterEncodedInfoJustBelow const):
(JSC::ExpressionInfo::entryForInstPC):
(JSC::ExpressionInfo::print):
(JSC::ExpressionInfo::dumpEncodedInfo):
(WTF::printInternal):
* Source/JavaScriptCore/bytecode/ExpressionInfo.h: Added.
(JSC::ExpressionInfo::Entry::reset):
(JSC::ExpressionInfo::Encoder::entry const):
(JSC::ExpressionInfo::Encoder::dumpEncodedInfo):
(JSC::ExpressionInfo::Decoder::currentInfo const):
(JSC::ExpressionInfo::Decoder::setNextInfo):
(JSC::ExpressionInfo::Decoder::entry const):
(JSC::ExpressionInfo::Decoder::setEntry):
(JSC::ExpressionInfo::Decoder::instPC const):
(JSC::ExpressionInfo::Decoder::divot const):
(JSC::ExpressionInfo::Decoder::startOffset const):
(JSC::ExpressionInfo::Decoder::endOffset const):
(JSC::ExpressionInfo::Decoder::lineColumn const):
(JSC::ExpressionInfo::Decoder::appendWide):
(JSC::ExpressionInfo::isEmpty const):
(JSC::ExpressionInfo::payloadSizeInBytes):
(JSC::ExpressionInfo::totalSizeInBytes):
(JSC::ExpressionInfo::chapters const):
(JSC::ExpressionInfo::encodedInfo const):
(JSC::ExpressionInfo::endEncodedInfo const):
(JSC::ExpressionInfo::endExtensionEncodedInfo const):
(JSC::ExpressionInfo::payloadSize const):
(JSC::ExpressionInfo::payload const):
* Source/JavaScriptCore/bytecode/ExpressionInfoInlines.h: Added.
(JSC::ExpressionInfo::Encoder::remap):
* Source/JavaScriptCore/bytecode/ExpressionRangeInfo.h: Removed.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp:
(JSC::UnlinkedCodeBlock::visitChildrenImpl):
(JSC::UnlinkedCodeBlock::RareData::sizeInBytes const):
(JSC::UnlinkedCodeBlock::lineColumnForBytecodeIndex):
(JSC::UnlinkedCodeBlock::expressionInfoForBytecodeIndex):
(JSC::dumpExpressionInfoDetails):
(JSC::UnlinkedCodeBlock::dumpExpressionInfo):
(JSC::UnlinkedCodeBlock::getLineAndColumn const): Deleted.
(JSC::dumpLineColumnEntry): Deleted.
(JSC::UnlinkedCodeBlock::dumpExpressionRangeInfo): Deleted.
(JSC::UnlinkedCodeBlock::expressionRangeForBytecodeIndex const): Deleted.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h:
(JSC::UnlinkedCodeBlock::hasExpressionInfo):
(JSC::UnlinkedCodeBlock::expressionInfo): Deleted.
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.cpp:
(JSC::UnlinkedCodeBlockGenerator::addExpressionInfo):
(JSC::UnlinkedCodeBlockGenerator::finalize):
(JSC::UnlinkedCodeBlockGenerator::applyModification):
* Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h:
* Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h:
* Source/JavaScriptCore/runtime/CachedTypes.cpp:
(JSC::CachedArray::encode):
(JSC::CachedArray::decode const):
(JSC::CachedCodeBlockRareData::encode):
(JSC::CachedCodeBlockRareData::decode const):
(JSC::CachedExpressionInfo::encode):
(JSC::CachedExpressionInfo::decode const):
(JSC::CachedCodeBlock<CodeBlockType>::decode const):
(JSC::CachedCodeBlock<CodeBlockType>::encode):
* Source/JavaScriptCore/runtime/ErrorInstance.cpp:
(JSC::appendSourceToErrorMessage):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgent.cpp:
(JSC::FileBasedFuzzerAgent::getPredictionInternal):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.cpp:
(JSC::FileBasedFuzzerAgentBase::getPrediction):
* Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.h:
* Source/JavaScriptCore/runtime/SamplingProfiler.cpp:
(JSC::SamplingProfiler::processUnverifiedStackTraces):
* Source/WTF/wtf/HashMap.h:
(WTF::Y>::byteSize const):
* Source/WTF/wtf/HashTable.h:
(WTF::HashTable::byteSize const):

Canonical link: https://commits.webkit.org/273233@main
@webkit-commit-queue webkit-commit-queue force-pushed the eng/Incorrect-error-message-in-accidental-function-call branch from 5f1edbc to 126d202 Compare January 19, 2024 20:06
@webkit-commit-queue
Copy link
Collaborator

Committed 273233@main (126d202): https://commits.webkit.org/273233@main

Reviewed commits have been landed. Closing PR #22880 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 126d202 into WebKit:main Jan 19, 2024
@webkit-commit-queue webkit-commit-queue removed the unsafe-merge-queue Applied to send a pull request to merge-queue, but skip building and testing label Jan 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues.
Projects
None yet
5 participants