Ignore:
Timestamp:
Mar 8, 2016, 9:16:47 PM (9 years ago)
Author:
[email protected]
Message:

DFG should be able to constant-fold strings
https://bugs.webkit.org/show_bug.cgi?id=155200

Reviewed by Geoffrey Garen.

Source/JavaScriptCore:

This adds constant-folding of string1 + string2 and string.length. The actual folding
rule is easy, but there are some gotchas.

The problem is that the DFG cannot allocate new JSString objects until we are on the
main thread. So, DFG IR must have a node for a JSValue string constant that hasn't been
created yet - i.e. it doesn't have any concrete JSValue bits yet.

We have the ability to speak of such things, using LazyJSValue. But that's a class, not
a node type. This patch now adds a node type, LazyJSConstant, which is a Node that holds
a LazyJSValue.

This puts us in a weird situation: AI uses JSValue to represent constants. It would take
a lot of work to change it to use LazyJSValue. So, this implements the constant folding
in StrengthReductionPhase. I created a bug and put a FIXME about moving these rules into
AI.

OTOH, our experience in B3 shows that constant folding in strength reduction is quite
nice. It would totally make sense to have strength reduction have constant folding rules
that mirror the rules in AI, or to factor out the AI constant folding rules, the same
way that B3 factors out those rules into Value methods.

Another issue is how to represent the cumulative result of possibly many foldings. I
initially considered adding LazyJSValue kinds that represented concatenation. Folding
the concatenation to a constant meand that this constant was actually a LazyJSValue that
represented the concatenation of two other things. But this would get super messy if we
wanted to fold an operation that uses the results of another folded operation.

So, the JIT thread folds string operations by creating a WTF::String that contains the
result. The DFG::Graph holds a +1 on the underlying StringImpl, so we can pass the
StringImpl* around without reference counting. The LazyJSValue now has a special kind
that means: we created this StringImpl* on the JIT thread, and once the JIT is done, we
will relinquish ownership of it. LazyJSValue has some magic to emit code for these
to-be-created-JSStrings while also transferring ownership of the StringImpl from the JIT
thread to the main thread and registering the JSString with the GC.

This just implements folding for concatenation and GetArrayLength. It's just a proof of
concept for evil things I want to do later.

This change is a 2.5x speed-up on the string concatenation microbenchmarks I added in
this patch.

  • dfg/DFGAbstractInterpreterInlines.h:

(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):

  • dfg/DFGClobberize.h:

(JSC::DFG::clobberize):

  • dfg/DFGDoesGC.cpp:

(JSC::DFG::doesGC):

  • dfg/DFGFixupPhase.cpp:

(JSC::DFG::FixupPhase::fixupNode):

  • dfg/DFGFrozenValue.cpp:

(JSC::DFG::FrozenValue::emptySingleton):
(JSC::DFG::FrozenValue::tryGetString):
(JSC::DFG::FrozenValue::dumpInContext):

  • dfg/DFGFrozenValue.h:

(JSC::DFG::FrozenValue::strength):

  • dfg/DFGGraph.h:
  • dfg/DFGLazyJSValue.cpp:

(JSC::DFG::LazyJSValue::newString):
(JSC::DFG::LazyJSValue::getValue):
(JSC::DFG::equalToStringImpl):
(JSC::DFG::LazyJSValue::tryGetStringImpl):
(JSC::DFG::LazyJSValue::tryGetString):
(JSC::DFG::LazyJSValue::strictEqual):
(JSC::DFG::LazyJSValue::switchLookupValue):
(JSC::DFG::LazyJSValue::emit):
(JSC::DFG::LazyJSValue::dumpInContext):

  • dfg/DFGLazyJSValue.h:

(JSC::DFG::LazyJSValue::LazyJSValue):
(JSC::DFG::LazyJSValue::knownStringImpl):
(JSC::DFG::LazyJSValue::kind):
(JSC::DFG::LazyJSValue::tryGetValue):
(JSC::DFG::LazyJSValue::character):
(JSC::DFG::LazyJSValue::stringImpl):

  • dfg/DFGMayExit.cpp:

(JSC::DFG::mayExit):

  • dfg/DFGNode.cpp:

(JSC::DFG::Node::convertToIdentityOn):
(JSC::DFG::Node::convertToLazyJSConstant):
(JSC::DFG::Node::convertToPutHint):
(JSC::DFG::Node::convertToPutClosureVarHint):
(JSC::DFG::Node::tryGetString):
(JSC::DFG::Node::promotedLocationDescriptor):

  • dfg/DFGNode.h:

(JSC::DFG::Node::convertToConstant):
(JSC::DFG::Node::convertToConstantStoragePointer):
(JSC::DFG::Node::castConstant):
(JSC::DFG::Node::hasLazyJSValue):
(JSC::DFG::Node::lazyJSValue):
(JSC::DFG::Node::initializationValueForActivation):

  • dfg/DFGNodeType.h:
  • dfg/DFGPredictionPropagationPhase.cpp:

(JSC::DFG::PredictionPropagationPhase::propagate):

  • dfg/DFGSafeToExecute.h:

(JSC::DFG::safeToExecute):

  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::compileSetRegExpObjectLastIndex):
(JSC::DFG::SpeculativeJIT::compileLazyJSConstant):

  • dfg/DFGSpeculativeJIT.h:
  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGStrengthReductionPhase.cpp:

(JSC::DFG::StrengthReductionPhase::handleNode):

  • ftl/FTLCapabilities.cpp:

(JSC::FTL::canCompile):

  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileInt52Constant):
(JSC::FTL::DFG::LowerDFGToB3::compileLazyJSConstant):
(JSC::FTL::DFG::LowerDFGToB3::compileDoubleRep):

Source/WTF:

Also disable assertions about reference counting strings on the JIT thread. We will do
that now and it's OK.

  • wtf/text/StringImpl.h:

(WTF::StringImpl::ref):
(WTF::StringImpl::deref):

LayoutTests:

  • js/regress/script-tests/strcat-const.js: Added.

(foo):
(bar):

  • js/regress/script-tests/strcat-length-const.js: Added.

(foo):
(bar):

  • js/regress/strcat-const-expected.txt: Added.
  • js/regress/strcat-const.html: Added.
  • js/regress/strcat-length-const-expected.txt: Added.
  • js/regress/strcat-length-const.html: Added.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/dfg/DFGLazyJSValue.cpp

    r173069 r197833  
    11/*
    2  * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
     2 * Copyright (C) 2013, 2014, 2016 Apple Inc. All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    2929#if ENABLE(DFG_JIT)
    3030
     31#include "CCallHelpers.h"
     32#include "DFGGraph.h"
    3133#include "JSCInlines.h"
     34#include "LinkBuffer.h"
    3235
    3336namespace JSC { namespace DFG {
    3437
     38LazyJSValue LazyJSValue::newString(Graph& graph, const String& string)
     39{
     40    LazyJSValue result;
     41    result.m_kind = NewStringImpl;
     42    result.u.stringImpl = graph.m_localStrings.add(string).iterator->impl();
     43    return result;
     44}
     45
    3546JSValue LazyJSValue::getValue(VM& vm) const
    3647{
     
    4152        return jsSingleCharacterString(&vm, u.character);
    4253    case KnownStringImpl:
     54    case NewStringImpl:
    4355        return jsString(&vm, u.stringImpl);
    4456    }
     
    7486   
    7587    return triState(WTF::equal(stringImpl, string));
     88}
     89
     90const StringImpl* LazyJSValue::tryGetStringImpl() const
     91{
     92    switch (m_kind) {
     93    case KnownStringImpl:
     94    case NewStringImpl:
     95        return u.stringImpl;
     96
     97    case KnownValue:
     98        if (JSString* string = jsDynamicCast<JSString*>(value()->value()))
     99            return string->tryGetValueImpl();
     100        return nullptr;
     101
     102    default:
     103        return nullptr;
     104    }
     105}
     106
     107String LazyJSValue::tryGetString(Graph& graph) const
     108{
     109    switch (m_kind) {
     110    case NewStringImpl:
     111        return u.stringImpl;
     112
     113    case SingleCharacterString:
     114        return String(&u.character, 1);
     115
     116    default:
     117        if (const StringImpl* string = tryGetStringImpl()) {
     118            unsigned ginormousStringLength = 10000;
     119            if (string->length() > ginormousStringLength)
     120                return String();
     121           
     122            auto result = graph.m_copiedStrings.add(string, String());
     123            if (result.isNewEntry)
     124                result.iterator->value = string->isolatedCopy();
     125            return result.iterator->value;
     126        }
     127       
     128        return String();
     129    }
    76130}
    77131
     
    86140            return equalToSingleCharacter(value()->value(), other.character());
    87141        case KnownStringImpl:
     142        case NewStringImpl:
    88143            return equalToStringImpl(value()->value(), other.stringImpl());
    89144        }
     
    94149            return triState(character() == other.character());
    95150        case KnownStringImpl:
     151        case NewStringImpl:
    96152            if (other.stringImpl()->length() != 1)
    97153                return FalseTriState;
     
    102158        break;
    103159    case KnownStringImpl:
     160    case NewStringImpl:
    104161        switch (other.m_kind) {
    105162        case KnownStringImpl:
     163        case NewStringImpl:
    106164            return triState(WTF::equal(stringImpl(), other.stringImpl()));
    107165        default:
     
    144202}
    145203
     204void LazyJSValue::emit(CCallHelpers& jit, JSValueRegs result) const
     205{
     206    if (m_kind == KnownValue) {
     207        jit.moveValue(value()->value(), result);
     208        return;
     209    }
     210
     211    // It must be some kind of cell.
     212#if USE(JSVALUE32_64)
     213    jit.move(CCallHelpers::TrustedImm32(JSValue::CellTag), result.tagGPR());
     214#endif
     215    CCallHelpers::DataLabelPtr label = jit.moveWithPatch(
     216        CCallHelpers::TrustedImmPtr(static_cast<size_t>(0xd1e7beeflu)),
     217        result.payloadGPR());
     218
     219    LazyJSValue thisValue = *this;
     220
     221    // Once we do this, we're committed. Otherwise we leak memory. Note that we call ref/deref
     222    // manually to ensure that there is no concurrency shadiness. We are doing something here
     223    // that might be rather brutal: transfering ownership of this string.
     224    if (m_kind == NewStringImpl)
     225        thisValue.u.stringImpl->ref();
     226
     227    CodeBlock* codeBlock = jit.codeBlock();
     228   
     229    jit.addLinkTask(
     230        [codeBlock, label, thisValue] (LinkBuffer& linkBuffer) {
     231            JSValue realValue = thisValue.getValue(linkBuffer.vm());
     232            RELEASE_ASSERT(realValue.isCell());
     233
     234            codeBlock->addConstant(realValue);
     235           
     236            if (thisValue.m_kind == NewStringImpl)
     237                thisValue.u.stringImpl->deref();
     238
     239            linkBuffer.patch(label, realValue.asCell());
     240        });
     241}
     242
    146243void LazyJSValue::dumpInContext(PrintStream& out, DumpContext* context) const
    147244{
     
    156253        return;
    157254    case KnownStringImpl:
    158         out.print("Lazy:String(", stringImpl(), ")");
     255        out.print("Lazy:KnownString(", stringImpl(), ")");
     256        return;
     257    case NewStringImpl:
     258        out.print("Lazy:NewString(", stringImpl(), ")");
    159259        return;
    160260    }
Note: See TracChangeset for help on using the changeset viewer.