[webkit-changes] [WebKit/WebKit] 126d20: Introduce ExpressionInfo as a replacement for Expr...

Commit Queue noreply at github.com
Fri Jan 19 12:07:11 PST 2024


  Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 126d2027f1ce6c13bd147e6307b7edb1b821599e
      https://github.com/WebKit/WebKit/commit/126d2027f1ce6c13bd147e6307b7edb1b821599e
  Author: Mark Lam <mark.lam at apple.com>
  Date:   2024-01-19 (Fri, 19 Jan 2024)

  Changed paths:
    M JSTests/wasm/references/externref_table.js
    M JSTests/wasm/references/func_ref.js
    M JSTests/wasm/references/multitable.js
    M JSTests/wasm/references/table_misc.js
    M JSTests/wasm/regress/unmatched-else.js
    M Source/JavaScriptCore/CMakeLists.txt
    M Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
    M Source/JavaScriptCore/Sources.txt
    M Source/JavaScriptCore/bytecode/BytecodeRewriter.h
    M Source/JavaScriptCore/bytecode/CodeBlock.cpp
    M Source/JavaScriptCore/bytecode/CodeBlock.h
    A Source/JavaScriptCore/bytecode/ExpressionInfo.cpp
    A Source/JavaScriptCore/bytecode/ExpressionInfo.h
    A Source/JavaScriptCore/bytecode/ExpressionInfoInlines.h
    R Source/JavaScriptCore/bytecode/ExpressionRangeInfo.h
    M Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp
    M Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h
    M Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.cpp
    M Source/JavaScriptCore/bytecode/UnlinkedCodeBlockGenerator.h
    M Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.h
    M Source/JavaScriptCore/runtime/CachedTypes.cpp
    M Source/JavaScriptCore/runtime/ErrorInstance.cpp
    M Source/JavaScriptCore/runtime/FileBasedFuzzerAgent.cpp
    M Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.cpp
    M Source/JavaScriptCore/runtime/FileBasedFuzzerAgentBase.h
    M Source/JavaScriptCore/runtime/SamplingProfiler.cpp
    M Source/WTF/wtf/HashMap.h
    M Source/WTF/wtf/HashTable.h

  Log Message:
  -----------
  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




More information about the webkit-changes mailing list