Commit 29dd7fc5 authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Lower ConsString creation in JSTypedLowering.

Extract String feedback on Add operation and utilize to lower ConsString
creation in JSTypedLowering when we know that a String addition will
definitely result in the creation of a ConsString.

Note that Crankshaft has to guard the potential length overflow of the
resulting string with an eager deoptimization exit, while we can safely
throw an exception in that case.

Also note that the bytecode pipeline does not currently provide the
String feedback for the addition, which has to be added.

BUG=v8:5267
R=jarin@chromium.org

Review-Url: https://codereview.chromium.org/2354853002
Cr-Commit-Position: refs/heads/master@{#39540}
parent 0e039730
......@@ -506,6 +506,7 @@ struct JSOperatorGlobalCache final {
Name##Operator<BinaryOperationHint::kSigned32> k##Name##Signed32Operator; \
Name##Operator<BinaryOperationHint::kNumberOrOddball> \
k##Name##NumberOrOddballOperator; \
Name##Operator<BinaryOperationHint::kString> k##Name##StringOperator; \
Name##Operator<BinaryOperationHint::kAny> k##Name##AnyOperator;
BINARY_OP_LIST(BINARY_OP)
#undef BINARY_OP
......@@ -553,6 +554,8 @@ CACHED_OP_LIST(CACHED_OP)
return &cache_.k##Name##Signed32Operator; \
case BinaryOperationHint::kNumberOrOddball: \
return &cache_.k##Name##NumberOrOddballOperator; \
case BinaryOperationHint::kString: \
return &cache_.k##Name##StringOperator; \
case BinaryOperationHint::kAny: \
return &cache_.k##Name##AnyOperator; \
} \
......
......@@ -46,6 +46,7 @@ class JSBinopReduction final {
return true;
case BinaryOperationHint::kAny:
case BinaryOperationHint::kNone:
case BinaryOperationHint::kString:
break;
}
}
......@@ -73,6 +74,30 @@ class JSBinopReduction final {
return false;
}
// Check if a string addition will definitely result in creating a ConsString,
// i.e. if the combined length of the resulting string exceeds the ConsString
// minimum length.
bool ShouldCreateConsString() {
DCHECK_EQ(IrOpcode::kJSAdd, node_->opcode());
if (BothInputsAre(Type::String()) ||
((lowering_->flags() & JSTypedLowering::kDeoptimizationEnabled) &&
BinaryOperationHintOf(node_->op()) == BinaryOperationHint::kString)) {
if (left_type()->IsConstant() &&
left_type()->AsConstant()->Value()->IsString()) {
Handle<String> left_string =
Handle<String>::cast(left_type()->AsConstant()->Value());
if (left_string->length() >= ConsString::kMinLength) return true;
}
if (right_type()->IsConstant() &&
right_type()->AsConstant()->Value()->IsString()) {
Handle<String> right_string =
Handle<String>::cast(right_type()->AsConstant()->Value());
if (right_string->length() >= ConsString::kMinLength) return true;
}
}
return false;
}
void ConvertInputsToNumber() {
// To convert the inputs to numbers, we have to provide frame states
// for lazy bailouts in the ToNumber conversions.
......@@ -467,6 +492,9 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
}
if (r.OneInputIs(Type::String())) {
if (r.ShouldCreateConsString()) {
return ReduceCreateConsString(node);
}
StringAddFlags flags = STRING_ADD_CHECK_NONE;
if (!r.LeftInputIs(Type::String())) {
flags = STRING_ADD_CONVERT_LEFT;
......@@ -544,6 +572,123 @@ Reduction JSTypedLowering::ReduceUI32Shift(Node* node, Signedness signedness) {
return NoChange();
}
Reduction JSTypedLowering::ReduceCreateConsString(Node* node) {
Node* first = NodeProperties::GetValueInput(node, 0);
Node* second = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Make sure {first} is actually a String.
Type* first_type = NodeProperties::GetType(first);
if (!first_type->Is(Type::String())) {
first = effect =
graph()->NewNode(simplified()->CheckString(), first, effect, control);
first_type = NodeProperties::GetType(first);
}
// Make sure {second} is actually a String.
Type* second_type = NodeProperties::GetType(second);
if (!second_type->Is(Type::String())) {
second = effect =
graph()->NewNode(simplified()->CheckString(), second, effect, control);
second_type = NodeProperties::GetType(second);
}
// Determine the {first} length.
Node* first_length =
first_type->IsConstant()
? jsgraph()->Constant(
Handle<String>::cast(first_type->AsConstant()->Value())
->length())
: effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForStringLength()),
first, effect, control);
// Determine the {second} length.
Node* second_length =
second_type->IsConstant()
? jsgraph()->Constant(
Handle<String>::cast(second_type->AsConstant()->Value())
->length())
: effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForStringLength()),
second, effect, control);
// Compute the resulting length.
Node* length =
graph()->NewNode(simplified()->NumberAdd(), first_length, second_length);
// Check if we would overflow the allowed maximum string length.
Node* check = graph()->NewNode(simplified()->NumberLessThanOrEqual(), length,
jsgraph()->Constant(String::kMaxLength));
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
// Throw a RangeError in case of overflow.
Node* vfalse = efalse = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowInvalidStringLength), context,
frame_state, efalse, if_false);
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
if_false = graph()->NewNode(common()->Throw(), vfalse, efalse, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
Revisit(graph()->end());
// Update potential {IfException} uses of {node} to point to the
// %ThrowInvalidStringLength runtime call node instead.
for (Edge edge : node->use_edges()) {
if (edge.from()->opcode() == IrOpcode::kIfException) {
DCHECK(NodeProperties::IsControlEdge(edge) ||
NodeProperties::IsEffectEdge(edge));
edge.UpdateTo(vfalse);
Revisit(edge.from());
}
}
}
control = graph()->NewNode(common()->IfTrue(), branch);
// Figure out the map for the resulting ConsString.
// TODO(turbofan): We currently just use the cons_string_map here for
// the sake of simplicity; we could also try to be smarter here and
// use the one_byte_cons_string_map instead when the resulting ConsString
// contains only one byte characters.
Node* value_map = jsgraph()->HeapConstant(factory()->cons_string_map());
// Allocate the resulting ConsString.
effect = graph()->NewNode(
common()->BeginRegion(RegionObservability::kNotObservable), effect);
Node* value = effect =
graph()->NewNode(simplified()->Allocate(NOT_TENURED),
jsgraph()->Constant(ConsString::kSize), effect, control);
NodeProperties::SetType(value, Type::OtherString());
effect = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()),
value, value_map, effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForNameHashField()), value,
jsgraph()->Uint32Constant(Name::kEmptyHashField), effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForStringLength()), value, length,
effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForConsStringFirst()), value,
first, effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForConsStringSecond()), value,
second, effect, control);
// Morph the {node} into a {FinishRegion}.
ReplaceWithValue(node, node, node, control);
node->ReplaceInput(0, value);
node->ReplaceInput(1, effect);
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, common()->FinishRegion());
return Changed(node);
}
Reduction JSTypedLowering::ReduceJSComparison(Node* node) {
JSBinopReduction r(this, node);
if (r.BothInputsAre(Type::String())) {
......
......@@ -73,6 +73,7 @@ class JSTypedLowering final : public AdvancedReducer {
Reduction ReduceNumberBinop(Node* node);
Reduction ReduceInt32Binop(Node* node);
Reduction ReduceUI32Shift(Node* node, Signedness signedness);
Reduction ReduceCreateConsString(Node* node);
Factory* factory() const;
Graph* graph() const;
......
......@@ -29,6 +29,7 @@ BinaryOperationHint ToBinaryOperationHint(Token::Value op,
case BinaryOpICState::NUMBER:
return BinaryOperationHint::kNumberOrOddball;
case BinaryOpICState::STRING:
return BinaryOperationHint::kString;
case BinaryOpICState::GENERIC:
return BinaryOperationHint::kAny;
}
......
......@@ -18,6 +18,8 @@ std::ostream& operator<<(std::ostream& os, BinaryOperationHint hint) {
return os << "Signed32";
case BinaryOperationHint::kNumberOrOddball:
return os << "NumberOrOddball";
case BinaryOperationHint::kString:
return os << "String";
case BinaryOperationHint::kAny:
return os << "Any";
}
......
......@@ -18,6 +18,7 @@ enum class BinaryOperationHint : uint8_t {
kSignedSmall,
kSigned32,
kNumberOrOddball,
kString,
kAny
};
......
// Copyright 2016 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
var a = "a".repeat(268435440);
(function() {
function foo(a, b) {
try {
return a + "0123456789012";
} catch (e) {
return e;
}
}
foo("a");
foo("a");
%OptimizeFunctionOnNextCall(foo);
foo("a");
assertInstanceof(foo(a), RangeError);
})();
(function() {
function foo(a, b) {
try {
return "0123456789012" + a;
} catch (e) {
return e;
}
}
foo("a");
foo("a");
%OptimizeFunctionOnNextCall(foo);
foo("a");
assertInstanceof(foo(a), RangeError);
})();
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