Commit e56b6d24 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Introduce a pure StringConcat operator.

This replaces the previous CheckStringAdd operator which deopts in case
the combined length overflows with a dedicated pure StringConcat operator.
This operator is similar to NewConsString in that it takes the resulting
length plus the two input strings. The operator relies on the length
being checked explicitly by the surrounding code instead of baking the
check into the operator itself. This way TurboFan can eliminate
redundant/unnecessary StringConcat operations, since they are pure now.

This also unifies the treatment of string addition in JSTypedLowering,
and generalizes the StringLength constant-folding to apply to more cases
not just the JSAdd cases inside JSTypedLowering.

Bug: v8:7902, v8:8015
Change-Id: I987ec39815a9464fd5fd9c4f7b26b709f94f2b3f
Reviewed-on: https://chromium-review.googlesource.com/1213205Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55725}
parent d3464198
......@@ -694,9 +694,6 @@ 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;
......@@ -836,6 +833,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kDeadValue:
result = LowerDeadValue(node);
break;
case IrOpcode::kStringConcat:
result = LowerStringConcat(node);
break;
case IrOpcode::kStringFromSingleCharCode:
result = LowerStringFromSingleCharCode(node);
break;
......@@ -1547,18 +1547,9 @@ 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);
Node* EffectControlLinearizer::LowerStringConcat(Node* node) {
Node* lhs = node->InputAt(1);
Node* rhs = node->InputAt(2);
Callable const callable =
CodeFactory::StringAdd(isolate(), STRING_ADD_CHECK_NONE, NOT_TENURED);
......
......@@ -67,7 +67,6 @@ 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);
......@@ -124,6 +123,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerArrayBufferWasNeutered(Node* node);
Node* LowerSameValue(Node* node);
Node* LowerDeadValue(Node* node);
Node* LowerStringConcat(Node* node);
Node* LowerStringToNumber(Node* node);
Node* LowerStringCharCodeAt(Node* node);
Node* LowerStringCodePointAt(Node* node, UnicodeEncoding encoding);
......
......@@ -5452,6 +5452,7 @@ Reduction JSCallReducer::ReduceStringPrototypeConcat(
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* receiver = effect =
......@@ -5463,16 +5464,21 @@ Reduction JSCallReducer::ReduceStringPrototypeConcat(
return Replace(receiver);
}
if (!isolate()->IsStringLengthOverflowIntact()) {
return NoChange();
}
Node* argument = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()),
NodeProperties::GetValueInput(node, 2), effect, control);
Node* value = effect = graph()->NewNode(simplified()->CheckStringAdd(),
receiver, argument, effect, control);
Node* receiver_length =
graph()->NewNode(simplified()->StringLength(), receiver);
Node* argument_length =
graph()->NewNode(simplified()->StringLength(), argument);
Node* length = graph()->NewNode(simplified()->NumberAdd(), receiver_length,
argument_length);
length = effect = graph()->NewNode(
simplified()->CheckBounds(p.feedback()), length,
jsgraph()->Constant(String::kMaxLength + 1), effect, control);
Node* value = graph()->NewNode(simplified()->StringConcat(), length, receiver,
argument);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
......
......@@ -509,61 +509,123 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
r.ConvertInputsToNumber();
return r.ChangeToPureOperator(simplified()->NumberAdd(), Type::Number());
}
// Strength-reduce if one input is already known to be a string.
if (r.LeftInputIs(Type::String())) {
// JSAdd(x:string, y) => JSAdd(x, JSToString(y))
Reduction const reduction = ReduceJSToStringInput(r.right());
if (reduction.Changed()) {
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 1);
}
} else if (r.RightInputIs(Type::String())) {
// JSAdd(x, y:string) => JSAdd(JSToString(x), y)
Reduction const reduction = ReduceJSToStringInput(r.left());
if (reduction.Changed()) {
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 0);
}
}
// Always bake in String feedback into the graph.
if (BinaryOperationHintOf(node->op()) == BinaryOperationHint::kString) {
// Always bake in String feedback into the graph.
// TODO(bmeurer): Consider adding a SpeculativeStringAdd operator,
// and use that in JSTypeHintLowering instead of looking at the
// binary operation feedback here.
r.CheckInputsToString();
}
if (r.OneInputIs(Type::String())) {
// We know that (at least) one input is already a String,
// so try to strength-reduce the non-String input.
if (r.LeftInputIs(Type::String())) {
Reduction const reduction = ReduceJSToStringInput(r.right());
if (reduction.Changed()) {
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 1);
}
} else if (r.RightInputIs(Type::String())) {
Reduction const reduction = ReduceJSToStringInput(r.left());
if (reduction.Changed()) {
NodeProperties::ReplaceValueInput(node, reduction.replacement(), 0);
}
}
// We might know for sure that we're creating a ConsString here.
if (r.ShouldCreateConsString()) {
return ReduceCreateConsString(node);
// Strength-reduce concatenation of empty strings if both sides are
// primitives, as in that case the ToPrimitive on the other side is
// definitely going to be a no-op.
if (r.BothInputsAre(Type::Primitive())) {
if (r.LeftInputIs(empty_string_type_)) {
// JSAdd("", x:primitive) => JSToString(x)
NodeProperties::ReplaceValueInputs(node, r.right());
NodeProperties::ChangeOp(node, javascript()->ToString());
Reduction const reduction = ReduceJSToString(node);
return reduction.Changed() ? reduction : Changed(node);
} else if (r.RightInputIs(empty_string_type_)) {
// JSAdd(x:primitive, "") => JSToString(x)
NodeProperties::ReplaceValueInputs(node, r.left());
NodeProperties::ChangeOp(node, javascript()->ToString());
Reduction const reduction = ReduceJSToString(node);
return reduction.Changed() ? reduction : Changed(node);
}
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()),
r.right(), effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
} else if (r.RightInputIs(empty_string_type_)) {
Node* value = effect =
graph()->NewNode(simplified()->CheckString(VectorSlotPair()),
r.left(), effect, control);
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);
// Lower to string addition if both inputs are known to be strings.
if (r.BothInputsAre(Type::String())) {
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
// Compute the resulting length.
Node* left_length =
graph()->NewNode(simplified()->StringLength(), r.left());
Node* right_length =
graph()->NewNode(simplified()->StringLength(), r.right());
Node* length =
graph()->NewNode(simplified()->NumberAdd(), left_length, right_length);
if (isolate()->IsStringLengthOverflowIntact()) {
// We can just deoptimize if the {length} is out-of-bounds. Besides
// generating a shorter code sequence than the version below, this
// has the additional benefit of not holding on to the lazy {frame_state}
// and thus potentially reduces the number of live ranges and allows for
// more truncations.
length = effect = graph()->NewNode(
simplified()->CheckBounds(VectorSlotPair()), length,
jsgraph()->Constant(String::kMaxLength + 1), effect, control);
} else {
// 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 = if_false = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowInvalidStringLength),
context, frame_state, efalse, if_false);
// Update potential {IfException} uses of {node} to point to the
// %ThrowInvalidStringLength runtime call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse);
NodeProperties::ReplaceEffectInput(on_exception, efalse);
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
Revisit(on_exception);
}
ReplaceWithValue(node, value, value);
return Replace(value);
// The above %ThrowInvalidStringLength runtime call is an unconditional
// throw, making it impossible to return a successful completion in this
// case. We simply connect the successful completion to the graph end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
Revisit(graph()->end());
}
control = graph()->NewNode(common()->IfTrue(), branch);
length = effect =
graph()->NewNode(common()->TypeGuard(type_cache_.kStringLengthType),
length, effect, control);
}
// TODO(bmeurer): Ideally this should always use StringConcat and decide to
// optimize to NewConsString later during SimplifiedLowering, but for that
// to work we need to know that it's safe to create a ConsString.
Operator const* const op = r.ShouldCreateConsString()
? simplified()->NewConsString()
: simplified()->StringConcat();
Node* value = graph()->NewNode(op, length, r.left(), r.right());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
// We never get here when we had String feedback.
DCHECK_NE(BinaryOperationHint::kString, BinaryOperationHintOf(node->op()));
if (r.OneInputIs(Type::String())) {
StringAddFlags flags = STRING_ADD_CHECK_NONE;
if (!r.LeftInputIs(Type::String())) {
flags = STRING_ADD_CONVERT_LEFT;
......@@ -591,8 +653,6 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
NodeProperties::ChangeOp(node, common()->Call(call_descriptor));
return Changed(node);
}
// We never get here when we had String feedback.
DCHECK_NE(BinaryOperationHint::kString, BinaryOperationHintOf(node->op()));
return NoChange();
}
......@@ -643,103 +703,6 @@ 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(VectorSlotPair()), 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(VectorSlotPair()), second, effect, control);
second_type = NodeProperties::GetType(second);
}
// Determine the {first} length.
Node* first_length = BuildGetStringLength(first);
Node* second_length = BuildGetStringLength(second);
// Compute the resulting length.
Node* length =
graph()->NewNode(simplified()->NumberAdd(), first_length, second_length);
if (isolate()->IsStringLengthOverflowIntact()) {
// We can just deoptimize if the {length} is out-of-bounds. Besides
// generating a shorter code sequence than the version below, this
// has the additional benefit of not holding on to the lazy {frame_state}
// and thus potentially reduces the number of live ranges and allows for
// more truncations.
length = effect = graph()->NewNode(
simplified()->CheckBounds(VectorSlotPair()), length,
jsgraph()->Constant(String::kMaxLength), effect, control);
} else {
// 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 = if_false = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowInvalidStringLength),
context, frame_state, efalse, if_false);
// Update potential {IfException} uses of {node} to point to the
// %ThrowInvalidStringLength runtime call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse);
NodeProperties::ReplaceEffectInput(on_exception, efalse);
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
Revisit(on_exception);
}
// The above %ThrowInvalidStringLength runtime call is an unconditional
// throw, making it impossible to return a successful completion in this
// case. We simply connect the successful completion to the graph end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
Revisit(graph()->end());
}
control = graph()->NewNode(common()->IfTrue(), branch);
length = effect =
graph()->NewNode(common()->TypeGuard(type_cache_.kStringLengthType),
length, effect, control);
}
Node* value =
graph()->NewNode(simplified()->NewConsString(), length, first, second);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Node* JSTypedLowering::BuildGetStringLength(Node* value) {
// TODO(bmeurer): Get rid of this hack and instead have a way to
// express the string length in the types.
HeapObjectMatcher m(value);
if (!m.HasValue() || !m.Ref(js_heap_broker()).IsString()) {
return graph()->NewNode(simplified()->StringLength(), value);
}
return jsgraph()->Constant(m.Ref(js_heap_broker()).AsString().length());
}
Reduction JSTypedLowering::ReduceSpeculativeNumberComparison(Node* node) {
JSBinopReduction r(this, node);
if (r.BothInputsAre(Type::Signed32()) ||
......
......@@ -82,7 +82,6 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
Reduction ReduceNumberBinop(Node* node);
Reduction ReduceInt32Binop(Node* node);
Reduction ReduceUI32Shift(Node* node, Signedness signedness);
Reduction ReduceCreateConsString(Node* node);
Reduction ReduceSpeculativeNumberAdd(Node* node);
Reduction ReduceSpeculativeNumberMultiply(Node* node);
Reduction ReduceSpeculativeNumberBinop(Node* node);
......@@ -93,9 +92,6 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
// Helper for ReduceJSLoadModule and ReduceJSStoreModule.
Node* BuildGetModuleCell(Node* node);
// Helpers for ReduceJSCreateConsString.
Node* BuildGetStringLength(Node* value);
Factory* factory() const;
Graph* graph() const;
JSGraph* jsgraph() const { return jsgraph_; }
......
......@@ -349,6 +349,7 @@
V(PlainPrimitiveToWord32) \
V(PlainPrimitiveToFloat64) \
V(BooleanNot) \
V(StringConcat) \
V(StringToNumber) \
V(StringCharCodeAt) \
V(StringCodePointAt) \
......@@ -373,7 +374,6 @@
V(CheckNotTaggedHole) \
V(CheckEqualsInternalizedString) \
V(CheckEqualsSymbol) \
V(CheckStringAdd) \
V(CompareMaps) \
V(ConvertReceiver) \
V(ConvertTaggedHoleToUndefined) \
......
......@@ -32,7 +32,6 @@ 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:
......
......@@ -2344,6 +2344,18 @@ class RepresentationSelector {
SetOutput(node, MachineRepresentation::kTaggedPointer);
return;
}
case IrOpcode::kStringConcat: {
// TODO(turbofan): We currently depend on having this first length input
// to make sure that the overflow check is properly scheduled before the
// actual string concatenation. We should also use the length to pass it
// to the builtin or decide in optimized code how to construct the
// resulting string (i.e. cons string or sequential string).
ProcessInput(node, 0, UseInfo::TaggedSigned()); // length
ProcessInput(node, 1, UseInfo::AnyTagged()); // first
ProcessInput(node, 2, UseInfo::AnyTagged()); // second
SetOutput(node, MachineRepresentation::kTaggedPointer);
return;
}
case IrOpcode::kStringEqual:
case IrOpcode::kStringLessThan:
case IrOpcode::kStringLessThanOrEqual: {
......@@ -3141,7 +3153,6 @@ 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)
......
......@@ -709,6 +709,7 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(NumberToUint32, Operator::kNoProperties, 1, 0) \
V(NumberToUint8Clamped, Operator::kNoProperties, 1, 0) \
V(NumberSilenceNaN, Operator::kNoProperties, 1, 0) \
V(StringConcat, Operator::kNoProperties, 3, 0) \
V(StringToNumber, Operator::kNoProperties, 1, 0) \
V(StringFromSingleCharCode, Operator::kNoProperties, 1, 0) \
V(StringIndexOf, Operator::kNoProperties, 3, 0) \
......@@ -790,8 +791,7 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(CheckedInt32Mod, 2, 1) \
V(CheckedInt32Sub, 2, 1) \
V(CheckedUint32Div, 2, 1) \
V(CheckedUint32Mod, 2, 1) \
V(CheckStringAdd, 2, 1)
V(CheckedUint32Mod, 2, 1)
#define CHECKED_WITH_FEEDBACK_OP_LIST(V) \
V(CheckBounds, 2, 1) \
......
......@@ -616,6 +616,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* ToBoolean();
const Operator* StringConcat();
const Operator* StringEqual();
const Operator* StringLessThan();
const Operator* StringLessThanOrEqual();
......@@ -678,8 +679,6 @@ 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();
......
......@@ -71,6 +71,8 @@ Reduction TypedOptimization::Reduce(Node* node) {
case IrOpcode::kStringLessThan:
case IrOpcode::kStringLessThanOrEqual:
return ReduceStringComparison(node);
case IrOpcode::kStringLength:
return ReduceStringLength(node);
case IrOpcode::kSameValue:
return ReduceSameValue(node);
case IrOpcode::kSelect:
......@@ -454,6 +456,30 @@ Reduction TypedOptimization::ReduceStringComparison(Node* node) {
return NoChange();
}
Reduction TypedOptimization::ReduceStringLength(Node* node) {
DCHECK_EQ(IrOpcode::kStringLength, node->opcode());
Node* const input = NodeProperties::GetValueInput(node, 0);
switch (input->opcode()) {
case IrOpcode::kHeapConstant: {
// Constant-fold the String::length of the {input}.
HeapObjectMatcher m(input);
if (m.Ref(js_heap_broker()).IsString()) {
uint32_t const length = m.Ref(js_heap_broker()).AsString().length();
Node* value = jsgraph()->Constant(length);
return Replace(value);
}
break;
}
case IrOpcode::kStringConcat: {
// The first value input to the {input} is the resulting length.
return Replace(input->InputAt(0));
}
default:
break;
}
return NoChange();
}
Reduction TypedOptimization::ReduceSameValue(Node* node) {
DCHECK_EQ(IrOpcode::kSameValue, node->opcode());
Node* const lhs = NodeProperties::GetValueInput(node, 0);
......
......@@ -50,6 +50,7 @@ class V8_EXPORT_PRIVATE TypedOptimization final
Reduction ReducePhi(Node* node);
Reduction ReduceReferenceEqual(Node* node);
Reduction ReduceStringComparison(Node* node);
Reduction ReduceStringLength(Node* node);
Reduction ReduceSameValue(Node* node);
Reduction ReduceSelect(Node* node);
Reduction ReduceSpeculativeToNumber(Node* node);
......
......@@ -1864,6 +1864,8 @@ Type Typer::Visitor::TypeSpeculativeNumberLessThanOrEqual(Node* node) {
return TypeBinaryOp(node, NumberLessThanOrEqualTyper);
}
Type Typer::Visitor::TypeStringConcat(Node* node) { return Type::String(); }
Type Typer::Visitor::TypeStringToNumber(Node* node) {
return TypeUnaryOp(node, ToNumber);
}
......@@ -2000,8 +2002,6 @@ 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));
}
......
......@@ -1094,6 +1094,12 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::PlainPrimitive());
CheckTypeIs(node, Type::Number());
break;
case IrOpcode::kStringConcat:
CheckValueInputIs(node, 0, TypeCache::Get().kStringLengthType);
CheckValueInputIs(node, 1, Type::String());
CheckValueInputIs(node, 2, Type::String());
CheckTypeIs(node, Type::String());
break;
case IrOpcode::kStringEqual:
case IrOpcode::kStringLessThan:
case IrOpcode::kStringLessThanOrEqual:
......@@ -1434,11 +1440,6 @@ 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());
......
......@@ -401,7 +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(), IsCheckStringAdd(lhs, rhs));
EXPECT_THAT(r.replacement(), IsStringConcat(_, lhs, rhs));
}
} // namespace compiler
......
......@@ -1401,6 +1401,43 @@ class IsBinopMatcher final : public TestNodeMatcher {
const Matcher<Node*> rhs_matcher_;
};
class IsStringConcatMatcher final : public TestNodeMatcher {
public:
IsStringConcatMatcher(const Matcher<Node*>& length_matcher,
const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher)
: TestNodeMatcher(IrOpcode::kStringConcat),
length_matcher_(length_matcher),
lhs_matcher_(lhs_matcher),
rhs_matcher_(rhs_matcher) {}
void DescribeTo(std::ostream* os) const final {
TestNodeMatcher::DescribeTo(os);
*os << " whose length (";
length_matcher_.DescribeTo(os);
*os << ") and lhs (";
lhs_matcher_.DescribeTo(os);
*os << ") and rhs (";
rhs_matcher_.DescribeTo(os);
*os << ")";
}
bool MatchAndExplain(Node* node, MatchResultListener* listener) const final {
return (TestNodeMatcher::MatchAndExplain(node, listener) &&
PrintMatchAndExplain(NodeProperties::GetValueInput(node, 0),
"length", length_matcher_, listener) &&
PrintMatchAndExplain(NodeProperties::GetValueInput(node, 1), "lhs",
lhs_matcher_, listener) &&
PrintMatchAndExplain(NodeProperties::GetValueInput(node, 2), "rhs",
rhs_matcher_, listener));
}
private:
const Matcher<Node*> length_matcher_;
const Matcher<Node*> lhs_matcher_;
const Matcher<Node*> rhs_matcher_;
};
class IsUnopMatcher final : public TestNodeMatcher {
public:
IsUnopMatcher(IrOpcode::Value opcode, const Matcher<Node*>& input_matcher)
......@@ -1913,6 +1950,13 @@ Matcher<Node*> IsTailCall(
SPECULATIVE_BINOPS(DEFINE_SPECULATIVE_BINOP_MATCHER);
#undef DEFINE_SPECULATIVE_BINOP_MATCHER
Matcher<Node*> IsStringConcat(const Matcher<Node*>& length_matcher,
const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher) {
return MakeMatcher(
new IsStringConcatMatcher(length_matcher, lhs_matcher, rhs_matcher));
}
Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& control_matcher) {
......@@ -2126,7 +2170,6 @@ IS_BINOP_MATCHER(Float64Sub)
IS_BINOP_MATCHER(Float64Mul)
IS_BINOP_MATCHER(Float64InsertLowWord32)
IS_BINOP_MATCHER(Float64InsertHighWord32)
IS_BINOP_MATCHER(CheckStringAdd)
#undef IS_BINOP_MATCHER
......
......@@ -272,6 +272,9 @@ Matcher<Node*> IsNumberSqrt(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberTan(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberTanh(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsNumberTrunc(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsStringConcat(const Matcher<Node*>& length_matcher,
const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
Matcher<Node*> IsStringFromSingleCharCode(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsStringLength(const Matcher<Node*>& value_matcher);
Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
......@@ -496,9 +499,6 @@ 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