instr = AssignPointerMap(instr);
if (hinstr->HasObservableSideEffects()) {
- ASSERT(hinstr->next()->IsSimulate());
- HSimulate* sim = HSimulate::cast(hinstr->next());
ASSERT(instruction_pending_deoptimization_environment_ == NULL);
ASSERT(pending_deoptimization_ast_id_.IsNone());
+ if (!hinstr->IsControlInstruction()) {
+ ASSERT(hinstr->next()->IsSimulate());
+ HSimulate* sim = HSimulate::cast(hinstr->next());
+ pending_deoptimization_ast_id_ = sim->ast_id();
+ } else {
+ pending_deoptimization_ast_id_ = BailoutId::PendingMarker();
+ }
instruction_pending_deoptimization_environment_ = instr;
- pending_deoptimization_ast_id_ = sim->ast_id();
}
// If instruction does not have side-effects lazy deoptimization
}
-LInstruction* LChunkBuilder::DoCompareGeneric(HCompareGeneric* instr) {
+LInstruction* LChunkBuilder::DoCompareGenericAndBranch(
+ HCompareGenericAndBranch* instr) {
ASSERT(instr->left()->representation().IsTagged());
ASSERT(instr->right()->representation().IsTagged());
LOperand* left = UseFixed(instr->left(), r1);
LOperand* right = UseFixed(instr->right(), r0);
- LCmpT* result = new(zone()) LCmpT(left, right);
- return MarkAsCall(DefineFixed(result, r0), instr);
+ return MarkAsCall(new(zone()) LCompareGenericAndBranch(left, right), instr);
}
// If there is an instruction pending deoptimization environment create a
// lazy bailout instruction to capture the environment.
- if (pending_deoptimization_ast_id_ == instr->ast_id()) {
+ if (!pending_deoptimization_ast_id_.IsNone()) {
+ ASSERT(pending_deoptimization_ast_id_ == instr->ast_id() ||
+ pending_deoptimization_ast_id_.IsPendingMarker());
LInstruction* result = new(zone()) LLazyBailout;
result = AssignEnvironment(result);
// Store the lazy deopt environment with the instruction if needed. Right
V(CmpObjectEqAndBranch) \
V(CmpHoleAndBranch) \
V(CmpMapAndBranch) \
- V(CmpT) \
+ V(CompareGenericAndBranch) \
V(ConstantD) \
V(ConstantE) \
V(ConstantI) \
};
-class LCmpT V8_FINAL : public LTemplateInstruction<1, 2, 0> {
+class LCompareGenericAndBranch V8_FINAL : public LControlInstruction<2, 0> {
public:
- LCmpT(LOperand* left, LOperand* right) {
+ LCompareGenericAndBranch(LOperand* left, LOperand* right) {
inputs_[0] = left;
inputs_[1] = right;
}
LOperand* left() { return inputs_[0]; }
LOperand* right() { return inputs_[1]; }
- DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
- DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
+ DECLARE_CONCRETE_INSTRUCTION(CompareGenericAndBranch,
+ "compare-generic-and-branch")
+ DECLARE_HYDROGEN_ACCESSOR(CompareGenericAndBranch)
Token::Value op() const { return hydrogen()->token(); }
};
}
-void LCodeGen::DoCmpT(LCmpT* instr) {
+void LCodeGen::DoCompareGenericAndBranch(LCompareGenericAndBranch* instr) {
Token::Value op = instr->op();
Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op);
__ cmp(r0, Operand::Zero());
Condition condition = ComputeCompareCondition(op);
- __ LoadRoot(ToRegister(instr->result()),
- Heap::kTrueValueRootIndex,
- condition);
- __ LoadRoot(ToRegister(instr->result()),
- Heap::kFalseValueRootIndex,
- NegateCondition(condition));
+ EmitBranch(instr, condition);
}
// Verify that instructions that may have side-effects are followed
// by a simulate instruction.
- if (HasObservableSideEffects() && !IsOsrEntry()) {
+ if (HasObservableSideEffects() && !IsOsrEntry() && !IsControlInstruction()) {
ASSERT(next()->IsSimulate());
}
}
+#ifdef DEBUG
+void HControlInstruction::Verify() {
+ HInstruction::Verify();
+ if (!HasObservableSideEffects()) return;
+ for (HSuccessorIterator it(this); !it.Done(); it.Advance()) {
+ // For ControlInstructions we need to verify that the successors all start
+ // with a Simulate.
+ HInstruction* first = it.Current()->first()->next();
+ ASSERT(first->IsSimulate() ||
+ (first->IsLeaveInlined() && first->next()->IsSimulate()));
+ }
+}
+#endif
+
+
void HUnaryControlInstruction::PrintDataTo(StringStream* stream) {
value()->PrintNameTo(stream);
HControlInstruction::PrintDataTo(stream);
}
-void HCompareGeneric::PrintDataTo(StringStream* stream) {
+void HCompareGenericAndBranch::PrintDataTo(StringStream* stream) {
stream->Add(Token::Name(token()));
stream->Add(" ");
- HBinaryOperation::PrintDataTo(stream);
+ left()->PrintNameTo(stream);
+ stream->Add(" ");
+ right()->PrintNameTo(stream);
+ if (CheckFlag(kCanOverflow)) stream->Add(" !");
+ if (CheckFlag(kBailoutOnMinusZero)) stream->Add(" -0?");
}
V(ClassOfTestAndBranch) \
V(CompareNumericAndBranch) \
V(CompareHoleAndBranch) \
- V(CompareGeneric) \
+ V(CompareGenericAndBranch) \
V(CompareObjectEqAndBranch) \
V(CompareMap) \
V(Constant) \
};
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P0(I) \
+ static I* New(Zone* zone, HValue* context) { \
+ return new(zone) I(context); \
+}
+
#define DECLARE_INSTRUCTION_FACTORY_P0(I) \
static I* New(Zone* zone, HValue* context) { \
return new(zone) I(); \
}
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P1(I, P1) \
+ static I* New(Zone* zone, HValue* context, P1 p1) { \
+ return new(zone) I(context, p1); \
+ }
+
#define DECLARE_INSTRUCTION_FACTORY_P1(I, P1) \
static I* New(Zone* zone, HValue* context, P1 p1) { \
return new(zone) I(p1); \
}
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P2(I, P1, P2) \
+ static I* New(Zone* zone, HValue* context, P1 p1, P2 p2) { \
+ return new(zone) I(context, p1, p2); \
+ }
+
#define DECLARE_INSTRUCTION_FACTORY_P2(I, P1, P2) \
static I* New(Zone* zone, HValue* context, P1 p1, P2 p2) { \
return new(zone) I(p1, p2); \
}
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P3(I, P1, P2, P3) \
+ static I* New(Zone* zone, HValue* context, P1 p1, P2 p2, P3 p3) { \
+ return new(zone) I(context, p1, p2, p3); \
+ }
+
#define DECLARE_INSTRUCTION_FACTORY_P3(I, P1, P2, P3) \
static I* New(Zone* zone, HValue* context, P1 p1, P2 p2, P3 p3) { \
return new(zone) I(p1, p2, p3); \
}
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P4(I, P1, P2, P3, P4) \
+ static I* New(Zone* zone, \
+ HValue* context, \
+ P1 p1, \
+ P2 p2, \
+ P3 p3, \
+ P4 p4) { \
+ return new(zone) I(context, p1, p2, p3, p4); \
+ }
+
#define DECLARE_INSTRUCTION_FACTORY_P4(I, P1, P2, P3, P4) \
static I* New(Zone* zone, \
HValue* context, \
return new(zone) I(p1, p2, p3, p4); \
}
+#define DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P5(I, P1, P2, P3, P4, P5) \
+ static I* New(Zone* zone, \
+ HValue* context, \
+ P1 p1, \
+ P2 p2, \
+ P3 p3, \
+ P4 p4, \
+ P5 p5) { \
+ return new(zone) I(context, p1, p2, p3, p4, p5); \
+ }
+
#define DECLARE_INSTRUCTION_FACTORY_P5(I, P1, P2, P3, P4, P5) \
static I* New(Zone* zone, \
HValue* context, \
SetSuccessorAt(1, swap);
}
+#ifdef DEBUG
+ virtual void Verify() V8_OVERRIDE;
+#endif
+
DECLARE_ABSTRACT_INSTRUCTION(ControlInstruction)
};
};
-class HCompareGeneric V8_FINAL : public HBinaryOperation {
+class HCompareGenericAndBranch V8_FINAL
+ : public HTemplateControlInstruction<2, 3> {
public:
- HCompareGeneric(HValue* context,
- HValue* left,
- HValue* right,
- Token::Value token)
- : HBinaryOperation(context, left, right, HType::Boolean()),
- token_(token) {
- ASSERT(Token::IsCompareOp(token));
- set_representation(Representation::Tagged());
- SetAllSideEffects();
- }
+ DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P3(HCompareGenericAndBranch,
+ HValue*, HValue*, Token::Value);
+ DECLARE_INSTRUCTION_WITH_CONTEXT_FACTORY_P5(HCompareGenericAndBranch,
+ HValue*, HValue*, Token::Value,
+ HBasicBlock*, HBasicBlock*);
+ HValue* context() { return OperandAt(0); }
+ HValue* left() { return OperandAt(1); }
+ HValue* right() { return OperandAt(2); }
+ Token::Value token() const { return token_; }
virtual Representation RequiredInputRepresentation(int index) V8_OVERRIDE {
return index == 0
: representation();
}
- Token::Value token() const { return token_; }
+ void set_observed_input_representation(Representation left,
+ Representation right) {
+ observed_input_representation_[0] = left;
+ observed_input_representation_[1] = right;
+ }
+
virtual void PrintDataTo(StringStream* stream) V8_OVERRIDE;
- DECLARE_CONCRETE_INSTRUCTION(CompareGeneric)
+ DECLARE_CONCRETE_INSTRUCTION(CompareGenericAndBranch)
private:
+ HCompareGenericAndBranch(HValue* context,
+ HValue* left,
+ HValue* right,
+ Token::Value token,
+ HBasicBlock* true_target = NULL,
+ HBasicBlock* false_target = NULL)
+ : token_(token) {
+ set_representation(Representation::Tagged());
+ SetAllSideEffects();
+ SetOperandAt(0, context);
+ SetOperandAt(1, left);
+ SetOperandAt(2, right);
+ SetSuccessorAt(0, true_target);
+ SetSuccessorAt(1, false_target);
+ }
+
+ Representation observed_input_representation_[2];
Token::Value token_;
};
class HStringCompareAndBranch : public HTemplateControlInstruction<2, 3> {
public:
HStringCompareAndBranch(HValue* context,
- HValue* left,
- HValue* right,
- Token::Value token)
+ HValue* left,
+ HValue* right,
+ Token::Value token)
: token_(token) {
ASSERT(Token::IsCompareOp(token));
SetOperandAt(0, context);
void EffectContext::ReturnControl(HControlInstruction* instr,
BailoutId ast_id) {
- ASSERT(!instr->HasObservableSideEffects());
HBasicBlock* empty_true = owner()->graph()->CreateBasicBlock();
HBasicBlock* empty_false = owner()->graph()->CreateBasicBlock();
instr->SetSuccessorAt(0, empty_true);
void ValueContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) {
- ASSERT(!instr->HasObservableSideEffects());
if (!arguments_allowed() && instr->CheckFlag(HValue::kIsArguments)) {
return owner()->Bailout(kBadValueContextForArgumentsObjectValue);
}
void TestContext::ReturnControl(HControlInstruction* instr, BailoutId ast_id) {
- ASSERT(!instr->HasObservableSideEffects());
+ // We can ignore ObservableSideEffects here since both HGoto instructions
+ // insert a different Simulate, thus we will directly deoptimize into the
+ // correct branch.
HBasicBlock* empty_true = owner()->graph()->CreateBasicBlock();
HBasicBlock* empty_false = owner()->graph()->CreateBasicBlock();
instr->SetSuccessorAt(0, empty_true);
return ast_context()->ReturnControl(result, expr->id());
} else {
if (combined_rep.IsTagged() || combined_rep.IsNone()) {
- HCompareGeneric* result =
- new(zone()) HCompareGeneric(context, left, right, op);
- result->set_observed_input_representation(1, left_rep);
- result->set_observed_input_representation(2, right_rep);
+ HCompareGenericAndBranch* result =
+ New<HCompareGenericAndBranch>(left, right, op);
+ result->set_observed_input_representation(left_rep, right_rep);
result->set_position(expr->position());
- return ast_context()->ReturnInstruction(result, expr->id());
+ return ast_context()->ReturnControl(result, expr->id());
} else {
HCompareNumericAndBranch* result =
New<HCompareNumericAndBranch>(left, right, op);
}
-void LCodeGen::DoCmpT(LCmpT* instr) {
+void LCodeGen::DoCompareGenericAndBranch(LCompareGenericAndBranch* instr) {
Token::Value op = instr->op();
Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op);
CallCode(ic, RelocInfo::CODE_TARGET, instr);
Condition condition = ComputeCompareCondition(op);
- Label true_value, done;
__ test(eax, Operand(eax));
- __ j(condition, &true_value, Label::kNear);
- __ mov(ToRegister(instr->result()), factory()->false_value());
- __ jmp(&done, Label::kNear);
- __ bind(&true_value);
- __ mov(ToRegister(instr->result()), factory()->true_value());
- __ bind(&done);
+ EmitBranch(instr, condition);
}
instr = AssignPointerMap(instr);
if (hinstr->HasObservableSideEffects()) {
- ASSERT(hinstr->next()->IsSimulate());
- HSimulate* sim = HSimulate::cast(hinstr->next());
ASSERT(instruction_pending_deoptimization_environment_ == NULL);
ASSERT(pending_deoptimization_ast_id_.IsNone());
+ if (!hinstr->IsControlInstruction()) {
+ ASSERT(hinstr->next()->IsSimulate());
+ HSimulate* sim = HSimulate::cast(hinstr->next());
+ pending_deoptimization_ast_id_ = sim->ast_id();
+ } else {
+ pending_deoptimization_ast_id_ = BailoutId::PendingMarker();
+ }
instruction_pending_deoptimization_environment_ = instr;
- pending_deoptimization_ast_id_ = sim->ast_id();
}
// If instruction does not have side-effects lazy deoptimization
}
-LInstruction* LChunkBuilder::DoCompareGeneric(HCompareGeneric* instr) {
+LInstruction* LChunkBuilder::DoCompareGenericAndBranch(
+ HCompareGenericAndBranch* instr) {
ASSERT(instr->left()->representation().IsSmiOrTagged());
ASSERT(instr->right()->representation().IsSmiOrTagged());
LOperand* context = UseFixed(instr->context(), esi);
LOperand* left = UseFixed(instr->left(), edx);
LOperand* right = UseFixed(instr->right(), eax);
- LCmpT* result = new(zone()) LCmpT(context, left, right);
- return MarkAsCall(DefineFixed(result, eax), instr);
+ return MarkAsCall(new(zone()) LCompareGenericAndBranch(context, left, right),
+ instr);
}
// If there is an instruction pending deoptimization environment create a
// lazy bailout instruction to capture the environment.
if (!pending_deoptimization_ast_id_.IsNone()) {
- ASSERT(pending_deoptimization_ast_id_ == instr->ast_id());
+ ASSERT(pending_deoptimization_ast_id_ == instr->ast_id() ||
+ pending_deoptimization_ast_id_.IsPendingMarker());
LLazyBailout* lazy_bailout = new(zone()) LLazyBailout;
LInstruction* result = AssignEnvironment(lazy_bailout);
// Store the lazy deopt environment with the instruction if needed. Right
V(CmpObjectEqAndBranch) \
V(CmpHoleAndBranch) \
V(CmpMapAndBranch) \
- V(CmpT) \
+ V(CompareGenericAndBranch) \
V(ConstantD) \
V(ConstantE) \
V(ConstantI) \
};
-class LCmpT V8_FINAL : public LTemplateInstruction<1, 3, 0> {
+class LCompareGenericAndBranch V8_FINAL : public LControlInstruction<3, 0> {
public:
- LCmpT(LOperand* context, LOperand* left, LOperand* right) {
+ LCompareGenericAndBranch(LOperand* context, LOperand* left, LOperand* right) {
inputs_[0] = context;
inputs_[1] = left;
inputs_[2] = right;
}
- DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
- DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
+ DECLARE_CONCRETE_INSTRUCTION(CompareGenericAndBranch,
+ "compare-generic-and-branch")
+ DECLARE_HYDROGEN_ACCESSOR(CompareGenericAndBranch)
Token::Value op() const { return hydrogen()->token(); }
};
static BailoutId Declarations() { return BailoutId(kDeclarationsId); }
static BailoutId FirstUsable() { return BailoutId(kFirstUsableId); }
static BailoutId StubEntry() { return BailoutId(kStubEntryId); }
+ static BailoutId PendingMarker() { return BailoutId(kPendingMarkerId); }
bool IsNone() const { return id_ == kNoneId; }
+ bool IsPendingMarker() const { return id_ == kPendingMarkerId; }
bool operator==(const BailoutId& other) const { return id_ == other.id_; }
private:
// Every compiled stub starts with this id.
static const int kStubEntryId = 5;
+ // For Control instructions we cannot verify the ast_id, since there is no
+ // 1:1 mapping but it corresponds to two simulates for each branch.
+ static const int kPendingMarkerId = 6;
+
int id_;
};
}
-void LCodeGen::DoCmpT(LCmpT* instr) {
+void LCodeGen::DoCompareGenericAndBranch(LCompareGenericAndBranch* instr) {
Token::Value op = instr->op();
Handle<Code> ic = CompareIC::GetUninitialized(isolate(), op);
Condition condition = TokenToCondition(op, false);
Label true_value, done;
__ testq(rax, rax);
- __ j(condition, &true_value, Label::kNear);
- __ LoadRoot(ToRegister(instr->result()), Heap::kFalseValueRootIndex);
- __ jmp(&done, Label::kNear);
- __ bind(&true_value);
- __ LoadRoot(ToRegister(instr->result()), Heap::kTrueValueRootIndex);
- __ bind(&done);
+ EmitBranch(instr, condition);
}
instr = AssignPointerMap(instr);
if (hinstr->HasObservableSideEffects()) {
- ASSERT(hinstr->next()->IsSimulate());
- HSimulate* sim = HSimulate::cast(hinstr->next());
ASSERT(instruction_pending_deoptimization_environment_ == NULL);
ASSERT(pending_deoptimization_ast_id_.IsNone());
+ // For Control instructions we cannot verify the ast_id, since there is no
+ // 1:1 mapping but it corresponds to two simulates for each branch.
+ if (!hinstr->IsControlInstruction()) {
+ ASSERT(hinstr->next()->IsSimulate());
+ HSimulate* sim = HSimulate::cast(hinstr->next());
+ pending_deoptimization_ast_id_ = sim->ast_id();
+ } else {
+ pending_deoptimization_ast_id_ = BailoutId::PendingMarker();
+ }
instruction_pending_deoptimization_environment_ = instr;
- pending_deoptimization_ast_id_ = sim->ast_id();
}
// If instruction does not have side-effects lazy deoptimization
}
-LInstruction* LChunkBuilder::DoCompareGeneric(HCompareGeneric* instr) {
+LInstruction* LChunkBuilder::DoCompareGenericAndBranch(
+ HCompareGenericAndBranch* instr) {
ASSERT(instr->left()->representation().IsTagged());
ASSERT(instr->right()->representation().IsTagged());
LOperand* left = UseFixed(instr->left(), rdx);
LOperand* right = UseFixed(instr->right(), rax);
- LCmpT* result = new(zone()) LCmpT(left, right);
- return MarkAsCall(DefineFixed(result, rax), instr);
+ return MarkAsCall(new(zone()) LCompareGenericAndBranch(left, right), instr);
}
// If there is an instruction pending deoptimization environment create a
// lazy bailout instruction to capture the environment.
- if (pending_deoptimization_ast_id_ == instr->ast_id()) {
+ if (!pending_deoptimization_ast_id_.IsNone()) {
+ ASSERT(pending_deoptimization_ast_id_ == instr->ast_id() ||
+ pending_deoptimization_ast_id_.IsPendingMarker());
LLazyBailout* lazy_bailout = new(zone()) LLazyBailout;
LInstruction* result = AssignEnvironment(lazy_bailout);
// Store the lazy deopt environment with the instruction if needed. Right
V(CmpObjectEqAndBranch) \
V(CmpHoleAndBranch) \
V(CmpMapAndBranch) \
- V(CmpT) \
+ V(CompareGenericAndBranch) \
V(ConstantD) \
V(ConstantE) \
V(ConstantI) \
};
-class LCmpT V8_FINAL : public LTemplateInstruction<1, 2, 0> {
+class LCompareGenericAndBranch V8_FINAL : public LControlInstruction<2, 0> {
public:
- LCmpT(LOperand* left, LOperand* right) {
+ LCompareGenericAndBranch(LOperand* left, LOperand* right) {
inputs_[0] = left;
inputs_[1] = right;
}
LOperand* left() { return inputs_[0]; }
LOperand* right() { return inputs_[1]; }
- DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t")
- DECLARE_HYDROGEN_ACCESSOR(CompareGeneric)
+ DECLARE_CONCRETE_INSTRUCTION(CompareGenericAndBranch,
+ "compare-generic-and-branch")
+ DECLARE_HYDROGEN_ACCESSOR(CompareGenericAndBranch)
Token::Value op() const { return hydrogen()->token(); }
};
--- /dev/null
+// Copyright 2013 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Flags: --allow-natives-syntax
+
+// Test the correct placement of the simulates after HCompareGenericAndBranch:
+function Checker() {
+ this.str = "1";
+ var toStringCalled = 0;
+ var toStringExpected = 0;
+ this.toString = function() {
+ toStringCalled++;
+ return this.str;
+ };
+ this.check = function() {
+ toStringExpected++;
+ assertEquals(toStringExpected, toStringCalled);
+ };
+};
+var left = new Checker();
+var right = new Checker();
+
+// This test compares a < b against x < y where
+// x/y are objects providing a/b as toString. In the end we
+// check if the observable side effects match our
+// expectations, thus we make sure that we deopted to a
+// simulate after the comparison was done.
+function test(a,b) {
+ left.str = a;
+ right.str = b;
+ if (left >= right) {
+ assertTrue(a >= b);
+ } else {
+ assertFalse(a >= b);
+ }
+ left.check();
+ right.check();
+}
+
+test("ab","abc");
+test("ab","a");
+%OptimizeFunctionOnNextCall(test);
+test("a","ab");
+test(1,"a");
+test("a","ab");
+%OptimizeFunctionOnNextCall(test);
+test("a","ab");
+test("a",1);
+test("ab","a");
+
+
+// Use generic compare in value, effect and test contexts
+
+function Checker2() {
+ var valueOfCalled = 0;
+ this.valueOf = function() {
+ return valueOfCalled++;
+ }
+ this.valueOfCalled = function() {
+ return valueOfCalled;
+ }
+}
+
+var x = new Checker2();
+var y = new Checker2();
+
+if (x < y || y < x || x <= y) {
+ assertEquals(3, x.valueOfCalled());
+ assertEquals(3, y.valueOfCalled());
+ assertEquals(1, (x < y) + (y < x) + (x <= y))
+ assertEquals(6, x.valueOfCalled());
+ assertEquals(6, y.valueOfCalled());
+ x < y;
+ assertEquals(7, x.valueOfCalled());
+ assertEquals(7, y.valueOfCalled());
+ x < y;
+ assertEquals(8, x.valueOfCalled());
+ assertEquals(8, y.valueOfCalled());
+ var res;
+ if (x <= y) {
+ res = 1+(x > {});
+ } else {
+ assertTrue(false);
+ res = y <= {};
+ }
+ assertEquals(10, x.valueOfCalled());
+ assertEquals(9, y.valueOfCalled());
+ assertEquals(1, res);
+ assertFalse(x < y);
+
+ var tb = 0, fb = 0;
+ var val = 0;
+ for (var i = 1; i < 10; i++) {
+ var res = 0;
+ // uses x,y in control context
+ if (x <= y) {
+ res += val;
+ assertTrue(x <= y);
+ // adds 1 + 0, uses x in value context
+ res += 1+(x > {});
+ tb++;
+ assertEquals(fb, tb);
+ } else {
+ res += val;
+ assertFalse(x < y);
+ // adds 1, uses y in value context, increments 2
+ res += (y <= y);
+ // use x in value context, increments x once to make it equal to y again
+ x + 2;
+ assertEquals(fb, tb);
+ fb++;
+ }
+ assertEquals(11+(2*i)+tb+fb, x.valueOfCalled());
+ assertEquals(10+(2*i)+(2*fb), y.valueOfCalled());
+ assertEquals(1 + val, res);
+ // Triggers deopt inside branch.
+ if (i%5 == 0) val += 0.5;
+ }
+} else {
+ assertTrue(false);
+}
+
+
+function t(a,b) { return (b < a) - (a < b); };
+function f() {
+ x = new Checker2();
+ y = new Checker2();
+ var tb = 0, fb = 0;
+ var val = 0;
+ for (var i = 1; i < 10; i++) {
+ var res = 0;
+ if ((x < y) + (y < x)) {
+ res += val;
+ res += x<0;
+ fb++;
+ } else {
+ res += val;
+ res += y<0;
+ tb++;
+ }
+ assertEquals(0, res + 1 - res - 1);
+ assertEquals((2*i)+fb, x.valueOfCalled());
+ assertEquals((2*i)+tb, y.valueOfCalled());
+ assertEquals(val, res);
+ if (i%4 == 0) val += 0.5;
+ }
+}
+
+f();
+%OptimizeFunctionOnNextCall(f);
+f();
+
+var a = {valueOf: function(){this.conv++; return 1;}};
+var b = {valueOf: function(){this.conv++; return 2;}};
+
+a.conv = 0;
+b.conv = 0;
+
+function f2(a,b,d1,d2) {
+ var runs = 0;
+ if ((a < b) + (a < b)) {
+ if (d2) { d2 += 0.2; }
+ runs++;
+ } else {
+ assertUnreachable();
+ }
+ assertEquals(1, runs);
+ if (a > b) {
+ assertUnreachable();
+ } else {
+ if (d1) { d1 += 0.2; }
+ runs++;
+ }
+ assertEquals(2, runs);
+}
+
+f2(a,b);
+f2(a,b);
+
+%OptimizeFunctionOnNextCall(f2);
+f2(a,b);
+f2(a,b);
+
+f2(a,b,true);
+f2(a,b);
+
+%OptimizeFunctionOnNextCall(f2);
+f2(a,b);
+f2(a,b);
+
+f2(a,b,false,true);
+f2(a,b);
+
+assertEquals(30, a.conv);
+assertEquals(30, b.conv);
+
+b.valueOf = function(){ return {}; }
+try {
+ f2(a,b);
+} catch(e) {
+ res = e.stack;
+}