Bug 1260435 - Make RegExp hoistable when it's used only in RegExpBuiltinExec. r=h4writer

This commit is contained in:
Hannes Verschore 2016-05-26 01:26:35 +09:00
parent 03effee7b4
commit 200bc0e463
5 changed files with 231 additions and 23 deletions

View File

@ -809,7 +809,7 @@ function RegExpBuiltinExec(R, S, forTest) {
// This check is here for RegExpTest. RegExp_prototype_Exec does same
// thing already.
if (!IsRegExpObject(R))
return callFunction(CallRegExpMethodIfWrapped, R, R, S, forTest, "RegExpBuiltinExec");
return UnwrapAndCallRegExpBuiltinExec(R, S, forTest);
// Steps 1-2 (skipped).
@ -863,6 +863,14 @@ function RegExpBuiltinExec(R, S, forTest) {
return result;
}
function UnwrapAndCallRegExpBuiltinExec(R, S, forTest) {
return callFunction(CallRegExpMethodIfWrapped, R, S, forTest, "CallRegExpBuiltinExec");
}
function CallRegExpBuiltinExec(S, forTest) {
return RegExpBuiltinExec(this, S, forTest);
}
// ES6 21.2.5.13.
function RegExpTest(string) {
// Steps 1-2.

View File

@ -16,6 +16,8 @@
#include "jit/LIR.h"
#include "jit/Lowering.h"
#include "jit/MIRGraph.h"
#include "vm/RegExpObject.h"
#include "vm/SelfHosting.h"
#include "jsobjinlines.h"
#include "jsopcodeinlines.h"
@ -1827,9 +1829,207 @@ jit::ApplyTypeInformation(MIRGenerator* mir, MIRGraph& graph)
return true;
}
// Check if `def` is only the N-th operand of `useDef`.
static inline size_t
IsExclusiveNthOperand(MDefinition* useDef, size_t n, MDefinition* def)
{
uint32_t num = useDef->numOperands();
if (n >= num || useDef->getOperand(n) != def)
return false;
for (uint32_t i = 0; i < num; i++) {
if (i == n)
continue;
if (useDef->getOperand(i) == def)
return false;
}
return true;
}
static size_t
IsExclusiveThisArg(MCall* call, MDefinition* def)
{
return IsExclusiveNthOperand(call, MCall::IndexOfThis(), def);
}
static size_t
IsExclusiveFirstArg(MCall* call, MDefinition* def)
{
return IsExclusiveNthOperand(call, MCall::IndexOfArgument(0), def);
}
static bool
IsRegExpHoistableCall(MCall* call, MDefinition* def)
{
if (call->isConstructing())
return false;
JSAtom* name;
JSFunction* fun = call->getSingleTarget();
if (fun) {
if (!fun->isSelfHostedBuiltin())
return false;
name = GetSelfHostedFunctionName(fun);
} else {
MDefinition* funDef = call->getFunction();
if (funDef->isDebugCheckSelfHosted())
funDef = funDef->toDebugCheckSelfHosted()->input();
if (funDef->isTypeBarrier())
funDef = funDef->toTypeBarrier()->input();
if (!funDef->isCallGetIntrinsicValue())
return false;
name = funDef->toCallGetIntrinsicValue()->name();
}
// Hoistable only if the RegExp is the first argument of RegExpBuiltinExec.
CompileRuntime* runtime = GetJitContext()->runtime;
if (name == runtime->names().RegExpBuiltinExec ||
name == runtime->names().UnwrapAndCallRegExpBuiltinExec ||
name == runtime->names().RegExpMatcher ||
name == runtime->names().RegExpTester ||
name == runtime->names().RegExpSearcher)
{
return IsExclusiveFirstArg(call, def);
}
if (name == runtime->names().RegExp_prototype_Exec)
return IsExclusiveThisArg(call, def);
return false;
}
static bool
CanCompareWithoutToPrimitive(MCompare* compare, MDefinition* def)
{
JSOp op = compare->jsop();
// Strict equality comparison won't invoke @@toPrimitive.
if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE)
return true;
if (op != JSOP_EQ && op != JSOP_NE) {
// Relational comparison always invoke @@toPrimitive.
MOZ_ASSERT(op == JSOP_GT || op == JSOP_GE || op == JSOP_LT || op == JSOP_LE);
return false;
}
// Loose equality comparison can invoke @@toPrimitive.
MDefinition* value;
if (compare->lhs() == def) {
value = compare->rhs();
} else {
MOZ_ASSERT(compare->rhs() == def);
value = compare->lhs();
}
if (value->mightBeType(MIRType::Boolean) || value->mightBeType(MIRType::String) ||
value->mightBeType(MIRType::Int32) ||
value->mightBeType(MIRType::Double) || value->mightBeType(MIRType::Float32) ||
value->mightBeType(MIRType::Symbol))
{
return false;
}
return true;
}
static inline void
SetNotInWorklist(MDefinitionVector& worklist)
{
for (size_t i = 0; i < worklist.length(); i++)
worklist[i]->setNotInWorklist();
}
static bool
IsRegExpHoistable(MDefinition* regexp, MDefinitionVector& worklist, bool* hoistable)
{
MOZ_ASSERT(worklist.length() == 0);
if (!worklist.append(regexp))
return false;
regexp->setInWorklist();
for (size_t i = 0; i < worklist.length(); i++) {
MDefinition* def = worklist[i];
for (MUseIterator use = def->usesBegin(); use != def->usesEnd(); use++) {
// Ignore resume points. At this point all uses are listed.
// No DCE or GVN or something has happened.
if (use->consumer()->isResumePoint())
continue;
MDefinition* useDef = use->consumer()->toDefinition();
// Step through a few white-listed ops.
if (useDef->isPhi() || useDef->isFilterTypeSet() || useDef->isGuardShape()) {
if (useDef->isInWorklist())
continue;
if (!worklist.append(useDef))
return false;
useDef->setInWorklist();
continue;
}
// Instructions that doesn't invoke unknown code that may modify
// RegExp instance or pass it to elsewhere.
if (useDef->isRegExpMatcher() || useDef->isRegExpTester() ||
useDef->isRegExpSearcher())
{
if (IsExclusiveNthOperand(useDef, 0, def))
continue;
} else if (useDef->isLoadFixedSlot() || useDef->isTypeOf()) {
continue;
} else if (useDef->isCompare()) {
if (CanCompareWithoutToPrimitive(useDef->toCompare(), def))
continue;
}
// Instructions that modifies `lastIndex` property.
else if (useDef->isStoreFixedSlot()) {
if (IsExclusiveNthOperand(useDef, 0, def)) {
MStoreFixedSlot* store = useDef->toStoreFixedSlot();
if (store->slot() == RegExpObject::lastIndexSlot())
continue;
}
} else if (useDef->isSetPropertyCache()) {
if (IsExclusiveNthOperand(useDef, 0, def)) {
MSetPropertyCache* setProp = useDef->toSetPropertyCache();
if (setProp->idval()->isConstant()) {
Value propIdVal = setProp->idval()->toConstant()->toJSValue();
if (propIdVal.isString()) {
CompileRuntime* runtime = GetJitContext()->runtime;
if (propIdVal.toString() == runtime->names().lastIndex)
continue;
}
}
}
}
// MCall is safe only for some known safe functions.
else if (useDef->isCall()) {
if (IsRegExpHoistableCall(useDef->toCall(), def))
continue;
}
// Everything else is unsafe.
SetNotInWorklist(worklist);
worklist.clear();
*hoistable = false;
return true;
}
}
SetNotInWorklist(worklist);
worklist.clear();
*hoistable = true;
return true;
}
bool
jit::MakeMRegExpHoistable(MIRGraph& graph)
{
MDefinitionVector worklist(graph.alloc());
for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
for (MDefinitionIterator iter(*block); iter; iter++) {
if (!*iter)
@ -1840,24 +2040,9 @@ jit::MakeMRegExpHoistable(MIRGraph& graph)
MRegExp* regexp = iter->toRegExp();
// Test if MRegExp is hoistable by looking at all uses.
bool hoistable = true;
for (MUseIterator i = regexp->usesBegin(); i != regexp->usesEnd(); i++) {
// Ignore resume points. At this point all uses are listed.
// No DCE or GVN or something has happened.
if (i->consumer()->isResumePoint())
continue;
MOZ_ASSERT(i->consumer()->isDefinition());
// All MRegExp* MIR's don't adjust the regexp.
MDefinition* use = i->consumer()->toDefinition();
if (use->isRegExpMatcher() || use->isRegExpTester() || use->isRegExpSearcher())
continue;
hoistable = false;
break;
}
bool hoistable = false;
if (!IsRegExpHoistable(regexp, worklist, &hoistable))
return false;
if (!hoistable)
continue;
@ -1865,8 +2050,9 @@ jit::MakeMRegExpHoistable(MIRGraph& graph)
// Make MRegExp hoistable
regexp->setMovable();
// That would be incorrect for global/sticky, because lastIndex could be wrong.
// Therefore setting the lastIndex to 0. That is faster than a not movable regexp.
// That would be incorrect for global/sticky, because lastIndex
// could be wrong. Therefore setting the lastIndex to 0. That is
// faster than a not movable regexp.
RegExpObject* source = regexp->source();
if (source->sticky() || source->global()) {
MOZ_ASSERT(regexp->mustClone());

View File

@ -299,6 +299,12 @@
macro(wasm, wasm, "wasm") \
macro(watch, watch, "watch") \
macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
macro(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
macro(UnwrapAndCallRegExpBuiltinExec, UnwrapAndCallRegExpBuiltinExec, "UnwrapAndCallRegExpBuiltinExec") \
macro(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec") \
macro(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \
macro(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \
macro(RegExpTester, RegExpTester, "RegExpTester") \
macro(weekday, weekday, "weekday") \
macro(writable, writable, "writable") \
macro(year, year, "year") \

View File

@ -3069,8 +3069,13 @@ js::SelfHostedFunction(JSContext* cx, HandlePropertyName propName)
bool
js::IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name)
{
return fun->isSelfHostedBuiltin() &&
fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString() == name;
return fun->isSelfHostedBuiltin() && GetSelfHostedFunctionName(fun) == name;
}
JSAtom*
js::GetSelfHostedFunctionName(JSFunction* fun)
{
return &fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString()->asAtom();
}
static_assert(JSString::MAX_LENGTH <= INT32_MAX,

View File

@ -23,6 +23,9 @@ namespace js {
bool
IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name);
JSAtom*
GetSelfHostedFunctionName(JSFunction* fun);
bool
IsCallSelfHostedNonGenericMethod(NativeImpl impl);