Commit 394d53d1 authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbofan] Add inlining for RegExp#test

This CL adds a TFS stub for RegExp#test and moves several checks to
the JSCallReducer. In particular, the JSCallReducer checks that
 - property {exec} on the regexp is still the original exec
 - property {lastIndex} on the regexp is a non-negative smi
The stub does not repeat these checks in release mode.

This effectively means that if the regexp is known, we can perform these
checks at compile time, and get away with a map dependency.

Bug: v8:7779, v8:7200

Change-Id: I0c6d711d4f1d2f6f325a1c02855b0e1b62e014c8
Reviewed-on: https://chromium-review.googlesource.com/1074654
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53630}
parent d6c49a72
......@@ -947,6 +947,7 @@ namespace internal {
TFJ(RegExpPrototypeStickyGetter, 0) \
/* ES #sec-regexp.prototype.test */ \
TFJ(RegExpPrototypeTest, 1, kString) \
TFS(RegExpPrototypeTestFast, kReceiver, kString) \
CPP(RegExpPrototypeToString) \
/* ES #sec-get-regexp.prototype.unicode */ \
TFJ(RegExpPrototypeUnicodeGetter, 0) \
......
......@@ -114,7 +114,7 @@ TNode<Object> RegExpBuiltinsAssembler::RegExpCreate(TNode<Context> context,
pattern, flags);
}
Node* RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) {
TNode<Object> RegExpBuiltinsAssembler::FastLoadLastIndex(Node* regexp) {
// Load the in-object field.
static const int field_offset =
JSRegExp::kSize + JSRegExp::kLastIndexFieldIndex * kPointerSize;
......@@ -672,7 +672,7 @@ Node* RegExpBuiltinsAssembler::RegExpPrototypeExecBodyWithoutResult(
Node* const smi_zero = SmiConstant(0);
if (is_fastpath) {
CSA_ASSERT(this, IsFastRegExpNoPrototype(context, regexp));
CSA_ASSERT(this, HasInstanceType(regexp, JS_REGEXP_TYPE));
} else {
ThrowIfNotInstanceType(context, regexp, JS_REGEXP_TYPE,
"RegExp.prototype.exec");
......@@ -875,6 +875,51 @@ Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context,
return var_result.value();
}
// We also return true if exec is undefined (and hence per spec)
// the original {exec} will be used.
TNode<BoolT> RegExpBuiltinsAssembler::IsFastRegExpWithOriginalExec(
TNode<Context> context, TNode<JSRegExp> object) {
CSA_ASSERT(this, TaggedIsNotSmi(object));
Label out(this);
Label check_last_index(this);
TVARIABLE(BoolT, var_result);
#ifdef V8_ENABLE_FORCE_SLOW_PATH
var_result = BoolConstant(0);
GotoIfForceSlowPath(&out);
#endif
TNode<BoolT> is_regexp = HasInstanceType(object, JS_REGEXP_TYPE);
var_result = is_regexp;
GotoIfNot(is_regexp, &out);
TNode<Context> native_context = LoadNativeContext(context);
TNode<Object> original_exec =
LoadContextElement(native_context, Context::REGEXP_EXEC_FUNCTION_INDEX);
TNode<Object> regexp_exec =
GetProperty(context, object, isolate()->factory()->exec_string());
TNode<BoolT> has_initialexec = WordEqual(regexp_exec, original_exec);
var_result = has_initialexec;
GotoIf(has_initialexec, &check_last_index);
TNode<BoolT> is_undefined = IsUndefined(regexp_exec);
var_result = is_undefined;
GotoIfNot(is_undefined, &out);
Goto(&check_last_index);
BIND(&check_last_index);
// The smi check is required to omit ToLength(lastIndex) calls with possible
// user-code execution on the fast path.
TNode<Object> last_index = FastLoadLastIndex(object);
var_result = TaggedIsPositiveSmi(last_index);
Goto(&out);
BIND(&out);
return var_result.value();
}
Node* RegExpBuiltinsAssembler::IsFastRegExpNoPrototype(Node* const context,
Node* const object) {
CSA_ASSERT(this, TaggedIsNotSmi(object));
......@@ -1741,6 +1786,20 @@ TF_BUILTIN(RegExpPrototypeTest, RegExpBuiltinsAssembler) {
}
}
TF_BUILTIN(RegExpPrototypeTestFast, RegExpBuiltinsAssembler) {
TNode<JSRegExp> const regexp = CAST(Parameter(Descriptor::kReceiver));
TNode<String> const string = CAST(Parameter(Descriptor::kString));
TNode<Context> const context = CAST(Parameter(Descriptor::kContext));
Label if_didnotmatch(this);
CSA_ASSERT(this, IsFastRegExpWithOriginalExec(context, regexp));
RegExpPrototypeExecBodyWithoutResult(context, regexp, string, &if_didnotmatch,
true);
Return(TrueConstant());
BIND(&if_didnotmatch);
Return(FalseConstant());
}
Node* RegExpBuiltinsAssembler::AdvanceStringIndex(Node* const string,
Node* const index,
Node* const is_unicode,
......
......@@ -41,7 +41,7 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
Node* AllocateRegExpResult(Node* context, Node* length, Node* index,
Node* input);
Node* FastLoadLastIndex(Node* regexp);
TNode<Object> FastLoadLastIndex(Node* regexp);
Node* SlowLoadLastIndex(Node* context, Node* regexp);
Node* LoadLastIndex(Node* context, Node* regexp, bool is_fastpath);
......@@ -89,6 +89,8 @@ class RegExpBuiltinsAssembler : public CodeStubAssembler {
// Performs fast path checks on the given object itself, but omits prototype
// checks.
Node* IsFastRegExpNoPrototype(Node* const context, Node* const object);
TNode<BoolT> IsFastRegExpWithOriginalExec(TNode<Context> context,
TNode<JSRegExp> object);
Node* IsFastRegExpNoPrototype(Node* const context, Node* const object,
Node* const map);
......
......@@ -11,10 +11,12 @@
#include "src/code-stubs.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/property-access-builder.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/feedback-vector-inl.h"
......@@ -3507,6 +3509,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceMapPrototypeGet(node);
case Builtins::kMapPrototypeHas:
return ReduceMapPrototypeHas(node);
case Builtins::kRegExpPrototypeTest:
return ReduceRegExpPrototypeTest(node);
case Builtins::kReturnReceiver:
return ReduceReturnReceiver(node);
case Builtins::kStringPrototypeIndexOf:
......@@ -6741,6 +6745,99 @@ Reduction JSCallReducer::ReduceNumberParseInt(Node* node) {
return Changed(node);
}
Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) {
if (FLAG_force_slow_path) return NoChange();
if (node->op()->ValueInputCount() < 3) return NoChange();
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* regexp = NodeProperties::GetValueInput(node, 1);
// Check if we know something about the {regexp}.
ZoneHandleSet<Map> regexp_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(regexp, effect, &regexp_maps);
bool need_map_check = false;
switch (result) {
case NodeProperties::kNoReceiverMaps:
return NoChange();
case NodeProperties::kUnreliableReceiverMaps:
need_map_check = true;
break;
case NodeProperties::kReliableReceiverMaps:
break;
}
for (auto map : regexp_maps) {
if (map->instance_type() != JS_REGEXP_TYPE) return NoChange();
}
// Compute property access info for "exec" on {resolution}.
PropertyAccessInfo ai_exec;
AccessInfoFactory access_info_factory(dependencies(), js_heap_broker(),
native_context(), graph()->zone());
if (!access_info_factory.ComputePropertyAccessInfo(
MapHandles(regexp_maps.begin(), regexp_maps.end()),
factory()->exec_string(), AccessMode::kLoad, &ai_exec)) {
return NoChange();
}
// If "exec" has been modified on {regexp}, we can't do anything.
if (!ai_exec.IsDataConstant()) return NoChange();
Handle<Object> exec_on_proto = ai_exec.constant();
if (*exec_on_proto != *isolate()->regexp_exec_function()) return NoChange();
PropertyAccessBuilder access_builder(jsgraph(), dependencies());
// Add proper dependencies on the {regexp}s [[Prototype]]s.
Handle<JSObject> holder;
if (ai_exec.holder().ToHandle(&holder)) {
access_builder.AssumePrototypesStable(native_context(),
ai_exec.receiver_maps(), holder);
}
if (need_map_check) {
effect =
graph()->NewNode(simplified()->CheckMaps(CheckMapsFlag::kNone,
regexp_maps, p.feedback()),
regexp, effect, control);
}
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* search = NodeProperties::GetValueInput(node, 2);
Node* search_string = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), search, effect, control);
Node* lastIndex = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSRegExpLastIndex()), regexp,
effect, control);
Node* lastIndexSmi = effect = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), lastIndex, effect, control);
Node* is_positive = graph()->NewNode(simplified()->NumberLessThanOrEqual(),
jsgraph()->ZeroConstant(), lastIndexSmi);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kNotASmi, p.feedback()),
is_positive, effect, control);
node->ReplaceInput(0, regexp);
node->ReplaceInput(1, search_string);
node->ReplaceInput(2, context);
node->ReplaceInput(3, frame_state);
node->ReplaceInput(4, effect);
node->ReplaceInput(5, control);
node->TrimInputCount(6);
NodeProperties::ChangeOp(node, javascript()->RegExpTest());
return Changed(node);
}
Graph* JSCallReducer::graph() const { return jsgraph()->graph(); }
Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); }
......
......@@ -25,6 +25,7 @@ class CallFrequency;
class CommonOperatorBuilder;
struct FieldAccess;
class JSGraph;
class JSHeapBroker;
class JSOperatorBuilder;
class SimplifiedOperatorBuilder;
......@@ -36,11 +37,12 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
enum Flag { kNoFlags = 0u, kBailoutOnUninitialized = 1u << 0 };
typedef base::Flags<Flag> Flags;
JSCallReducer(Editor* editor, JSGraph* jsgraph, Flags flags,
Handle<Context> native_context,
JSCallReducer(Editor* editor, JSGraph* jsgraph, JSHeapBroker* js_heap_broker,
Flags flags, Handle<Context> native_context,
CompilationDependencies* dependencies)
: AdvancedReducer(editor),
jsgraph_(jsgraph),
js_heap_broker_(js_heap_broker),
flags_(flags),
native_context_(native_context),
dependencies_(dependencies) {}
......@@ -109,6 +111,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceJSCall(Node* node, Handle<SharedFunctionInfo> shared);
Reduction ReduceJSCallWithArrayLike(Node* node);
Reduction ReduceJSCallWithSpread(Node* node);
Reduction ReduceRegExpPrototypeTest(Node* node);
Reduction ReduceReturnReceiver(Node* node);
Reduction ReduceStringPrototypeIndexOf(Node* node);
Reduction ReduceStringPrototypeSubstring(Node* node);
......@@ -225,6 +228,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
const JSHeapBroker* js_heap_broker() const { return js_heap_broker_; }
Isolate* isolate() const;
Factory* factory() const;
Handle<Context> native_context() const { return native_context_; }
......@@ -236,6 +240,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
CompilationDependencies* dependencies() const { return dependencies_; }
JSGraph* const jsgraph_;
const JSHeapBroker* const js_heap_broker_;
Flags const flags_;
Handle<Context> const native_context_;
CompilationDependencies* const dependencies_;
......
......@@ -406,6 +406,13 @@ void JSGenericLowering::LowerJSParseInt(Node* node) {
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSRegExpTest(Node* node) {
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kRegExpPrototypeTestFast);
ReplaceWithStubCall(node, callable, flags);
}
void JSGenericLowering::LowerJSCreateClosure(Node* node) {
CreateClosureParameters const& p = CreateClosureParametersOf(node->op());
Handle<SharedFunctionInfo> const shared_info = p.shared_info();
......
......@@ -617,7 +617,8 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(RejectPromise, Operator::kNoDeopt | Operator::kNoThrow, 3, 1) \
V(ResolvePromise, Operator::kNoDeopt | Operator::kNoThrow, 2, 1) \
V(GetSuperConstructor, Operator::kNoWrite, 1, 1) \
V(ParseInt, Operator::kNoProperties, 2, 1)
V(ParseInt, Operator::kNoProperties, 2, 1) \
V(RegExpTest, Operator::kNoProperties, 2, 1)
#define BINARY_OP_LIST(V) V(Add)
......
......@@ -816,6 +816,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* ObjectIsArray();
const Operator* ParseInt();
const Operator* RegExpTest();
private:
Zone* zone() const { return zone_; }
......
......@@ -209,6 +209,7 @@
V(JSResolvePromise) \
V(JSStackCheck) \
V(JSObjectIsArray) \
V(JSRegExpTest) \
V(JSDebugger)
#define JS_OP_LIST(V) \
......
......@@ -123,6 +123,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSResolvePromise:
case IrOpcode::kJSPerformPromiseThen:
case IrOpcode::kJSObjectIsArray:
case IrOpcode::kJSRegExpTest:
return true;
default:
......
......@@ -1145,12 +1145,12 @@ struct InliningPhase {
CommonOperatorReducer common_reducer(data->isolate(), &graph_reducer,
data->graph(), data->common(),
data->machine(), temp_zone);
JSCallReducer call_reducer(&graph_reducer, data->jsgraph(),
data->info()->is_bailout_on_uninitialized()
? JSCallReducer::kBailoutOnUninitialized
: JSCallReducer::kNoFlags,
data->native_context(),
data->info()->dependencies());
JSCallReducer call_reducer(
&graph_reducer, data->jsgraph(), data->js_heap_broker(),
data->info()->is_bailout_on_uninitialized()
? JSCallReducer::kBailoutOnUninitialized
: JSCallReducer::kNoFlags,
data->native_context(), data->info()->dependencies());
JSContextSpecialization context_specialization(
&graph_reducer, data->jsgraph(),
ChooseSpecializationContext(data->info()),
......
......@@ -1228,6 +1228,8 @@ Type Typer::Visitor::TypeJSLoadGlobal(Node* node) {
Type Typer::Visitor::TypeJSParseInt(Node* node) { return Type::Number(); }
Type Typer::Visitor::TypeJSRegExpTest(Node* node) { return Type::Boolean(); }
// Returns a somewhat larger range if we previously assigned
// a (smaller) range to this node. This is used to speed up
// the fixpoint calculation in case there appears to be a loop
......
......@@ -645,12 +645,15 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckTypeIs(node, Type::Receiver());
break;
case IrOpcode::kJSParseInt:
// Type is Receiver.
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::Any());
CheckTypeIs(node, Type::Number());
break;
case IrOpcode::kJSRegExpTest:
CheckValueInputIs(node, 0, Type::Any());
CheckValueInputIs(node, 1, Type::String());
CheckTypeIs(node, Type::Boolean());
break;
case IrOpcode::kJSCreate:
// Type is Object.
CheckTypeIs(node, Type::Object());
......
......@@ -21,7 +21,10 @@ namespace compiler {
class JSCallReducerTest : public TypedGraphTest {
public:
JSCallReducerTest()
: TypedGraphTest(3), javascript_(zone()), deps_(isolate(), zone()) {}
: TypedGraphTest(3),
javascript_(zone()),
deps_(isolate(), zone()),
js_heap_broker(isolate()) {}
~JSCallReducerTest() override {}
protected:
......@@ -33,8 +36,8 @@ class JSCallReducerTest : public TypedGraphTest {
// TODO(titzer): mock the GraphReducer here for better unit testing.
GraphReducer graph_reducer(zone(), graph());
JSCallReducer reducer(&graph_reducer, &jsgraph, JSCallReducer::kNoFlags,
native_context(), &deps_);
JSCallReducer reducer(&graph_reducer, &jsgraph, &js_heap_broker,
JSCallReducer::kNoFlags, native_context(), &deps_);
return reducer.Reduce(node);
}
......@@ -129,6 +132,7 @@ class JSCallReducerTest : public TypedGraphTest {
private:
JSOperatorBuilder javascript_;
CompilationDependencies deps_;
JSHeapBroker js_heap_broker;
static bool old_flag_lazy_;
static bool old_flag_lazy_handler_;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment