Bug 723640 - Don't clone regexps in Ion code if cloning is not observable. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Sat, 19 Oct 2013 17:33:10 +0200
changeset 165256 5e8f8322f8fd6564accfeb61c960e3ed7fbb2a10
parent 165255 14789271ad201a947a2f6b2dbebe1415f7eb85ff
child 165257 8c34a47385fdfa606bef4b0f250e429505de3faa
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs723640
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 723640 - Don't clone regexps in Ion code if cloning is not observable. r=bhackett
js/src/jit-test/tests/ion/regexp-clone.js
js/src/jit/IonBuilder.cpp
js/src/jit/Lowering.cpp
js/src/jit/MIR.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/regexp-clone.js
@@ -0,0 +1,8 @@
+var i=0;
+function f() {
+    assertEq(/^[a-z0-9\.]+$/gi.test("Foo.Bar"), true);
+    i++;
+    if (i < 100)
+	f();
+}
+f();
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -18,16 +18,17 @@
 #include "jit/BaselineInspector.h"
 #include "jit/ExecutionModeInlines.h"
 #include "jit/Ion.h"
 #include "jit/IonSpewer.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGraph.h"
 
 #include "vm/ArgumentsObject.h"
+#include "vm/RegExpStatics.h"
 
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/CompileInfo-inl.h"
 
 using namespace js;
@@ -8868,26 +8869,51 @@ IonBuilder::jsop_delelem()
 
 bool
 IonBuilder::jsop_regexp(RegExpObject *reobj)
 {
     JSObject *prototype = script()->global().getOrCreateRegExpPrototype(cx);
     if (!prototype)
         return false;
 
-    MRegExp *regexp = MRegExp::New(reobj, prototype);
+    JS_ASSERT(&reobj->JSObject::global() == &script()->global());
+
+    // JS semantics require regular expression literals to create different
+    // objects every time they execute. We only need to do this cloning if the
+    // script could actually observe the effect of such cloning, for instance
+    // by getting or setting properties on it.
+    //
+    // First, make sure the regex is one we can safely optimize. Lowering can
+    // then check if this regex object only flows into known natives and can
+    // avoid cloning in this case.
+
+    bool mustClone = true;
+    types::TypeObjectKey *typeObj = types::TypeObjectKey::get(&script()->global());
+    if (!typeObj->hasFlags(constraints(), types::OBJECT_FLAG_REGEXP_FLAGS_SET)) {
+        RegExpStatics *res = script()->global().getRegExpStatics();
+
+        DebugOnly<uint32_t> origFlags = reobj->getFlags();
+        DebugOnly<uint32_t> staticsFlags = res->getFlags();
+        JS_ASSERT((origFlags & staticsFlags) == staticsFlags);
+
+        if (!reobj->global() && !reobj->sticky())
+            mustClone = false;
+    }
+
+    MRegExp *regexp = MRegExp::New(reobj, prototype, mustClone);
     current->add(regexp);
     current->push(regexp);
 
     regexp->setMovable();
 
     // The MRegExp is set to be movable.
     // That would be incorrect for global/sticky, because lastIndex could be wrong.
     // Therefore setting the lastIndex to 0. That is faster than removing the movable flag.
     if (reobj->sticky() || reobj->global()) {
+        JS_ASSERT(mustClone);
         MConstant *zero = MConstant::New(Int32Value(0));
         current->add(zero);
 
         MStoreFixedSlot *lastIndex =
             MStoreFixedSlot::New(regexp, RegExpObject::lastIndexSlot(), zero);
         current->add(lastIndex);
     }
 
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -1771,19 +1771,97 @@ LIRGenerator::visitToString(MToString *i
       }
 
       default:
         // Objects might be effectful. (see ToPrimitive)
         MOZ_ASSUME_UNREACHABLE("unexpected type");
     }
 }
 
+static bool
+MustCloneRegExpForCall(MPassArg *arg)
+{
+    // |arg| is a regex literal flowing into a call. Return |false| iff
+    // this is a native call that does not let the regex escape.
+
+    JS_ASSERT(arg->getArgument()->isRegExp());
+
+    for (MUseIterator iter(arg->usesBegin()); iter != arg->usesEnd(); iter++) {
+        MNode *node = iter->consumer();
+        if (!node->isDefinition())
+            return true;
+
+        MDefinition *def = node->toDefinition();
+        if (!def->isCall())
+            return true;
+
+        MCall *call = def->toCall();
+        JSFunction *target = call->getSingleTarget();
+        if (!target || !target->isNative())
+            return true;
+
+        if (iter->index() == MCall::IndexOfThis() &&
+            (target->native() == regexp_exec || target->native() == regexp_test))
+        {
+            continue;
+        }
+
+        if (iter->index() == MCall::IndexOfArgument(0) &&
+            (target->native() == str_split ||
+             target->native() == str_replace ||
+             target->native() == str_match ||
+             target->native() == str_search))
+        {
+            continue;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+
+static bool
+MustCloneRegExp(MRegExp *regexp)
+{
+    if (regexp->mustClone())
+        return true;
+
+    // If this regex literal only flows into known natives that don't let
+    // it escape, we don't have to clone it.
+
+    for (MUseIterator iter(regexp->usesBegin()); iter != regexp->usesEnd(); iter++) {
+        MNode *node = iter->consumer();
+        if (!node->isDefinition())
+            return true;
+
+        MDefinition *def = node->toDefinition();
+        if (def->isRegExpTest() && iter->index() == 1) {
+            // Optimized RegExp.prototype.test.
+            JS_ASSERT(def->toRegExpTest()->regexp() == regexp);
+            continue;
+        }
+
+        if (def->isPassArg() && !MustCloneRegExpForCall(def->toPassArg()))
+            continue;
+
+        return true;
+    }
+    return false;
+}
+
 bool
 LIRGenerator::visitRegExp(MRegExp *ins)
 {
+    if (!MustCloneRegExp(ins)) {
+        RegExpObject *source = ins->source();
+        return define(new LPointer(source), ins);
+    }
+
     LRegExp *lir = new LRegExp();
     return defineReturn(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitRegExpTest(MRegExpTest *ins)
 {
     JS_ASSERT(ins->regexp()->type() == MIRType_Object);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1827,16 +1827,23 @@ class MCall
     MDefinition *getArg(uint32_t index) const {
         return getOperand(NumNonArgumentOperands + index);
     }
 
     void replaceArg(uint32_t index, MDefinition *def) {
         replaceOperand(NumNonArgumentOperands + index, def);
     }
 
+    static size_t IndexOfThis() {
+        return NumNonArgumentOperands;
+    }
+    static size_t IndexOfArgument(size_t index) {
+        return NumNonArgumentOperands + index + 1; // +1 to skip |this|.
+    }
+
     // For TI-informed monomorphic callsites.
     JSFunction *getSingleTarget() const {
         return target_;
     }
 
     bool isConstructing() const {
         return construct_;
     }
@@ -4476,34 +4483,39 @@ class MDefFun : public MUnaryInstruction
         return true;
     }
 };
 
 class MRegExp : public MNullaryInstruction
 {
     CompilerRoot<RegExpObject *> source_;
     CompilerRootObject prototype_;
-
-    MRegExp(RegExpObject *source, JSObject *prototype)
+    bool mustClone_;
+
+    MRegExp(RegExpObject *source, JSObject *prototype, bool mustClone)
       : source_(source),
-        prototype_(prototype)
+        prototype_(prototype),
+        mustClone_(mustClone)
     {
         setResultType(MIRType_Object);
 
         JS_ASSERT(source->getProto() == prototype);
         setResultTypeSet(MakeSingletonTypeSet(source));
     }
 
   public:
     INSTRUCTION_HEADER(RegExp)
 
-    static MRegExp *New(RegExpObject *source, JSObject *prototype) {
-        return new MRegExp(source, prototype);
-    }
-
+    static MRegExp *New(RegExpObject *source, JSObject *prototype, bool mustClone) {
+        return new MRegExp(source, prototype, mustClone);
+    }
+
+    bool mustClone() const {
+        return mustClone_;
+    }
     RegExpObject *source() const {
         return source_;
     }
     JSObject *getRegExpPrototype() const {
         return prototype_;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();