Bug 723640 - Don't clone regexps in Ion code if cloning is not observable. r=bhackett

This commit is contained in:
Jan de Mooij 2013-10-19 17:33:10 +02:00
parent 27586aa6a7
commit d55f9aed12
4 changed files with 129 additions and 5 deletions

View File

@ -0,0 +1,8 @@
var i=0;
function f() {
assertEq(/^[a-z0-9\.]+$/gi.test("Foo.Bar"), true);
i++;
if (i < 100)
f();
}
f();

View File

@ -23,6 +23,7 @@
#include "jit/MIRGraph.h"
#include "vm/ArgumentsObject.h"
#include "vm/RegExpStatics.h"
#include "jsinferinlines.h"
#include "jsobjinlines.h"
@ -8873,7 +8874,31 @@ IonBuilder::jsop_regexp(RegExpObject *reobj)
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);
@ -8883,6 +8908,7 @@ IonBuilder::jsop_regexp(RegExpObject *reobj)
// 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);

View File

@ -1776,9 +1776,87 @@ LIRGenerator::visitToString(MToString *ins)
}
}
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);
}

View File

@ -1832,6 +1832,13 @@ class MCall
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_;
@ -4481,10 +4488,12 @@ class MRegExp : public MNullaryInstruction
{
CompilerRoot<RegExpObject *> source_;
CompilerRootObject prototype_;
bool mustClone_;
MRegExp(RegExpObject *source, JSObject *prototype)
MRegExp(RegExpObject *source, JSObject *prototype, bool mustClone)
: source_(source),
prototype_(prototype)
prototype_(prototype),
mustClone_(mustClone)
{
setResultType(MIRType_Object);
@ -4495,10 +4504,13 @@ class MRegExp : public MNullaryInstruction
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_;
}