diff --git a/js/src/jit-test/tests/ion/compare-string.js b/js/src/jit-test/tests/ion/compare-string.js index 5edf3c04c8df..db5d295417b2 100644 --- a/js/src/jit-test/tests/ion/compare-string.js +++ b/js/src/jit-test/tests/ion/compare-string.js @@ -1,40 +1,131 @@ function compareToAtom(a) { - return a == 'test'; + return a == 'test-test-test-test-test-test-test-test'; +} + +function compareToAtomStrict(a) { + return a === 'test-test-test-test-test-test-test-test'; } function compareToAtomNe(a) { - return a != 'test'; + return a != 'test-test-test-test-test-test-test-test'; } -var st = 'st'; +function compareToAtomNeStrict(a) { + return a !== 'test-test-test-test-test-test-test-test'; +} + +function compareToAtomLessThan(a) { + return a < 'test-test-test-test-test-test-test-test'; +} + +function compareToAtomLessThanOrEquals(a) { + return a <= 'test-test-test-test-test-test-test-test'; +} + +function compareToAtomGreaterThan(a) { + return a > 'test-test-test-test-test-test-test-test'; +} + +function compareToAtomGreaterThanOrEquals(a) { + return a >= 'test-test-test-test-test-test-test-test'; +} + +var st = 'st-test-test-test-test-test-test-test'; function compareToRope(a) { return a == ('te' + st); } +function compareToRopeStrict(a) { + return a === ('te' + st); +} + function compareToRopeNe(a) { - var st = 'st'; + var st = 'st-test-test-test-test-test-test-test'; return a != ('te' + st); } +function compareToRopeNeStrict(a) { + var st = 'st-test-test-test-test-test-test-test'; + return a !== ('te' + st); +} + +function compareToRopeLessThan(a) { + var st = 'st-test-test-test-test-test-test-test'; + return a < ('te' + st); +} + +function compareToRopeLessThanOrEquals(a) { + var st = 'st-test-test-test-test-test-test-test'; + return a <= ('te' + st); +} + +function compareToRopeGreaterThan(a) { + var st = 'st-test-test-test-test-test-test-test'; + return a > ('te' + st); +} + +function compareToRopeGreaterThanOrEquals(a) { + var st = 'st-test-test-test-test-test-test-test'; + return a >= ('te' + st); +} + function main() { - var test = 'test'; + // |test| must be longer than |JSFatInlineString::MAX_LENGTH_LATIN1| to + // ensure the above functions create ropes when concatenating strings. + var test = 'test-test-test-test-test-test-test-test'; var foobar = 'foobar'; assertEq(compareToAtom(test), true); assertEq(compareToAtom(foobar), false); + assertEq(compareToAtomStrict(test), true); + assertEq(compareToAtomStrict(foobar), false); + assertEq(compareToAtomNe(test), false); assertEq(compareToAtomNe(foobar), true); + assertEq(compareToAtomNeStrict(test), false); + assertEq(compareToAtomNeStrict(foobar), true); + + assertEq(compareToAtomLessThan(test), false); + assertEq(compareToAtomLessThan(foobar), true); + + assertEq(compareToAtomLessThanOrEquals(test), true); + assertEq(compareToAtomLessThanOrEquals(foobar), true); + + assertEq(compareToAtomGreaterThan(test), false); + assertEq(compareToAtomGreaterThan(foobar), false); + + assertEq(compareToAtomGreaterThanOrEquals(test), true); + assertEq(compareToAtomGreaterThanOrEquals(foobar), false); + assertEq(compareToRope(test), true); assertEq(compareToRope(foobar), false); + assertEq(compareToRopeStrict(test), true); + assertEq(compareToRopeStrict(foobar), false); + assertEq(compareToRopeNe(test), false); assertEq(compareToRopeNe(foobar), true); + + assertEq(compareToRopeNeStrict(test), false); + assertEq(compareToRopeNeStrict(foobar), true); + + assertEq(compareToRopeLessThan(test), false); + assertEq(compareToRopeLessThan(foobar), true); + + assertEq(compareToRopeLessThanOrEquals(test), true); + assertEq(compareToRopeLessThanOrEquals(foobar), true); + + assertEq(compareToRopeGreaterThan(test), false); + assertEq(compareToRopeGreaterThan(foobar), false); + + assertEq(compareToRopeGreaterThanOrEquals(test), true); + assertEq(compareToRopeGreaterThanOrEquals(foobar), false); } -for (var i = 0; i < 100000; i++) { +for (var i = 0; i < 10000; i++) { main(); } diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index b787329cf799..6f4045506cd0 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -5742,8 +5742,7 @@ bool CompareIRGenerator::tryAttachStringNumber(ValOperandId lhsId, bool CompareIRGenerator::tryAttachStub() { MOZ_ASSERT(cacheKind_ == CacheKind::Compare); - MOZ_ASSERT(IsEqualityOp(op_) || op_ == JSOP_LE || op_ == JSOP_LT || - op_ == JSOP_GE || op_ == JSOP_GT); + MOZ_ASSERT(IsEqualityOp(op_) || IsRelationalOp(op_)); AutoAssertNoPendingException aanpe(cx_); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 14d5b9b6efb9..71656285c70d 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -8045,10 +8045,26 @@ void CodeGenerator::emitCompareS(LInstruction* lir, JSOp op, Register left, if (op == JSOP_EQ || op == JSOP_STRICTEQ) { ool = oolCallVM>(lir, ArgList(left, right), StoreRegisterTo(output)); - } else { - MOZ_ASSERT(op == JSOP_NE || op == JSOP_STRICTNE); + } else if (op == JSOP_NE || op == JSOP_STRICTNE) { ool = oolCallVM>(lir, ArgList(left, right), StoreRegisterTo(output)); + } else if (op == JSOP_LT) { + ool = oolCallVM>(lir, ArgList(left, right), + StoreRegisterTo(output)); + } else if (op == JSOP_LE) { + // Push the operands in reverse order for JSOP_LE: + // - |left <= right| is implemented as |right >= left|. + ool = oolCallVM>(lir, ArgList(right, left), + StoreRegisterTo(output)); + } else if (op == JSOP_GT) { + // Push the operands in reverse order for JSOP_GT: + // - |left > right| is implemented as |right < left|. + ool = oolCallVM>(lir, ArgList(right, left), + StoreRegisterTo(output)); + } else { + MOZ_ASSERT(op == JSOP_GE); + ool = oolCallVM>(lir, ArgList(left, right), + StoreRegisterTo(output)); } masm.compareStrings(op, left, right, output, ool->entry()); diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 04196b2beb6e..076fd638f52b 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2361,8 +2361,7 @@ static bool CanCompareRegExp(MCompare* compare, MDefinition* def) { if (op != JSOP_EQ && op != JSOP_NE) { // Relational comparison always invoke @@toPrimitive. - MOZ_ASSERT(op == JSOP_GT || op == JSOP_GE || op == JSOP_LT || - op == JSOP_LE); + MOZ_ASSERT(IsRelationalOp(op)); return false; } diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index fcf4a984b650..d1ef9a0d6e50 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -3473,9 +3473,8 @@ MCompare::CompareType MCompare::determineCompareType(JSOp op, MDefinition* left, return Compare_Object; } - // Handle string comparisons. (Relational string compares are still - // unsupported). - if (!relationalEq && lhs == MIRType::String && rhs == MIRType::String) { + // Handle string comparisons. + if (lhs == MIRType::String && rhs == MIRType::String) { return Compare_String; } diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 13449b0e93c7..6879dcba2747 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1206,36 +1206,42 @@ void MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result, Label* fail) { MOZ_ASSERT(left != result); MOZ_ASSERT(right != result); - MOZ_ASSERT(IsEqualityOp(op)); + MOZ_ASSERT(IsEqualityOp(op) || IsRelationalOp(op)); - Label done; Label notPointerEqual; // If operands point to the same instance, the strings are trivially equal. - branchPtr(Assembler::NotEqual, left, right, ¬PointerEqual); - move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ), result); - jump(&done); + branchPtr(Assembler::NotEqual, left, right, + IsEqualityOp(op) ? ¬PointerEqual : fail); + move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ || op == JSOP_LE || + op == JSOP_GE), + result); - bind(¬PointerEqual); + if (IsEqualityOp(op)) { + Label done; + jump(&done); - Label leftIsNotAtom; - Label setNotEqualResult; - // Atoms cannot be equal to each other if they point to different strings. - Imm32 nonAtomBit(JSString::NON_ATOM_BIT); - branchTest32(Assembler::NonZero, Address(left, JSString::offsetOfFlags()), - nonAtomBit, &leftIsNotAtom); - branchTest32(Assembler::Zero, Address(right, JSString::offsetOfFlags()), - nonAtomBit, &setNotEqualResult); + bind(¬PointerEqual); - bind(&leftIsNotAtom); - // Strings of different length can never be equal. - loadStringLength(left, result); - branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), result, - fail); + Label leftIsNotAtom; + Label setNotEqualResult; + // Atoms cannot be equal to each other if they point to different strings. + Imm32 nonAtomBit(JSString::NON_ATOM_BIT); + branchTest32(Assembler::NonZero, Address(left, JSString::offsetOfFlags()), + nonAtomBit, &leftIsNotAtom); + branchTest32(Assembler::Zero, Address(right, JSString::offsetOfFlags()), + nonAtomBit, &setNotEqualResult); - bind(&setNotEqualResult); - move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result); + bind(&leftIsNotAtom); + // Strings of different length can never be equal. + loadStringLength(left, result); + branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), + result, fail); - bind(&done); + bind(&setNotEqualResult); + move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result); + + bind(&done); + } } void MacroAssembler::loadStringChars(Register str, Register dest, diff --git a/js/src/jit/VMFunctionList-inl.h b/js/src/jit/VMFunctionList-inl.h index 6c614f3d7988..fed23e60e59d 100644 --- a/js/src/jit/VMFunctionList-inl.h +++ b/js/src/jit/VMFunctionList-inl.h @@ -229,6 +229,8 @@ namespace jit { _(StringToLowerCase, js::StringToLowerCase) \ _(StringToNumber, js::StringToNumber) \ _(StringToUpperCase, js::StringToUpperCase) \ + _(StringsCompareGreaterThanOrEquals, js::jit::StringsCompare) \ + _(StringsCompareLessThan, js::jit::StringsCompare) \ _(StringsEqual, js::jit::StringsEqual) \ _(StringsNotEqual, js::jit::StringsEqual) \ _(SubValues, js::SubValues) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 3dbcbbe97287..eddee4d405ed 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -414,6 +414,26 @@ template bool StringsEqual(JSContext* cx, HandleString lhs, template bool StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); +template +bool StringsCompare(JSContext* cx, HandleString lhs, HandleString rhs, + bool* res) { + int32_t result; + if (!js::CompareStrings(cx, lhs, rhs, &result)) { + return false; + } + if (LessThan) { + *res = result < 0; + } else { + *res = result >= 0; + } + return true; +} + +template bool StringsCompare(JSContext* cx, HandleString lhs, + HandleString rhs, bool* res); +template bool StringsCompare(JSContext* cx, HandleString lhs, + HandleString rhs, bool* res); + bool StringSplitHelper(JSContext* cx, HandleString str, HandleString sep, HandleObjectGroup group, uint32_t limit, MutableHandleValue result) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 92589305bc3c..657a42e57a69 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -856,8 +856,11 @@ bool GreaterThanOrEqual(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res); template -bool StringsEqual(JSContext* cx, HandleString left, HandleString right, - bool* res); +bool StringsEqual(JSContext* cx, HandleString lhs, HandleString rhs, bool* res); + +template +bool StringsCompare(JSContext* cx, HandleString lhs, HandleString rhs, + bool* res); MOZ_MUST_USE bool StringSplitHelper(JSContext* cx, HandleString str, HandleString sep, HandleObjectGroup group, diff --git a/js/src/vm/BytecodeUtil.h b/js/src/vm/BytecodeUtil.h index 45e25e67becd..ff70f3659607 100644 --- a/js/src/vm/BytecodeUtil.h +++ b/js/src/vm/BytecodeUtil.h @@ -535,6 +535,10 @@ inline bool IsEqualityOp(JSOp op) { op == JSOP_STRICTNE; } +inline bool IsRelationalOp(JSOp op) { + return op == JSOP_LT || op == JSOP_LE || op == JSOP_GT || op == JSOP_GE; +} + inline bool IsCheckStrictOp(JSOp op) { return CodeSpec[op].format & JOF_CHECKSTRICT; }