Ignore:
Timestamp:
Jun 5, 2020, 4:25:43 AM (5 years ago)
Author:
[email protected]
Message:

[JSC] Add support for private class fields
https://bugs.webkit.org/show_bug.cgi?id=206431

Reviewed by Saam Barati.

JSTests:

Expands upon the earlier public fields patch, adding a number of private
field tests.

  • stress/class-fields-private-as-function.js: Added.
  • stress/class-fields-private-cached-bytecode.js: Added.
  • stress/class-fields-private-freeze-out-of-line.js: Added.
  • stress/class-fields-private-freeze.js: Added.
  • stress/class-fields-private-on-proxy.js: Added.
  • stress/class-fields-private-out-of-line.js: Added.
  • stress/class-fields-private-prevent-extensions-out-of-line.js: Added.
  • stress/class-fields-private-prevent-extensions.js: Added.
  • stress/class-fields-private-seal-out-of-line.js: Added.
  • stress/class-fields-private-seal.js: Added.
  • stress/class-fields-private-use-eval.js: Added.
  • stress/class-fields-stress-instance.js:
  • stress/optional-chaining-and-private-fields.js: Added.
  • stress/private-name-access-in-computed-property.js: Added.
  • stress/put-by-val-direct-addprivate.js: Added.
  • stress/put-by-val-direct-putprivate.js: Added.
  • test262/config.yaml:

Source/JavaScriptCore:

Expanding upon the earlier public class fields patch, we implement the remaining (and
significant parts) of the instance fields (https://tc39.es/proposal-class-fields/).

There are a variety of key changes here:

  • Parser now understands the concept of private names (Token PRIVATENAME).
  • 1 new opcode (op_get_private_name), one changed opcode (op_put_by_val_direct).
  • A method for creating Symbol objects with a null PrivateSymbolImpl is exposed as a LinkTimeConstant (@createPrivateSymbol).
  • Null Private Symbols are stored by name (not a valid identifier) in a JSScope, and are loaded from the outer scope whenever they are used by the modified opcodes.

The changes to op_put_by_val_direct include a new bytecode operand (PutByValFlags) which are
used to distinguish between overwriting or defining a new private field. Specifically, when it
comes to private field accesses, it's necessary to throw an exception when accessing a field
which does not exist, or when attempting to define a private field which has already been
defined.

During the evaluation of a class expression, before the class element list is evaluated (in case
any computed property names expressions refer to a new private field), a new PrivateSymbol is
created for each individual private field name, and stored in the class lexical scope.

Private field names are loaded from scope before their use. This prevents multiple evaluations
of the same class source from accessing each other's private fields, because the values of the
symbols loaded from the class scope would be distinct. This is required by the proposal text,
and is the key reason why we use ByVal lookups rather than ById lookups.

To illustrate, typical private field access will look like:

<Field Reads>
resolve_scope <scope=>, <currentScope>, "#x", GlobalProperty, 0
get_from_scope <symbol=>, <scope>, "#x", 1050624<DoNotThrowIfNotFound|GlobalProperty|NotInitialization>, 0, 0
get_private_name <value=>, <receiver --- probably 'this'>, <symbol>

<Field Writes>
resolve_scope <scope=>, <currentScope>, "#x", GlobalProperty, 0
get_from_scope <symbol=>, <scope>, "#x", 1050624<DoNotThrowIfNotFound|GlobalProperty|NotInitialization>, 0, 0
put_by_val_direct <receiver, probably 'this'>, <symbol>, <value>, <PutByValPrivateName>

<Field Definition>
resolve_scope <scope=>, <currentScope>, "#x", GlobalProperty, 0
get_from_scope <symbol=>, <scope>, "#x", 1050624<DoNotThrowIfNotFound|GlobalProperty|NotInitialization>, 0, 0
put_by_val_direct <receiver, probably 'this'>, <symbol>, <value>, <PutByValPrivateName|PutByValThrowIfExists>

The feature is currently hidden behind the feature flag JSC::Options::usePrivateClassFields.

  • CMakeLists.txt:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • Sources.txt:
  • builtins/BuiltinNames.h:
  • bytecode/BytecodeList.rb:
  • bytecode/BytecodeUseDef.cpp:

(JSC::computeUsesForBytecodeIndexImpl):
(JSC::computeDefsForBytecodeIndexImpl):

  • bytecode/CodeBlock.cpp:

(JSC::CodeBlock::finalizeLLIntInlineCaches):

  • bytecode/Fits.h:
  • bytecode/LinkTimeConstant.h:
  • bytecode/PutByValFlags.cpp: Copied from Source/JavaScriptCore/bytecode/PutKind.h.

(WTF::printInternal):

  • bytecode/PutByValFlags.h: Added.

(JSC::PutByValFlags::create):
(JSC::PutByValFlags::createDirect):
(JSC::PutByValFlags::createDefinePrivateField):
(JSC::PutByValFlags::createPutPrivateField):
(JSC::PutByValFlags::isDirect const):
(JSC::PutByValFlags::ecmaMode const):
(JSC::PutByValFlags::privateFieldAccessKind const):
(JSC::PutByValFlags::isPrivateFieldAccess const):
(JSC::PutByValFlags::isPrivateFieldPut const):
(JSC::PutByValFlags::isPrivateFieldAdd const):
(JSC::PutByValFlags::PutByValFlags):

  • bytecode/PutKind.h:
  • bytecode/UnlinkedFunctionExecutable.cpp:

(JSC::generateUnlinkedFunctionCodeBlock):

  • bytecompiler/BytecodeGenerator.cpp:

(JSC::BytecodeGenerator::instantiateLexicalVariables):
(JSC::BytecodeGenerator::emitDirectGetByVal):
(JSC::BytecodeGenerator::emitDirectPutByVal):
(JSC::BytecodeGenerator::emitDefinePrivateField):
(JSC::BytecodeGenerator::emitPrivateFieldPut):

  • bytecompiler/BytecodeGenerator.h:
  • bytecompiler/NodesCodegen.cpp:

(JSC::PropertyListNode::emitDeclarePrivateFieldNames):
(JSC::PropertyListNode::emitBytecode):
(JSC::PropertyListNode::emitPutConstantProperty):
(JSC::DotAccessorNode::emitBytecode):
(JSC::BaseDotNode::emitGetPropertyValue):
(JSC::BaseDotNode::emitPutProperty):
(JSC::FunctionCallDotNode::emitBytecode):
(JSC::PostfixNode::emitDot):
(JSC::PrefixNode::emitDot):
(JSC::AssignDotNode::emitBytecode):
(JSC::ReadModifyDotNode::emitBytecode):
(JSC::DefineFieldNode::emitBytecode):
(JSC::ClassExprNode::emitBytecode):

  • dfg/DFGByteCodeParser.cpp:

(JSC::DFG::ecmaMode):
(JSC::DFG::ecmaMode<OpPutByValDirect>):
(JSC::DFG::ByteCodeParser::handlePutByVal):

  • dfg/DFGCapabilities.cpp:

(JSC::DFG::capabilityLevel):

  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::cachedPutById):

  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::compilePutById):

  • generator/DSL.rb:
  • jit/ICStats.h:
  • jit/JIT.cpp:

(JSC::JIT::privateCompileMainPass):

  • jit/JIT.h:
  • jit/JITInlineCacheGenerator.cpp:

(JSC::JITPutByIdGenerator::JITPutByIdGenerator):
(JSC::JITPutByIdGenerator::slowPathFunction):

  • jit/JITInlineCacheGenerator.h:

(JSC::JITPutByIdGenerator::JITPutByIdGenerator):

  • jit/JITInlines.h:

(JSC::JIT::ecmaMode):
(JSC::JIT::ecmaMode<OpPutById>):
(JSC::JIT::ecmaMode<OpPutByValDirect>):
(JSC::JIT::privateFieldAccessKind):
(JSC::JIT::privateFieldAccessKind<OpPutByValDirect>):

  • jit/JITOperations.cpp:

(JSC::putPrivateField):
(JSC::definePrivateField):

  • jit/JITOperations.h:
  • jit/JITPropertyAccess.cpp:

(JSC::JIT::emitPutByValWithCachedId):
(JSC::JIT::emitSlow_op_put_by_val):
(JSC::JIT::emit_op_put_by_id):

  • jit/JITPropertyAccess32_64.cpp:

(JSC::JIT::emitSlow_op_put_by_val):
(JSC::JIT::emit_op_put_by_id):

  • jit/Repatch.cpp:

(JSC::appropriateGenericPutByIdFunction):
(JSC::appropriateOptimizingPutByIdFunction):
(JSC::tryCachePutByID):

  • llint/LLIntOffsetsExtractor.cpp:
  • llint/LLIntSlowPaths.cpp:

(JSC::LLInt::LLINT_SLOW_PATH_DECL):

  • llint/LLIntSlowPaths.h:
  • llint/LowLevelInterpreter32_64.asm:
  • llint/LowLevelInterpreter64.asm:
  • parser/ASTBuilder.h:

(JSC::ASTBuilder::createDotAccess):
(JSC::ASTBuilder::isPrivateLocation):
(JSC::ASTBuilder::makeFunctionCallNode):
(JSC::ASTBuilder::makeAssignNode):

  • parser/Lexer.cpp:

(JSC::Lexer<LChar>::parseIdentifier):
(JSC::Lexer<UChar>::parseIdentifier):
(JSC::Lexer<CharacterType>::parseIdentifierSlowCase):
(JSC::Lexer<T>::lexWithoutClearingLineTerminator):

  • parser/NodeConstructors.h:

(JSC::BaseDotNode::BaseDotNode):
(JSC::DotAccessorNode::DotAccessorNode):
(JSC::FunctionCallDotNode::FunctionCallDotNode):
(JSC::CallFunctionCallDotNode::CallFunctionCallDotNode):
(JSC::ApplyFunctionCallDotNode::ApplyFunctionCallDotNode):
(JSC::HasOwnPropertyFunctionCallDotNode::HasOwnPropertyFunctionCallDotNode):
(JSC::AssignDotNode::AssignDotNode):
(JSC::ReadModifyDotNode::ReadModifyDotNode):

  • parser/Nodes.cpp:

(JSC::PropertyListNode::shouldCreateLexicalScopeForClass):

  • parser/Nodes.h:

(JSC::ExpressionNode::isPrivateLocation const):
(JSC::BaseDotNode::base const):
(JSC::BaseDotNode::identifier const):
(JSC::BaseDotNode::type const):
(JSC::BaseDotNode::isPrivateField const):

  • parser/Parser.cpp:

(JSC::Parser<LexerType>::parseVariableDeclarationList):
(JSC::Parser<LexerType>::parseDestructuringPattern):
(JSC::Parser<LexerType>::parseClass):
(JSC::Parser<LexerType>::parseInstanceFieldInitializerSourceElements):
(JSC::Parser<LexerType>::usePrivateName):
(JSC::Parser<LexerType>::parseMemberExpression):
(JSC::Parser<LexerType>::parseUnaryExpression):
(JSC::Parser<LexerType>::printUnexpectedTokenText):

  • parser/Parser.h:

(JSC::Scope::isPrivateNameScope const):
(JSC::Scope::setIsPrivateNameScope):
(JSC::Scope::hasPrivateName):
(JSC::Scope::copyUndeclaredPrivateNamesTo):
(JSC::Scope::hasUsedButUndeclaredPrivateNames const):
(JSC::Scope::usePrivateName):
(JSC::Scope::declarePrivateName):
(JSC::Parser::findPrivateNameScope):
(JSC::Parser::privateNameScope):
(JSC::Parser::copyUndeclaredPrivateNamesToOuterScope):
(JSC::Parser::matchAndUpdate):
(JSC::Parser<LexerType>::parse):
(JSC::parse):

  • parser/ParserTokens.h:
  • parser/SyntaxChecker.h:

(JSC::SyntaxChecker::createDotAccess):
(JSC::SyntaxChecker::operatorStackPop):

  • parser/VariableEnvironment.cpp:

(JSC::VariableEnvironment::operator=):
(JSC::VariableEnvironment::swap):
(JSC::CompactVariableEnvironment::CompactVariableEnvironment):

  • parser/VariableEnvironment.h:

(JSC::VariableEnvironmentEntry::isPrivateName const):
(JSC::VariableEnvironmentEntry::setIsPrivateName):
(JSC::PrivateNameEntry::PrivateNameEntry):
(JSC::PrivateNameEntry::isUsed const):
(JSC::PrivateNameEntry::isDeclared const):
(JSC::PrivateNameEntry::setIsUsed):
(JSC::PrivateNameEntry::setIsDeclared):
(JSC::PrivateNameEntry::bits const):
(JSC::PrivateNameEntry::operator== const):
(JSC::VariableEnvironment::VariableEnvironment):
(JSC::VariableEnvironment::size const):
(JSC::VariableEnvironment::mapSize const):
(JSC::VariableEnvironment::declarePrivateName):
(JSC::VariableEnvironment::usePrivateName):
(JSC::VariableEnvironment::privateNames const):
(JSC::VariableEnvironment::privateNamesSize const):
(JSC::VariableEnvironment::hasPrivateName):
(JSC::VariableEnvironment::copyPrivateNamesTo const):
(JSC::VariableEnvironment::copyUndeclaredPrivateNamesTo const):
(JSC::VariableEnvironment::RareData::RareData):
(JSC::VariableEnvironment::getOrAddPrivateName):

  • runtime/CachedTypes.cpp:

(JSC::CachedOptional::decodeAsPtr const):
(JSC::CachedVariableEnvironmentRareData::encode):
(JSC::CachedVariableEnvironmentRareData::decode const):
(JSC::CachedVariableEnvironment::encode):
(JSC::CachedVariableEnvironment::decode const):
(JSC::CachedSymbolTableRareData::encode):
(JSC::CachedSymbolTableRareData::decode const):
(JSC::CachedSymbolTable::encode):
(JSC::CachedSymbolTable::decode const):

  • runtime/CodeCache.cpp:

(JSC::generateUnlinkedCodeBlockImpl):

  • runtime/CommonIdentifiers.cpp:

(JSC::CommonIdentifiers::CommonIdentifiers):

  • runtime/CommonIdentifiers.h:
  • runtime/CommonSlowPaths.cpp:

(JSC::SLOW_PATH_DECL):

  • runtime/CommonSlowPaths.h:
  • runtime/ExceptionHelpers.cpp:

(JSC::createInvalidPrivateNameError):
(JSC::createRedefinedPrivateNameError):

  • runtime/ExceptionHelpers.h:
  • runtime/JSGlobalObject.cpp:

(JSC::createPrivateSymbol):
(JSC::JSGlobalObject::init):

  • runtime/JSObject.h:
  • runtime/JSObjectInlines.h:

(JSC::JSObject::getPrivateFieldSlot):
(JSC::JSObject::getPrivateField):
(JSC::JSObject::putPrivateField):
(JSC::JSObject::definePrivateField):

  • runtime/JSScope.cpp:

(JSC::JSScope::collectClosureVariablesUnderTDZ):

  • runtime/OptionsList.h:
  • runtime/SymbolTable.cpp:

(JSC::SymbolTable::cloneScopePart):

  • runtime/SymbolTable.h:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/parser/VariableEnvironment.h

    r255659 r262613  
    2828#include "Identifier.h"
    2929#include <wtf/HashMap.h>
     30#include <wtf/HashSet.h>
     31#include <wtf/IteratorRange.h>
    3032
    3133namespace JSC {
     
    4345    ALWAYS_INLINE bool isParameter() const { return m_bits & IsParameter; }
    4446    ALWAYS_INLINE bool isSloppyModeHoistingCandidate() const { return m_bits & IsSloppyModeHoistingCandidate; }
     47    ALWAYS_INLINE bool isPrivateName() const { return m_bits & IsPrivateName; }
    4548
    4649    ALWAYS_INLINE void setIsCaptured() { m_bits |= IsCaptured; }
     
    5457    ALWAYS_INLINE void setIsParameter() { m_bits |= IsParameter; }
    5558    ALWAYS_INLINE void setIsSloppyModeHoistingCandidate() { m_bits |= IsSloppyModeHoistingCandidate; }
     59    ALWAYS_INLINE void setIsPrivateName() { m_bits |= IsPrivateName; }
    5660
    5761    ALWAYS_INLINE void clearIsVar() { m_bits &= ~IsVar; }
     
    7579        IsFunction = 1 << 7,
    7680        IsParameter = 1 << 8,
    77         IsSloppyModeHoistingCandidate = 1 << 9
     81        IsSloppyModeHoistingCandidate = 1 << 9,
     82        IsPrivateName = 1 << 10,
    7883    };
    7984    uint16_t m_bits { 0 };
     
    8489};
    8590
     91struct PrivateNameEntry {
     92public:
     93    PrivateNameEntry(uint16_t traits = 0) { m_bits = traits; }
     94
     95    ALWAYS_INLINE bool isUsed() const { return m_bits & IsUsed; }
     96    ALWAYS_INLINE bool isDeclared() const { return m_bits & IsDeclared; }
     97
     98    ALWAYS_INLINE void setIsUsed() { m_bits |= IsUsed; }
     99    ALWAYS_INLINE void setIsDeclared() { m_bits |= IsDeclared; }
     100
     101    uint16_t bits() const { return m_bits; }
     102
     103    bool operator==(const PrivateNameEntry& other) const
     104    {
     105        return m_bits == other.m_bits;
     106    }
     107
     108    enum Traits : uint16_t {
     109        IsUsed = 1 << 0,
     110        IsDeclared = 1 << 1,
     111    };
     112
     113private:
     114    uint16_t m_bits { 0 };
     115};
     116
     117struct PrivateNameEntryHashTraits : HashTraits<PrivateNameEntry> {
     118    static const bool needsDestruction = false;
     119};
     120
    86121class VariableEnvironment {
    87122private:
    88123    typedef HashMap<PackedRefPtr<UniquedStringImpl>, VariableEnvironmentEntry, IdentifierRepHash, HashTraits<RefPtr<UniquedStringImpl>>, VariableEnvironmentEntryHashTraits> Map;
     124    typedef HashMap<PackedRefPtr<UniquedStringImpl>, PrivateNameEntry, IdentifierRepHash, HashTraits<RefPtr<UniquedStringImpl>>, PrivateNameEntryHashTraits> PrivateNames;
    89125public:
    90     VariableEnvironment() = default;
    91     VariableEnvironment(VariableEnvironment&& other) = default;
    92     VariableEnvironment(const VariableEnvironment&) = default;
    93     VariableEnvironment& operator=(const VariableEnvironment&) = default;
    94     VariableEnvironment& operator=(VariableEnvironment&&) = default;
     126    VariableEnvironment() { }
     127    VariableEnvironment(VariableEnvironment&& other)
     128        : m_map(WTFMove(other.m_map))
     129        , m_isEverythingCaptured(other.m_isEverythingCaptured)
     130        , m_rareData(WTFMove(other.m_rareData))
     131    {
     132    }
     133    VariableEnvironment(const VariableEnvironment& other)
     134        : m_map(other.m_map)
     135        , m_isEverythingCaptured(other.m_isEverythingCaptured)
     136        , m_rareData(other.m_rareData ? WTF::makeUnique<VariableEnvironment::RareData>(*other.m_rareData) : nullptr)
     137    {
     138    }
     139    VariableEnvironment& operator=(const VariableEnvironment& other);
    95140
    96141    ALWAYS_INLINE Map::iterator begin() { return m_map.begin(); }
     
    100145    ALWAYS_INLINE Map::AddResult add(const RefPtr<UniquedStringImpl>& identifier) { return m_map.add(identifier, VariableEnvironmentEntry()); }
    101146    ALWAYS_INLINE Map::AddResult add(const Identifier& identifier) { return add(identifier.impl()); }
    102     ALWAYS_INLINE unsigned size() const { return m_map.size(); }
     147    ALWAYS_INLINE unsigned size() const { return m_map.size() + privateNamesSize(); }
     148    ALWAYS_INLINE unsigned mapSize() const { return m_map.size(); }
    103149    ALWAYS_INLINE bool contains(const RefPtr<UniquedStringImpl>& identifier) const { return m_map.contains(identifier); }
    104150    ALWAYS_INLINE bool remove(const RefPtr<UniquedStringImpl>& identifier) { return m_map.remove(identifier); }
     
    117163    bool isEmpty() const { return !m_map.size(); }
    118164
     165    using PrivateNamesRange = WTF::IteratorRange<PrivateNames::iterator>;
     166
     167    ALWAYS_INLINE Map::AddResult declarePrivateName(const Identifier& identifier) { return declarePrivateName(identifier.impl()); }
     168    ALWAYS_INLINE void usePrivateName(const Identifier& identifier) { usePrivateName(identifier.impl()); }
     169
     170    Map::AddResult declarePrivateName(const RefPtr<UniquedStringImpl>& identifier)
     171    {
     172        auto& meta = getOrAddPrivateName(identifier.get());
     173        meta.setIsDeclared();
     174        auto entry = VariableEnvironmentEntry();
     175        entry.setIsPrivateName();
     176        entry.setIsConst();
     177        entry.setIsCaptured();
     178        return m_map.add(identifier, entry);
     179    }
     180    void usePrivateName(const RefPtr<UniquedStringImpl>& identifier)
     181    {
     182        auto& meta = getOrAddPrivateName(identifier.get());
     183        meta.setIsUsed();
     184        if (meta.isDeclared())
     185            find(identifier)->value.setIsCaptured();
     186    }
     187
     188    ALWAYS_INLINE PrivateNamesRange privateNames() const
     189    {
     190        // Use of the IteratorRange must be guarded to prevent ASSERT failures in checkValidity().
     191        ASSERT(privateNamesSize() > 0);
     192        return makeIteratorRange(m_rareData->m_privateNames.begin(), m_rareData->m_privateNames.end());
     193    }
     194
     195    ALWAYS_INLINE unsigned privateNamesSize() const
     196    {
     197        if (!m_rareData)
     198            return 0;
     199        return m_rareData->m_privateNames.size();
     200    }
     201
     202    ALWAYS_INLINE bool hasPrivateName(const Identifier& identifier)
     203    {
     204        if (!m_rareData)
     205            return false;
     206        return m_rareData->m_privateNames.contains(identifier.impl());
     207    }
     208
     209    ALWAYS_INLINE void copyPrivateNamesTo(VariableEnvironment& other) const
     210    {
     211        if (!m_rareData)
     212            return;
     213        if (!other.m_rareData)
     214            other.m_rareData = WTF::makeUnique<VariableEnvironment::RareData>();
     215        if (privateNamesSize() > 0) {
     216            for (auto entry : privateNames()) {
     217                if (!(entry.value.isUsed() && entry.value.isDeclared()))
     218                    other.m_rareData->m_privateNames.add(entry.key, entry.value);
     219            }
     220        }
     221    }
     222
     223    ALWAYS_INLINE void copyUndeclaredPrivateNamesTo(VariableEnvironment& outer) const {
     224        // Used by the Parser to transfer recorded uses of PrivateNames from an
     225        // inner PrivateNameEnvironment into an outer one, in case a PNE is used
     226        // earlier in the source code than it is defined.
     227        if (privateNamesSize() > 0) {
     228            for (auto entry : privateNames()) {
     229                if (entry.value.isUsed() && !entry.value.isDeclared())
     230                    outer.getOrAddPrivateName(entry.key.get()).setIsUsed();
     231            }
     232        }
     233    }
     234
     235    struct RareData {
     236        WTF_MAKE_STRUCT_FAST_ALLOCATED;
     237
     238        RareData() { }
     239        RareData(RareData&& other)
     240            : m_privateNames(WTFMove(other.m_privateNames))
     241        {
     242        }
     243        RareData(const RareData&) = default;
     244        RareData& operator=(const RareData&) = default;
     245        PrivateNames m_privateNames;
     246    };
     247
    119248private:
    120249    friend class CachedVariableEnvironment;
     
    122251    Map m_map;
    123252    bool m_isEverythingCaptured { false };
     253
     254    PrivateNameEntry& getOrAddPrivateName(UniquedStringImpl* impl)
     255    {
     256        if (!m_rareData)
     257            m_rareData = WTF::makeUnique<VariableEnvironment::RareData>();
     258
     259        return m_rareData->m_privateNames.add(impl, PrivateNameEntry()).iterator->value;
     260    }
     261
     262    std::unique_ptr<VariableEnvironment::RareData> m_rareData;
    124263};
    125264
Note: See TracChangeset for help on using the changeset viewer.