Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 126d2027f1ce6c13bd147e6307b7edb1b821599e
      
https://github.com/WebKit/WebKit/commit/126d2027f1ce6c13bd147e6307b7edb1b821599e
  Author: Mark Lam <[email protected]>
  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


_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to