Commit c05be62f authored by jameslahm's avatar jameslahm Committed by V8 LUCI CQ

[compiler] Optimize String.prototype.startsWith

We could optimize String#startsWith in JSCallReducer for
three conditions:
- If search_element is definitely not a string, we make no change.
- If search_element is definitely a string and its length is less
or equal than max inline matching sequence threshold, we could
inline the entire matching sequence.
- Else we try to inline, and have a runtime deopt if search_element
is not a string.

Bug: v8:8400
Change-Id: I505090b91d35fbc2c91cdf985717c68135cba807
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3517936Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Commit-Queue: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79621}
parent be859096
...@@ -377,6 +377,12 @@ TNode<FixedArrayBase> JSGraphAssembler::MaybeGrowFastElements( ...@@ -377,6 +377,12 @@ TNode<FixedArrayBase> JSGraphAssembler::MaybeGrowFastElements(
new_length, old_length, effect(), control())); new_length, old_length, effect(), control()));
} }
Node* JSGraphAssembler::StringCharCodeAt(TNode<String> string,
TNode<Number> position) {
return AddNode(graph()->NewNode(simplified()->StringCharCodeAt(), string,
position, effect(), control()));
}
Node* GraphAssembler::TypeGuard(Type type, Node* value) { Node* GraphAssembler::TypeGuard(Type type, Node* value) {
return AddNode( return AddNode(
graph()->NewNode(common()->TypeGuard(type), value, effect(), control())); graph()->NewNode(common()->TypeGuard(type), value, effect(), control()));
......
...@@ -878,6 +878,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler { ...@@ -878,6 +878,7 @@ class V8_EXPORT_PRIVATE JSGraphAssembler : public GraphAssembler {
TNode<FixedArrayBase> elements, TNode<FixedArrayBase> elements,
TNode<Number> new_length, TNode<Number> new_length,
TNode<Number> old_length); TNode<Number> old_length);
Node* StringCharCodeAt(TNode<String> string, TNode<Number> position);
JSGraph* jsgraph() const { return jsgraph_; } JSGraph* jsgraph() const { return jsgraph_; }
Isolate* isolate() const { return jsgraph()->isolate(); } Isolate* isolate() const { return jsgraph()->isolate(); }
......
...@@ -1300,20 +1300,22 @@ base::Optional<int> StringRef::length() const { ...@@ -1300,20 +1300,22 @@ base::Optional<int> StringRef::length() const {
} }
} }
base::Optional<uint16_t> StringRef::GetFirstChar() { base::Optional<uint16_t> StringRef::GetFirstChar() const { return GetChar(0); }
base::Optional<uint16_t> StringRef::GetChar(int index) const {
if (data_->kind() == kNeverSerializedHeapObject && !SupportedStringKind()) { if (data_->kind() == kNeverSerializedHeapObject && !SupportedStringKind()) {
TRACE_BROKER_MISSING( TRACE_BROKER_MISSING(
broker(), broker(),
"first char for kNeverSerialized unsupported string kind " << *this); "get char for kNeverSerialized unsupported string kind " << *this);
return base::nullopt; return base::nullopt;
} }
if (!broker()->IsMainThread()) { if (!broker()->IsMainThread()) {
return object()->Get(0, broker()->local_isolate()); return object()->Get(index, broker()->local_isolate());
} else { } else {
// TODO(solanes, v8:7790): Remove this case once the inlining phase is // TODO(solanes, v8:7790): Remove this case once the inlining phase is
// done concurrently all the time. // done concurrently all the time.
return object()->Get(0); return object()->Get(index);
} }
} }
......
...@@ -920,7 +920,8 @@ class StringRef : public NameRef { ...@@ -920,7 +920,8 @@ class StringRef : public NameRef {
// base::nullopt for these methods. // base::nullopt for these methods.
base::Optional<Handle<String>> ObjectIfContentAccessible(); base::Optional<Handle<String>> ObjectIfContentAccessible();
base::Optional<int> length() const; base::Optional<int> length() const;
base::Optional<uint16_t> GetFirstChar(); base::Optional<uint16_t> GetFirstChar() const;
base::Optional<uint16_t> GetChar(int index) const;
base::Optional<double> ToNumber(); base::Optional<double> ToNumber();
bool IsSeqString() const; bool IsSeqString() const;
......
...@@ -86,6 +86,9 @@ class JSCallReducerAssembler : public JSGraphAssembler { ...@@ -86,6 +86,9 @@ class JSCallReducerAssembler : public JSGraphAssembler {
TNode<Object> ReduceMathUnary(const Operator* op); TNode<Object> ReduceMathUnary(const Operator* op);
TNode<Object> ReduceMathBinary(const Operator* op); TNode<Object> ReduceMathBinary(const Operator* op);
TNode<String> ReduceStringPrototypeSubstring(); TNode<String> ReduceStringPrototypeSubstring();
TNode<Boolean> ReduceStringPrototypeStartsWith();
TNode<Boolean> ReduceStringPrototypeStartsWith(
const StringRef& search_element_string);
TNode<String> ReduceStringPrototypeSlice(); TNode<String> ReduceStringPrototypeSlice();
TNode<Object> TargetInput() const { return JSCallNode{node_ptr()}.target(); } TNode<Object> TargetInput() const { return JSCallNode{node_ptr()}.target(); }
...@@ -1194,6 +1197,91 @@ TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSubstring() { ...@@ -1194,6 +1197,91 @@ TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSubstring() {
return StringSubstring(receiver_string, from, to); return StringSubstring(receiver_string, from, to);
} }
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeStartsWith(
const StringRef& search_element_string) {
TNode<Object> receiver = ReceiverInput();
TNode<Object> start = ArgumentOrZero(1);
TNode<String> receiver_string = CheckString(receiver);
TNode<Smi> start_smi = CheckSmi(start);
TNode<Number> length = StringLength(receiver_string);
TNode<Number> zero = ZeroConstant();
TNode<Number> clamped_start = NumberMin(NumberMax(start_smi, zero), length);
int search_string_length = search_element_string.length().value();
DCHECK(search_string_length <= JSCallReducer::kMaxInlineMatchSequence);
auto out = MakeLabel(MachineRepresentation::kTagged);
auto search_string_too_long =
NumberLessThan(NumberSubtract(length, clamped_start),
NumberConstant(search_string_length));
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
STATIC_ASSERT(String::kMaxLength <= kSmiMaxValue);
for (int i = 0; i < search_string_length; i++) {
TNode<Number> k = NumberConstant(i);
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, clamped_start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
Node* search_string_char =
jsgraph()->Constant(search_element_string.GetChar(i).value());
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
search_string_char, receiver_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
}
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<Boolean> JSCallReducerAssembler::ReduceStringPrototypeStartsWith() {
TNode<Object> receiver = ReceiverInput();
TNode<Object> search_element = ArgumentOrUndefined(0);
TNode<Object> start = ArgumentOrZero(1);
TNode<String> receiver_string = CheckString(receiver);
TNode<String> search_string = CheckString(search_element);
TNode<Smi> start_smi = CheckSmi(start);
TNode<Number> length = StringLength(receiver_string);
TNode<Number> zero = ZeroConstant();
TNode<Number> clamped_start = NumberMin(NumberMax(start_smi, zero), length);
TNode<Number> search_string_length = StringLength(search_string);
auto out = MakeLabel(MachineRepresentation::kTagged);
auto search_string_too_long = NumberLessThan(
NumberSubtract(length, clamped_start), search_string_length);
GotoIf(search_string_too_long, &out, BranchHint::kFalse, FalseConstant());
STATIC_ASSERT(String::kMaxLength <= kSmiMaxValue);
ForZeroUntil(search_string_length).Do([&](TNode<Number> k) {
TNode<Number> receiver_string_position = TNode<Number>::UncheckedCast(
TypeGuard(Type::UnsignedSmall(), NumberAdd(k, clamped_start)));
Node* receiver_string_char =
StringCharCodeAt(receiver_string, receiver_string_position);
Node* search_string_char = StringCharCodeAt(search_string, k);
auto is_equal = graph()->NewNode(simplified()->NumberEqual(),
receiver_string_char, search_string_char);
GotoIfNot(is_equal, &out, FalseConstant());
});
Goto(&out, TrueConstant());
Bind(&out);
return out.PhiAt<Boolean>(0);
}
TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSlice() { TNode<String> JSCallReducerAssembler::ReduceStringPrototypeSlice() {
TNode<Object> receiver = ReceiverInput(); TNode<Object> receiver = ReceiverInput();
TNode<Object> start = Argument(0); TNode<Object> start = Argument(0);
...@@ -6403,74 +6491,37 @@ Reduction JSCallReducer::ReduceStringPrototypeStartsWith(Node* node) { ...@@ -6403,74 +6491,37 @@ Reduction JSCallReducer::ReduceStringPrototypeStartsWith(Node* node) {
return NoChange(); return NoChange();
} }
Node* receiver = n.receiver(); TNode<Object> search_element = n.ArgumentOrUndefined(0, jsgraph());
Effect effect = n.effect();
Control control = n.control(); // Here are three conditions:
// First, If search_element is definitely not a string, we make no change.
Node* search_string = n.ArgumentOr(0, jsgraph()->UndefinedConstant()); // Second, If search_element is definitely a string and its length is less
Node* position = n.ArgumentOr(1, jsgraph()->ZeroConstant()); // or equal than max inline matching sequence threshold, we could inline
// the entire matching sequence.
HeapObjectMatcher m(search_string); // Third, we try to inline, and have a runtime deopt if search_element is
if (m.HasResolvedValue()) { // not a string.
ObjectRef target_ref = m.Ref(broker()); HeapObjectMatcher search_element_matcher(search_element);
if (target_ref.IsString()) { if (search_element_matcher.HasResolvedValue()) {
StringRef str = target_ref.AsString(); ObjectRef target_ref = search_element_matcher.Ref(broker());
if (str.length().has_value()) { if (!target_ref.IsString()) return NoChange();
receiver = effect = graph()->NewNode( StringRef search_element_string = target_ref.AsString();
simplified()->CheckString(p.feedback()), receiver, effect, control); if (search_element_string.length().has_value()) {
int length = search_element_string.length().value();
position = effect = graph()->NewNode( // If search_element's length is less or equal than
simplified()->CheckSmi(p.feedback()), position, effect, control); // kMaxInlineMatchSequence, we inline the entire
// matching sequence.
if (str.length().value() == 0) { if (length <= kMaxInlineMatchSequence) {
Node* value = jsgraph()->TrueConstant(); JSCallReducerAssembler a(this, node);
ReplaceWithValue(node, value, effect, control); Node* subgraph =
return Replace(value); a.ReduceStringPrototypeStartsWith(search_element_string);
} return ReplaceWithSubgraph(&a, subgraph);
if (str.length().value() == 1) {
Node* string_length =
graph()->NewNode(simplified()->StringLength(), receiver);
Node* unsigned_position = graph()->NewNode(
simplified()->NumberMax(), position, jsgraph()->ZeroConstant());
Node* check = graph()->NewNode(simplified()->NumberLessThan(),
unsigned_position, string_length);
Node* branch = graph()->NewNode(common()->Branch(BranchHint::kNone),
check, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = jsgraph()->FalseConstant();
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
Node* string_first = etrue =
graph()->NewNode(simplified()->StringCharCodeAt(), receiver,
unsigned_position, etrue, if_true);
Node* search_first =
jsgraph()->Constant(str.GetFirstChar().value());
vtrue = graph()->NewNode(simplified()->NumberEqual(), string_first,
search_first);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
} }
} }
} }
return NoChange(); JSCallReducerAssembler a(this, node);
Node* subgraph = a.ReduceStringPrototypeStartsWith();
return ReplaceWithSubgraph(&a, subgraph);
} }
// ES section 21.1.3.1 String.prototype.charAt ( pos ) // ES section 21.1.3.1 String.prototype.charAt ( pos )
......
...@@ -55,6 +55,10 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer { ...@@ -55,6 +55,10 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
temp_zone_(temp_zone), temp_zone_(temp_zone),
flags_(flags) {} flags_(flags) {}
// Max string length for inlining entire match sequence for
// String.prototype.startsWith in JSCallReducer.
static constexpr int kMaxInlineMatchSequence = 3;
const char* reducer_name() const override { return "JSCallReducer"; } const char* reducer_name() const override { return "JSCallReducer"; }
Reduction Reduce(Node* node) final; Reduction Reduce(Node* node) final;
......
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