[compiler] Introduce StringEqualStub and StringNotEqualStub.

These new stubs perform exactly the same job as the string equality case
for the CompareIC, but are platform independent and usable outside of
fullcodegen and Crankshaft. We use them in the StrictEqualStub and the
StrictNotEqualStub instead of falling back to the runtime immediately
for String comparisons, and we also use them in TurboFan to perform
String equality or inequality comparisons.

These stubs currently handle only internalized and one byte strings w/o
going to C++, but it should be easy to add support for more string cases
later, i.e. utilizing already flattened cons strings or comparing two
byte strings as well.

Review URL: https://codereview.chromium.org/1761823002

Cr-Commit-Position: refs/heads/master@{#34459}
This commit is contained in:
bmeurer 2016-03-03 02:10:30 -08:00 committed by Commit bot
parent 0b3e436aa5
commit 2689548e38
15 changed files with 328 additions and 51 deletions

View File

@ -220,6 +220,17 @@ Callable CodeFactory::StringCompare(Isolate* isolate) {
return Callable(stub.GetCode(), stub.GetCallInterfaceDescriptor());
}
// static
Callable CodeFactory::StringEqual(Isolate* isolate) {
StringEqualStub stub(isolate);
return Callable(stub.GetCode(), stub.GetCallInterfaceDescriptor());
}
// static
Callable CodeFactory::StringNotEqual(Isolate* isolate) {
StringNotEqualStub stub(isolate);
return Callable(stub.GetCode(), stub.GetCallInterfaceDescriptor());
}
// static
Callable CodeFactory::SubString(Isolate* isolate) {

View File

@ -81,6 +81,8 @@ class CodeFactory final {
static Callable StringAdd(Isolate* isolate, StringAddFlags flags,
PretenureFlag pretenure_flag);
static Callable StringCompare(Isolate* isolate);
static Callable StringEqual(Isolate* isolate);
static Callable StringNotEqual(Isolate* isolate);
static Callable SubString(Isolate* isolate);
static Callable Typeof(Isolate* isolate);

View File

@ -7,6 +7,7 @@
#include <sstream>
#include "src/bootstrapper.h"
#include "src/code-factory.h"
#include "src/compiler/code-stub-assembler.h"
#include "src/factory.h"
#include "src/gdb-jit.h"
@ -500,12 +501,12 @@ void StringLengthStub::GenerateAssembly(
namespace {
enum StrictEqualMode { kStrictEqual, kStrictNotEqual };
enum ResultMode { kDontNegateResult, kNegateResult };
void GenerateStrictEqual(compiler::CodeStubAssembler* assembler,
StrictEqualMode mode) {
// Here's pseudo-code for the algorithm below in case of kStrictEqual mode;
// for kStrictNotEqual mode we properly negate the result.
Isolate* isolate, ResultMode mode) {
// Here's pseudo-code for the algorithm below in case of kDontNegateResult
// mode; for kNegateResult mode we properly negate the result.
//
// if (lhs == rhs) {
// if (lhs->IsHeapNumber()) return HeapNumber::cast(lhs)->value() != NaN;
@ -705,12 +706,10 @@ void GenerateStrictEqual(compiler::CodeStubAssembler* assembler,
assembler->Bind(&if_rhsisstring);
{
// TODO(bmeurer): Optimize this further once the StringEqual
// functionality is available in TurboFan land.
Runtime::FunctionId function_id = (mode == kStrictEqual)
? Runtime::kStringEqual
: Runtime::kStringNotEqual;
assembler->TailCallRuntime(function_id, context, lhs, rhs);
Callable callable = (mode == kDontNegateResult)
? CodeFactory::StringEqual(isolate)
: CodeFactory::StringNotEqual(isolate);
assembler->TailCallStub(callable, context, lhs, rhs);
}
assembler->Bind(&if_rhsisnotstring);
@ -730,7 +729,7 @@ void GenerateStrictEqual(compiler::CodeStubAssembler* assembler,
assembler->Bind(&if_lhsissimd128value);
{
// TODO(bmeurer): Inline the Simd128Value equality check.
Runtime::FunctionId function_id = (mode == kStrictEqual)
Runtime::FunctionId function_id = (mode == kDontNegateResult)
? Runtime::kStrictEqual
: Runtime::kStrictNotEqual;
assembler->TailCallRuntime(function_id, context, lhs, rhs);
@ -785,22 +784,209 @@ void GenerateStrictEqual(compiler::CodeStubAssembler* assembler,
}
assembler->Bind(&if_equal);
assembler->Return(assembler->BooleanConstant(mode == kStrictEqual));
assembler->Return(assembler->BooleanConstant(mode == kDontNegateResult));
assembler->Bind(&if_notequal);
assembler->Return(assembler->BooleanConstant(mode == kStrictNotEqual));
assembler->Return(assembler->BooleanConstant(mode == kNegateResult));
}
void GenerateStringEqual(compiler::CodeStubAssembler* assembler,
ResultMode mode) {
// Here's pseudo-code for the algorithm below in case of kDontNegateResult
// mode; for kNegateResult mode we properly negate the result.
//
// if (lhs == rhs) return true;
// if (lhs->length() != rhs->length()) return false;
// if (lhs->IsInternalizedString() && rhs->IsInternalizedString()) {
// return false;
// }
// if (lhs->IsSeqOneByteString() && rhs->IsSeqOneByteString()) {
// for (i = 0; i != lhs->length(); ++i) {
// if (lhs[i] != rhs[i]) return false;
// }
// return true;
// }
// return %StringEqual(lhs, rhs);
typedef compiler::CodeStubAssembler::Label Label;
typedef compiler::Node Node;
typedef compiler::CodeStubAssembler::Variable Variable;
Node* lhs = assembler->Parameter(0);
Node* rhs = assembler->Parameter(1);
Node* context = assembler->Parameter(2);
Label if_equal(assembler), if_notequal(assembler);
// Fast check to see if {lhs} and {rhs} refer to the same String object.
Label if_same(assembler), if_notsame(assembler);
assembler->Branch(assembler->WordEqual(lhs, rhs), &if_same, &if_notsame);
assembler->Bind(&if_same);
assembler->Goto(&if_equal);
assembler->Bind(&if_notsame);
{
// The {lhs} and {rhs} don't refer to the exact same String object.
// Load the length of {lhs} and {rhs}.
Node* lhs_length = assembler->LoadObjectField(lhs, String::kLengthOffset);
Node* rhs_length = assembler->LoadObjectField(rhs, String::kLengthOffset);
// Check if the lengths of {lhs} and {rhs} are equal.
Label if_lengthisequal(assembler), if_lengthisnotequal(assembler);
assembler->Branch(assembler->WordEqual(lhs_length, rhs_length),
&if_lengthisequal, &if_lengthisnotequal);
assembler->Bind(&if_lengthisequal);
{
// Load instance types of {lhs} and {rhs}.
Node* lhs_instance_type = assembler->LoadInstanceType(lhs);
Node* rhs_instance_type = assembler->LoadInstanceType(rhs);
// Combine the instance types into a single 16-bit value, so we can check
// both of them at once.
Node* both_instance_types = assembler->Word32Or(
lhs_instance_type,
assembler->Word32Shl(rhs_instance_type, assembler->Int32Constant(8)));
// Check if both {lhs} and {rhs} are internalized.
int const kBothInternalizedMask =
kIsNotInternalizedMask | (kIsNotInternalizedMask << 8);
int const kBothInternalizedTag =
kInternalizedTag | (kInternalizedTag << 8);
Label if_bothinternalized(assembler), if_notbothinternalized(assembler);
assembler->Branch(assembler->Word32Equal(
assembler->Word32And(both_instance_types,
assembler->Int32Constant(
kBothInternalizedMask)),
assembler->Int32Constant(kBothInternalizedTag)),
&if_bothinternalized, &if_notbothinternalized);
assembler->Bind(&if_bothinternalized);
{
// Fast negative check for internalized-to-internalized equality.
assembler->Goto(&if_notequal);
}
assembler->Bind(&if_notbothinternalized);
{
// Check that both {lhs} and {rhs} are flat one-byte strings.
int const kBothSeqOneByteStringMask =
kStringEncodingMask | kStringRepresentationMask |
((kStringEncodingMask | kStringRepresentationMask) << 8);
int const kBothSeqOneByteStringTag =
kOneByteStringTag | kSeqStringTag |
((kOneByteStringTag | kSeqStringTag) << 8);
Label if_bothonebyteseqstrings(assembler),
if_notbothonebyteseqstrings(assembler);
assembler->Branch(
assembler->Word32Equal(
assembler->Word32And(
both_instance_types,
assembler->Int32Constant(kBothSeqOneByteStringMask)),
assembler->Int32Constant(kBothSeqOneByteStringTag)),
&if_bothonebyteseqstrings, &if_notbothonebyteseqstrings);
assembler->Bind(&if_bothonebyteseqstrings);
{
// Compute the effective offset of the first character.
Node* begin = assembler->IntPtrConstant(
SeqOneByteString::kHeaderSize - kHeapObjectTag);
// Compute the first offset after the string from the length.
Node* end =
assembler->IntPtrAdd(begin, assembler->SmiUntag(lhs_length));
// Loop over the {lhs} and {rhs} strings to see if they are equal.
Variable var_offset(assembler, MachineType::PointerRepresentation());
Label loop(assembler, &var_offset);
var_offset.Bind(begin);
assembler->Goto(&loop);
assembler->Bind(&loop);
{
// Check if {offset} equals {end}.
Node* offset = var_offset.value();
Label if_done(assembler), if_notdone(assembler);
assembler->Branch(assembler->WordEqual(offset, end), &if_done,
&if_notdone);
assembler->Bind(&if_notdone);
{
// Load the next characters from {lhs} and {rhs}.
Node* lhs_value =
assembler->Load(MachineType::Uint8(), lhs, offset);
Node* rhs_value =
assembler->Load(MachineType::Uint8(), rhs, offset);
// Check if the characters match.
Label if_valueissame(assembler), if_valueisnotsame(assembler);
assembler->Branch(assembler->Word32Equal(lhs_value, rhs_value),
&if_valueissame, &if_valueisnotsame);
assembler->Bind(&if_valueissame);
{
// Advance to next character.
var_offset.Bind(
assembler->IntPtrAdd(offset, assembler->IntPtrConstant(1)));
}
assembler->Goto(&loop);
assembler->Bind(&if_valueisnotsame);
assembler->Goto(&if_notequal);
}
assembler->Bind(&if_done);
assembler->Goto(&if_equal);
}
}
assembler->Bind(&if_notbothonebyteseqstrings);
{
// TODO(bmeurer): Add fast case support for flattened cons strings;
// also add support for two byte string equality checks.
Runtime::FunctionId function_id = (mode == kDontNegateResult)
? Runtime::kStringEqual
: Runtime::kStringNotEqual;
assembler->TailCallRuntime(function_id, context, lhs, rhs);
}
}
}
assembler->Bind(&if_lengthisnotequal);
{
// Mismatch in length of {lhs} and {rhs}, cannot be equal.
assembler->Goto(&if_notequal);
}
}
assembler->Bind(&if_equal);
assembler->Return(assembler->BooleanConstant(mode == kDontNegateResult));
assembler->Bind(&if_notequal);
assembler->Return(assembler->BooleanConstant(mode == kNegateResult));
}
} // namespace
void StrictEqualStub::GenerateAssembly(
compiler::CodeStubAssembler* assembler) const {
GenerateStrictEqual(assembler, kStrictEqual);
GenerateStrictEqual(assembler, isolate(), kDontNegateResult);
}
void StrictNotEqualStub::GenerateAssembly(
compiler::CodeStubAssembler* assembler) const {
GenerateStrictEqual(assembler, kStrictNotEqual);
GenerateStrictEqual(assembler, isolate(), kNegateResult);
}
void StringEqualStub::GenerateAssembly(
compiler::CodeStubAssembler* assembler) const {
GenerateStringEqual(assembler, kDontNegateResult);
}
void StringNotEqualStub::GenerateAssembly(
compiler::CodeStubAssembler* assembler) const {
GenerateStringEqual(assembler, kNegateResult);
}
void ToBooleanStub::GenerateAssembly(

View File

@ -103,6 +103,8 @@ namespace internal {
V(StringLength) \
V(StrictEqual) \
V(StrictNotEqual) \
V(StringEqual) \
V(StringNotEqual) \
V(ToBoolean) \
/* IC Handler stubs */ \
V(ArrayBufferViewLoadField) \
@ -653,6 +655,26 @@ class StrictNotEqualStub final : public TurboFanCodeStub {
DEFINE_CODE_STUB(StrictNotEqual, TurboFanCodeStub);
};
class StringEqualStub final : public TurboFanCodeStub {
public:
explicit StringEqualStub(Isolate* isolate) : TurboFanCodeStub(isolate) {}
void GenerateAssembly(compiler::CodeStubAssembler* assembler) const final;
DEFINE_CALL_INTERFACE_DESCRIPTOR(Compare);
DEFINE_CODE_STUB(StringEqual, TurboFanCodeStub);
};
class StringNotEqualStub final : public TurboFanCodeStub {
public:
explicit StringNotEqualStub(Isolate* isolate) : TurboFanCodeStub(isolate) {}
void GenerateAssembly(compiler::CodeStubAssembler* assembler) const final;
DEFINE_CALL_INTERFACE_DESCRIPTOR(Compare);
DEFINE_CODE_STUB(StringNotEqual, TurboFanCodeStub);
};
class ToBooleanStub final : public TurboFanCodeStub {
public:
explicit ToBooleanStub(Isolate* isolate) : TurboFanCodeStub(isolate) {}

View File

@ -602,6 +602,14 @@ Node* CodeStubAssembler::CallStub(const CallInterfaceDescriptor& descriptor,
return CallN(call_descriptor, target, args);
}
Node* CodeStubAssembler::TailCallStub(Callable const& callable, Node* context,
Node* arg1, Node* arg2,
size_t result_size) {
Node* target = HeapConstant(callable.code());
return TailCallStub(callable.descriptor(), target, context, arg1, arg2,
result_size);
}
Node* CodeStubAssembler::TailCallStub(const CallInterfaceDescriptor& descriptor,
Node* target, Node* context, Node* arg1,
Node* arg2, size_t result_size) {

View File

@ -19,6 +19,7 @@
namespace v8 {
namespace internal {
class Callable;
class CallInterfaceDescriptor;
class Isolate;
class Factory;
@ -215,6 +216,9 @@ class CodeStubAssembler {
Node* context, Node* arg1, Node* arg2, Node* arg3, Node* arg4,
Node* arg5, size_t result_size = 1);
Node* TailCallStub(Callable const& callable, Node* context, Node* arg1,
Node* arg2, size_t result_size = 1);
Node* TailCallStub(const CallInterfaceDescriptor& descriptor, Node* target,
Node* context, Node* arg1, Node* arg2,
size_t result_size = 1);

View File

@ -294,6 +294,10 @@ class RepresentationSelector {
return NodeOutputInfo(MachineRepresentation::kTagged, Type::Any());
}
static NodeOutputInfo BoolTagged() {
return NodeOutputInfo(MachineRepresentation::kTagged, Type::Boolean());
}
static NodeOutputInfo NumberTagged() {
return NodeOutputInfo(MachineRepresentation::kTagged, Type::Number());
}
@ -1139,8 +1143,20 @@ class RepresentationSelector {
break;
}
case IrOpcode::kStringEqual: {
VisitBinop(node, UseInfo::AnyTagged(), NodeOutputInfo::Bool());
if (lower()) lowering->DoStringEqual(node);
VisitBinop(node, UseInfo::AnyTagged(), NodeOutputInfo::BoolTagged());
if (lower()) {
// StringEqual(x, y) => Call(StringEqualStub, x, y, no-context)
Operator::Properties properties = node->op()->properties();
Callable callable = CodeFactory::StringEqual(jsgraph_->isolate());
CallDescriptor::Flags flags = CallDescriptor::kNoFlags;
CallDescriptor* desc = Linkage::GetStubCallDescriptor(
jsgraph_->isolate(), jsgraph_->zone(), callable.descriptor(), 0,
flags, properties);
node->InsertInput(jsgraph_->zone(), 0,
jsgraph_->HeapConstant(callable.code()));
node->InsertInput(jsgraph_->zone(), 3, jsgraph_->NoContextConstant());
NodeProperties::ChangeOp(node, jsgraph_->common()->Call(desc));
}
break;
}
case IrOpcode::kStringLessThan: {
@ -1890,16 +1906,6 @@ void ReplaceEffectUses(Node* node, Node* replacement) {
} // namespace
void SimplifiedLowering::DoStringEqual(Node* node) {
Node* comparison = StringComparison(node);
ReplaceEffectUses(node, comparison);
node->ReplaceInput(0, comparison);
node->ReplaceInput(1, jsgraph()->SmiConstant(EQUAL));
node->TrimInputCount(2);
NodeProperties::ChangeOp(node, machine()->WordEqual());
}
void SimplifiedLowering::DoStringLessThan(Node* node) {
Node* comparison = StringComparison(node);
ReplaceEffectUses(node, comparison);

View File

@ -37,7 +37,6 @@ class SimplifiedLowering final {
RepresentationChanger* changer);
void DoStoreBuffer(Node* node);
void DoShift(Node* node, Operator const* op, Type* rhs_type);
void DoStringEqual(Node* node);
void DoStringLessThan(Node* node);
void DoStringLessThanOrEqual(Node* node);

View File

@ -2366,9 +2366,15 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(r1));
DCHECK(ToRegister(instr->right()).is(r0));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ cmp(r0, Operand::Zero());
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ cmp(r0, Operand::Zero());
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ CompareRoot(r0, Heap::kTrueValueRootIndex);
}
EmitBranch(instr, ComputeCompareCondition(instr->op()));
}

View File

@ -5193,10 +5193,18 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(x1));
DCHECK(ToRegister(instr->right()).is(x0));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
EmitCompareAndBranch(instr, TokenToCondition(instr->op(), false), x0, 0);
EmitCompareAndBranch(instr, TokenToCondition(instr->op(), false), x0, 0);
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ CompareRoot(x0, Heap::kTrueValueRootIndex);
EmitBranch(instr, TokenToCondition(instr->op(), false));
}
}

View File

@ -2168,9 +2168,15 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(edx));
DCHECK(ToRegister(instr->right()).is(eax));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ test(eax, eax);
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ test(eax, eax);
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ CompareRoot(eax, Heap::kTrueValueRootIndex);
}
EmitBranch(instr, ComputeCompareCondition(instr->op()));
}

View File

@ -2275,11 +2275,19 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(a1));
DCHECK(ToRegister(instr->right()).is(a0));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0,
Operand(zero_reg));
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0,
Operand(zero_reg));
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ LoadRoot(at, Heap::kTrueValueRootIndex);
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0, Operand(at));
}
}

View File

@ -2392,11 +2392,19 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(a1));
DCHECK(ToRegister(instr->right()).is(a0));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0,
Operand(zero_reg));
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0,
Operand(zero_reg));
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ LoadRoot(at, Heap::kTrueValueRootIndex);
EmitBranch(instr, ComputeCompareCondition(instr->op()), v0, Operand(at));
}
}

View File

@ -2318,9 +2318,15 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
DCHECK(ToRegister(instr->left()).is(rdx));
DCHECK(ToRegister(instr->right()).is(rax));
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ testp(rax, rax);
if (Token::IsOrderedRelationalCompareOp(instr->op())) {
Handle<Code> code = CodeFactory::StringCompare(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ testp(rax, rax);
} else {
Handle<Code> code = CodeFactory::StringEqual(isolate()).code();
CallCode(code, RelocInfo::CODE_TARGET, instr);
__ CompareRoot(rax, Heap::kTrueValueRootIndex);
}
EmitBranch(instr, TokenToCondition(instr->op(), false));
}

View File

@ -1144,13 +1144,10 @@ TEST(LowerReferenceEqual_to_wordeq) {
TEST(LowerStringOps_to_call_and_compare) {
// These tests need linkage for the calls.
TestingGraph t(Type::String(), Type::String());
IrOpcode::Value compare_eq =
static_cast<IrOpcode::Value>(t.machine()->WordEqual()->opcode());
IrOpcode::Value compare_lt =
static_cast<IrOpcode::Value>(t.machine()->IntLessThan()->opcode());
IrOpcode::Value compare_le = static_cast<IrOpcode::Value>(
t.machine()->IntLessThanOrEqual()->opcode());
t.CheckLoweringStringBinop(compare_eq, t.simplified()->StringEqual());
t.CheckLoweringStringBinop(compare_lt, t.simplified()->StringLessThan());
t.CheckLoweringStringBinop(compare_le,
t.simplified()->StringLessThanOrEqual());