Commit bcb5d452 authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbofan] Introduce StringSubstring operator

Adding the StringSubstring simplified operator is a precursor to
improve inlining of String.p.{substr,substring,slice}.
This also contains a drive-by renaming to normalize different
spellings of 'Substring'.

Bug: v8:7250, v8:7340
Change-Id: I89e0fbafeab80f5d2f3ef348a5303d32c0abfe0a
Reviewed-on: https://chromium-review.googlesource.com/919084
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51522}
parent b47bf130
......@@ -101,7 +101,7 @@ namespace internal {
TFC(StringLessThan, Compare, 1) \
TFC(StringLessThanOrEqual, Compare, 1) \
TFS(StringRepeat, kString, kCount) \
TFS(SubString, kString, kFrom, kTo) \
TFS(StringSubstring, kString, kFrom, kTo) \
\
/* OrderedHashTable helpers */ \
TFS(OrderedHashTableHealIndex, kTable, kIndex) \
......
......@@ -1144,7 +1144,7 @@ compiler::Node* StringBuiltinsAssembler::GetSubstitution(
CSA_ASSERT(this, TaggedIsPositiveSmi(dollar_index));
Node* const matched =
CallBuiltin(Builtins::kSubString, context, subject_string,
CallBuiltin(Builtins::kStringSubstring, context, subject_string,
match_start_index, match_end_index);
Node* const replacement_string =
CallRuntime(Runtime::kGetSubstitution, context, matched, subject_string,
......@@ -1382,8 +1382,8 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
GotoIf(SmiEqual(match_start_index, smi_zero), &next);
Node* const prefix =
CallBuiltin(Builtins::kSubString, context, subject_string, smi_zero,
match_start_index);
CallBuiltin(Builtins::kStringSubstring, context, subject_string,
smi_zero, match_start_index);
var_result.Bind(prefix);
Goto(&next);
......@@ -1423,7 +1423,7 @@ TF_BUILTIN(StringPrototypeReplace, StringBuiltinsAssembler) {
BIND(&out);
{
Node* const suffix =
CallBuiltin(Builtins::kSubString, context, subject_string,
CallBuiltin(Builtins::kStringSubstring, context, subject_string,
match_end_index, subject_length);
Node* const result =
CallStub(stringadd_callable, context, var_result.value(), suffix);
......@@ -1604,7 +1604,7 @@ class StringPadAssembler : public StringBuiltinsAssembler {
GotoIfNot(remaining_word32, &return_result);
{
Node* const remainder_string = CallBuiltin(
Builtins::kSubString, context, var_fill_string.value(),
Builtins::kStringSubstring, context, var_fill_string.value(),
SmiConstant(0), SmiFromInt32(remaining_word32));
var_pad.Bind(CallStub(stringadd_callable, context, var_pad.value(),
remainder_string));
......@@ -1941,7 +1941,7 @@ TNode<Smi> StringBuiltinsAssembler::ToSmiBetweenZeroAnd(
return var_result.value();
}
TF_BUILTIN(SubString, CodeStubAssembler) {
TF_BUILTIN(StringSubstring, CodeStubAssembler) {
TNode<String> string = CAST(Parameter(Descriptor::kString));
Node* from = Parameter(Descriptor::kFrom);
Node* to = Parameter(Descriptor::kTo);
......
......@@ -5120,8 +5120,8 @@ TNode<String> CodeStubAssembler::SubString(TNode<String> string,
// Fall back to a runtime call.
BIND(&runtime);
{
var_result = CAST(CallRuntime(Runtime::kSubString, NoContextConstant(),
string, from, to));
var_result = CAST(CallRuntime(Runtime::kStringSubstring,
NoContextConstant(), string, from, to));
Goto(&end);
}
......
......@@ -863,6 +863,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kStringToUpperCaseIntl:
result = LowerStringToUpperCaseIntl(node);
break;
case IrOpcode::kStringSubstring:
result = LowerStringSubstring(node);
break;
case IrOpcode::kStringEqual:
result = LowerStringEqual(node);
break;
......@@ -3191,6 +3194,21 @@ Node* EffectControlLinearizer::LowerStringComparison(Callable const& callable,
__ NoContextConstant());
}
Node* EffectControlLinearizer::LowerStringSubstring(Node* node) {
Node* receiver = node->InputAt(0);
Node* start = node->InputAt(1);
Node* end = node->InputAt(2);
Callable callable =
Builtins::CallableFor(isolate(), Builtins::kStringSubstring);
Operator::Properties properties = Operator::kEliminatable;
CallDescriptor::Flags flags = CallDescriptor::kNoFlags;
auto call_descriptor = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0, flags, properties);
return __ Call(call_descriptor, __ HeapConstant(callable.code()), receiver,
start, end, __ NoContextConstant());
}
Node* EffectControlLinearizer::LowerStringEqual(Node* node) {
return LowerStringComparison(
Builtins::CallableFor(isolate(), Builtins::kStringEqual), node);
......
......@@ -126,6 +126,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerStringFromCharCode(Node* node);
Node* LowerStringFromCodePoint(Node* node);
Node* LowerStringIndexOf(Node* node);
Node* LowerStringSubstring(Node* node);
Node* LowerStringLength(Node* node);
Node* LowerStringEqual(Node* node);
Node* LowerStringLessThan(Node* node);
......
......@@ -3325,6 +3325,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
case Builtins::kStringPrototypeCodePointAt:
return ReduceStringPrototypeStringAt(
simplified()->StringCodePointAt(UnicodeEncoding::UTF32), node);
case Builtins::kStringPrototypeSubstring:
return ReduceStringPrototypeSubstring(node);
#ifdef V8_INTL_SUPPORT
case Builtins::kStringPrototypeToLowerCaseIntl:
return ReduceStringPrototypeToLowerCaseIntl(node);
......@@ -3610,6 +3612,109 @@ Reduction JSCallReducer::ReduceJSConstruct(Node* node) {
return NoChange();
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
Reduction JSCallReducer::ReduceStringPrototypeIndexOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (node->op()->ValueInputCount() >= 3) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* new_receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), receiver, effect, control);
Node* search_string = NodeProperties::GetValueInput(node, 2);
Node* new_search_string = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()), search_string,
effect, control);
Node* new_position = jsgraph()->ZeroConstant();
if (node->op()->ValueInputCount() >= 4) {
Node* position = NodeProperties::GetValueInput(node, 3);
new_position = effect = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), position, effect, control);
}
NodeProperties::ReplaceEffectInput(node, effect);
RelaxEffectsAndControls(node);
node->ReplaceInput(0, new_receiver);
node->ReplaceInput(1, new_search_string);
node->ReplaceInput(2, new_position);
node->TrimInputCount(3);
NodeProperties::ChangeOp(node, simplified()->StringIndexOf());
return Changed(node);
}
return NoChange();
}
// #sec-string.prototype.substring
Reduction JSCallReducer::ReduceStringPrototypeSubstring(Node* node) {
if (node->op()->ValueInputCount() < 3) return NoChange();
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* start = NodeProperties::GetValueInput(node, 2);
Node* end = node->op()->ValueInputCount() > 3
? NodeProperties::GetValueInput(node, 3)
: jsgraph()->UndefinedConstant();
receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()),
receiver, effect, control);
start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start,
effect, control);
Node* length = graph()->NewNode(simplified()->StringLength(), receiver);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end,
jsgraph()->UndefinedConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue = length;
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = efalse = graph()->NewNode(simplified()->CheckSmi(p.feedback()),
end, efalse, if_false);
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
Node* finalStart =
graph()->NewNode(simplified()->NumberMin(),
graph()->NewNode(simplified()->NumberMax(), start,
jsgraph()->ZeroConstant()),
length);
Node* finalEnd =
graph()->NewNode(simplified()->NumberMin(),
graph()->NewNode(simplified()->NumberMax(), end,
jsgraph()->ZeroConstant()),
length);
Node* from =
graph()->NewNode(simplified()->NumberMin(), finalStart, finalEnd);
Node* to = graph()->NewNode(simplified()->NumberMax(), finalStart, finalEnd);
Node* value =
graph()->NewNode(simplified()->StringSubstring(), receiver, from, to);
ReplaceWithValue(node, value, effect, control);
return Replace(node);
}
Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) {
DCHECK_EQ(IrOpcode::kJSConstructWithArrayLike, node->opcode());
CallFrequency frequency = CallFrequencyOf(node->op());
......@@ -4100,50 +4205,8 @@ Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) {
return Replace(value);
}
// ES6 String.prototype.indexOf(searchString [, position])
// #sec-string.prototype.indexof
Reduction JSCallReducer::ReduceStringPrototypeIndexOf(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
CallParameters const& p = CallParametersOf(node->op());
if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) {
return NoChange();
}
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (node->op()->ValueInputCount() >= 3) {
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* new_receiver = effect = graph()->NewNode(
simplified()->CheckString(p.feedback()), receiver, effect, control);
Node* search_string = NodeProperties::GetValueInput(node, 2);
Node* new_search_string = effect =
graph()->NewNode(simplified()->CheckString(p.feedback()), search_string,
effect, control);
Node* new_position = jsgraph()->ZeroConstant();
if (node->op()->ValueInputCount() >= 4) {
Node* position = NodeProperties::GetValueInput(node, 3);
new_position = effect = graph()->NewNode(
simplified()->CheckSmi(p.feedback()), position, effect, control);
}
NodeProperties::ReplaceEffectInput(node, effect);
RelaxEffectsAndControls(node);
node->ReplaceInput(0, new_receiver);
node->ReplaceInput(1, new_search_string);
node->ReplaceInput(2, new_position);
node->TrimInputCount(3);
NodeProperties::ChangeOp(node, simplified()->StringIndexOf());
return Changed(node);
}
return NoChange();
}
// ES6 section 21.1.3.1 String.prototype.charAt ( pos )
// and
// ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos )
// and
// ES6 section 21.1.3.3 String.prototype.codePointAt ( pos )
Reduction JSCallReducer::ReduceStringPrototypeStringAt(
const Operator* string_access_operator, Node* node) {
......
......@@ -99,6 +99,7 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceJSCallWithSpread(Node* node);
Reduction ReduceReturnReceiver(Node* node);
Reduction ReduceStringPrototypeIndexOf(Node* node);
Reduction ReduceStringPrototypeSubstring(Node* node);
Reduction ReduceStringPrototypeStringAt(
const Operator* string_access_operator, Node* node);
......
......@@ -350,6 +350,7 @@
V(StringLength) \
V(StringToLowerCaseIntl) \
V(StringToUpperCaseIntl) \
V(StringSubstring) \
V(CheckBounds) \
V(CheckIf) \
V(CheckMaps) \
......
......@@ -2408,6 +2408,13 @@ class RepresentationSelector {
MachineRepresentation::kTaggedSigned);
return;
}
case IrOpcode::kStringSubstring: {
ProcessInput(node, 0, UseInfo::AnyTagged());
ProcessInput(node, 1, UseInfo::TaggedSigned());
ProcessInput(node, 2, UseInfo::TaggedSigned());
SetOutput(node, MachineRepresentation::kTaggedPointer);
return;
}
case IrOpcode::kStringToLowerCaseIntl:
case IrOpcode::kStringToUpperCaseIntl: {
VisitUnop(node, UseInfo::AnyTagged(),
......
......@@ -676,6 +676,7 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(StringLength, Operator::kNoProperties, 1, 0) \
V(StringToLowerCaseIntl, Operator::kNoProperties, 1, 0) \
V(StringToUpperCaseIntl, Operator::kNoProperties, 1, 0) \
V(StringSubstring, Operator::kNoProperties, 3, 0) \
V(TypeOf, Operator::kNoProperties, 1, 1) \
V(PlainPrimitiveToNumber, Operator::kNoProperties, 1, 0) \
V(PlainPrimitiveToWord32, Operator::kNoProperties, 1, 0) \
......
......@@ -531,6 +531,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* StringLength();
const Operator* StringToLowerCaseIntl();
const Operator* StringToUpperCaseIntl();
const Operator* StringSubstring();
const Operator* FindOrderedHashMapEntry();
const Operator* FindOrderedHashMapEntryForInt32Key();
......
......@@ -2002,6 +2002,8 @@ Type* Typer::Visitor::TypeStringLength(Node* node) {
return typer_->cache_.kStringLengthType;
}
Type* Typer::Visitor::TypeStringSubstring(Node* node) { return Type::String(); }
Type* Typer::Visitor::TypeMaskIndexWithBound(Node* node) {
return Type::Union(Operand(node, 0), typer_->cache_.kSingletonZero, zone());
}
......
......@@ -1134,7 +1134,12 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 0, Type::String());
CheckTypeIs(node, Type::String());
break;
case IrOpcode::kStringSubstring:
CheckValueInputIs(node, 0, Type::String());
CheckValueInputIs(node, 1, Type::SignedSmall());
CheckValueInputIs(node, 2, Type::SignedSmall());
CheckTypeIs(node, Type::String());
break;
case IrOpcode::kReferenceEqual:
// (Unique, Any) -> Boolean and
// (Any, Unique) -> Boolean
......
......@@ -313,7 +313,7 @@ bool IntrinsicHasNoSideEffect(Runtime::FunctionId id) {
V(StringReplaceOneCharWithString) \
V(StringToNumber) \
V(StringTrim) \
V(SubString) \
V(StringSubstring) \
V(RegExpInternalReplace) \
/* BigInts */ \
V(BigIntEqualToBigInt) \
......@@ -765,7 +765,7 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kStringPrototypeTrimStart:
case Builtins::kStringPrototypeValueOf:
case Builtins::kStringToNumber:
case Builtins::kSubString:
case Builtins::kStringSubstring:
// Symbol builtins.
case Builtins::kSymbolConstructor:
case Builtins::kSymbolKeyFor:
......
......@@ -216,7 +216,7 @@ RUNTIME_FUNCTION(Runtime_StringLastIndexOf) {
isolate->factory()->undefined_value());
}
RUNTIME_FUNCTION(Runtime_SubString) {
RUNTIME_FUNCTION(Runtime_StringSubstring) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, string, 0);
......
......@@ -526,7 +526,7 @@ namespace internal {
F(StringIndexOf, 3, 1) \
F(StringIndexOfUnchecked, 3, 1) \
F(StringLastIndexOf, 2, 1) \
F(SubString, 3, 1) \
F(StringSubstring, 3, 1) \
F(StringAdd, 2, 1) \
F(InternalizeString, 1, 1) \
F(StringCharCodeAt, 2, 1) \
......
......@@ -29,3 +29,29 @@
f("abc", {}, 1);
assertOptimized(f);
})();
(()=> {
function f(a, b, c) {
return a.substring(b, c);
}
f("abcde", 1, 4);
f("abcde", 1, 4);
%OptimizeFunctionOnNextCall(f);
f("abcde", 1, {});
%OptimizeFunctionOnNextCall(f);
f("abcde", 1, {});
assertOptimized(f);
})();
(()=> {
function f(a, b, c) {
return a.substring(b, c);
}
f("abcde", 1, 4);
f("abcde", 1, 4);
%OptimizeFunctionOnNextCall(f);
f("abcde", {}, 4);
%OptimizeFunctionOnNextCall(f);
f("abcde", {}, 4);
assertOptimized(f);
})();
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