[strong] Disallow implicit conversions for comparison
Implements the strong mode proposal's restrictions on implicit conversions for the binary >, >=, <, and <= operators. BUG=v8:3956 LOG=N Review URL: https://codereview.chromium.org/1130283002 Cr-Commit-Position: refs/heads/master@{#28370}
This commit is contained in:
parent
188297160d
commit
03ef40b46c
@ -93,9 +93,8 @@ void InternalArrayNArgumentsConstructorStub::InitializeDescriptor(
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cond);
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cond, bool strong);
|
||||
static void EmitSmiNonsmiComparison(MacroAssembler* masm,
|
||||
Register lhs,
|
||||
Register rhs,
|
||||
@ -238,9 +237,8 @@ void DoubleToIStub::Generate(MacroAssembler* masm) {
|
||||
// Handle the case where the lhs and rhs are the same object.
|
||||
// Equality is almost reflexive (everything but NaN), so this is a test
|
||||
// for "identity and not NaN".
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cond) {
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cond, bool strong) {
|
||||
Label not_identical;
|
||||
Label heap_number, return_equal;
|
||||
__ cmp(r0, r1);
|
||||
@ -251,10 +249,20 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
// They are both equal and they are not both Smis so both of them are not
|
||||
// Smis. If it's not a heap number, then return equal.
|
||||
if (cond == lt || cond == gt) {
|
||||
// Call runtime on identical JSObjects.
|
||||
__ CompareObjectType(r0, r4, r4, FIRST_SPEC_OBJECT_TYPE);
|
||||
__ b(ge, slow);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ cmp(r4, Operand(SYMBOL_TYPE));
|
||||
__ b(eq, slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics, since
|
||||
// we need to throw a TypeError. Smis have already been ruled out.
|
||||
__ cmp(r4, Operand(HEAP_NUMBER_TYPE));
|
||||
__ b(eq, &return_equal);
|
||||
__ tst(r4, Operand(kIsNotStringMask));
|
||||
__ b(ne, slow);
|
||||
}
|
||||
} else {
|
||||
__ CompareObjectType(r0, r4, r4, HEAP_NUMBER_TYPE);
|
||||
__ b(eq, &heap_number);
|
||||
@ -262,8 +270,16 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
if (cond != eq) {
|
||||
__ cmp(r4, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
__ b(ge, slow);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ cmp(r4, Operand(SYMBOL_TYPE));
|
||||
__ b(eq, slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics,
|
||||
// since we need to throw a TypeError. Smis and heap numbers have
|
||||
// already been ruled out.
|
||||
__ tst(r4, Operand(kIsNotStringMask));
|
||||
__ b(ne, slow);
|
||||
}
|
||||
// Normally here we fall through to return_equal, but undefined is
|
||||
// special: (undefined == undefined) == true, but
|
||||
// (undefined <= undefined) == false! See ECMAScript 11.8.5.
|
||||
@ -561,7 +577,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
|
||||
// Handle the case where the objects are identical. Either returns the answer
|
||||
// or goes to slow. Only falls through if the objects were not identical.
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc);
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc, strong());
|
||||
|
||||
// If either is a Smi (we know that not both are), then they can only
|
||||
// be strictly equal if the other is a HeapNumber.
|
||||
@ -663,7 +679,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == eq) {
|
||||
native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
native = Builtins::COMPARE;
|
||||
native = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
int ncr; // NaN compare result
|
||||
if (cc == lt || cc == le) {
|
||||
ncr = GREATER;
|
||||
@ -3567,7 +3583,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ Jump(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1059,8 +1059,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5238,7 +5238,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
PrepareForBailoutBeforeSplit(expr, true, if_true, if_false);
|
||||
|
@ -1197,6 +1197,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -2612,7 +2612,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// This instruction also signals no smi code inlined.
|
||||
__ cmp(r0, Operand::Zero());
|
||||
@ -2929,7 +2929,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// This instruction also signals no smi code inlined.
|
||||
__ cmp(r0, Operand::Zero());
|
||||
|
@ -203,13 +203,11 @@ void DoubleToIStub::Generate(MacroAssembler* masm) {
|
||||
|
||||
|
||||
// See call site for description.
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Register left,
|
||||
Register right,
|
||||
Register scratch,
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Register left,
|
||||
Register right, Register scratch,
|
||||
FPRegister double_scratch,
|
||||
Label* slow,
|
||||
Condition cond) {
|
||||
Label* slow, Condition cond,
|
||||
bool strong) {
|
||||
DCHECK(!AreAliased(left, right, scratch));
|
||||
Label not_identical, return_equal, heap_number;
|
||||
Register result = x0;
|
||||
@ -223,10 +221,20 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
// Smis. If it's not a heap number, then return equal.
|
||||
Register right_type = scratch;
|
||||
if ((cond == lt) || (cond == gt)) {
|
||||
// Call runtime on identical JSObjects. Otherwise return equal.
|
||||
__ JumpIfObjectType(right, right_type, right_type, FIRST_SPEC_OBJECT_TYPE,
|
||||
slow, ge);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Cmp(right_type, SYMBOL_TYPE);
|
||||
__ B(eq, slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics, since
|
||||
// we need to throw a TypeError. Smis have already been ruled out.
|
||||
__ Cmp(right_type, Operand(HEAP_NUMBER_TYPE));
|
||||
__ B(eq, &return_equal);
|
||||
__ Tst(right_type, Operand(kIsNotStringMask));
|
||||
__ B(ne, slow);
|
||||
}
|
||||
} else if (cond == eq) {
|
||||
__ JumpIfHeapNumber(right, &heap_number);
|
||||
} else {
|
||||
@ -235,8 +243,16 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
// Comparing JS objects with <=, >= is complicated.
|
||||
__ Cmp(right_type, FIRST_SPEC_OBJECT_TYPE);
|
||||
__ B(ge, slow);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Cmp(right_type, SYMBOL_TYPE);
|
||||
__ B(eq, slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics,
|
||||
// since we need to throw a TypeError. Smis and heap numbers have
|
||||
// already been ruled out.
|
||||
__ Tst(right_type, Operand(kIsNotStringMask));
|
||||
__ B(ne, slow);
|
||||
}
|
||||
// Normally here we fall through to return_equal, but undefined is
|
||||
// special: (undefined == undefined) == true, but
|
||||
// (undefined <= undefined) == false! See ECMAScript 11.8.5.
|
||||
@ -513,7 +529,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
|
||||
// Handle the case where the objects are identical. Either returns the answer
|
||||
// or goes to slow. Only falls through if the objects were not identical.
|
||||
EmitIdenticalObjectComparison(masm, lhs, rhs, x10, d0, &slow, cond);
|
||||
EmitIdenticalObjectComparison(masm, lhs, rhs, x10, d0, &slow, cond, strong());
|
||||
|
||||
// If either is a smi (we know that at least one is not a smi), then they can
|
||||
// only be strictly equal if the other is a HeapNumber.
|
||||
@ -632,7 +648,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cond == eq) {
|
||||
native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
native = Builtins::COMPARE;
|
||||
native = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
int ncr; // NaN compare result
|
||||
if ((cond == lt) || (cond == le)) {
|
||||
ncr = GREATER;
|
||||
@ -3485,7 +3501,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
__ Ret();
|
||||
|
||||
__ Bind(&unordered);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ Jump(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1058,8 +1058,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -4925,7 +4925,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
PrepareForBailoutBeforeSplit(expr, true, if_true, if_false);
|
||||
|
@ -1182,6 +1182,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -2555,7 +2555,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
|
||||
DCHECK(ToRegister(instr->left()).Is(x1));
|
||||
DCHECK(ToRegister(instr->right()).Is(x0));
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// Signal that we don't inline smi code before this stub.
|
||||
InlineSmiCheckInfo::EmitNotInlined(masm());
|
||||
@ -5634,7 +5635,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
InlineSmiCheckInfo::EmitNotInlined(masm());
|
||||
|
||||
|
@ -171,6 +171,7 @@ enum BuiltinExtraArguments {
|
||||
V(EQUALS, 1) \
|
||||
V(STRICT_EQUALS, 1) \
|
||||
V(COMPARE, 2) \
|
||||
V(COMPARE_STRONG, 2) \
|
||||
V(ADD, 1) \
|
||||
V(ADD_STRONG, 1) \
|
||||
V(SUB, 1) \
|
||||
|
@ -105,8 +105,10 @@ Callable CodeFactory::KeyedStoreICInOptimizedCode(
|
||||
|
||||
|
||||
// static
|
||||
Callable CodeFactory::CompareIC(Isolate* isolate, Token::Value op) {
|
||||
Handle<Code> code = CompareIC::GetUninitialized(isolate, op);
|
||||
Callable CodeFactory::CompareIC(Isolate* isolate, Token::Value op,
|
||||
LanguageMode language_mode) {
|
||||
Handle<Code> code =
|
||||
CompareIC::GetUninitialized(isolate, op, is_strong(language_mode));
|
||||
return Callable(code, CompareDescriptor(isolate));
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,8 @@ class CodeFactory final {
|
||||
Isolate* isolate, LanguageMode mode,
|
||||
InlineCacheState initialization_state);
|
||||
|
||||
static Callable CompareIC(Isolate* isolate, Token::Value op);
|
||||
static Callable CompareIC(Isolate* isolate, Token::Value op,
|
||||
LanguageMode language_mode);
|
||||
|
||||
static Callable BinaryOpIC(Isolate* isolate, Token::Value op,
|
||||
LanguageMode language_mode);
|
||||
|
@ -1523,12 +1523,14 @@ class StringAddStub final : public HydrogenCodeStub {
|
||||
|
||||
class CompareICStub : public PlatformCodeStub {
|
||||
public:
|
||||
CompareICStub(Isolate* isolate, Token::Value op, CompareICState::State left,
|
||||
CompareICState::State right, CompareICState::State state)
|
||||
CompareICStub(Isolate* isolate, Token::Value op, bool strong,
|
||||
CompareICState::State left, CompareICState::State right,
|
||||
CompareICState::State state)
|
||||
: PlatformCodeStub(isolate) {
|
||||
DCHECK(Token::IsCompareOp(op));
|
||||
minor_key_ = OpBits::encode(op - Token::EQ) | LeftStateBits::encode(left) |
|
||||
RightStateBits::encode(right) | StateBits::encode(state);
|
||||
minor_key_ = OpBits::encode(op - Token::EQ) | StrongBits::encode(strong) |
|
||||
LeftStateBits::encode(left) | RightStateBits::encode(right) |
|
||||
StateBits::encode(state);
|
||||
}
|
||||
|
||||
void set_known_map(Handle<Map> map) { known_map_ = map; }
|
||||
@ -1539,6 +1541,8 @@ class CompareICStub : public PlatformCodeStub {
|
||||
return static_cast<Token::Value>(Token::EQ + OpBits::decode(minor_key_));
|
||||
}
|
||||
|
||||
bool strong() const { return StrongBits::decode(minor_key_); }
|
||||
|
||||
CompareICState::State left() const {
|
||||
return LeftStateBits::decode(minor_key_);
|
||||
}
|
||||
@ -1570,9 +1574,10 @@ class CompareICStub : public PlatformCodeStub {
|
||||
}
|
||||
|
||||
class OpBits : public BitField<int, 0, 3> {};
|
||||
class LeftStateBits : public BitField<CompareICState::State, 3, 4> {};
|
||||
class RightStateBits : public BitField<CompareICState::State, 7, 4> {};
|
||||
class StateBits : public BitField<CompareICState::State, 11, 4> {};
|
||||
class StrongBits : public BitField<bool, 3, 1> {};
|
||||
class LeftStateBits : public BitField<CompareICState::State, 4, 4> {};
|
||||
class RightStateBits : public BitField<CompareICState::State, 8, 4> {};
|
||||
class StateBits : public BitField<CompareICState::State, 12, 4> {};
|
||||
|
||||
Handle<Map> known_map_;
|
||||
|
||||
|
@ -116,7 +116,8 @@ static CallDescriptor::Flags FlagsForNode(Node* node) {
|
||||
|
||||
|
||||
void JSGenericLowering::ReplaceWithCompareIC(Node* node, Token::Value token) {
|
||||
Callable callable = CodeFactory::CompareIC(isolate(), token);
|
||||
Callable callable =
|
||||
CodeFactory::CompareIC(isolate(), token, OpParameter<LanguageMode>(node));
|
||||
CallDescriptor* desc_compare = Linkage::GetStubCallDescriptor(
|
||||
isolate(), zone(), callable.descriptor(), 0,
|
||||
CallDescriptor::kPatchableCallSiteWithNop | FlagsForNode(node),
|
||||
|
@ -470,6 +470,9 @@ Reduction JSTypedLowering::ReduceJSComparison(Node* node) {
|
||||
}
|
||||
return r.ChangeToPureOperator(stringOp);
|
||||
}
|
||||
if (r.IsStrong() && !r.BothInputsAre(Type::Number())) {
|
||||
return NoChange();
|
||||
}
|
||||
#if 0
|
||||
// TODO(turbofan): General ToNumber disabled for now because:
|
||||
// a) The inserted ToNumber operation screws up observability of valueOf.
|
||||
|
@ -11201,7 +11201,8 @@ HControlInstruction* HOptimizedGraphBuilder::BuildCompareInstruction(
|
||||
return result;
|
||||
} else {
|
||||
if (combined_rep.IsTagged() || combined_rep.IsNone()) {
|
||||
HCompareGeneric* result = Add<HCompareGeneric>(left, right, op);
|
||||
HCompareGeneric* result =
|
||||
Add<HCompareGeneric>(left, right, op, function_language_mode());
|
||||
result->set_observed_input_representation(1, left_rep);
|
||||
result->set_observed_input_representation(2, right_rep);
|
||||
if (result->HasObservableSideEffects()) {
|
||||
|
@ -1666,7 +1666,7 @@ static void BranchIfNotInternalizedString(MacroAssembler* masm,
|
||||
|
||||
|
||||
void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
Label check_unequal_objects;
|
||||
Label runtime_call, check_unequal_objects;
|
||||
Condition cc = GetCondition();
|
||||
|
||||
Label miss;
|
||||
@ -1700,12 +1700,17 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc != equal) {
|
||||
// Check for undefined. undefined OP undefined is false even though
|
||||
// undefined == undefined.
|
||||
Label check_for_nan;
|
||||
__ cmp(edx, isolate()->factory()->undefined_value());
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Move(eax, Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
if (strong()) {
|
||||
// In strong mode, this comparison must throw, so call the runtime.
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
} else {
|
||||
Label check_for_nan;
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Move(eax, Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for NaN. Compare heap numbers in a general way,
|
||||
@ -1714,12 +1719,20 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
Immediate(isolate()->factory()->heap_number_map()));
|
||||
__ j(equal, &generic_heap_number_comparison, Label::kNear);
|
||||
if (cc != equal) {
|
||||
__ mov(ecx, FieldOperand(eax, HeapObject::kMapOffset));
|
||||
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset));
|
||||
// Call runtime on identical JSObjects. Otherwise return equal.
|
||||
__ CmpObjectType(eax, FIRST_SPEC_OBJECT_TYPE, ecx);
|
||||
__ j(above_equal, ¬_identical);
|
||||
__ cmpb(ecx, static_cast<uint8_t>(FIRST_SPEC_OBJECT_TYPE));
|
||||
__ j(above_equal, &runtime_call, Label::kFar);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ CmpObjectType(eax, SYMBOL_TYPE, ecx);
|
||||
__ j(equal, ¬_identical);
|
||||
__ cmpb(ecx, static_cast<uint8_t>(SYMBOL_TYPE));
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
if (strong()) {
|
||||
// We have already tested for smis and heap numbers, so if both
|
||||
// arguments are not strings we must proceed to the slow case.
|
||||
__ test(ecx, Immediate(kIsNotStringMask));
|
||||
__ j(not_zero, &runtime_call, Label::kFar);
|
||||
}
|
||||
}
|
||||
__ Move(eax, Immediate(Smi::FromInt(EQUAL)));
|
||||
__ ret(0);
|
||||
@ -1864,7 +1877,6 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Non-strict equality. Objects are unequal if
|
||||
// they are both JSObjects and not undetectable,
|
||||
// and their pointers are different.
|
||||
Label not_both_objects;
|
||||
Label return_unequal;
|
||||
// At most one is a smi, so we can test for smi by adding the two.
|
||||
// A smi plus a heap object has the low bit set, a heap object plus
|
||||
@ -1873,11 +1885,11 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
STATIC_ASSERT(kSmiTagMask == 1);
|
||||
__ lea(ecx, Operand(eax, edx, times_1, 0));
|
||||
__ test(ecx, Immediate(kSmiTagMask));
|
||||
__ j(not_zero, ¬_both_objects, Label::kNear);
|
||||
__ j(not_zero, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(eax, FIRST_SPEC_OBJECT_TYPE, ecx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(edx, FIRST_SPEC_OBJECT_TYPE, ebx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
// We do not bail out after this point. Both are JSObjects, and
|
||||
// they are equal if and only if both are undetectable.
|
||||
// The and of the undetectable flags is 1 if and only if they are equal.
|
||||
@ -1894,8 +1906,8 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Return non-equal by returning the non-zero object pointer in eax,
|
||||
// or return equal if we fell through to here.
|
||||
__ ret(0); // rax, rdx were pushed
|
||||
__ bind(¬_both_objects);
|
||||
}
|
||||
__ bind(&runtime_call);
|
||||
|
||||
// Push arguments below the return address.
|
||||
__ pop(ecx);
|
||||
@ -1907,7 +1919,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == equal) {
|
||||
builtin = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
builtin = Builtins::COMPARE;
|
||||
builtin = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
__ push(Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
}
|
||||
|
||||
@ -3638,7 +3650,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ jmp(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -996,8 +996,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5164,7 +5164,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
|
@ -2503,7 +2503,7 @@ static Condition ComputeCompareCondition(Token::Value op) {
|
||||
void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
@ -2774,7 +2774,8 @@ void LCodeGen::DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr,
|
||||
void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
|
@ -1191,6 +1191,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
LOperand* context() { return inputs_[0]; }
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
16
src/ic/ic.cc
16
src/ic/ic.cc
@ -599,7 +599,8 @@ void CompareIC::Clear(Isolate* isolate, Address address, Code* target,
|
||||
CompareICStub stub(target->stub_key(), isolate);
|
||||
// Only clear CompareICs that can retain objects.
|
||||
if (stub.state() != CompareICState::KNOWN_OBJECT) return;
|
||||
SetTargetAtAddress(address, GetRawUninitialized(isolate, stub.op()),
|
||||
SetTargetAtAddress(address,
|
||||
GetRawUninitialized(isolate, stub.op(), stub.strong()),
|
||||
constant_pool);
|
||||
PatchInlinedSmiCode(address, DISABLE_INLINED_SMI_CHECK);
|
||||
}
|
||||
@ -2678,8 +2679,9 @@ RUNTIME_FUNCTION(BinaryOpIC_MissWithAllocationSite) {
|
||||
}
|
||||
|
||||
|
||||
Code* CompareIC::GetRawUninitialized(Isolate* isolate, Token::Value op) {
|
||||
CompareICStub stub(isolate, op, CompareICState::UNINITIALIZED,
|
||||
Code* CompareIC::GetRawUninitialized(Isolate* isolate, Token::Value op,
|
||||
bool strong) {
|
||||
CompareICStub stub(isolate, op, strong, CompareICState::UNINITIALIZED,
|
||||
CompareICState::UNINITIALIZED,
|
||||
CompareICState::UNINITIALIZED);
|
||||
Code* code = NULL;
|
||||
@ -2688,8 +2690,9 @@ Code* CompareIC::GetRawUninitialized(Isolate* isolate, Token::Value op) {
|
||||
}
|
||||
|
||||
|
||||
Handle<Code> CompareIC::GetUninitialized(Isolate* isolate, Token::Value op) {
|
||||
CompareICStub stub(isolate, op, CompareICState::UNINITIALIZED,
|
||||
Handle<Code> CompareIC::GetUninitialized(Isolate* isolate, Token::Value op,
|
||||
bool strong) {
|
||||
CompareICStub stub(isolate, op, strong, CompareICState::UNINITIALIZED,
|
||||
CompareICState::UNINITIALIZED,
|
||||
CompareICState::UNINITIALIZED);
|
||||
return stub.GetCode();
|
||||
@ -2706,7 +2709,8 @@ Code* CompareIC::UpdateCaches(Handle<Object> x, Handle<Object> y) {
|
||||
CompareICState::State state = CompareICState::TargetState(
|
||||
old_stub.state(), old_stub.left(), old_stub.right(), op_,
|
||||
HasInlinedSmiCode(address()), x, y);
|
||||
CompareICStub stub(isolate(), op_, new_left, new_right, state);
|
||||
CompareICStub stub(isolate(), op_, old_stub.strong(), new_left, new_right,
|
||||
state);
|
||||
if (state == CompareICState::KNOWN_OBJECT) {
|
||||
stub.set_known_map(
|
||||
Handle<Map>(Handle<JSObject>::cast(x)->map(), isolate()));
|
||||
|
@ -704,7 +704,8 @@ class CompareIC : public IC {
|
||||
static Condition ComputeCondition(Token::Value op);
|
||||
|
||||
// Factory method for getting an uninitialized compare stub.
|
||||
static Handle<Code> GetUninitialized(Isolate* isolate, Token::Value op);
|
||||
static Handle<Code> GetUninitialized(Isolate* isolate, Token::Value op,
|
||||
bool strong);
|
||||
|
||||
private:
|
||||
static bool HasInlinedSmiCode(Address address);
|
||||
@ -712,7 +713,8 @@ class CompareIC : public IC {
|
||||
bool strict() const { return op_ == Token::EQ_STRICT; }
|
||||
Condition GetCondition() const { return ComputeCondition(op_); }
|
||||
|
||||
static Code* GetRawUninitialized(Isolate* isolate, Token::Value op);
|
||||
static Code* GetRawUninitialized(Isolate* isolate, Token::Value op,
|
||||
bool strong);
|
||||
|
||||
static void Clear(Isolate* isolate, Address address, Code* target,
|
||||
ConstantPoolArray* constant_pool);
|
||||
|
@ -93,9 +93,8 @@ void InternalArrayNArgumentsConstructorStub::InitializeDescriptor(
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cc);
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cc, bool strong);
|
||||
static void EmitSmiNonsmiComparison(MacroAssembler* masm,
|
||||
Register lhs,
|
||||
Register rhs,
|
||||
@ -276,9 +275,8 @@ void DoubleToIStub::Generate(MacroAssembler* masm) {
|
||||
// Handle the case where the lhs and rhs are the same object.
|
||||
// Equality is almost reflexive (everything but NaN), so this is a test
|
||||
// for "identity and not NaN".
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cc) {
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cc, bool strong) {
|
||||
Label not_identical;
|
||||
Label heap_number, return_equal;
|
||||
Register exp_mask_reg = t5;
|
||||
@ -293,14 +291,31 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
// Smis. If it's not a heap number, then return equal.
|
||||
__ GetObjectType(a0, t4, t4);
|
||||
if (cc == less || cc == greater) {
|
||||
// Call runtime on identical JSObjects.
|
||||
__ Branch(slow, greater, t4, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Branch(slow, eq, t4, Operand(SYMBOL_TYPE));
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics, since
|
||||
// we need to throw a TypeError. Smis have already been ruled out.
|
||||
__ Branch(&return_equal, eq, t4, Operand(HEAP_NUMBER_TYPE));
|
||||
__ And(t4, t4, Operand(kIsNotStringMask));
|
||||
__ Branch(slow, ne, t4, Operand(zero_reg));
|
||||
}
|
||||
} else {
|
||||
__ Branch(&heap_number, eq, t4, Operand(HEAP_NUMBER_TYPE));
|
||||
// Comparing JS objects with <=, >= is complicated.
|
||||
if (cc != eq) {
|
||||
__ Branch(slow, greater, t4, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Branch(slow, eq, t4, Operand(SYMBOL_TYPE));
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics,
|
||||
// since we need to throw a TypeError. Smis and heap numbers have
|
||||
// already been ruled out.
|
||||
__ And(t4, t4, Operand(kIsNotStringMask));
|
||||
__ Branch(slow, ne, t4, Operand(zero_reg));
|
||||
}
|
||||
// Normally here we fall through to return_equal, but undefined is
|
||||
// special: (undefined == undefined) == true, but
|
||||
// (undefined <= undefined) == false! See ECMAScript 11.8.5.
|
||||
@ -585,7 +600,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
|
||||
// Handle the case where the objects are identical. Either returns the answer
|
||||
// or goes to slow. Only falls through if the objects were not identical.
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc);
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc, strong());
|
||||
|
||||
// If either is a Smi (we know that not both are), then they can only
|
||||
// be strictly equal if the other is a HeapNumber.
|
||||
@ -713,7 +728,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == eq) {
|
||||
native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
native = Builtins::COMPARE;
|
||||
native = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
int ncr; // NaN compare result.
|
||||
if (cc == lt || cc == le) {
|
||||
ncr = GREATER;
|
||||
@ -3743,7 +3758,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ Jump(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1053,8 +1053,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5238,7 +5238,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
}
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
PrepareForBailoutBeforeSplit(expr, true, if_true, if_false);
|
||||
|
@ -2543,7 +2543,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
@ -2845,7 +2845,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// On MIPS there is no need for a "no inlined smi code" marker (nop).
|
||||
|
||||
|
@ -1175,6 +1175,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -92,9 +92,8 @@ void InternalArrayNArgumentsConstructorStub::InitializeDescriptor(
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cc);
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cc, bool strong);
|
||||
static void EmitSmiNonsmiComparison(MacroAssembler* masm,
|
||||
Register lhs,
|
||||
Register rhs,
|
||||
@ -272,9 +271,8 @@ void DoubleToIStub::Generate(MacroAssembler* masm) {
|
||||
// Handle the case where the lhs and rhs are the same object.
|
||||
// Equality is almost reflexive (everything but NaN), so this is a test
|
||||
// for "identity and not NaN".
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
Label* slow,
|
||||
Condition cc) {
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cc, bool strong) {
|
||||
Label not_identical;
|
||||
Label heap_number, return_equal;
|
||||
Register exp_mask_reg = t1;
|
||||
@ -289,14 +287,31 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm,
|
||||
// Smis. If it's not a heap number, then return equal.
|
||||
__ GetObjectType(a0, t0, t0);
|
||||
if (cc == less || cc == greater) {
|
||||
// Call runtime on identical JSObjects.
|
||||
__ Branch(slow, greater, t0, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Branch(slow, eq, t0, Operand(SYMBOL_TYPE));
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics, since
|
||||
// we need to throw a TypeError. Smis have already been ruled out.
|
||||
__ Branch(&return_equal, eq, t0, Operand(HEAP_NUMBER_TYPE));
|
||||
__ And(t0, t0, Operand(kIsNotStringMask));
|
||||
__ Branch(slow, ne, t0, Operand(zero_reg));
|
||||
}
|
||||
} else {
|
||||
__ Branch(&heap_number, eq, t0, Operand(HEAP_NUMBER_TYPE));
|
||||
// Comparing JS objects with <=, >= is complicated.
|
||||
if (cc != eq) {
|
||||
__ Branch(slow, greater, t0, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ Branch(slow, eq, t0, Operand(SYMBOL_TYPE));
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics,
|
||||
// since we need to throw a TypeError. Smis and heap numbers have
|
||||
// already been ruled out.
|
||||
__ And(t0, t0, Operand(kIsNotStringMask));
|
||||
__ Branch(slow, ne, t0, Operand(zero_reg));
|
||||
}
|
||||
// Normally here we fall through to return_equal, but undefined is
|
||||
// special: (undefined == undefined) == true, but
|
||||
// (undefined <= undefined) == false! See ECMAScript 11.8.5.
|
||||
@ -580,7 +595,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
|
||||
// Handle the case where the objects are identical. Either returns the answer
|
||||
// or goes to slow. Only falls through if the objects were not identical.
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc);
|
||||
EmitIdenticalObjectComparison(masm, &slow, cc, strong());
|
||||
|
||||
// If either is a Smi (we know that not both are), then they can only
|
||||
// be strictly equal if the other is a HeapNumber.
|
||||
@ -708,7 +723,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == eq) {
|
||||
native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
native = Builtins::COMPARE;
|
||||
native = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
int ncr; // NaN compare result.
|
||||
if (cc == lt || cc == le) {
|
||||
ncr = GREATER;
|
||||
@ -3786,7 +3801,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ Jump(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1050,8 +1050,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5241,7 +5241,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
}
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
PrepareForBailoutBeforeSplit(expr, true, if_true, if_false);
|
||||
|
@ -2542,7 +2542,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
@ -2844,7 +2844,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// On MIPS there is no need for a "no inlined smi code" marker (nop).
|
||||
|
||||
|
@ -1174,6 +1174,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -94,7 +94,7 @@ void InternalArrayNArgumentsConstructorStub::InitializeDescriptor(
|
||||
|
||||
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cond);
|
||||
Condition cond, bool strong);
|
||||
static void EmitSmiNonsmiComparison(MacroAssembler* masm, Register lhs,
|
||||
Register rhs, Label* lhs_not_nan,
|
||||
Label* slow, bool strict);
|
||||
@ -249,7 +249,7 @@ void DoubleToIStub::Generate(MacroAssembler* masm) {
|
||||
// Equality is almost reflexive (everything but NaN), so this is a test
|
||||
// for "identity and not NaN".
|
||||
static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
Condition cond) {
|
||||
Condition cond, bool strong) {
|
||||
Label not_identical;
|
||||
Label heap_number, return_equal;
|
||||
__ cmp(r3, r4);
|
||||
@ -260,10 +260,20 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
// They are both equal and they are not both Smis so both of them are not
|
||||
// Smis. If it's not a heap number, then return equal.
|
||||
if (cond == lt || cond == gt) {
|
||||
// Call runtime on identical JSObjects.
|
||||
__ CompareObjectType(r3, r7, r7, FIRST_SPEC_OBJECT_TYPE);
|
||||
__ bge(slow);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ cmpi(r7, Operand(SYMBOL_TYPE));
|
||||
__ beq(slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics, since
|
||||
// we need to throw a TypeError. Smis have already been ruled out.
|
||||
__ cmpi(r7, Operand(HEAP_NUMBER_TYPE));
|
||||
__ beq(&return_equal);
|
||||
__ andi(r7, r7, Operand(kIsNotStringMask));
|
||||
__ bne(slow, cr0);
|
||||
}
|
||||
} else {
|
||||
__ CompareObjectType(r3, r7, r7, HEAP_NUMBER_TYPE);
|
||||
__ beq(&heap_number);
|
||||
@ -271,8 +281,16 @@ static void EmitIdenticalObjectComparison(MacroAssembler* masm, Label* slow,
|
||||
if (cond != eq) {
|
||||
__ cmpi(r7, Operand(FIRST_SPEC_OBJECT_TYPE));
|
||||
__ bge(slow);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ cmpi(r7, Operand(SYMBOL_TYPE));
|
||||
__ beq(slow);
|
||||
if (strong) {
|
||||
// Call the runtime on anything that is converted in the semantics,
|
||||
// since we need to throw a TypeError. Smis and heap numbers have
|
||||
// already been ruled out.
|
||||
__ andi(r7, r7, Operand(kIsNotStringMask));
|
||||
__ bne(slow, cr0);
|
||||
}
|
||||
// Normally here we fall through to return_equal, but undefined is
|
||||
// special: (undefined == undefined) == true, but
|
||||
// (undefined <= undefined) == false! See ECMAScript 11.8.5.
|
||||
@ -687,7 +705,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == eq) {
|
||||
native = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
native = Builtins::COMPARE;
|
||||
native = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
int ncr; // NaN compare result
|
||||
if (cc == lt || cc == le) {
|
||||
ncr = GREATER;
|
||||
@ -3780,7 +3798,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ Jump(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1017,8 +1017,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5251,7 +5251,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
PrepareForBailoutBeforeSplit(expr, true, if_true, if_false);
|
||||
|
@ -2671,7 +2671,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// This instruction also signals no smi code inlined
|
||||
__ cmpi(r3, Operand::Zero());
|
||||
@ -2978,7 +2978,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(cp));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
// This instruction also signals no smi code inlined
|
||||
__ cmpi(r3, Operand::Zero());
|
||||
|
@ -1151,6 +1151,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
var EQUALS;
|
||||
var STRICT_EQUALS;
|
||||
var COMPARE;
|
||||
var COMPARE_STRONG;
|
||||
var ADD;
|
||||
var ADD_STRONG;
|
||||
var STRING_ADD_LEFT;
|
||||
@ -204,6 +205,14 @@ COMPARE = function COMPARE(x, ncr) {
|
||||
}
|
||||
}
|
||||
|
||||
// Strong mode COMPARE throws if an implicit conversion would be performed
|
||||
COMPARE_STRONG = function COMPARE_STRONG(x, ncr) {
|
||||
if (IS_STRING(this) && IS_STRING(x)) return %_StringCompare(this, x);
|
||||
if (IS_NUMBER(this) && IS_NUMBER(x)) return %NumberCompare(this, x, ncr);
|
||||
|
||||
throw %MakeTypeError('strong_implicit_cast');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -----------------------------------
|
||||
|
@ -1533,7 +1533,7 @@ static void BranchIfNotInternalizedString(MacroAssembler* masm,
|
||||
|
||||
|
||||
void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
Label check_unequal_objects, done;
|
||||
Label runtime_call, check_unequal_objects, done;
|
||||
Condition cc = GetCondition();
|
||||
Factory* factory = isolate()->factory();
|
||||
|
||||
@ -1566,12 +1566,17 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc != equal) {
|
||||
// Check for undefined. undefined OP undefined is false even though
|
||||
// undefined == undefined.
|
||||
Label check_for_nan;
|
||||
__ CompareRoot(rdx, Heap::kUndefinedValueRootIndex);
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Set(rax, NegativeComparisonResult(cc));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
if (strong()) {
|
||||
// In strong mode, this comparison must throw, so call the runtime.
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
} else {
|
||||
Label check_for_nan;
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Set(rax, NegativeComparisonResult(cc));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for NaN. Sadly, we can't just compare to Factory::nan_value(),
|
||||
@ -1582,12 +1587,20 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
factory->heap_number_map());
|
||||
__ j(equal, &heap_number, Label::kNear);
|
||||
if (cc != equal) {
|
||||
__ movp(rcx, FieldOperand(rax, HeapObject::kMapOffset));
|
||||
__ movzxbl(rcx, FieldOperand(rcx, Map::kInstanceTypeOffset));
|
||||
// Call runtime on identical objects. Otherwise return equal.
|
||||
__ CmpObjectType(rax, FIRST_SPEC_OBJECT_TYPE, rcx);
|
||||
__ j(above_equal, ¬_identical, Label::kNear);
|
||||
__ cmpb(rcx, Immediate(static_cast<uint8_t>(FIRST_SPEC_OBJECT_TYPE)));
|
||||
__ j(above_equal, &runtime_call, Label::kFar);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ CmpObjectType(rax, SYMBOL_TYPE, rcx);
|
||||
__ j(equal, ¬_identical, Label::kNear);
|
||||
__ cmpb(rcx, Immediate(static_cast<uint8_t>(SYMBOL_TYPE)));
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
if (strong()) {
|
||||
// We have already tested for smis and heap numbers, so if both
|
||||
// arguments are not strings we must proceed to the slow case.
|
||||
__ testb(rcx, Immediate(kIsNotStringMask));
|
||||
__ j(not_zero, &runtime_call, Label::kFar);
|
||||
}
|
||||
}
|
||||
__ Set(rax, EQUAL);
|
||||
__ ret(0);
|
||||
@ -1734,7 +1747,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Not strict equality. Objects are unequal if
|
||||
// they are both JSObjects and not undetectable,
|
||||
// and their pointers are different.
|
||||
Label not_both_objects, return_unequal;
|
||||
Label return_unequal;
|
||||
// At most one is a smi, so we can test for smi by adding the two.
|
||||
// A smi plus a heap object has the low bit set, a heap object plus
|
||||
// a heap object has the low bit clear.
|
||||
@ -1742,11 +1755,11 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
STATIC_ASSERT(kSmiTagMask == 1);
|
||||
__ leap(rcx, Operand(rax, rdx, times_1, 0));
|
||||
__ testb(rcx, Immediate(kSmiTagMask));
|
||||
__ j(not_zero, ¬_both_objects, Label::kNear);
|
||||
__ j(not_zero, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(rax, FIRST_SPEC_OBJECT_TYPE, rbx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(rdx, FIRST_SPEC_OBJECT_TYPE, rcx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
__ testb(FieldOperand(rbx, Map::kBitFieldOffset),
|
||||
Immediate(1 << Map::kIsUndetectable));
|
||||
__ j(zero, &return_unequal, Label::kNear);
|
||||
@ -1760,8 +1773,8 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Return non-equal by returning the non-zero object pointer in rax,
|
||||
// or return equal if we fell through to here.
|
||||
__ ret(0);
|
||||
__ bind(¬_both_objects);
|
||||
}
|
||||
__ bind(&runtime_call);
|
||||
|
||||
// Push arguments below the return address to prepare jump to builtin.
|
||||
__ PopReturnAddressTo(rcx);
|
||||
@ -1773,7 +1786,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == equal) {
|
||||
builtin = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
builtin = Builtins::COMPARE;
|
||||
builtin = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
__ Push(Smi::FromInt(NegativeComparisonResult(cc)));
|
||||
}
|
||||
|
||||
@ -3598,7 +3611,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ jmp(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -1019,8 +1019,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5181,7 +5181,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
|
@ -2549,7 +2549,7 @@ void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(rsi));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = TokenToCondition(op, false);
|
||||
@ -2833,7 +2833,8 @@ void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
DCHECK(ToRegister(instr->context()).is(rsi));
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = TokenToCondition(op, false);
|
||||
|
@ -1174,6 +1174,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
||||
|
@ -1341,7 +1341,7 @@ static void BranchIfNotInternalizedString(MacroAssembler* masm,
|
||||
|
||||
|
||||
void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
Label check_unequal_objects;
|
||||
Label runtime_call, check_unequal_objects;
|
||||
Condition cc = GetCondition();
|
||||
|
||||
Label miss;
|
||||
@ -1375,26 +1375,39 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc != equal) {
|
||||
// Check for undefined. undefined OP undefined is false even though
|
||||
// undefined == undefined.
|
||||
Label check_for_nan;
|
||||
__ cmp(edx, isolate()->factory()->undefined_value());
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Move(eax, Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
if (strong()) {
|
||||
// In strong mode, this comparison must throw, so call the runtime.
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
} else {
|
||||
Label check_for_nan;
|
||||
__ j(not_equal, &check_for_nan, Label::kNear);
|
||||
__ Move(eax, Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
__ ret(0);
|
||||
__ bind(&check_for_nan);
|
||||
}
|
||||
}
|
||||
|
||||
// Test for NaN. Compare heap numbers in a general way,
|
||||
// to hanlde NaNs correctly.
|
||||
// to handle NaNs correctly.
|
||||
__ cmp(FieldOperand(edx, HeapObject::kMapOffset),
|
||||
Immediate(isolate()->factory()->heap_number_map()));
|
||||
__ j(equal, &generic_heap_number_comparison, Label::kNear);
|
||||
if (cc != equal) {
|
||||
__ mov(ecx, FieldOperand(eax, HeapObject::kMapOffset));
|
||||
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset));
|
||||
// Call runtime on identical JSObjects. Otherwise return equal.
|
||||
__ CmpObjectType(eax, FIRST_SPEC_OBJECT_TYPE, ecx);
|
||||
__ j(above_equal, ¬_identical);
|
||||
__ cmpb(ecx, static_cast<uint8_t>(FIRST_SPEC_OBJECT_TYPE));
|
||||
__ j(above_equal, &runtime_call, Label::kFar);
|
||||
// Call runtime on identical symbols since we need to throw a TypeError.
|
||||
__ CmpObjectType(eax, SYMBOL_TYPE, ecx);
|
||||
__ j(equal, ¬_identical);
|
||||
__ cmpb(ecx, static_cast<uint8_t>(SYMBOL_TYPE));
|
||||
__ j(equal, &runtime_call, Label::kFar);
|
||||
if (strong()) {
|
||||
// We have already tested for smis and heap numbers, so if both
|
||||
// arguments are not strings we must proceed to the slow case.
|
||||
__ test(ecx, Immediate(kIsNotStringMask));
|
||||
__ j(not_zero, &runtime_call, Label::kFar);
|
||||
}
|
||||
}
|
||||
__ Move(eax, Immediate(Smi::FromInt(EQUAL)));
|
||||
__ ret(0);
|
||||
@ -1551,7 +1564,6 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Non-strict equality. Objects are unequal if
|
||||
// they are both JSObjects and not undetectable,
|
||||
// and their pointers are different.
|
||||
Label not_both_objects;
|
||||
Label return_unequal;
|
||||
// At most one is a smi, so we can test for smi by adding the two.
|
||||
// A smi plus a heap object has the low bit set, a heap object plus
|
||||
@ -1560,11 +1572,11 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
STATIC_ASSERT(kSmiTagMask == 1);
|
||||
__ lea(ecx, Operand(eax, edx, times_1, 0));
|
||||
__ test(ecx, Immediate(kSmiTagMask));
|
||||
__ j(not_zero, ¬_both_objects, Label::kNear);
|
||||
__ j(not_zero, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(eax, FIRST_SPEC_OBJECT_TYPE, ecx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
__ CmpObjectType(edx, FIRST_SPEC_OBJECT_TYPE, ebx);
|
||||
__ j(below, ¬_both_objects, Label::kNear);
|
||||
__ j(below, &runtime_call, Label::kNear);
|
||||
// We do not bail out after this point. Both are JSObjects, and
|
||||
// they are equal if and only if both are undetectable.
|
||||
// The and of the undetectable flags is 1 if and only if they are equal.
|
||||
@ -1581,8 +1593,8 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
// Return non-equal by returning the non-zero object pointer in eax,
|
||||
// or return equal if we fell through to here.
|
||||
__ ret(0); // rax, rdx were pushed
|
||||
__ bind(¬_both_objects);
|
||||
}
|
||||
__ bind(&runtime_call);
|
||||
|
||||
// Push arguments below the return address.
|
||||
__ pop(ecx);
|
||||
@ -1594,7 +1606,7 @@ void CompareICStub::GenerateGeneric(MacroAssembler* masm) {
|
||||
if (cc == equal) {
|
||||
builtin = strict() ? Builtins::STRICT_EQUALS : Builtins::EQUALS;
|
||||
} else {
|
||||
builtin = Builtins::COMPARE;
|
||||
builtin = strong() ? Builtins::COMPARE_STRONG : Builtins::COMPARE;
|
||||
__ push(Immediate(Smi::FromInt(NegativeComparisonResult(cc))));
|
||||
}
|
||||
|
||||
@ -3300,7 +3312,7 @@ void CompareICStub::GenerateNumbers(MacroAssembler* masm) {
|
||||
|
||||
__ bind(&unordered);
|
||||
__ bind(&generic_stub);
|
||||
CompareICStub stub(isolate(), op(), CompareICState::GENERIC,
|
||||
CompareICStub stub(isolate(), op(), strong(), CompareICState::GENERIC,
|
||||
CompareICState::GENERIC, CompareICState::GENERIC);
|
||||
__ jmp(stub.GetCode(), RelocInfo::CODE_TARGET);
|
||||
|
||||
|
@ -988,8 +988,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
|
||||
// Record position before stub call for type feedback.
|
||||
SetSourcePosition(clause->position());
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), Token::EQ_STRICT).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), Token::EQ_STRICT,
|
||||
language_mode()).code();
|
||||
CallIC(ic, clause->CompareId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
@ -5151,7 +5151,8 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) {
|
||||
|
||||
// Record position and call the compare IC.
|
||||
SetSourcePosition(expr->position());
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, language_mode()).code();
|
||||
CallIC(ic, expr->CompareOperationFeedbackId());
|
||||
patch_site.EmitPatchInfo();
|
||||
|
||||
|
@ -2779,7 +2779,7 @@ static Condition ComputeCompareCondition(Token::Value op) {
|
||||
void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) {
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op, SLOPPY).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
@ -3051,7 +3051,8 @@ void LCodeGen::DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr,
|
||||
void LCodeGen::DoCmpT(LCmpT* instr) {
|
||||
Token::Value op = instr->op();
|
||||
|
||||
Handle<Code> ic = CodeFactory::CompareIC(isolate(), op).code();
|
||||
Handle<Code> ic =
|
||||
CodeFactory::CompareIC(isolate(), op, instr->language_mode()).code();
|
||||
CallCode(ic, RelocInfo::CODE_TARGET, instr);
|
||||
|
||||
Condition condition = ComputeCompareCondition(op);
|
||||
|
@ -1202,6 +1202,8 @@ class LCmpT final : public LTemplateInstruction<1, 3, 0> {
|
||||
DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
|
||||
DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
|
||||
|
||||
LanguageMode language_mode() { return hydrogen()->language_mode(); }
|
||||
|
||||
LOperand* context() { return inputs_[0]; }
|
||||
Token::Value op() const { return hydrogen()->token(); }
|
||||
};
|
||||
|
@ -688,18 +688,20 @@ TEST_WITH_STRONG(MixedComparison1) {
|
||||
for (size_t j = 0; j < arraysize(types); j++) {
|
||||
Node* p1 = R.Parameter(types[j], 1);
|
||||
{
|
||||
Node* cmp = R.Binop(R.javascript.LessThan(language_mode), p0, p1);
|
||||
const Operator* less_than = R.javascript.LessThan(language_mode);
|
||||
Node* cmp = R.Binop(less_than, p0, p1);
|
||||
Node* r = R.reduce(cmp);
|
||||
|
||||
if (!types[i]->Maybe(Type::String()) ||
|
||||
!types[j]->Maybe(Type::String())) {
|
||||
if (types[i]->Is(Type::String()) && types[j]->Is(Type::String())) {
|
||||
R.CheckPureBinop(R.simplified.StringLessThan(), r);
|
||||
} else {
|
||||
R.CheckPureBinop(R.simplified.NumberLessThan(), r);
|
||||
}
|
||||
if (types[i]->Is(Type::String()) && types[j]->Is(Type::String())) {
|
||||
R.CheckPureBinop(R.simplified.StringLessThan(), r);
|
||||
} else if ((types[i]->Is(Type::Number()) &&
|
||||
types[j]->Is(Type::Number())) ||
|
||||
(!is_strong(language_mode) &&
|
||||
(!types[i]->Maybe(Type::String()) ||
|
||||
!types[j]->Maybe(Type::String())))) {
|
||||
R.CheckPureBinop(R.simplified.NumberLessThan(), r);
|
||||
} else {
|
||||
CHECK_EQ(cmp, r); // No reduction of mixed types.
|
||||
// No reduction of mixed types.
|
||||
CHECK_EQ(r->op(), less_than);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
//******************************************************************************
|
||||
// Number function declarations
|
||||
function inline_add_strong(x, y) {
|
||||
"use strong";
|
||||
return x + y;
|
||||
@ -105,6 +107,42 @@ function inline_sar_strong_outer(x, y) {
|
||||
return inline_sar_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_less_strong(x, y) {
|
||||
"use strong";
|
||||
return x < y;
|
||||
}
|
||||
|
||||
function inline_less_strong_outer(x, y) {
|
||||
return inline_less_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_strong(x, y) {
|
||||
"use strong";
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function inline_greater_strong_outer(x, y) {
|
||||
return inline_greater_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_less_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function inline_less_equal_strong_outer(x, y) {
|
||||
return inline_less_equal_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return x >= y;
|
||||
}
|
||||
|
||||
function inline_greater_equal_strong_outer(x, y) {
|
||||
return inline_greater_equal_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_add(x, y) {
|
||||
return x + y;
|
||||
}
|
||||
@ -204,19 +242,170 @@ function inline_sar_outer_strong(x, y) {
|
||||
return inline_sar(x, y);
|
||||
}
|
||||
|
||||
let strong_inner_funcs = [inline_add_strong_outer, inline_sub_strong_outer,
|
||||
inline_mul_strong_outer, inline_div_strong_outer,
|
||||
inline_mod_strong_outer, inline_or_strong_outer,
|
||||
inline_and_strong_outer, inline_xor_strong_outer,
|
||||
inline_shl_strong_outer, inline_shr_strong_outer];
|
||||
function inline_less(x, y) {
|
||||
return x < y;
|
||||
}
|
||||
|
||||
let strong_outer_funcs = [inline_add_outer_strong, inline_sub_outer_strong,
|
||||
inline_mul_outer_strong, inline_div_outer_strong,
|
||||
inline_mod_outer_strong, inline_or_outer_strong,
|
||||
inline_and_outer_strong, inline_xor_outer_strong,
|
||||
inline_shl_outer_strong, inline_shr_outer_strong];
|
||||
function inline_less_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_less(x, y);
|
||||
}
|
||||
|
||||
for (let strong_inner_func of strong_inner_funcs) {
|
||||
function inline_greater(x, y) {
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function inline_greater_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_greater(x, y);
|
||||
}
|
||||
|
||||
function inline_less_equal(x, y) {
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function inline_less_equal_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_less_equal(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_equal(x, y) {
|
||||
return x >>> y;
|
||||
}
|
||||
|
||||
function inline_greater_equal_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_greater_equal(x, y);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// String function declarations
|
||||
function inline_add_string_strong(x, y) {
|
||||
"use strong";
|
||||
return x + y;
|
||||
}
|
||||
|
||||
function inline_add_string_strong_outer(x, y) {
|
||||
return inline_add_string_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_less_string_strong(x, y) {
|
||||
"use strong";
|
||||
return x < y;
|
||||
}
|
||||
|
||||
function inline_less_string_strong_outer(x, y) {
|
||||
return inline_less_string_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_string_strong(x, y) {
|
||||
"use strong";
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function inline_greater_string_strong_outer(x, y) {
|
||||
return inline_greater_string_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_less_equal_string_strong(x, y) {
|
||||
"use strong";
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function inline_less_equal_string_strong_outer(x, y) {
|
||||
return inline_less_equal_string_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_equal_string_strong(x, y) {
|
||||
"use strong";
|
||||
return x >= y;
|
||||
}
|
||||
|
||||
function inline_greater_equal_string_strong_outer(x, y) {
|
||||
return inline_greater_equal_string_strong(x, y);
|
||||
}
|
||||
|
||||
function inline_add_string(x, y) {
|
||||
return x + y;
|
||||
}
|
||||
|
||||
function inline_add_string_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_add_string(x, y);
|
||||
}
|
||||
|
||||
function inline_less_string(x, y) {
|
||||
return x < y;
|
||||
}
|
||||
|
||||
function inline_less_string_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_less_string(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_string(x, y) {
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function inline_greater_string_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_greater_string(x, y);
|
||||
}
|
||||
|
||||
function inline_less_equal_string(x, y) {
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function inline_less_equal_string_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_less_equal_string(x, y);
|
||||
}
|
||||
|
||||
function inline_greater_equal_string(x, y) {
|
||||
return x >= y;
|
||||
}
|
||||
|
||||
function inline_greater_equal_string_outer_strong(x, y) {
|
||||
"use strong";
|
||||
return inline_greater_equal_string(x, y);
|
||||
}
|
||||
|
||||
|
||||
//******************************************************************************
|
||||
// Testing
|
||||
let strong_inner_funcs_num = [inline_add_strong_outer, inline_sub_strong_outer,
|
||||
inline_mul_strong_outer, inline_div_strong_outer,
|
||||
inline_mod_strong_outer, inline_or_strong_outer,
|
||||
inline_and_strong_outer, inline_xor_strong_outer,
|
||||
inline_shl_strong_outer, inline_shr_strong_outer,
|
||||
inline_less_strong_outer,
|
||||
inline_greater_strong_outer,
|
||||
inline_less_equal_strong_outer,
|
||||
inline_greater_equal_strong_outer];
|
||||
|
||||
let strong_outer_funcs_num = [inline_add_outer_strong, inline_sub_outer_strong,
|
||||
inline_mul_outer_strong, inline_div_outer_strong,
|
||||
inline_mod_outer_strong, inline_or_outer_strong,
|
||||
inline_and_outer_strong, inline_xor_outer_strong,
|
||||
inline_shl_outer_strong, inline_shr_outer_strong,
|
||||
inline_less_outer_strong,
|
||||
inline_greater_outer_strong,
|
||||
inline_less_equal_outer_strong,
|
||||
inline_greater_equal_outer_strong];
|
||||
|
||||
let strong_inner_funcs_string = [inline_add_string_strong_outer,
|
||||
inline_less_string_strong_outer,
|
||||
inline_greater_string_strong_outer,
|
||||
inline_less_equal_string_strong_outer,
|
||||
inline_greater_equal_string_strong_outer];
|
||||
|
||||
let strong_outer_funcs_string = [inline_add_string_outer_strong,
|
||||
inline_less_string_outer_strong,
|
||||
inline_greater_string_outer_strong,
|
||||
inline_less_equal_string_outer_strong,
|
||||
inline_greater_equal_string_outer_strong];
|
||||
|
||||
for (let strong_inner_func of strong_inner_funcs_num) {
|
||||
assertThrows(function(){strong_inner_func(1, {})}, TypeError);
|
||||
for (var i = 0; i < 100; i++) {
|
||||
strong_inner_func(1, 2);
|
||||
@ -225,7 +414,7 @@ for (let strong_inner_func of strong_inner_funcs) {
|
||||
assertThrows(function(){strong_inner_func(1, {})}, TypeError);
|
||||
}
|
||||
|
||||
for (let strong_outer_func of strong_outer_funcs) {
|
||||
for (let strong_outer_func of strong_outer_funcs_num) {
|
||||
assertDoesNotThrow(function(){strong_outer_func(1, {})});
|
||||
for (var i = 0; i < 100; i++) {
|
||||
strong_outer_func(1, 2);
|
||||
@ -233,3 +422,21 @@ for (let strong_outer_func of strong_outer_funcs) {
|
||||
%OptimizeFunctionOnNextCall(strong_outer_func);
|
||||
assertDoesNotThrow(function(){strong_outer_func(1, {})});
|
||||
}
|
||||
|
||||
for (let strong_inner_func of strong_inner_funcs_string) {
|
||||
assertThrows(function(){strong_inner_func("foo", {})}, TypeError);
|
||||
for (var i = 0; i < 100; i++) {
|
||||
strong_inner_func("foo", "bar");
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(strong_inner_func);
|
||||
assertThrows(function(){strong_inner_func("foo", {})}, TypeError);
|
||||
}
|
||||
|
||||
for (let strong_outer_func of strong_outer_funcs_string) {
|
||||
assertDoesNotThrow(function(){strong_outer_func("foo", {})});
|
||||
for (var i = 0; i < 100; i++) {
|
||||
strong_outer_func("foo", "bar");
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(strong_outer_func);
|
||||
assertDoesNotThrow(function(){strong_outer_func("foo", {})});
|
||||
}
|
||||
|
@ -6,22 +6,26 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
// TODO(conradw): Implement other strong operators
|
||||
// Boolean indicates whether an operator can be part of a compound assignment.
|
||||
let strongNumberBinops = [
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
"|",
|
||||
"&",
|
||||
"^",
|
||||
"<<",
|
||||
">>",
|
||||
">>>",
|
||||
["-", true],
|
||||
["*", true],
|
||||
["/", true],
|
||||
["%", true],
|
||||
["|", true],
|
||||
["&", true],
|
||||
["^", true],
|
||||
["<<", true],
|
||||
[">>", true],
|
||||
[">>>", true]
|
||||
];
|
||||
|
||||
let strongStringOrNumberBinops = [
|
||||
"+"
|
||||
["+", true],
|
||||
["<", false],
|
||||
[">", false],
|
||||
["<=", false],
|
||||
[">=", false]
|
||||
];
|
||||
|
||||
let strongBinops = strongNumberBinops.concat(strongStringOrNumberBinops);
|
||||
@ -33,6 +37,8 @@ let strongUnops = [
|
||||
];
|
||||
|
||||
let nonStringOrNumberValues = [
|
||||
"null",
|
||||
"undefined",
|
||||
"{}",
|
||||
"false",
|
||||
"(function(){})",
|
||||
@ -132,6 +138,46 @@ function sar_strong(x, y) {
|
||||
return x >>> y;
|
||||
}
|
||||
|
||||
function less_strong(x, y) {
|
||||
"use strong";
|
||||
return x < y;
|
||||
}
|
||||
|
||||
function less_num_strong(x, y) {
|
||||
"use strong";
|
||||
return x < y;
|
||||
}
|
||||
|
||||
function greater_strong(x, y) {
|
||||
"use strong";
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function greater_num_strong(x, y) {
|
||||
"use strong";
|
||||
return x > y;
|
||||
}
|
||||
|
||||
function less_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function less_equal_num_strong(x, y) {
|
||||
"use strong";
|
||||
return x <= y;
|
||||
}
|
||||
|
||||
function greater_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return x >= y;
|
||||
}
|
||||
|
||||
function greater_equal_num_strong(x, y) {
|
||||
"use strong";
|
||||
return x >= y;
|
||||
}
|
||||
|
||||
function typed_add_strong(x, y) {
|
||||
"use strong";
|
||||
return (+x) + (+y);
|
||||
@ -187,15 +233,40 @@ function typed_sar_strong(x, y) {
|
||||
return (+x) >>> (+y);
|
||||
}
|
||||
|
||||
function typed_less_strong(x, y) {
|
||||
"use strong";
|
||||
return (+x) < (+y);
|
||||
}
|
||||
|
||||
function typed_greater_strong(x, y) {
|
||||
"use strong";
|
||||
return (+x) > (+y);
|
||||
}
|
||||
|
||||
function typed_less_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return (+x) <= (+y);
|
||||
}
|
||||
|
||||
function typed_greater_equal_strong(x, y) {
|
||||
"use strong";
|
||||
return (+x) >= (+y);
|
||||
}
|
||||
|
||||
let strongNumberFuncs = [add_num_strong, sub_strong, mul_strong, div_strong,
|
||||
mod_strong, or_strong, and_strong, xor_strong,
|
||||
shl_strong, shr_strong, sar_strong, typed_add_strong,
|
||||
shl_strong, shr_strong, sar_strong, less_num_strong,
|
||||
greater_num_strong, less_equal_num_strong,
|
||||
greater_equal_num_strong, typed_add_strong,
|
||||
typed_sub_strong, typed_mul_strong, typed_div_strong,
|
||||
typed_mod_strong, typed_or_strong, typed_and_strong,
|
||||
typed_xor_strong, typed_shl_strong, typed_shr_strong,
|
||||
typed_sar_strong];
|
||||
typed_sar_strong, typed_less_strong,
|
||||
typed_greater_strong, typed_less_equal_strong,
|
||||
typed_greater_equal_strong];
|
||||
|
||||
let strongStringOrNumberFuncs = [add_strong];
|
||||
let strongStringOrNumberFuncs = [add_strong, less_strong, greater_strong,
|
||||
less_equal_strong, greater_equal_strong];
|
||||
|
||||
let strongFuncs = strongNumberFuncs.concat(strongStringOrNumberFuncs);
|
||||
|
||||
@ -214,16 +285,20 @@ function assertStrongThrowBehaviour(expr) {
|
||||
|
||||
function checkArgumentCombinations(op, leftList, rightList, willThrow) {
|
||||
for (let v1 of leftList) {
|
||||
let assignExpr = "foo " + op + "= " + v1 + ";";
|
||||
let assignExpr = "foo " + op[0] + "= " + v1 + ";";
|
||||
for (let v2 of rightList) {
|
||||
let compoundAssignment = "'use strong'; let foo = " + v2 + "; " +
|
||||
assignExpr;
|
||||
if(willThrow) {
|
||||
assertThrows(compoundAssignment, TypeError);
|
||||
assertStrongThrowBehaviour("(" + v1 + op + v2 + ")");
|
||||
if (willThrow) {
|
||||
if (op[1]) {
|
||||
assertThrows(compoundAssignment, TypeError);
|
||||
}
|
||||
assertStrongThrowBehaviour("(" + v1 + op[0] + v2 + ")");
|
||||
} else {
|
||||
assertDoesNotThrow(compoundAssignment);
|
||||
assertStrongNonThrowBehaviour("(" + v1 + op + v2 + ")");
|
||||
if (op[1]) {
|
||||
assertDoesNotThrow(compoundAssignment);
|
||||
}
|
||||
assertStrongNonThrowBehaviour("(" + v1 + op[0] + v2 + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user