[turbofan] Introduce a dedicated StringLength operator.
Strings are immutable in JavaScript land (contrast with the runtime, where we can truncate strings that haven't escaped to JavaScript yet), so the length of a String is immutable. Thus loading the length of a String is a pure operation and should be expressed as such (i.e. doesn't depend on control or effect). The StringLength operator does exactly this and is hooked up to the effect chain in the EffectControlLinearizer. This will eventually allow us to simplify the optimization of string concatention and other operations that are a bit cumbersome in TurboFan currently, and it will also allow us to optimize string operations across effectful operations, for example combining multiple invocations to String#slice with the same inputs. Bug: v8:5269, v8:6936, v8:7109, v8:7137 Change-Id: Iffcccbb0c7fc4cfe1281c10e7af24b40eba4c987 Reviewed-on: https://chromium-review.googlesource.com/799690 Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Cr-Commit-Position: refs/heads/master@{#49731}
This commit is contained in:
parent
b43b11f98d
commit
500d7b9315
@ -860,6 +860,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
|
||||
case IrOpcode::kStringIndexOf:
|
||||
result = LowerStringIndexOf(node);
|
||||
break;
|
||||
case IrOpcode::kStringLength:
|
||||
result = LowerStringLength(node);
|
||||
break;
|
||||
case IrOpcode::kStringToNumber:
|
||||
result = LowerStringToNumber(node);
|
||||
break;
|
||||
@ -2843,6 +2846,12 @@ Node* EffectControlLinearizer::LowerStringIndexOf(Node* node) {
|
||||
position, __ NoContextConstant());
|
||||
}
|
||||
|
||||
Node* EffectControlLinearizer::LowerStringLength(Node* node) {
|
||||
Node* subject = node->InputAt(0);
|
||||
|
||||
return __ LoadField(AccessBuilder::ForStringLength(), subject);
|
||||
}
|
||||
|
||||
Node* EffectControlLinearizer::LowerStringComparison(Callable const& callable,
|
||||
Node* node) {
|
||||
Node* lhs = node->InputAt(0);
|
||||
|
@ -117,6 +117,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
|
||||
Node* LowerStringFromCharCode(Node* node);
|
||||
Node* LowerStringFromCodePoint(Node* node);
|
||||
Node* LowerStringIndexOf(Node* node);
|
||||
Node* LowerStringLength(Node* node);
|
||||
Node* LowerStringEqual(Node* node);
|
||||
Node* LowerStringLessThan(Node* node);
|
||||
Node* LowerStringLessThanOrEqual(Node* node);
|
||||
|
@ -2391,9 +2391,8 @@ Reduction JSBuiltinReducer::ReduceStringCharAt(Node* node) {
|
||||
}
|
||||
|
||||
// Determine the {receiver} length.
|
||||
Node* receiver_length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||
effect, control);
|
||||
Node* receiver_length =
|
||||
graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
|
||||
// Check if {index} is less than {receiver} length.
|
||||
Node* check = graph()->NewNode(simplified()->NumberLessThan(), index,
|
||||
@ -2445,9 +2444,8 @@ Reduction JSBuiltinReducer::ReduceStringCharCodeAt(Node* node) {
|
||||
}
|
||||
|
||||
// Determine the {receiver} length.
|
||||
Node* receiver_length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||
effect, control);
|
||||
Node* receiver_length =
|
||||
graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
|
||||
// Check if {index} is less than {receiver} length.
|
||||
Node* check = graph()->NewNode(simplified()->NumberLessThan(), index,
|
||||
@ -2577,9 +2575,7 @@ Reduction JSBuiltinReducer::ReduceStringIteratorNext(Node* node) {
|
||||
Node* index = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSStringIteratorIndex()),
|
||||
receiver, effect, control);
|
||||
Node* length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), string,
|
||||
effect, control);
|
||||
Node* length = graph()->NewNode(simplified()->StringLength(), string);
|
||||
|
||||
// branch0: if (index < length)
|
||||
Node* check0 =
|
||||
@ -2670,9 +2666,8 @@ Reduction JSBuiltinReducer::ReduceStringIteratorNext(Node* node) {
|
||||
simplified()->StringFromCodePoint(UnicodeEncoding::UTF16), vtrue0);
|
||||
|
||||
// Update iterator.[[NextIndex]]
|
||||
Node* char_length = etrue0 = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), vtrue0,
|
||||
etrue0, if_true0);
|
||||
Node* char_length =
|
||||
graph()->NewNode(simplified()->StringLength(), vtrue0);
|
||||
index = graph()->NewNode(simplified()->NumberAdd(), index, char_length);
|
||||
etrue0 = graph()->NewNode(
|
||||
simplified()->StoreField(AccessBuilder::ForJSStringIteratorIndex()),
|
||||
@ -2721,9 +2716,8 @@ Reduction JSBuiltinReducer::ReduceStringSlice(Node* node) {
|
||||
|
||||
if (start_type->Is(type_cache_.kSingletonMinusOne) &&
|
||||
end_type->Is(Type::Undefined())) {
|
||||
Node* receiver_length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||
effect, control);
|
||||
Node* receiver_length =
|
||||
graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
|
||||
Node* check =
|
||||
graph()->NewNode(simplified()->NumberEqual(), receiver_length,
|
||||
|
@ -1065,9 +1065,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess(
|
||||
effect, control);
|
||||
|
||||
// Determine the {receiver} length.
|
||||
Node* length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||
effect, control);
|
||||
Node* length = graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
|
||||
// Load the single character string from {receiver} or yield undefined
|
||||
// if the {index} is out of bounds (depending on the {load_mode}).
|
||||
|
@ -648,8 +648,8 @@ Reduction JSTypedLowering::ReduceCreateConsString(Node* node) {
|
||||
}
|
||||
|
||||
// Determine the {first} length.
|
||||
Node* first_length = BuildGetStringLength(first, &effect, control);
|
||||
Node* second_length = BuildGetStringLength(second, &effect, control);
|
||||
Node* first_length = BuildGetStringLength(first);
|
||||
Node* second_length = BuildGetStringLength(second);
|
||||
|
||||
// Compute the resulting length.
|
||||
Node* length =
|
||||
@ -708,15 +708,14 @@ Reduction JSTypedLowering::ReduceCreateConsString(Node* node) {
|
||||
return Replace(value);
|
||||
}
|
||||
|
||||
Node* JSTypedLowering::BuildGetStringLength(Node* value, Node** effect,
|
||||
Node* control) {
|
||||
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);
|
||||
Node* length =
|
||||
(m.HasValue() && m.Value()->IsString())
|
||||
? jsgraph()->Constant(Handle<String>::cast(m.Value())->length())
|
||||
: (*effect) = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()),
|
||||
value, *effect, control);
|
||||
: graph()->NewNode(simplified()->StringLength(), value);
|
||||
return length;
|
||||
}
|
||||
|
||||
@ -1118,16 +1117,12 @@ Reduction JSTypedLowering::ReduceJSLoadNamed(Node* node) {
|
||||
DCHECK_EQ(IrOpcode::kJSLoadNamed, node->opcode());
|
||||
Node* receiver = NodeProperties::GetValueInput(node, 0);
|
||||
Type* receiver_type = NodeProperties::GetType(receiver);
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
Node* control = NodeProperties::GetControlInput(node);
|
||||
Handle<Name> name = NamedAccessOf(node->op()).name();
|
||||
// Optimize "length" property of strings.
|
||||
if (name.is_identical_to(factory()->length_string()) &&
|
||||
receiver_type->Is(Type::String())) {
|
||||
Node* value = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForStringLength()), receiver,
|
||||
effect, control);
|
||||
ReplaceWithValue(node, value, effect);
|
||||
Node* value = graph()->NewNode(simplified()->StringLength(), receiver);
|
||||
ReplaceWithValue(node, value);
|
||||
return Replace(value);
|
||||
}
|
||||
return NoChange();
|
||||
|
@ -85,8 +85,8 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
|
||||
// Helper for ReduceJSLoadModule and ReduceJSStoreModule.
|
||||
Node* BuildGetModuleCell(Node* node);
|
||||
|
||||
// Helpers for ReduceJSCreateConsString and ReduceJSStringConcat.
|
||||
Node* BuildGetStringLength(Node* value, Node** effect, Node* control);
|
||||
// Helpers for ReduceJSCreateConsString.
|
||||
Node* BuildGetStringLength(Node* value);
|
||||
|
||||
Factory* factory() const;
|
||||
Graph* graph() const;
|
||||
|
@ -335,6 +335,7 @@
|
||||
V(StringFromCharCode) \
|
||||
V(StringFromCodePoint) \
|
||||
V(StringIndexOf) \
|
||||
V(StringLength) \
|
||||
V(StringToLowerCaseIntl) \
|
||||
V(StringToUpperCaseIntl) \
|
||||
V(CheckBounds) \
|
||||
|
@ -2415,6 +2415,14 @@ class RepresentationSelector {
|
||||
SetOutput(node, MachineRepresentation::kTaggedSigned);
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kStringLength: {
|
||||
// TODO(bmeurer): The input representation should be TaggedPointer.
|
||||
// Fix this once we have a dedicated StringConcat/JSStringAdd
|
||||
// operator, which marks it's output as TaggedPointer properly.
|
||||
VisitUnop(node, UseInfo::AnyTagged(),
|
||||
MachineRepresentation::kTaggedSigned);
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kStringToLowerCaseIntl:
|
||||
case IrOpcode::kStringToUpperCaseIntl: {
|
||||
VisitUnop(node, UseInfo::AnyTagged(),
|
||||
|
@ -590,6 +590,7 @@ DeoptimizeReason DeoptimizeReasonOf(const Operator* op) {
|
||||
V(SeqStringCharCodeAt, Operator::kNoProperties, 2, 1) \
|
||||
V(StringFromCharCode, Operator::kNoProperties, 1, 0) \
|
||||
V(StringIndexOf, Operator::kNoProperties, 3, 0) \
|
||||
V(StringLength, Operator::kNoProperties, 1, 0) \
|
||||
V(StringToLowerCaseIntl, Operator::kNoProperties, 1, 0) \
|
||||
V(StringToUpperCaseIntl, Operator::kNoProperties, 1, 0) \
|
||||
V(TypeOf, Operator::kNoProperties, 1, 1) \
|
||||
|
@ -405,6 +405,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
|
||||
const Operator* StringFromCharCode();
|
||||
const Operator* StringFromCodePoint(UnicodeEncoding encoding);
|
||||
const Operator* StringIndexOf();
|
||||
const Operator* StringLength();
|
||||
const Operator* StringToLowerCaseIntl();
|
||||
const Operator* StringToUpperCaseIntl();
|
||||
|
||||
|
@ -1978,6 +1978,10 @@ Type* Typer::Visitor::TypeStringFromCodePoint(Node* node) {
|
||||
|
||||
Type* Typer::Visitor::TypeStringIndexOf(Node* node) { UNREACHABLE(); }
|
||||
|
||||
Type* Typer::Visitor::TypeStringLength(Node* node) {
|
||||
return typer_->cache_.kStringLengthType;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeCheckBounds(Node* node) {
|
||||
Type* index = Operand(node, 0);
|
||||
Type* length = Operand(node, 1);
|
||||
|
@ -1058,6 +1058,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
|
||||
CheckValueInputIs(node, 2, Type::SignedSmall());
|
||||
CheckTypeIs(node, Type::SignedSmall());
|
||||
break;
|
||||
case IrOpcode::kStringLength:
|
||||
CheckValueInputIs(node, 0, Type::String());
|
||||
CheckTypeIs(node, TypeCache::Get().kStringLengthType);
|
||||
break;
|
||||
case IrOpcode::kStringToLowerCaseIntl:
|
||||
case IrOpcode::kStringToUpperCaseIntl:
|
||||
CheckValueInputIs(node, 0, Type::String());
|
||||
|
@ -382,8 +382,7 @@ TEST_F(JSTypedLoweringTest, JSLoadNamedStringLength) {
|
||||
Reduce(graph()->NewNode(javascript()->LoadNamed(name, feedback), receiver,
|
||||
context, EmptyFrameState(), effect, control));
|
||||
ASSERT_TRUE(r.Changed());
|
||||
EXPECT_THAT(r.replacement(), IsLoadField(AccessBuilder::ForStringLength(),
|
||||
receiver, effect, control));
|
||||
EXPECT_THAT(r.replacement(), IsStringLength(receiver));
|
||||
}
|
||||
|
||||
|
||||
|
@ -2182,6 +2182,7 @@ IS_UNOP_MATCHER(ObjectIsReceiver)
|
||||
IS_UNOP_MATCHER(ObjectIsSmi)
|
||||
IS_UNOP_MATCHER(ObjectIsUndetectable)
|
||||
IS_UNOP_MATCHER(StringFromCharCode)
|
||||
IS_UNOP_MATCHER(StringLength)
|
||||
IS_UNOP_MATCHER(Word32Clz)
|
||||
IS_UNOP_MATCHER(Word32Ctz)
|
||||
IS_UNOP_MATCHER(Word32Popcnt)
|
||||
|
@ -272,6 +272,7 @@ 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*> IsStringFromCharCode(const Matcher<Node*>& value_matcher);
|
||||
Matcher<Node*> IsStringLength(const Matcher<Node*>& value_matcher);
|
||||
Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
|
||||
const Matcher<Node*>& effect_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
|
Loading…
Reference in New Issue
Block a user