[turbofan] Optimize string comparisons more

Bug: v8:7531, chromium:822170
Change-Id: Ida5b0714a584df3d830f95088db51c1841b63cc2
Reviewed-on: https://chromium-review.googlesource.com/966062
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52153}
This commit is contained in:
Sigurd Schneider 2018-03-21 16:16:50 +01:00 committed by Commit Bot
parent b8695906f2
commit 543c007ea6
3 changed files with 341 additions and 21 deletions

View File

@ -105,7 +105,9 @@ Reduction TypedOptimization::Reduce(Node* node) {
case IrOpcode::kReferenceEqual:
return ReduceReferenceEqual(node);
case IrOpcode::kStringEqual:
return ReduceStringEqual(node);
case IrOpcode::kStringLessThan:
case IrOpcode::kStringLessThanOrEqual:
return ReduceStringComparison(node);
case IrOpcode::kSameValue:
return ReduceSameValue(node);
case IrOpcode::kSelect:
@ -350,26 +352,90 @@ Reduction TypedOptimization::ReduceReferenceEqual(Node* node) {
return NoChange();
}
Reduction TypedOptimization::TryReduceStringEqualOfCharAtAndConstant(
Node* equal, Node* char_at, Node* constant) {
DCHECK_EQ(IrOpcode::kStringFromCharCode, char_at->opcode());
const Operator* TypedOptimization::NumberComparisonFor(const Operator* op) {
switch (op->opcode()) {
case IrOpcode::kStringEqual:
return simplified()->NumberEqual();
case IrOpcode::kStringLessThan:
return simplified()->NumberLessThan();
case IrOpcode::kStringLessThanOrEqual:
return simplified()->NumberLessThanOrEqual();
default:
break;
}
UNREACHABLE();
}
Reduction
TypedOptimization::TryReduceStringComparisonOfCharAtAndStringToConstant(
Node* comparison, Handle<String> string, bool inverted) {
switch (comparison->opcode()) {
case IrOpcode::kStringEqual:
if (string->length() != 1) {
// String.fromCharCode(x) == "" is always false.
return Replace(jsgraph()->BooleanConstant(false));
}
break;
case IrOpcode::kStringLessThan:
V8_FALLTHROUGH;
case IrOpcode::kStringLessThanOrEqual:
if (string->length() == 0) {
// String.fromCharCode(x) <= "" is always false,
// "" < String.fromCharCode(x) is always true.
return Replace(jsgraph()->BooleanConstant(inverted));
}
break;
default:
UNREACHABLE();
}
return NoChange();
}
// Try to reduces a string comparison of the form
// String.fromCharCode(x) {comparison} {constant} if inverted is false,
// and {constant} {comparison} String.fromCharCode(x) if inverted is true.
Reduction TypedOptimization::TryReduceStringComparisonOfCharAtAndConstant(
Node* comparison, Node* from_char_code, Node* constant, bool inverted) {
DCHECK_EQ(IrOpcode::kStringFromCharCode, from_char_code->opcode());
HeapObjectMatcher m(constant);
if (!m.HasValue() || !m.Value()->IsString()) return NoChange();
Handle<String> string = Handle<String>::cast(m.Value());
if (string->length() != 1) {
// StringEqual can never be true. Out-of-bounds
// accesses will be handled by the deopt through CheckBounds.
return Replace(jsgraph()->BooleanConstant(false));
// Check if comparison can be resolved statically.
Reduction red = TryReduceStringComparisonOfCharAtAndStringToConstant(
comparison, string, inverted);
if (red.Changed()) return red;
const Operator* comparison_op = NumberComparisonFor(comparison->op());
Node* from_char_code_repl = NodeProperties::GetValueInput(from_char_code, 0);
Node* constant_repl = jsgraph()->Constant(string->Get(0));
Node* number_comparison = nullptr;
if (inverted) {
// "x..." <= String.fromCharCode(z) is true if x < z.
if (string->length() > 1 &&
comparison->opcode() == IrOpcode::kStringLessThanOrEqual) {
comparison_op = simplified()->NumberLessThan();
}
number_comparison =
graph()->NewNode(comparison_op, constant_repl, from_char_code_repl);
} else {
// String.fromCharCode(z) < "x..." is true if z <= x.
if (string->length() > 1 &&
comparison->opcode() == IrOpcode::kStringLessThan) {
comparison_op = simplified()->NumberLessThanOrEqual();
}
number_comparison =
graph()->NewNode(comparison_op, from_char_code_repl, constant_repl);
}
Node* number_equal = graph()->NewNode(
simplified()->NumberEqual(), NodeProperties::GetValueInput(char_at, 0),
jsgraph()->Constant(string->Get(0)));
ReplaceWithValue(equal, number_equal);
return Replace(number_equal);
ReplaceWithValue(comparison, number_comparison);
return Replace(number_comparison);
}
Reduction TypedOptimization::ReduceStringEqual(Node* node) {
DCHECK_EQ(IrOpcode::kStringEqual, node->opcode());
Reduction TypedOptimization::ReduceStringComparison(Node* node) {
DCHECK(IrOpcode::kStringEqual == node->opcode() ||
IrOpcode::kStringLessThan == node->opcode() ||
IrOpcode::kStringLessThanOrEqual == node->opcode());
Node* const lhs = NodeProperties::GetValueInput(node, 0);
Node* const rhs = NodeProperties::GetValueInput(node, 1);
if (lhs->opcode() == IrOpcode::kStringFromCharCode) {
@ -392,14 +458,16 @@ Reduction TypedOptimization::ReduceStringEqual(Node* node) {
simplified()->NumberBitwiseAnd(), right,
jsgraph()->Constant(std::numeric_limits<uint16_t>::max()));
}
Node* equal = graph()->NewNode(simplified()->NumberEqual(), left, right);
Node* equal =
graph()->NewNode(NumberComparisonFor(node->op()), left, right);
ReplaceWithValue(node, equal);
return Replace(equal);
} else {
return TryReduceStringEqualOfCharAtAndConstant(node, lhs, rhs);
return TryReduceStringComparisonOfCharAtAndConstant(node, lhs, rhs,
false);
}
} else if (rhs->opcode() == IrOpcode::kStringFromCharCode) {
return TryReduceStringEqualOfCharAtAndConstant(node, rhs, lhs);
return TryReduceStringComparisonOfCharAtAndConstant(node, rhs, lhs, true);
}
return NoChange();
}

View File

@ -49,7 +49,7 @@ class V8_EXPORT_PRIVATE TypedOptimization final
Reduction ReduceNumberToUint8Clamped(Node* node);
Reduction ReducePhi(Node* node);
Reduction ReduceReferenceEqual(Node* node);
Reduction ReduceStringEqual(Node* node);
Reduction ReduceStringComparison(Node* node);
Reduction ReduceSameValue(Node* node);
Reduction ReduceSelect(Node* node);
Reduction ReduceSpeculativeToNumber(Node* node);
@ -57,8 +57,13 @@ class V8_EXPORT_PRIVATE TypedOptimization final
Reduction ReduceTypeOf(Node* node);
Reduction ReduceToBoolean(Node* node);
Reduction TryReduceStringEqualOfCharAtAndConstant(Node* equal, Node* char_at,
Node* constant);
Reduction TryReduceStringComparisonOfCharAtAndConstant(Node* comparison,
Node* from_char_code,
Node* constant,
bool inverted);
Reduction TryReduceStringComparisonOfCharAtAndStringToConstant(
Node* comparison, Handle<String> string, bool inverted);
const Operator* NumberComparisonFor(const Operator* op);
CompilationDependencies* dependencies() const { return dependencies_; }
Factory* factory() const;

View File

@ -0,0 +1,247 @@
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file.
// Flags: --allow-natives-syntax
(()=> {
function f(a) {
return a.charAt(1) == "";
}
assertEquals(false, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) < "";
}
assertEquals(false, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) <= "";
}
assertEquals(false, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) > "";
}
assertEquals(true, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) >= "";
}
assertEquals(true, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) == a.charAt(2);
}
assertEquals(false, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(false, f("acb"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(false, f("acb"));
})();
(()=> {
function f(a) {
return a.charAt(1) < a.charAt(2);
}
assertEquals(true, f("aab"));
assertEquals(false, f("aaa"));
assertEquals(false, f("acb"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aab"));
assertEquals(false, f("aaa"));
assertEquals(false, f("acb"));
})();
(()=> {
function f(a) {
return a.charAt(1) <= a.charAt(2);
}
assertEquals(true, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(false, f("acb"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(false, f("acb"));
})();
(()=> {
function f(a) {
return a.charAt(1) > a.charAt(2);
}
assertEquals(false, f("aab"));
assertEquals(false, f("aaa"));
assertEquals(true, f("acb"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aab"));
assertEquals(false, f("aaa"));
assertEquals(true, f("acb"));
})();
(()=> {
function f(a) {
return a.charAt(1) >= a.charAt(2);
}
assertEquals(false, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(true, f("acb"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aab"));
assertEquals(true, f("aaa"));
assertEquals(true, f("acb"));
})();
(()=> {
function f(a) {
return a.charAt(1) == "b";
}
assertEquals(false, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) == "bb";
}
assertEquals(false, f("aaa"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
})();
(()=> {
function f(a) {
return a.charAt(1) < "b";
}
assertEquals(true, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(false, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(false, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) < "bb";
}
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) <= "b";
}
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) <= "bb";
}
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(true, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(false, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) > "b";
}
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) > "bb";
}
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) >= "b";
}
assertEquals(false, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(true, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
assertEquals(true, f("bbb"));
assertEquals(true, f("ccc"));
})();
(()=> {
function f(a) {
return a.charAt(1) >= "bb";
}
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
%OptimizeFunctionOnNextCall(f);
assertEquals(false, f("aaa"));
assertEquals(false, f("bbb"));
assertEquals(true, f("ccc"));
})();