Commit 2c296b7e authored by Ross McIlroy's avatar Ross McIlroy Committed by Commit Bot

[TurboFan] Add typing for the EmptyString and use this for JSToPrimitiveToString

Add the ability for the typer to track whether a string could be the empty
string. This is needed for typed lowering of JSStringConcat since we can't
create cons string chain with the empty string in arbitrary positions.

The ToPrimitiveToString bytecode handler is modified to collect feedback on
whether it has ever seen the empty string, which is used by
SpeculativeToPrimitiveToString to ensure that the output is non-empty (or
depot) which will subsiquently be used to enable inline cons-string creation
for the JSStringConcat operator in typed lowering in a subsiquent CL.

BUG=v8:6243

Change-Id: I41b99b59798993f756aada8cff90fb137d65ea52
Reviewed-on: https://chromium-review.googlesource.com/522122
Commit-Queue: Ross McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#45786}
parent e5fb221d
......@@ -155,7 +155,10 @@ compiler::Node* ConversionBuiltinsAssembler::ToPrimitiveToString(
BIND(&is_string);
{
if (feedback) {
feedback->Bind(SmiConstant(BinaryOperationFeedback::kString));
feedback->Bind(
SelectSmiConstant(WordEqual(input, EmptyStringConstant()),
BinaryOperationFeedback::kString,
BinaryOperationFeedback::kNonEmptyString));
}
Goto(&done);
}
......
......@@ -650,6 +650,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kCheckSeqString:
result = LowerCheckSeqString(node, frame_state);
break;
case IrOpcode::kCheckNonEmptyString:
result = LowerCheckNonEmptyString(node, frame_state);
break;
case IrOpcode::kCheckInternalizedString:
result = LowerCheckInternalizedString(node, frame_state);
break;
......@@ -1354,6 +1357,26 @@ Node* EffectControlLinearizer::LowerCheckSeqString(Node* node,
return value;
}
Node* EffectControlLinearizer::LowerCheckNonEmptyString(Node* node,
Node* frame_state) {
Node* value = node->InputAt(0);
Node* value_map = __ LoadField(AccessBuilder::ForMap(), value);
Node* value_instance_type =
__ LoadField(AccessBuilder::ForMapInstanceType(), value_map);
Node* is_string = __ Uint32LessThan(value_instance_type,
__ Uint32Constant(FIRST_NONSTRING_TYPE));
Node* is_non_empty = __ Word32Equal(
__ WordEqual(value, __ EmptyStringConstant()), __ Int32Constant(0));
Node* is_non_empty_string = __ Word32And(is_string, is_non_empty);
__ DeoptimizeUnless(DeoptimizeReason::kWrongInstanceType, is_non_empty_string,
frame_state);
return value;
}
Node* EffectControlLinearizer::LowerCheckInternalizedString(Node* node,
Node* frame_state) {
Node* value = node->InputAt(0);
......
......@@ -59,6 +59,7 @@ class V8_EXPORT_PRIVATE EffectControlLinearizer {
Node* LowerCheckReceiver(Node* node, Node* frame_state);
Node* LowerCheckString(Node* node, Node* frame_state);
Node* LowerCheckSeqString(Node* node, Node* frame_state);
Node* LowerCheckNonEmptyString(Node* node, Node* frame_state);
Node* LowerCheckSymbol(Node* node, Node* frame_state);
Node* LowerCheckIf(Node* node, Node* frame_state);
Node* LowerCheckedInt32Add(Node* node, Node* frame_state);
......
......@@ -2009,7 +2009,8 @@ Node* GetStringWitness(Node* node) {
for (Node* dominator = effect;;) {
if ((dominator->opcode() == IrOpcode::kCheckString ||
dominator->opcode() == IrOpcode::kCheckInternalizedString ||
dominator->opcode() == IrOpcode::kCheckSeqString) &&
dominator->opcode() == IrOpcode::kCheckSeqString ||
dominator->opcode() == IrOpcode::kCheckNonEmptyString) &&
NodeProperties::IsSame(dominator->InputAt(0), receiver)) {
return dominator;
}
......
......@@ -670,6 +670,8 @@ struct JSOperatorGlobalCache final {
Name##Operator<BinaryOperationHint::kNumber> k##Name##NumberOperator; \
Name##Operator<BinaryOperationHint::kNumberOrOddball> \
k##Name##NumberOrOddballOperator; \
Name##Operator<BinaryOperationHint::kNonEmptyString> \
k##Name##NonEmptyStringOperator; \
Name##Operator<BinaryOperationHint::kString> k##Name##StringOperator; \
Name##Operator<BinaryOperationHint::kAny> k##Name##AnyOperator;
BINARY_OP_LIST(BINARY_OP)
......@@ -725,6 +727,8 @@ CACHED_OP_LIST(CACHED_OP)
return &cache_.k##Name##NumberOperator; \
case BinaryOperationHint::kNumberOrOddball: \
return &cache_.k##Name##NumberOrOddballOperator; \
case BinaryOperationHint::kNonEmptyString: \
return &cache_.k##Name##NonEmptyStringOperator; \
case BinaryOperationHint::kString: \
return &cache_.k##Name##StringOperator; \
case BinaryOperationHint::kAny: \
......
......@@ -33,6 +33,7 @@ bool BinaryOperationHintToNumberOperationHint(
return true;
case BinaryOperationHint::kAny:
case BinaryOperationHint::kNone:
case BinaryOperationHint::kNonEmptyString:
case BinaryOperationHint::kString:
break;
}
......@@ -261,7 +262,11 @@ Reduction JSTypeHintLowering::ReduceToPrimitiveToStringOperation(
DCHECK(!slot.IsInvalid());
BinaryOpICNexus nexus(feedback_vector(), slot);
BinaryOperationHint hint = nexus.GetBinaryOperationFeedback();
if (hint == BinaryOperationHint::kString) {
if (hint == BinaryOperationHint::kNonEmptyString) {
Node* node = jsgraph()->graph()->NewNode(
jsgraph()->simplified()->CheckNonEmptyString(), input, effect, control);
return Reduction(node);
} else if (hint == BinaryOperationHint::kString) {
Node* node = jsgraph()->graph()->NewNode(
jsgraph()->simplified()->CheckString(), input, effect, control);
return Reduction(node);
......
......@@ -515,11 +515,9 @@ JSTypedLowering::JSTypedLowering(Editor* editor,
dependencies_(dependencies),
flags_(flags),
jsgraph_(jsgraph),
empty_string_type_(
Type::HeapConstant(factory()->empty_string(), graph()->zone())),
pointer_comparable_type_(
Type::Union(Type::Oddball(),
Type::Union(Type::SymbolOrReceiver(), empty_string_type_,
Type::Union(Type::SymbolOrReceiver(), Type::EmptyString(),
graph()->zone()),
graph()->zone())),
type_cache_(TypeCache::Get()) {
......@@ -568,12 +566,12 @@ Reduction JSTypedLowering::ReduceJSAdd(Node* node) {
BinaryOperationHintOf(node->op()) == BinaryOperationHint::kString) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (r.LeftInputIs(empty_string_type_)) {
if (r.LeftInputIs(Type::EmptyString())) {
Node* value = effect = graph()->NewNode(simplified()->CheckString(),
r.right(), effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
} else if (r.RightInputIs(empty_string_type_)) {
} else if (r.RightInputIs(Type::EmptyString())) {
Node* value = effect = graph()->NewNode(simplified()->CheckString(),
r.left(), effect, control);
ReplaceWithValue(node, value, effect, control);
......
......@@ -106,7 +106,6 @@ class V8_EXPORT_PRIVATE JSTypedLowering final
CompilationDependencies* dependencies_;
Flags flags_;
JSGraph* jsgraph_;
Type* empty_string_type_;
Type* shifted_int32_ranges_[4];
Type* pointer_comparable_type_;
TypeCache const& type_cache_;
......
......@@ -325,6 +325,7 @@
V(CheckReceiver) \
V(CheckString) \
V(CheckSeqString) \
V(CheckNonEmptyString) \
V(CheckSymbol) \
V(CheckSmi) \
V(CheckHeapObject) \
......
......@@ -28,6 +28,7 @@ Reduction RedundancyElimination::Reduce(Node* node) {
case IrOpcode::kCheckSmi:
case IrOpcode::kCheckString:
case IrOpcode::kCheckSeqString:
case IrOpcode::kCheckNonEmptyString:
case IrOpcode::kCheckNotTaggedHole:
case IrOpcode::kCheckedFloat64ToInt32:
case IrOpcode::kCheckedInt32Add:
......@@ -124,9 +125,11 @@ namespace {
bool IsCompatibleCheck(Node const* a, Node const* b) {
if (a->op() != b->op()) {
if (a->opcode() == IrOpcode::kCheckInternalizedString &&
b->opcode() == IrOpcode::kCheckString) {
// CheckInternalizedString(node) implies CheckString(node)
if (b->opcode() == IrOpcode::kCheckString &&
(a->opcode() == IrOpcode::kCheckInternalizedString ||
a->opcode() == IrOpcode::kCheckSeqString ||
a->opcode() == IrOpcode::kCheckNonEmptyString)) {
// Check[Internalized,Seq,NonEmpty]String(node) implies CheckString(node)
} else {
return false;
}
......
......@@ -2446,22 +2446,18 @@ class RepresentationSelector {
VisitCheck(node, Type::String(), lowering);
return;
}
case IrOpcode::kCheckSymbol: {
VisitCheck(node, Type::Symbol(), lowering);
case IrOpcode::kCheckSeqString: {
VisitCheck(node, Type::SeqString(), lowering);
return;
}
case IrOpcode::kCheckSeqString: {
if (InputIs(node, Type::SeqString())) {
VisitUnop(node, UseInfo::AnyTagged(),
MachineRepresentation::kTaggedPointer);
if (lower()) DeferReplacement(node, node->InputAt(0));
} else {
VisitUnop(node, UseInfo::CheckedHeapObjectAsTaggedPointer(),
MachineRepresentation::kTaggedPointer);
}
case IrOpcode::kCheckNonEmptyString: {
VisitCheck(node, Type::NonEmptyString(), lowering);
return;
}
case IrOpcode::kCheckSymbol: {
VisitCheck(node, Type::Symbol(), lowering);
return;
}
case IrOpcode::kAllocate: {
ProcessInput(node, 0, UseInfo::TruncatingWord32());
ProcessRemainingInputs(node, 1);
......
......@@ -525,6 +525,7 @@ UnicodeEncoding UnicodeEncodingOf(const Operator* op) {
V(CheckSmi, 1, 1) \
V(CheckString, 1, 1) \
V(CheckSeqString, 1, 1) \
V(CheckNonEmptyString, 1, 1) \
V(CheckSymbol, 1, 1) \
V(CheckNotTaggedHole, 1, 1) \
V(CheckedInt32Add, 2, 1) \
......
......@@ -416,6 +416,7 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* CheckSmi();
const Operator* CheckString();
const Operator* CheckSeqString();
const Operator* CheckNonEmptyString();
const Operator* CheckSymbol();
const Operator* CheckReceiver();
......
......@@ -86,6 +86,8 @@ Reduction TypedOptimization::Reduce(Node* node) {
return ReduceCheckString(node);
case IrOpcode::kCheckSeqString:
return ReduceCheckSeqString(node);
case IrOpcode::kCheckNonEmptyString:
return ReduceCheckNonEmptyString(node);
case IrOpcode::kLoadField:
return ReduceLoadField(node);
case IrOpcode::kNumberCeil:
......@@ -198,6 +200,16 @@ Reduction TypedOptimization::ReduceCheckSeqString(Node* node) {
return NoChange();
}
Reduction TypedOptimization::ReduceCheckNonEmptyString(Node* node) {
Node* const input = NodeProperties::GetValueInput(node, 0);
Type* const input_type = NodeProperties::GetType(input);
if (input_type->Is(Type::NonEmptyString())) {
ReplaceWithValue(node, input);
return Replace(input);
}
return NoChange();
}
Reduction TypedOptimization::ReduceLoadField(Node* node) {
Node* const object = NodeProperties::GetValueInput(node, 0);
Type* const object_type = NodeProperties::GetType(object);
......
......@@ -47,6 +47,7 @@ class V8_EXPORT_PRIVATE TypedOptimization final
Reduction ReduceCheckNumber(Node* node);
Reduction ReduceCheckString(Node* node);
Reduction ReduceCheckSeqString(Node* node);
Reduction ReduceCheckNonEmptyString(Node* node);
Reduction ReduceLoadField(Node* node);
Reduction ReduceNumberFloor(Node* node);
Reduction ReduceNumberRoundop(Node* node);
......
......@@ -43,7 +43,7 @@ Typer::Typer(Isolate* isolate, Flags flags, Graph* graph)
Zone* zone = this->zone();
Factory* const factory = isolate->factory();
singleton_empty_string_ = Type::HeapConstant(factory->empty_string(), zone);
singleton_empty_string_ = Type::NewConstant(factory->empty_string(), zone);
singleton_false_ = operation_typer_.singleton_false();
singleton_true_ = operation_typer_.singleton_true();
falsish_ = Type::Union(
......@@ -1862,6 +1862,11 @@ Type* Typer::Visitor::TypeCheckSeqString(Node* node) {
return Type::Intersect(arg, Type::SeqString(), zone());
}
Type* Typer::Visitor::TypeCheckNonEmptyString(Node* node) {
Type* arg = Operand(node, 0);
return Type::Intersect(arg, Type::NonEmptyString(), zone());
}
Type* Typer::Visitor::TypeCheckSymbol(Node* node) {
Type* arg = Operand(node, 0);
return Type::Intersect(arg, Type::Symbol(), zone());
......
......@@ -464,6 +464,8 @@ HeapConstantType::HeapConstantType(BitsetType::bitset bitset,
: TypeBase(kHeapConstant), bitset_(bitset), object_(object) {
DCHECK(!object->IsHeapNumber());
DCHECK_IMPLIES(object->IsString(), object->IsInternalizedString());
DCHECK_IMPLIES(object->IsString(),
i::Handle<i::String>::cast(object)->length() != 0);
}
// -----------------------------------------------------------------------------
......@@ -838,8 +840,13 @@ Type* Type::NewConstant(i::Handle<i::Object> value, Zone* zone) {
return Range(v, v, zone);
} else if (value->IsHeapNumber()) {
return NewConstant(value->Number(), zone);
} else if (value->IsString() && !value->IsInternalizedString()) {
return Type::OtherString();
} else if (value->IsString()) {
i::Isolate* isolate = i::Handle<i::HeapObject>::cast(value)->GetIsolate();
if (!value->IsInternalizedString()) {
return Type::OtherString();
} else if (*value == isolate->heap()->empty_string()) {
return Type::EmptyString();
}
}
return HeapConstant(i::Handle<i::HeapObject>::cast(value), zone);
}
......
......@@ -114,21 +114,22 @@ namespace compiler {
V(MinusZero, 1u << 10) \
V(NaN, 1u << 11) \
V(Symbol, 1u << 12) \
V(InternalizedNonSeqString, 1u << 13) \
V(InternalizedSeqString, 1u << 14) \
V(OtherNonSeqString, 1u << 15) \
V(OtherSeqString, 1u << 16) \
V(OtherCallable, 1u << 17) \
V(OtherObject, 1u << 18) \
V(OtherUndetectable, 1u << 19) \
V(CallableProxy, 1u << 20) \
V(OtherProxy, 1u << 21) \
V(Function, 1u << 22) \
V(BoundFunction, 1u << 23) \
V(Hole, 1u << 24) \
V(OtherInternal, 1u << 25) \
V(ExternalPointer, 1u << 26) \
V(Array, 1u << 27) \
V(EmptyString, 1u << 13) \
V(InternalizedNonSeqString, 1u << 14) \
V(InternalizedSeqString, 1u << 15) \
V(OtherNonSeqString, 1u << 16) \
V(OtherSeqString, 1u << 17) \
V(OtherCallable, 1u << 18) \
V(OtherObject, 1u << 19) \
V(OtherUndetectable, 1u << 20) \
V(CallableProxy, 1u << 21) \
V(OtherProxy, 1u << 22) \
V(Function, 1u << 23) \
V(BoundFunction, 1u << 24) \
V(Hole, 1u << 25) \
V(OtherInternal, 1u << 26) \
V(ExternalPointer, 1u << 27) \
V(Array, 1u << 28) \
\
V(Signed31, kUnsigned30 | kNegative31) \
V(Signed32, kSigned31 | kOtherUnsigned31 | \
......@@ -148,13 +149,17 @@ namespace compiler {
V(OrderedNumber, kPlainNumber | kMinusZero) \
V(MinusZeroOrNaN, kMinusZero | kNaN) \
V(Number, kOrderedNumber | kNaN) \
V(InternalizedString, kInternalizedNonSeqString | \
V(NonEmptyInternalizedString, kInternalizedNonSeqString | \
kInternalizedSeqString) \
V(InternalizedString, kNonEmptyInternalizedString | \
kEmptyString) \
V(OtherString, kOtherNonSeqString | kOtherSeqString) \
V(SeqString, kInternalizedSeqString | kOtherSeqString) \
V(NonEmptySeqString, kInternalizedSeqString | kOtherSeqString) \
V(SeqString, kNonEmptySeqString | kEmptyString) \
V(NonSeqString, kInternalizedNonSeqString | \
kOtherNonSeqString) \
V(String, kInternalizedString | kOtherString) \
V(NonEmptyString, kNonEmptyInternalizedString | kOtherString) \
V(String, kNonEmptyString | kEmptyString) \
V(UniqueName, kSymbol | kInternalizedString) \
V(Name, kSymbol | kString) \
V(InternalizedStringOrNull, kInternalizedString | kNull) \
......
......@@ -1199,6 +1199,10 @@ void Verifier::Visitor::Check(Node* node) {
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::SeqString());
break;
case IrOpcode::kCheckNonEmptyString:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::NonEmptyString());
break;
case IrOpcode::kCheckSymbol:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::Symbol());
......
......@@ -150,6 +150,8 @@ BinaryOperationHint BinaryOperationHintFromFeedback(int type_feedback) {
return BinaryOperationHint::kNumber;
case BinaryOperationFeedback::kNumberOrOddball:
return BinaryOperationHint::kNumberOrOddball;
case BinaryOperationFeedback::kNonEmptyString:
return BinaryOperationHint::kNonEmptyString;
case BinaryOperationFeedback::kString:
return BinaryOperationHint::kString;
case BinaryOperationFeedback::kAny:
......
......@@ -1241,7 +1241,7 @@ inline uint32_t ObjectHash(Address address) {
// at different points by performing an 'OR' operation. Type feedback moves
// to a more generic type when we combine feedback.
// kSignedSmall -> kNumber -> kNumberOrOddball -> kAny
// kString -> kAny
// kNonEmptyString -> kString -> kAny
// TODO(mythria): Remove kNumber type when crankshaft can handle Oddballs
// similar to Numbers. We don't need kNumber feedback for Turbofan. Extra
// information about Number might reduce few instructions but causes more
......@@ -1254,8 +1254,9 @@ class BinaryOperationFeedback {
kSignedSmall = 0x1,
kNumber = 0x3,
kNumberOrOddball = 0x7,
kString = 0x8,
kAny = 0x1F
kNonEmptyString = 0x8,
kString = 0x18,
kAny = 0x3F
};
};
......
......@@ -19,6 +19,8 @@ std::ostream& operator<<(std::ostream& os, BinaryOperationHint hint) {
return os << "Number";
case BinaryOperationHint::kNumberOrOddball:
return os << "NumberOrOddball";
case BinaryOperationHint::kNonEmptyString:
return os << "NonEmptyString";
case BinaryOperationHint::kString:
return os << "String";
case BinaryOperationHint::kAny:
......
......@@ -18,6 +18,7 @@ enum class BinaryOperationHint : uint8_t {
kSigned32,
kNumber,
kNumberOrOddball,
kNonEmptyString,
kString,
kAny
};
......
......@@ -4937,6 +4937,9 @@ TEST(InterpreterToPrimitiveToString) {
} test_cases[] = {
{factory->NewStringFromAsciiChecked("Foo"),
factory->NewStringFromAsciiChecked("Foo"),
BinaryOperationFeedback::kNonEmptyString},
{factory->NewStringFromAsciiChecked(""),
factory->NewStringFromAsciiChecked(""),
BinaryOperationFeedback::kString},
{handle(Smi::FromInt(123), isolate),
factory->NewStringFromAsciiChecked("123"),
......
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