Commit 6a7872b7 authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[turbofan] Introduce a CheckStringAdd node instead of cons string lowering

The new node is introduced for literal string addition and calling
String.prototype.concat in the typed lowering phase. It later might get optimized
away during redundancy elimination, keeping the performance of already existing
benchmarks with string addition. In case the operation is about to throw
(due to too long string being constructed) we just deoptimize, reusing
the interpreter logic for creating the error.

Modify relevant mjsunit and unit tests for string concatenation.

Bug: v8:7902
Change-Id: Ie97d39534df4480fa8d4fe3ba276d02ed5e750e3
Reviewed-on: https://chromium-review.googlesource.com/1193342
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55482}
parent a279e23f
......@@ -694,6 +694,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kCheckIf:
LowerCheckIf(node, frame_state);
break;
case IrOpcode::kCheckStringAdd:
result = LowerCheckStringAdd(node, frame_state);
break;
case IrOpcode::kCheckedInt32Add:
result = LowerCheckedInt32Add(node, frame_state);
break;
......@@ -1544,6 +1547,32 @@ void EffectControlLinearizer::LowerCheckIf(Node* node, Node* frame_state) {
__ DeoptimizeIfNot(p.reason(), p.feedback(), value, frame_state);
}
Node* EffectControlLinearizer::LowerCheckStringAdd(Node* node,
Node* frame_state) {
Node* lhs = node->InputAt(0);
Node* rhs = node->InputAt(1);
Node* lhs_length = __ LoadField(AccessBuilder::ForStringLength(), lhs);
Node* rhs_length = __ LoadField(AccessBuilder::ForStringLength(), rhs);
Node* check = __ IntLessThan(__ IntAdd(lhs_length, rhs_length),
__ SmiConstant(String::kMaxLength));
__ DeoptimizeIfNot(DeoptimizeReason::kOverflow, VectorSlotPair(), check,
frame_state);
Callable const callable =
CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(), 0, CallDescriptor::kNoFlags,
Operator::kNoDeopt | Operator::kNoWrite | Operator::kNoThrow);
Node* value =
__ Call(call_descriptor, jsgraph()->HeapConstant(callable.code()), lhs,
rhs, __ NoContextConstant());
return value;
}
Node* EffectControlLinearizer::LowerCheckedInt32Add(Node* node,
Node* frame_state) {
Node* lhs = node->InputAt(0);
......
......@@ -67,6 +67,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckString(Node* node, Node* frame_state);
Node* LowerCheckSymbol(Node* node, Node* frame_state);
void LowerCheckIf(Node* node, Node* frame_state);
Node* LowerCheckStringAdd(Node* node, Node* frame_state);
Node* LowerCheckedInt32Add(Node* node, Node* frame_state);
Node* LowerCheckedInt32Sub(Node* node, Node* frame_state);
Node* LowerCheckedInt32Div(Node* node, Node* frame_state);
......
......@@ -5454,7 +5454,6 @@ Reduction JSCallReducer::ReduceStringPrototypeConcat(
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* receiver = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()),
NodeProperties::GetValueInput(node, 1), effect, control);
......@@ -5463,26 +5462,17 @@ Reduction JSCallReducer::ReduceStringPrototypeConcat(
ReplaceWithValue(node, receiver, effect, control);
return Replace(receiver);
}
if (!isolate()->IsStringLengthOverflowIntact()) {
return NoChange();
}
Node* argument = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()),
NodeProperties::GetValueInput(node, 2), effect, control);
Callable const callable =
CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
auto call_descriptor =
Linkage::GetStubCallDescriptor(graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState,
Operator::kNoDeopt | Operator::kNoWrite);
// TODO(turbofan): Massage the FrameState of the {node} here once we
// have an artificial builtin frame type, so that it looks like the
// exception from StringAdd overflow came from String.prototype.concat
// builtin instead of the calling function.
Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
Node* value = effect = control = graph()->NewNode(
common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()),
receiver, argument, context, outer_frame_state, effect, control);
Node* value = effect = graph()->NewNode(simplified()->CheckStringAdd(),
receiver, argument, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
......
......@@ -530,41 +530,15 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 0);
}
}
// We might be able to constant-fold the String concatenation now.
if (r.BothInputsAre(Type::String())) {
HeapObjectBinopMatcher m(node);
if (m.IsFoldable()) {
StringRef left = m.left().Ref(js_heap_broker()).AsString();
StringRef right = m.right().Ref(js_heap_broker()).AsString();
if (left.length() + right.length() > String::kMaxLength) {
// No point in trying to optimize this, as it will just throw.
return NoChange();
}
// TODO(mslekova): get rid of these allows by doing either one of:
// 1. remove the optimization and check if it ruins the performance
// 2. leave a placeholder and do the actual allocations once back on the
// MT
AllowHandleDereference allow_handle_dereference;
AllowHandleAllocation allow_handle_allocation;
AllowHeapAllocation allow_heap_allocation;
ObjectRef cons(
js_heap_broker(),
factory()
->NewConsString(left.object<String>(), right.object<String>())
.ToHandleChecked());
Node* value = jsgraph()->Constant(cons);
ReplaceWithValue(node, value);
return Replace(value);
}
}
// We might know for sure that we're creating a ConsString here.
if (r.ShouldCreateConsString()) {
return ReduceCreateConsString(node);
}
// Eliminate useless concatenation of empty string.
if (r.BothInputsAre(Type::String())) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Eliminate useless concatenation of empty string.
if (r.LeftInputIs(empty_string_type_)) {
Node* value = effect =
graph()->NewNode(simplified()->CheckString(VectorSlotPair()),
......@@ -578,7 +552,18 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// TODO(mslekova): Make sure this is executed for cons string
// as well.
if (isolate()->IsStringLengthOverflowIntact()) {
Node* value = graph()->NewNode(simplified()->CheckStringAdd(), r.left(),
r.right(), effect, control);
ReplaceWithValue(node, value, value);
return Replace(value);
}
}
StringAddFlags flags = STRING_ADD_CHECK_NONE;
if (!r.LeftInputIs(Type::String())) {
flags = STRING_ADD_CONVERT_LEFT;
......@@ -592,6 +577,7 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
// effects; it can still throw obviously.
properties = Operator::kNoWrite | Operator::kNoDeopt;
}
// JSAdd(x:string, y) => CallStub[StringAdd](x, y)
// JSAdd(x, y:string) => CallStub[StringAdd](x, y)
Callable const callable =
......
......@@ -372,6 +372,7 @@
V(CheckNotTaggedHole) \
V(CheckEqualsInternalizedString) \
V(CheckEqualsSymbol) \
V(CheckStringAdd) \
V(CompareMaps) \
V(ConvertReceiver) \
V(ConvertTaggedHoleToUndefined) \
......
......@@ -32,6 +32,7 @@ Reduction RedundancyElimination::Reduce(Node* node) {
case IrOpcode::kCheckSmi:
case IrOpcode::kCheckString:
case IrOpcode::kCheckSymbol:
case IrOpcode::kCheckStringAdd:
case IrOpcode::kCheckedFloat64ToInt32:
case IrOpcode::kCheckedInt32Add:
case IrOpcode::kCheckedInt32Div:
......
......@@ -3140,6 +3140,7 @@ class RepresentationSelector {
case IrOpcode::kArgumentsLengthState:
case IrOpcode::kUnreachable:
case IrOpcode::kRuntimeAbort:
case IrOpcode::kCheckStringAdd:
// All JavaScript operators except JSToNumber have uniform handling.
#define OPCODE_CASE(name) case IrOpcode::k##name:
JS_SIMPLE_BINOP_LIST(OPCODE_CASE)
......
......@@ -790,7 +790,8 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(CheckedInt32Mod, 2, 1) \
V(CheckedInt32Sub, 2, 1) \
V(CheckedUint32Div, 2, 1) \
V(CheckedUint32Mod, 2, 1)
V(CheckedUint32Mod, 2, 1) \
V(CheckStringAdd, 2, 1)
#define CHECKED_WITH_FEEDBACK_OP_LIST(V) \
V(CheckBounds, 2, 1) \
......
......@@ -678,6 +678,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckString(const VectorSlotPair& feedback);
const Operator* CheckSymbol();
const Operator* CheckStringAdd();
const Operator* CheckedFloat64ToInt32(CheckForMinusZeroMode,
const VectorSlotPair& feedback);
const Operator* CheckedInt32Add();
......
......@@ -2001,6 +2001,8 @@ Type Typer::Visitor::TypeCheckSymbol(Node* node) {
return Type::Intersect(arg, Type::Symbol(), zone());
}
Type Typer::Visitor::TypeCheckStringAdd(Node* node) { return Type::String(); }
Type Typer::Visitor::TypeCheckFloat64Hole(Node* node) {
return typer_->operation_typer_.CheckFloat64Hole(Operand(node, 0));
}
......
......@@ -1430,7 +1430,11 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::Symbol());
break;
case IrOpcode::kCheckStringAdd:
CheckValueInputIs(node, 0, Type::String());
CheckValueInputIs(node, 1, Type::String());
CheckTypeIs(node, Type::String());
break;
case IrOpcode::kConvertReceiver:
// (Any, Any) -> Receiver
CheckValueInputIs(node, 0, Type::Any());
......
......@@ -7,7 +7,9 @@ function NumberToString() {
var num = 10240;
var obj = {};
for ( var i = 0; i < num; i++ )
for ( var i = 0; i < num; i++ ) {
ret = obj["test" + num];
}
}
createSuite('NumberToString', 1000, NumberToString);
......@@ -4,6 +4,9 @@
// Flags: --allow-natives-syntax
// Test that string concatenation overflow (going over string max length)
// is handled gracefully, i.e. an error is thrown
var a = "a".repeat(%StringMaxLength());
(function() {
......@@ -37,3 +40,57 @@ var a = "a".repeat(%StringMaxLength());
foo("a");
assertInstanceof(foo(a), RangeError);
})();
(function() {
function foo(a, b) {
try {
return "0123456789012".concat(a);
} catch (e) {
return e;
}
}
foo("a");
foo("a");
%OptimizeFunctionOnNextCall(foo);
foo("a");
assertInstanceof(foo(a), RangeError);
})();
var obj = {
toString: function() {
throw new Error('toString has thrown');
}
};
(function() {
function foo(a, b) {
try {
return "0123456789012" + obj;
} catch (e) {
return e;
}
}
foo("a");
foo("a");
%OptimizeFunctionOnNextCall(foo);
foo("a");
assertInstanceof(foo(a), Error);
})();
(function() {
function foo(a, b) {
try {
return a + 123;
} catch (e) {
return e;
}
}
foo("a");
foo("a");
%OptimizeFunctionOnNextCall(foo);
foo("a");
assertInstanceof(foo(a), RangeError);
})();
......@@ -401,12 +401,7 @@ TEST_F(JSTypedLoweringTest, JSAddWithString) {
Reduction r = Reduce(graph()->NewNode(javascript()->Add(hint), lhs, rhs,
context, frame_state, effect, control));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(),
IsCall(_, IsHeapConstant(
CodeFactory::StringAdd(
isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED)
.code()),
lhs, rhs, context, frame_state, effect, control));
EXPECT_THAT(r.replacement(), IsCheckStringAdd(lhs, rhs));
}
} // namespace compiler
......
......@@ -2126,6 +2126,7 @@ IS_BINOP_MATCHER(Float64Sub)
IS_BINOP_MATCHER(Float64Mul)
IS_BINOP_MATCHER(Float64InsertLowWord32)
IS_BINOP_MATCHER(Float64InsertHighWord32)
IS_BINOP_MATCHER(CheckStringAdd)
#undef IS_BINOP_MATCHER
......
......@@ -496,6 +496,9 @@ Matcher<Node*> IsStackSlot();
Matcher<Node*> IsSpeculativeToNumber(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsCheckStringAdd(const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
// Helpers
static inline Matcher<Node*> IsIntPtrConstant(const intptr_t value) {
return kPointerSize == 8 ? IsInt64Constant(static_cast<int64_t>(value))
......
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