Changeset 262354 in webkit


Ignore:
Timestamp:
May 30, 2020 8:20:40 PM (4 years ago)
Author:
ysuzuki@apple.com
Message:

[JSC] for-in should allocate new temporary register for base
https://bugs.webkit.org/show_bug.cgi?id=212519
<rdar://problem/63722044>

Reviewed by Saam Barati.

JSTests:

  • microbenchmarks/has-own-property-for-in-loop-with-heap-variable.js: Added.

(assert):
(test1.count):
(test1):

  • microbenchmarks/has-own-property-for-in-loop-with-this.js: Added.

(assert):
(test1.count):
(test1):

  • stress/for-in-body-replace-enumerable.js: Added.

(foo):

  • stress/for-in-enumerable-shadow.js: Added.

(assert):
(test1.count):
(test1):

  • stress/for-in-enumerable-this-arrow.js: Added.

(assert):
(test1):

Source/JavaScriptCore:

While r262233 keeps for-in's enumerated object in variable register if possible to use this register for heuristics driving an optimization,
for-in body can replace the content of this register during enumeration and confuse enumerator.

Instead, we record Variable information in StructureForInContext. This allows us to detect patterns using heap-variables too.
Further, this patch extends pattern-matching code to support ThisNode too.

  • bytecompiler/BytecodeGenerator.cpp:

(JSC::BytecodeGenerator::pushStructureForInScope):

  • bytecompiler/BytecodeGenerator.h:

(JSC::Variable::Variable):
(JSC::Variable::isResolved const):
(JSC::Variable::symbolTableConstantIndex const):
(JSC::Variable::ident const):
(JSC::Variable::offset const):
(JSC::Variable::isLocal const):
(JSC::Variable::local const):
(JSC::Variable::isReadOnly const):
(JSC::Variable::isSpecial const):
(JSC::Variable::isConst const):
(JSC::Variable::setIsReadOnly):
(JSC::Variable::operator== const):
(JSC::StructureForInContext::StructureForInContext):
(JSC::StructureForInContext::baseVariable const):
(JSC::StructureForInContext::base const): Deleted.

  • bytecompiler/NodesCodegen.cpp:

(JSC::HasOwnPropertyFunctionCallDotNode::emitBytecode):
(JSC::ForInNode::emitBytecode):

  • parser/ASTBuilder.h:

(JSC::ASTBuilder::makeFunctionCallNode):

  • parser/Nodes.h:

(JSC::ExpressionNode::isThisNode const):

Location:
trunk
Files:
5 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r262342 r262354  
     12020-05-30  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] for-in should allocate new temporary register for base
     4        https://bugs.webkit.org/show_bug.cgi?id=212519
     5        <rdar://problem/63722044>
     6
     7        Reviewed by Saam Barati.
     8
     9        * microbenchmarks/has-own-property-for-in-loop-with-heap-variable.js: Added.
     10        (assert):
     11        (test1.count):
     12        (test1):
     13        * microbenchmarks/has-own-property-for-in-loop-with-this.js: Added.
     14        (assert):
     15        (test1.count):
     16        (test1):
     17        * stress/for-in-body-replace-enumerable.js: Added.
     18        (foo):
     19        * stress/for-in-enumerable-shadow.js: Added.
     20        (assert):
     21        (test1.count):
     22        (test1):
     23        * stress/for-in-enumerable-this-arrow.js: Added.
     24        (assert):
     25        (test1):
     26
    1272020-05-29  Yusuke Suzuki  <ysuzuki@apple.com>
    228
  • trunk/Source/JavaScriptCore/ChangeLog

    r262353 r262354  
     12020-05-30  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] for-in should allocate new temporary register for base
     4        https://bugs.webkit.org/show_bug.cgi?id=212519
     5        <rdar://problem/63722044>
     6
     7        Reviewed by Saam Barati.
     8
     9        While r262233 keeps for-in's enumerated object in variable register if possible to use this register for heuristics driving an optimization,
     10        for-in body can replace the content of this register during enumeration and confuse enumerator.
     11
     12        Instead, we record Variable information in StructureForInContext. This allows us to detect patterns using heap-variables too.
     13        Further, this patch extends pattern-matching code to support ThisNode too.
     14
     15        * bytecompiler/BytecodeGenerator.cpp:
     16        (JSC::BytecodeGenerator::pushStructureForInScope):
     17        * bytecompiler/BytecodeGenerator.h:
     18        (JSC::Variable::Variable):
     19        (JSC::Variable::isResolved const):
     20        (JSC::Variable::symbolTableConstantIndex const):
     21        (JSC::Variable::ident const):
     22        (JSC::Variable::offset const):
     23        (JSC::Variable::isLocal const):
     24        (JSC::Variable::local const):
     25        (JSC::Variable::isReadOnly const):
     26        (JSC::Variable::isSpecial const):
     27        (JSC::Variable::isConst const):
     28        (JSC::Variable::setIsReadOnly):
     29        (JSC::Variable::operator== const):
     30        (JSC::StructureForInContext::StructureForInContext):
     31        (JSC::StructureForInContext::baseVariable const):
     32        (JSC::StructureForInContext::base const): Deleted.
     33        * bytecompiler/NodesCodegen.cpp:
     34        (JSC::HasOwnPropertyFunctionCallDotNode::emitBytecode):
     35        (JSC::ForInNode::emitBytecode):
     36        * parser/ASTBuilder.h:
     37        (JSC::ASTBuilder::makeFunctionCallNode):
     38        * parser/Nodes.h:
     39        (JSC::ExpressionNode::isThisNode const):
     40
    1412020-05-30  Yusuke Suzuki  <ysuzuki@apple.com>
    242
  • trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp

    r262233 r262354  
    45344534}
    45354535
    4536 void BytecodeGenerator::pushStructureForInScope(RegisterID* localRegister, RegisterID* indexRegister, RegisterID* propertyRegister, RegisterID* enumeratorRegister, RegisterID* baseRegister)
     4536void BytecodeGenerator::pushStructureForInScope(RegisterID* localRegister, RegisterID* indexRegister, RegisterID* propertyRegister, RegisterID* enumeratorRegister, Optional<Variable> baseVariable)
    45374537{
    45384538    if (!localRegister)
    45394539        return;
    45404540    unsigned bodyBytecodeStartOffset = instructions().size();
    4541     m_forInContextStack.append(adoptRef(*new StructureForInContext(localRegister, indexRegister, propertyRegister, enumeratorRegister, baseRegister, bodyBytecodeStartOffset)));
     4541    m_forInContextStack.append(adoptRef(*new StructureForInContext(localRegister, indexRegister, propertyRegister, enumeratorRegister, baseVariable, bodyBytecodeStartOffset)));
    45424542}
    45434543
  • trunk/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h

    r262233 r262354  
    9393    };
    9494
     95    class Variable {
     96    public:
     97        enum VariableKind { NormalVariable, SpecialVariable };
     98
     99        Variable() = default;
     100       
     101        Variable(const Identifier& ident)
     102            : m_ident(ident)
     103            , m_local(nullptr)
     104            , m_attributes(0)
     105            , m_kind(NormalVariable) // This is somewhat meaningless here for this kind of Variable.
     106            , m_symbolTableConstantIndex(0) // This is meaningless here for this kind of Variable.
     107            , m_isLexicallyScoped(false)
     108        {
     109        }
     110
     111        Variable(const Identifier& ident, VarOffset offset, RegisterID* local, unsigned attributes, VariableKind kind, int symbolTableConstantIndex, bool isLexicallyScoped)
     112            : m_ident(ident)
     113            , m_offset(offset)
     114            , m_local(local)
     115            , m_attributes(attributes)
     116            , m_kind(kind)
     117            , m_symbolTableConstantIndex(symbolTableConstantIndex)
     118            , m_isLexicallyScoped(isLexicallyScoped)
     119        {
     120        }
     121
     122        // If it's unset, then it is a non-locally-scoped variable. If it is set, then it could be
     123        // a stack variable, a scoped variable in a local scope, or a variable captured in the
     124        // direct arguments object.
     125        bool isResolved() const { return !!m_offset; }
     126        int symbolTableConstantIndex() const { ASSERT(isResolved() && !isSpecial()); return m_symbolTableConstantIndex; }
     127       
     128        const Identifier& ident() const { return m_ident; }
     129       
     130        VarOffset offset() const { return m_offset; }
     131        bool isLocal() const { return m_offset.isStack(); }
     132        RegisterID* local() const { return m_local; }
     133
     134        bool isReadOnly() const { return m_attributes & PropertyAttribute::ReadOnly; }
     135        bool isSpecial() const { return m_kind != NormalVariable; }
     136        bool isConst() const { return isReadOnly() && m_isLexicallyScoped; }
     137        void setIsReadOnly() { m_attributes |= PropertyAttribute::ReadOnly; }
     138
     139        void dump(PrintStream&) const;
     140
     141        bool operator==(const Variable& other) const
     142        {
     143            return m_ident == other.m_ident
     144                && m_offset == other.m_offset
     145                && m_local == other.m_local
     146                && m_attributes == other.m_attributes
     147                && m_kind == other.m_kind
     148                && m_symbolTableConstantIndex == other.m_symbolTableConstantIndex
     149                && m_isLexicallyScoped == other.m_isLexicallyScoped;
     150        }
     151
     152    private:
     153        Identifier m_ident;
     154        VarOffset m_offset { };
     155        RegisterID* m_local { nullptr };
     156        unsigned m_attributes { 0 };
     157        VariableKind m_kind { NormalVariable };
     158        int m_symbolTableConstantIndex { 0 }; // This is meaningless here for this default NormalVariable kind of Variable.
     159        bool m_isLexicallyScoped { false };
     160    };
     161
    95162    // https://tc39.github.io/ecma262/#sec-completion-record-specification-type
    96163    //
     
    242309        using HasOwnPropertyJumpInst = std::tuple<unsigned, unsigned>;
    243310
    244         StructureForInContext(RegisterID* localRegister, RegisterID* indexRegister, RegisterID* propertyRegister, RegisterID* enumeratorRegister, RegisterID* base, unsigned bodyBytecodeStartOffset)
     311        StructureForInContext(RegisterID* localRegister, RegisterID* indexRegister, RegisterID* propertyRegister, RegisterID* enumeratorRegister, Optional<Variable> baseVariable, unsigned bodyBytecodeStartOffset)
    245312            : ForInContext(localRegister, Type::StructureForIn, bodyBytecodeStartOffset)
    246313            , m_indexRegister(indexRegister)
    247314            , m_propertyRegister(propertyRegister)
    248315            , m_enumeratorRegister(enumeratorRegister)
    249             , m_base(base)
     316            , m_baseVariable(baseVariable)
    250317        {
    251318        }
     
    254321        RegisterID* property() const { return m_propertyRegister.get(); }
    255322        RegisterID* enumerator() const { return m_enumeratorRegister.get(); }
    256         RegisterID* base() const { return m_base.get(); }
     323        const Optional<Variable>& baseVariable() const { return m_baseVariable; }
    257324
    258325        void addGetInst(unsigned instIndex, int propertyRegIndex)
     
    277344        RefPtr<RegisterID> m_propertyRegister;
    278345        RefPtr<RegisterID> m_enumeratorRegister;
    279         RefPtr<RegisterID> m_base;
     346        Optional<Variable> m_baseVariable;
    280347        Vector<GetInst> m_getInsts;
    281348        Vector<InInst> m_inInsts;
     
    310377        Ref<Label> start;
    311378        TryData* tryData;
    312     };
    313 
    314     class Variable {
    315     public:
    316         enum VariableKind { NormalVariable, SpecialVariable };
    317 
    318         Variable()
    319             : m_offset()
    320             , m_local(nullptr)
    321             , m_attributes(0)
    322             , m_kind(NormalVariable)
    323             , m_symbolTableConstantIndex(0) // This is meaningless here for this kind of Variable.
    324             , m_isLexicallyScoped(false)
    325         {
    326         }
    327        
    328         Variable(const Identifier& ident)
    329             : m_ident(ident)
    330             , m_local(nullptr)
    331             , m_attributes(0)
    332             , m_kind(NormalVariable) // This is somewhat meaningless here for this kind of Variable.
    333             , m_symbolTableConstantIndex(0) // This is meaningless here for this kind of Variable.
    334             , m_isLexicallyScoped(false)
    335         {
    336         }
    337 
    338         Variable(const Identifier& ident, VarOffset offset, RegisterID* local, unsigned attributes, VariableKind kind, int symbolTableConstantIndex, bool isLexicallyScoped)
    339             : m_ident(ident)
    340             , m_offset(offset)
    341             , m_local(local)
    342             , m_attributes(attributes)
    343             , m_kind(kind)
    344             , m_symbolTableConstantIndex(symbolTableConstantIndex)
    345             , m_isLexicallyScoped(isLexicallyScoped)
    346         {
    347         }
    348 
    349         // If it's unset, then it is a non-locally-scoped variable. If it is set, then it could be
    350         // a stack variable, a scoped variable in a local scope, or a variable captured in the
    351         // direct arguments object.
    352         bool isResolved() const { return !!m_offset; }
    353         int symbolTableConstantIndex() const { ASSERT(isResolved() && !isSpecial()); return m_symbolTableConstantIndex; }
    354        
    355         const Identifier& ident() const { return m_ident; }
    356        
    357         VarOffset offset() const { return m_offset; }
    358         bool isLocal() const { return m_offset.isStack(); }
    359         RegisterID* local() const { return m_local; }
    360 
    361         bool isReadOnly() const { return m_attributes & PropertyAttribute::ReadOnly; }
    362         bool isSpecial() const { return m_kind != NormalVariable; }
    363         bool isConst() const { return isReadOnly() && m_isLexicallyScoped; }
    364         void setIsReadOnly() { m_attributes |= PropertyAttribute::ReadOnly; }
    365 
    366         void dump(PrintStream&) const;
    367 
    368     private:
    369         Identifier m_ident;
    370         VarOffset m_offset;
    371         RegisterID* m_local;
    372         unsigned m_attributes;
    373         VariableKind m_kind;
    374         int m_symbolTableConstantIndex;
    375         bool m_isLexicallyScoped;
    376379    };
    377380
     
    10261029        void pushIndexedForInScope(RegisterID* local, RegisterID* index);
    10271030        void popIndexedForInScope(RegisterID* local);
    1028         void pushStructureForInScope(RegisterID* local, RegisterID* index, RegisterID* property, RegisterID* enumerator, RegisterID* base);
     1031        void pushStructureForInScope(RegisterID* local, RegisterID* index, RegisterID* property, RegisterID* enumerator, Optional<Variable> base);
    10291032        void popStructureForInScope(RegisterID* local);
    10301033
  • trunk/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

    r262233 r262354  
    18531853    }
    18541854
    1855     if (structureContext && structureContext->base() == base.get()) {
     1855    auto canUseFastHasOwnProperty = [&] {
     1856        if (!structureContext)
     1857            return false;
     1858        if (!structureContext->baseVariable())
     1859            return false;
     1860        if (m_base->isResolveNode())
     1861            return generator.variable(static_cast<ResolveNode*>(m_base)->identifier()) == structureContext->baseVariable().value();
     1862        if (m_base->isThisNode()) {
     1863            // After generator.ensureThis (which must be invoked in |base|'s materialization), we can ensure that |this| is in local this-register.
     1864            ASSERT(base);
     1865            return generator.variable(generator.propertyNames().thisIdentifier, ThisResolutionType::Local) == structureContext->baseVariable().value();
     1866        }
     1867        return false;
     1868    };
     1869
     1870    if (canUseFastHasOwnProperty()) {
     1871        // It is possible that base register is variable and each for-in body replaces JS object in the base register with a different one.
     1872        // Even though, this is OK since HasOwnStructureProperty will reject the replaced JS object.
    18561873        Ref<Label> realCall = generator.newLabel();
    18571874        Ref<Label> end = generator.newLabel();
     
    37483765        generator.emitNode(generator.ignoredResult(), m_lexpr);
    37493766
     3767    RefPtr<RegisterID> base = generator.newTemporary();
    37503768    RefPtr<RegisterID> length;
    37513769    RefPtr<RegisterID> enumerator;
    37523770
    3753     RefPtr<RegisterID> base = generator.emitNode(m_expr);
     3771    generator.emitNode(base.get(), m_expr);
    37543772    RefPtr<RegisterID> local = this->tryGetBoundLocal(generator);
    37553773    RefPtr<RegisterID> enumeratorIndex;
     3774
     3775    Optional<Variable> baseVariable;
     3776    if (m_expr->isResolveNode())
     3777        baseVariable = generator.variable(static_cast<ResolveNode*>(m_expr)->identifier());
     3778    else if (m_expr->isThisNode()) {
     3779        // After generator.ensureThis (which must be invoked in |base|'s materialization), we can ensure that |this| is in local this-register.
     3780        ASSERT(base);
     3781        baseVariable = generator.variable(generator.propertyNames().thisIdentifier, ThisResolutionType::Local);
     3782    }
    37563783
    37573784    // Pause at the assignment expression for each for..in iteration.
     
    38293856        generator.emitProfileControlFlow(profilerStartOffset);
    38303857
    3831         generator.pushStructureForInScope(local.get(), enumeratorIndex.get(), propertyName.get(), enumerator.get(), base.get());
     3858        generator.pushStructureForInScope(local.get(), enumeratorIndex.get(), propertyName.get(), enumerator.get(), baseVariable);
    38323859        generator.emitNode(dst, m_statement);
    38333860        generator.popStructureForInScope(local.get());
  • trunk/Source/JavaScriptCore/parser/ASTBuilder.h

    r262233 r262354  
    14431443        // FIXME: This check is only needed because we haven't taught the bytecode generator to inline
    14441444        // Reflect.apply yet. See https://bugs.webkit.org/show_bug.cgi?id=190668.
    1445         if (!dot->base()->isResolveNode() || static_cast<ResolveNode*>(dot->base())->identifier() != "Reflect")
     1445        if (!dot->base()->isResolveNode() || static_cast<ResolveNode*>(dot->base())->identifier() != m_vm.propertyNames->Reflect)
    14461446            node = new (m_parserArena) ApplyFunctionCallDotNode(location, dot->base(), dot->identifier(), args, divot, divotStart, divotEnd, callOrApplyChildDepth);
    14471447    } else if (!previousBaseWasSuper
     
    14511451        && args->m_listNode->m_expr->isResolveNode()
    14521452        && !args->m_listNode->m_next
    1453         && dot->base()->isResolveNode()
    1454         && static_cast<ResolveNode*>(dot->base())->identifier() != "Reflect") {
     1453        && ((dot->base()->isResolveNode() && static_cast<ResolveNode*>(dot->base())->identifier() != m_vm.propertyNames->Reflect) || dot->base()->isThisNode())) {
    14551454        // We match the AST pattern:
    1456         // <resolveNode>.hasOwnProperty(<resolveNode>)
     1455        // <resolveNode|thisNode>.hasOwnProperty(<resolveNode>)
    14571456        // i.e:
    14581457        // o.hasOwnProperty(p)
  • trunk/Source/JavaScriptCore/parser/Nodes.h

    r262233 r262354  
    201201        virtual bool isSubtract() const { return false; }
    202202        virtual bool isBoolean() const { return false; }
     203        virtual bool isThisNode() const { return false; }
    203204        virtual bool isSpreadExpression() const { return false; }
    204205        virtual bool isSuperNode() const { return false; }
     
    613614
    614615    private:
     616        bool isThisNode() const final { return true; }
    615617        RegisterID* emitBytecode(BytecodeGenerator&, RegisterID* = nullptr) final;
    616618    };
Note: See TracChangeset for help on using the changeset viewer.