Dart VM: Simplify code generation for equality operators.

By inserting the necessary checks for null inside the callee
at the AST level, the code generation of == operations can be
greatly simplified.

This is a performance-neutral change and a step for allowing
generic inlining of arbitrary == methods. So far we could only
inline them for a common set of types in the flow graph
optimizer.

R=srdjan@google.com

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

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@28084 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
fschneider@google.com
2013-10-01 10:10:50 +00:00
parent c540e349ed
commit a329b0e6bc
15 changed files with 128 additions and 696 deletions
+1
View File
@@ -33,6 +33,7 @@ DEFINE_NATIVE_ENTRY(FunctionImpl_equals, 2) {
isolate, arguments->NativeArgAt(0));
ASSERT(receiver.IsClosure());
GET_NATIVE_ARGUMENT(Instance, other, arguments->NativeArgAt(1));
ASSERT(!other.IsNull());
if (receiver.raw() == other.raw()) return Bool::True().raw();
if (other.IsClosure()) {
const Function& func_a = Function::Handle(Closure::function(receiver));
+3
View File
@@ -1595,6 +1595,9 @@ void Debugger::SingleStepCallback() {
if (!IsDebuggable(func)) {
return;
}
if (frame->TokenPos() == Scanner::kDummyTokenIndex) {
return;
}
if (FLAG_verbose_debug) {
OS::Print(">>> single step break at %s:%" Pd " (func %s token %" Pd ")\n",
-62
View File
@@ -361,14 +361,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
const Array& kNoArgumentNames = Object::null_array();
const int kNumArgumentsChecked = 2;
Label check_identity;
__ LoadImmediate(IP, reinterpret_cast<intptr_t>(Object::null()));
__ ldm(IA, SP, (1 << R0) | (1 << R1));
__ cmp(R1, ShifterOperand(IP));
__ b(&check_identity, EQ);
__ cmp(R0, ShifterOperand(IP));
__ b(&check_identity, EQ);
ICData& equality_ic_data = ICData::ZoneHandle();
if (compiler->is_optimizing() && FLAG_propagate_ic_data) {
ASSERT(!original_ic_data.IsNull());
@@ -395,42 +387,12 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
kNoArgumentNames,
locs,
equality_ic_data);
Label check_ne;
__ b(&check_ne);
__ Bind(&check_identity);
Label equality_done;
if (compiler->is_optimizing()) {
// No need to update IC data.
__ PopList((1 << R0) | (1 << R1));
__ cmp(R0, ShifterOperand(R1));
__ LoadObject(R0, Bool::Get(kind != Token::kEQ), NE);
__ LoadObject(R0, Bool::Get(kind == Token::kEQ), EQ);
if (kind == Token::kNE) {
// Skip not-equal result conversion.
__ b(&equality_done);
}
} else {
// Call stub, load IC data in register. The stub will update ICData if
// necessary.
Register ic_data_reg = locs->temp(0).reg();
ASSERT(ic_data_reg == R5); // Stub depends on it.
__ LoadObject(ic_data_reg, equality_ic_data);
// Pass left in R1 and right in R0.
compiler->GenerateCall(token_pos,
&StubCode::EqualityWithNullArgLabel(),
PcDescriptors::kRuntimeCall,
locs);
__ Drop(2);
}
__ Bind(&check_ne);
if (kind == Token::kNE) {
// Negate the condition: true label returns false and vice versa.
__ CompareObject(R0, Bool::True());
__ LoadObject(R0, Bool::True(), NE);
__ LoadObject(R0, Bool::False(), EQ);
}
__ Bind(&equality_done);
}
@@ -612,35 +574,11 @@ static void EmitGenericEqualityCompare(FlowGraphCompiler* compiler,
ASSERT(!ic_data.IsNull() && (ic_data.NumberOfChecks() > 0));
Register left = locs->in(0).reg();
Register right = locs->in(1).reg();
Label done, identity_compare, non_null_compare;
__ LoadImmediate(IP, reinterpret_cast<intptr_t>(Object::null()));
__ cmp(right, ShifterOperand(IP));
__ b(&identity_compare, EQ);
__ cmp(left, ShifterOperand(IP));
__ b(&non_null_compare, NE);
// Comparison with NULL is "===".
__ Bind(&identity_compare);
__ cmp(left, ShifterOperand(right));
Condition cond = TokenKindToSmiCondition(kind);
if (branch != NULL) {
branch->EmitBranchOnCondition(compiler, cond);
} else {
Register result = locs->out().reg();
Label load_true;
__ b(&load_true, cond);
__ LoadObject(result, Bool::False());
__ b(&done);
__ Bind(&load_true);
__ LoadObject(result, Bool::True());
}
__ b(&done);
__ Bind(&non_null_compare); // Receiver is not null.
ASSERT(left == R1);
ASSERT(right == R0);
__ PushList((1 << R0) | (1 << R1));
EmitEqualityAsPolymorphicCall(compiler, ic_data, locs, branch, kind,
deopt_id, token_pos);
__ Bind(&done);
}
-67
View File
@@ -310,14 +310,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
const Array& kNoArgumentNames = Object::null_array();
const int kNumArgumentsChecked = 2;
const Immediate& raw_null =
Immediate(reinterpret_cast<intptr_t>(Object::null()));
Label check_identity;
__ cmpl(Address(ESP, 0 * kWordSize), raw_null);
__ j(EQUAL, &check_identity);
__ cmpl(Address(ESP, 1 * kWordSize), raw_null);
__ j(EQUAL, &check_identity);
ICData& equality_ic_data = ICData::ZoneHandle();
if (compiler->is_optimizing() && FLAG_propagate_ic_data) {
ASSERT(!original_ic_data.IsNull());
@@ -344,39 +336,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
kNoArgumentNames,
locs,
equality_ic_data);
Label check_ne;
__ jmp(&check_ne);
__ Bind(&check_identity);
Label equality_done;
if (compiler->is_optimizing()) {
// No need to update IC data.
Label is_true;
__ popl(EAX);
__ popl(EDX);
__ cmpl(EAX, EDX);
__ j(EQUAL, &is_true);
__ LoadObject(EAX, Bool::Get(kind != Token::kEQ));
__ jmp(&equality_done);
__ Bind(&is_true);
__ LoadObject(EAX, Bool::Get(kind == Token::kEQ));
if (kind == Token::kNE) {
// Skip not-equal result conversion.
__ jmp(&equality_done);
}
} else {
// Call stub, load IC data in register. The stub will update ICData if
// necessary.
Register ic_data_reg = locs->temp(0).reg();
ASSERT(ic_data_reg == ECX); // Stub depends on it.
__ LoadObject(ic_data_reg, equality_ic_data);
compiler->GenerateCall(token_pos,
&StubCode::EqualityWithNullArgLabel(),
PcDescriptors::kRuntimeCall,
locs);
__ Drop(2);
}
__ Bind(&check_ne);
if (kind == Token::kNE) {
Label true_label, done;
// Negate the condition: true label returns false and vice versa.
@@ -388,7 +347,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
__ LoadObject(EAX, Bool::False());
__ Bind(&done);
}
__ Bind(&equality_done);
}
@@ -562,35 +520,10 @@ static void EmitGenericEqualityCompare(FlowGraphCompiler* compiler,
ASSERT(!ic_data.IsNull() && (ic_data.NumberOfChecks() > 0));
Register left = locs->in(0).reg();
Register right = locs->in(1).reg();
const Immediate& raw_null =
Immediate(reinterpret_cast<intptr_t>(Object::null()));
Label done, identity_compare, non_null_compare;
__ cmpl(right, raw_null);
__ j(EQUAL, &identity_compare, Assembler::kNearJump);
__ cmpl(left, raw_null);
__ j(NOT_EQUAL, &non_null_compare, Assembler::kNearJump);
// Comparison with NULL is "===".
__ Bind(&identity_compare);
__ cmpl(left, right);
Condition cond = TokenKindToSmiCondition(kind);
if (branch != NULL) {
branch->EmitBranchOnCondition(compiler, cond);
} else {
Register result = locs->out().reg();
Label load_true;
__ j(cond, &load_true, Assembler::kNearJump);
__ LoadObject(result, Bool::False());
__ jmp(&done);
__ Bind(&load_true);
__ LoadObject(result, Bool::True());
}
__ jmp(&done);
__ Bind(&non_null_compare); // Receiver is not null.
__ pushl(left);
__ pushl(right);
EmitEqualityAsPolymorphicCall(compiler, ic_data, locs, branch, kind,
deopt_id, token_pos);
__ Bind(&done);
}
-65
View File
@@ -356,12 +356,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
__ TraceSimMsg("EmitEqualityAsInstanceCall");
__ Comment("EmitEqualityAsInstanceCall");
Label check_identity;
__ lw(A1, Address(SP, 1 * kWordSize));
__ lw(A0, Address(SP, 0 * kWordSize));
__ LoadImmediate(CMPRES1, reinterpret_cast<int32_t>(Object::null()));
__ beq(A1, CMPRES1, &check_identity);
__ beq(A0, CMPRES1, &check_identity);
ICData& equality_ic_data = ICData::ZoneHandle();
if (compiler->is_optimizing() && FLAG_propagate_ic_data) {
@@ -389,40 +383,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
kNoArgumentNames,
locs,
equality_ic_data);
Label check_ne;
__ b(&check_ne);
__ Bind(&check_identity);
Label equality_done;
if (compiler->is_optimizing()) {
// No need to update IC data.
Label is_true;
__ lw(A1, Address(SP, 1 * kWordSize));
__ lw(A0, Address(SP, 0 * kWordSize));
__ addiu(SP, SP, Immediate(2 * kWordSize));
__ beq(A1, A0, &is_true);
__ LoadObject(V0, Bool::Get(kind != Token::kEQ));
__ b(&equality_done);
__ Bind(&is_true);
__ LoadObject(V0, Bool::Get(kind == Token::kEQ));
if (kind == Token::kNE) {
// Skip not-equal result conversion.
__ b(&equality_done);
}
} else {
// Call stub, load IC data in register. The stub will update ICData if
// necessary.
Register ic_data_reg = locs->temp(0).reg();
ASSERT(ic_data_reg == T0); // Stub depends on it.
__ LoadObject(ic_data_reg, equality_ic_data);
// Pass left in A1 and right in A0.
compiler->GenerateCall(token_pos,
&StubCode::EqualityWithNullArgLabel(),
PcDescriptors::kRuntimeCall,
locs);
__ Drop(2);
}
__ Bind(&check_ne);
if (kind == Token::kNE) {
Label true_label, done;
// Negate the condition: true label returns false and vice versa.
@@ -433,7 +393,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
__ LoadObject(V0, Bool::False());
__ Bind(&done);
}
__ Bind(&equality_done);
}
@@ -649,31 +608,8 @@ static void EmitGenericEqualityCompare(FlowGraphCompiler* compiler,
ASSERT(!ic_data.IsNull() && (ic_data.NumberOfChecks() > 0));
Register left = locs->in(0).reg();
Register right = locs->in(1).reg();
Label done, identity_compare, non_null_compare;
__ TraceSimMsg("EmitGenericEqualityCompare");
__ Comment("EmitGenericEqualityCompare");
__ LoadImmediate(CMPRES1, reinterpret_cast<int32_t>(Object::null()));
__ beq(right, CMPRES1, &identity_compare);
__ bne(left, CMPRES1, &non_null_compare);
// Comparison with NULL is "===".
__ Bind(&identity_compare);
Condition cond = TokenKindToSmiCondition(kind);
__ slt(CMPRES1, left, right);
__ slt(CMPRES2, right, left);
if (branch != NULL) {
branch->EmitBranchOnCondition(compiler, cond);
} else {
Register result = locs->out().reg();
Label load_true;
EmitBranchAfterCompare(compiler, cond, &load_true);
__ LoadObject(result, Bool::False());
__ b(&done);
__ Bind(&load_true);
__ LoadObject(result, Bool::True());
}
__ b(&done);
__ Bind(&non_null_compare); // Receiver is not null.
ASSERT(left == A1);
ASSERT(right == A0);
__ addiu(SP, SP, Immediate(-2 * kWordSize));
@@ -681,7 +617,6 @@ static void EmitGenericEqualityCompare(FlowGraphCompiler* compiler,
__ sw(A0, Address(SP, 0 * kWordSize));
EmitEqualityAsPolymorphicCall(compiler, ic_data, locs, branch, kind,
deopt_id, token_pos);
__ Bind(&done);
}
-65
View File
@@ -453,13 +453,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
const Array& kNoArgumentNames = Object::null_array();
const int kNumArgumentsChecked = 2;
Label check_identity;
__ LoadObject(TMP, Object::null_object(), PP);
__ cmpq(Address(RSP, 0 * kWordSize), TMP);
__ j(EQUAL, &check_identity);
__ cmpq(Address(RSP, 1 * kWordSize), TMP);
__ j(EQUAL, &check_identity);
ICData& equality_ic_data = ICData::ZoneHandle(original_ic_data.raw());
if (compiler->is_optimizing() && FLAG_propagate_ic_data) {
ASSERT(!original_ic_data.IsNull());
@@ -486,39 +479,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
kNoArgumentNames,
locs,
equality_ic_data);
Label check_ne;
__ jmp(&check_ne);
__ Bind(&check_identity);
Label equality_done;
if (compiler->is_optimizing()) {
// No need to update IC data.
Label is_true;
__ popq(RAX);
__ popq(RDX);
__ cmpq(RAX, RDX);
__ j(EQUAL, &is_true);
__ LoadObject(RAX, Bool::Get(kind != Token::kEQ), PP);
__ jmp(&equality_done);
__ Bind(&is_true);
__ LoadObject(RAX, Bool::Get(kind == Token::kEQ), PP);
if (kind == Token::kNE) {
// Skip not-equal result conversion.
__ jmp(&equality_done);
}
} else {
// Call stub, load IC data in register. The stub will update ICData if
// necessary.
Register ic_data_reg = locs->temp(0).reg();
ASSERT(ic_data_reg == RBX); // Stub depends on it.
__ LoadObject(ic_data_reg, equality_ic_data, PP);
compiler->GenerateCall(token_pos,
&StubCode::EqualityWithNullArgLabel(),
PcDescriptors::kRuntimeCall,
locs);
__ Drop(2);
}
__ Bind(&check_ne);
if (kind == Token::kNE) {
Label true_label, done;
// Negate the condition: true label returns false and vice versa.
@@ -530,7 +490,6 @@ static void EmitEqualityAsInstanceCall(FlowGraphCompiler* compiler,
__ LoadObject(RAX, Bool::False(), PP);
__ Bind(&done);
}
__ Bind(&equality_done);
}
@@ -704,34 +663,10 @@ static void EmitGenericEqualityCompare(FlowGraphCompiler* compiler,
ASSERT(!ic_data.IsNull() && (ic_data.NumberOfChecks() > 0));
Register left = locs->in(0).reg();
Register right = locs->in(1).reg();
Label done, identity_compare, non_null_compare;
__ CompareObject(right, Object::null_object(), PP);
__ j(EQUAL, &identity_compare, Assembler::kNearJump);
__ CompareObject(left, Object::null_object(), PP);
__ j(NOT_EQUAL, &non_null_compare, Assembler::kNearJump);
// Comparison with NULL is "===".
__ Bind(&identity_compare);
__ cmpq(left, right);
Condition cond = TokenKindToSmiCondition(kind);
if (branch != NULL) {
branch->EmitBranchOnCondition(compiler, cond);
} else {
Register result = locs->out().reg();
Label load_true;
__ j(cond, &load_true, Assembler::kNearJump);
__ LoadObject(result, Bool::False(), PP);
__ jmp(&done);
__ Bind(&load_true);
__ LoadObject(result, Bool::True(), PP);
}
__ jmp(&done);
__ Bind(&non_null_compare); // Receiver is not null.
__ pushq(left);
__ pushq(right);
EmitEqualityAsPolymorphicCall(compiler, ic_data, locs, branch, kind,
deopt_id, token_pos);
__ Bind(&done);
}
+44 -49
View File
@@ -1923,55 +1923,7 @@ AstNode* Parser::ParseSuperOperator() {
op_arguments = BuildNoSuchMethodArguments(
operator_pos, operator_function_name, *op_arguments);
}
if (super_operator.name() == Symbols::EqualOperator().raw()) {
// Expand super.== call to match correct == semantics into:
// Let t1 = left, t2 = right {
// (t1 === null || t2 === null) ? t1 === t2
// : static_call(super.==, t1, t2)
// }
// Normal == calls are not expanded at the AST level to produce
// more compact code and enable more optimization opportunities.
ASSERT(!is_no_such_method); // == is always found.
EnsureExpressionTemp(); // Needed for ConditionalExprNode.
LetNode* result = new LetNode(operator_pos);
AstNode* left =
new LoadLocalNode(operator_pos,
result->AddInitializer(op_arguments->NodeAt(0)));
AstNode* right =
new LoadLocalNode(operator_pos,
result->AddInitializer(op_arguments->NodeAt(1)));
LiteralNode* null_operand =
new LiteralNode(operator_pos, Instance::ZoneHandle());
ComparisonNode* is_left_null = new ComparisonNode(operator_pos,
Token::kEQ_STRICT,
left,
null_operand);
ComparisonNode* is_right_null = new ComparisonNode(operator_pos,
Token::kEQ_STRICT,
right,
null_operand);
BinaryOpNode* null_check = new BinaryOpNode(operator_pos,
Token::kOR,
is_left_null,
is_right_null);
ArgumentListNode* new_arguments = new ArgumentListNode(operator_pos);
new_arguments->Add(left);
new_arguments->Add(right);
StaticCallNode* call = new StaticCallNode(operator_pos,
super_operator,
new_arguments);
ComparisonNode* strict_eq = new ComparisonNode(operator_pos,
Token::kEQ_STRICT,
left,
right);
result->AddNode(new ConditionalExprNode(operator_pos,
null_check,
strict_eq,
call));
super_op = result;
} else {
super_op = new StaticCallNode(operator_pos, super_operator, op_arguments);
}
super_op = new StaticCallNode(operator_pos, super_operator, op_arguments);
if (negate_result) {
super_op = new UnaryOpNode(operator_pos, Token::kNOT, super_op);
}
@@ -2879,17 +2831,35 @@ SequenceNode* Parser::ParseFunc(const Function& func,
intptr_t end_token_pos = 0;
if (CurrentToken() == Token::kLBRACE) {
ConsumeToken();
if (String::Handle(func.name()).Equals(Symbols::EqualOperator())) {
const Class& owner = Class::Handle(func.Owner());
if (!owner.IsObjectClass()) {
AddEqualityNullCheck();
}
}
ParseStatementSequence();
end_token_pos = TokenPos();
ExpectToken(Token::kRBRACE);
} else if (CurrentToken() == Token::kARROW) {
ConsumeToken();
if (String::Handle(func.name()).Equals(Symbols::EqualOperator())) {
const Class& owner = Class::Handle(func.Owner());
if (!owner.IsObjectClass()) {
AddEqualityNullCheck();
}
}
const intptr_t expr_pos = TokenPos();
AstNode* expr = ParseExpr(kAllowConst, kConsumeCascades);
ASSERT(expr != NULL);
current_block_->statements->Add(new ReturnNode(expr_pos, expr));
end_token_pos = TokenPos();
} else if (IsLiteral("native")) {
if (String::Handle(func.name()).Equals(Symbols::EqualOperator())) {
const Class& owner = Class::Handle(func.Owner());
if (!owner.IsObjectClass()) {
AddEqualityNullCheck();
}
}
ParseNativeFunctionBlock(&params, func);
end_token_pos = TokenPos();
ExpectSemicolon();
@@ -2924,6 +2894,31 @@ SequenceNode* Parser::ParseFunc(const Function& func,
}
void Parser::AddEqualityNullCheck() {
const intptr_t token_pos = Scanner::kDummyTokenIndex;
AstNode* argument =
new LoadLocalNode(token_pos,
current_block_->scope->parent()->VariableAt(1));
LiteralNode* null_operand =
new LiteralNode(token_pos, Instance::ZoneHandle());
ComparisonNode* check_arg = new ComparisonNode(token_pos,
Token::kEQ_STRICT,
argument,
null_operand);
ComparisonNode* result = new ComparisonNode(token_pos,
Token::kEQ_STRICT,
LoadReceiver(token_pos),
null_operand);
SequenceNode* arg_is_null = new SequenceNode(token_pos, NULL);
arg_is_null->Add(new ReturnNode(token_pos, result));
IfNode* if_arg_null = new IfNode(token_pos,
check_arg,
arg_is_null,
NULL);
current_block_->statements->Add(if_arg_null);
}
void Parser::SkipIf(Token::Kind token) {
if (CurrentToken() == token) {
ConsumeToken();
+2
View File
@@ -662,6 +662,8 @@ class Parser : public ValueObject {
const Function& constructor,
ArgumentListNode* arguments);
void AddEqualityNullCheck();
RawInstance* TryCanonicalize(const Instance& instance, intptr_t token_pos);
Isolate* isolate() const { return isolate_; }
-1
View File
@@ -67,7 +67,6 @@ class RawCode;
V(TwoArgsUnoptimizedStaticCall) \
V(OptimizeFunction) \
V(BreakpointDynamic) \
V(EqualityWithNullArg) \
// class StubEntry is used to describe stub methods generated in dart to
// abstract out common code executed from generated dart code.
-86
View File
@@ -1966,92 +1966,6 @@ void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) {
}
// Implements equality operator when one of the arguments is null
// (identity check) and updates ICData if necessary.
// LR: return address.
// R1: left argument.
// R0: right argument.
// R5: ICData.
// R0: result.
// TODO(srdjan): Move to VM stubs once Boolean objects become VM objects.
void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) {
__ EnterStubFrame();
static const intptr_t kNumArgsTested = 2;
#if defined(DEBUG)
{ Label ok;
__ ldr(IP, FieldAddress(R5, ICData::num_args_tested_offset()));
__ cmp(IP, ShifterOperand(kNumArgsTested));
__ b(&ok, EQ);
__ Stop("Incorrect ICData for equality");
__ Bind(&ok);
}
#endif // DEBUG
// Check IC data, update if needed.
// R5: IC data object (preserved).
__ ldr(R6, FieldAddress(R5, ICData::ic_data_offset()));
// R6: ic_data_array with check entries: classes and target functions.
__ AddImmediate(R6, Array::data_offset() - kHeapObjectTag);
// R6: points directly to the first ic data array element.
Label get_class_id_as_smi, no_match, loop, found;
__ Bind(&loop);
// Check left.
__ mov(R2, ShifterOperand(R1));
__ bl(&get_class_id_as_smi);
__ ldr(R3, Address(R6, 0 * kWordSize));
__ cmp(R2, ShifterOperand(R3)); // Class id match?
__ b(&no_match, NE);
// Check right.
__ mov(R2, ShifterOperand(R0));
__ bl(&get_class_id_as_smi);
__ ldr(R3, Address(R6, 1 * kWordSize));
__ cmp(R2, ShifterOperand(R3)); // Class id match?
__ b(&found, EQ);
__ Bind(&no_match);
// Next check group.
__ AddImmediate(R6, kWordSize * ICData::TestEntryLengthFor(kNumArgsTested));
__ CompareImmediate(R3, Smi::RawValue(kIllegalCid)); // Done?
__ b(&loop, NE);
Label update_ic_data;
__ b(&update_ic_data);
__ Bind(&found);
const intptr_t count_offset =
ICData::CountIndexFor(kNumArgsTested) * kWordSize;
__ ldr(IP, Address(R6, count_offset));
__ adds(IP, IP, ShifterOperand(Smi::RawValue(1)));
__ LoadImmediate(IP, Smi::RawValue(Smi::kMaxValue), VS); // If overflow.
__ str(IP, Address(R6, count_offset));
Label compute_result;
__ Bind(&compute_result);
__ cmp(R0, ShifterOperand(R1));
__ LoadObject(R0, Bool::False(), NE);
__ LoadObject(R0, Bool::True(), EQ);
__ LeaveStubFrame();
__ Ret();
__ Bind(&get_class_id_as_smi);
// Test if Smi -> load Smi class for comparison.
__ tst(R2, ShifterOperand(kSmiTagMask));
__ mov(R2, ShifterOperand(Smi::RawValue(kSmiCid)), EQ);
__ bx(LR, EQ);
__ LoadClassId(R2, R2);
__ SmiTag(R2);
__ bx(LR);
__ Bind(&update_ic_data);
// R5: ICData
__ PushList((1 << R0) | (1 << R1));
__ PushObject(Symbols::EqualOperator()); // Target's name.
__ Push(R5); // ICData
__ CallRuntime(kUpdateICDataTwoArgsRuntimeEntry, 4); // Clobbers R4, R5.
__ Drop(2);
__ PopList((1 << R0) | (1 << R1));
__ b(&compute_result);
}
// Calls to the runtime to optimize the given function.
// R6: function to be reoptimized.
// R4: argument descriptor (preserved).
-99
View File
@@ -2032,105 +2032,6 @@ void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) {
}
// Implements equality operator when one of the arguments is null
// (identity check) and updates ICData if necessary.
// TOS + 0: return address
// TOS + 1: right argument
// TOS + 2: left argument
// ECX: ICData.
// EAX: result.
// TODO(srdjan): Move to VM stubs once Boolean objects become VM objects.
void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) {
static const intptr_t kNumArgsTested = 2;
#if defined(DEBUG)
{ Label ok;
__ movl(EAX, FieldAddress(ECX, ICData::num_args_tested_offset()));
__ cmpl(EAX, Immediate(kNumArgsTested));
__ j(EQUAL, &ok, Assembler::kNearJump);
__ Stop("Incorrect ICData for equality");
__ Bind(&ok);
}
#endif // DEBUG
// Check IC data, update if needed.
// ECX: IC data object (preserved).
__ movl(EBX, FieldAddress(ECX, ICData::ic_data_offset()));
// EBX: ic_data_array with check entries: classes and target functions.
__ leal(EBX, FieldAddress(EBX, Array::data_offset()));
// EBX: points directly to the first ic data array element.
Label get_class_id_as_smi, no_match, loop, compute_result, found;
__ Bind(&loop);
// Check left.
__ movl(EAX, Address(ESP, 2 * kWordSize));
__ call(&get_class_id_as_smi);
__ movl(EDI, Address(EBX, 0 * kWordSize));
__ cmpl(EAX, EDI); // Class id match?
__ j(NOT_EQUAL, &no_match, Assembler::kNearJump);
// Check right.
__ movl(EAX, Address(ESP, 1 * kWordSize));
__ call(&get_class_id_as_smi);
__ movl(EDI, Address(EBX, 1 * kWordSize));
__ cmpl(EAX, EDI); // Class id match?
__ j(EQUAL, &found, Assembler::kNearJump);
__ Bind(&no_match);
// Next check group.
__ addl(EBX, Immediate(
kWordSize * ICData::TestEntryLengthFor(kNumArgsTested)));
__ cmpl(EDI, Immediate(Smi::RawValue(kIllegalCid))); // Done?
__ j(NOT_EQUAL, &loop, Assembler::kNearJump);
Label update_ic_data;
__ jmp(&update_ic_data);
__ Bind(&found);
const intptr_t count_offset =
ICData::CountIndexFor(kNumArgsTested) * kWordSize;
__ addl(Address(EBX, count_offset), Immediate(Smi::RawValue(1)));
__ j(NO_OVERFLOW, &compute_result);
__ movl(Address(EBX, count_offset),
Immediate(Smi::RawValue(Smi::kMaxValue)));
__ Bind(&compute_result);
Label true_label;
__ movl(EAX, Address(ESP, 1 * kWordSize));
__ cmpl(EAX, Address(ESP, 2 * kWordSize));
__ j(EQUAL, &true_label, Assembler::kNearJump);
__ LoadObject(EAX, Bool::False());
__ ret();
__ Bind(&true_label);
__ LoadObject(EAX, Bool::True());
__ ret();
__ Bind(&get_class_id_as_smi);
Label not_smi;
// Test if Smi -> load Smi class for comparison.
__ testl(EAX, Immediate(kSmiTagMask));
__ j(NOT_ZERO, &not_smi, Assembler::kNearJump);
__ movl(EAX, Immediate(Smi::RawValue(kSmiCid)));
__ ret();
__ Bind(&not_smi);
__ LoadClassId(EAX, EAX);
__ SmiTag(EAX);
__ ret();
__ Bind(&update_ic_data);
// ECX: ICData
__ movl(EAX, Address(ESP, 1 * kWordSize));
__ movl(EDI, Address(ESP, 2 * kWordSize));
__ EnterStubFrame();
__ pushl(EDI); // arg 0
__ pushl(EAX); // arg 1
__ PushObject(Symbols::EqualOperator()); // Target's name.
__ pushl(ECX); // ICData
__ CallRuntime(kUpdateICDataTwoArgsRuntimeEntry, 4);
__ Drop(4);
__ LeaveFrame();
__ jmp(&compute_result, Assembler::kNearJump);
}
// Calls to the runtime to optimize the given function.
// EDI: function to be reoptimized.
// EDX: argument descriptor (preserved).
-104
View File
@@ -2232,110 +2232,6 @@ void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) {
}
// Implements equality operator when one of the arguments is null
// (identity check) and updates ICData if necessary.
// RA: return address.
// A1: left argument.
// A0: right argument.
// T0: ICData.
// V0: result.
// TODO(srdjan): Move to VM stubs once Boolean objects become VM objects.
void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) {
__ TraceSimMsg("EqualityWithNullArgStub");
__ Comment("EqualityWithNullArgStub");
__ EnterStubFrame();
static const intptr_t kNumArgsTested = 2;
#if defined(DEBUG)
{ Label ok;
__ lw(CMPRES1, FieldAddress(T0, ICData::num_args_tested_offset()));
__ BranchEqual(CMPRES1, kNumArgsTested, &ok);
__ Stop("Incorrect ICData for equality");
__ Bind(&ok);
}
#endif // DEBUG
// Check IC data, update if needed.
// T0: IC data object (preserved).
__ lw(T6, FieldAddress(T0, ICData::ic_data_offset()));
// T6: ic_data_array with check entries: classes and target functions.
__ AddImmediate(T6, Array::data_offset() - kHeapObjectTag);
// T6: points directly to the first ic data array element.
Label get_class_id_as_smi, no_match, loop, found;
__ Bind(&loop);
// Check left.
__ bal(&get_class_id_as_smi);
__ delay_slot()->mov(T2, A1);
__ lw(T3, Address(T6, 0 * kWordSize));
__ bne(T2, T3, &no_match); // Class id match?
// Check right.
__ bal(&get_class_id_as_smi);
__ delay_slot()->mov(T2, A0);
__ lw(T3, Address(T6, 1 * kWordSize));
__ beq(T2, T3, &found); // Class id match?
__ Bind(&no_match);
// Next check group.
intptr_t entry_bytes = kWordSize * ICData::TestEntryLengthFor(kNumArgsTested);
if (Utils::IsInt(kImmBits, entry_bytes)) {
__ BranchNotEqual(T3, Smi::RawValue(kIllegalCid), &loop); // Done?
__ delay_slot()->addiu(T6, T6, Immediate(entry_bytes));
} else {
__ AddImmediate(T6, entry_bytes);
__ BranchNotEqual(T3, Smi::RawValue(kIllegalCid), &loop); // Done?
}
Label update_ic_data;
__ b(&update_ic_data);
__ Bind(&found);
const intptr_t count_offset =
ICData::CountIndexFor(kNumArgsTested) * kWordSize;
Label no_overflow;
__ lw(T1, Address(T6, count_offset));
__ AddImmediateDetectOverflow(T1, T1, Smi::RawValue(1), CMPRES, T5);
__ bgez(CMPRES, &no_overflow);
__ delay_slot()->sw(T1, Address(T6, count_offset));
__ LoadImmediate(TMP1, Smi::RawValue(Smi::kMaxValue));
__ sw(TMP1, Address(T6, count_offset)); // If overflow.
__ Bind(&no_overflow);
Label compute_result;
__ Bind(&compute_result);
__ LoadObject(T4, Bool::True());
__ LoadObject(T5, Bool::False());
__ subu(CMPRES, A0, A1);
__ movz(V0, T4, CMPRES);
__ movn(V0, T5, CMPRES);
__ LeaveStubFrameAndReturn();
__ Bind(&get_class_id_as_smi);
// Test if Smi -> load Smi class for comparison.
Label not_smi;
__ andi(CMPRES, T2, Immediate(kSmiTagMask));
__ bne(CMPRES, ZR, &not_smi);
__ jr(RA);
__ delay_slot()->addiu(T2, ZR, Immediate(Smi::RawValue(kSmiCid)));
__ Bind(&not_smi);
__ LoadClassId(T2, T2);
__ jr(RA);
__ delay_slot()->SmiTag(T2);
__ Bind(&update_ic_data);
// T0: ICData
__ addiu(SP, SP, Immediate(-4 * kWordSize));
__ sw(A1, Address(SP, 3 * kWordSize));
__ sw(A0, Address(SP, 2 * kWordSize));
__ LoadObject(TMP1, Symbols::EqualOperator()); // Target's name.
__ sw(TMP1, Address(SP, 1 * kWordSize));
__ sw(T0, Address(SP, 0 * kWordSize)); // ICData.
__ CallRuntime(kUpdateICDataTwoArgsRuntimeEntry, 4);
__ lw(A0, Address(SP, 2 * kWordSize));
__ lw(A1, Address(SP, 3 * kWordSize));
__ b(&compute_result);
__ delay_slot()->addiu(SP, SP, Immediate(4 * kWordSize));
}
// Calls to the runtime to optimize the given function.
// T0: function to be reoptimized.
// S4: argument descriptor (preserved).
-98
View File
@@ -2003,104 +2003,6 @@ void StubCode::GenerateJumpToExceptionHandlerStub(Assembler* assembler) {
}
// Implements equality operator when one of the arguments is null
// (identity check) and updates ICData if necessary.
// TOS + 0: return address
// TOS + 1: right argument
// TOS + 2: left argument
// RBX: ICData.
// RAX: result.
// TODO(srdjan): Move to VM stubs once Boolean objects become VM objects.
void StubCode::GenerateEqualityWithNullArgStub(Assembler* assembler) {
static const intptr_t kNumArgsTested = 2;
#if defined(DEBUG)
{ Label ok;
__ movq(RCX, FieldAddress(RBX, ICData::num_args_tested_offset()));
__ cmpq(RCX, Immediate(kNumArgsTested));
__ j(EQUAL, &ok, Assembler::kNearJump);
__ Stop("Incorrect ICData for equality");
__ Bind(&ok);
}
#endif // DEBUG
// Check IC data, update if needed.
// RBX: IC data object (preserved).
__ movq(R12, FieldAddress(RBX, ICData::ic_data_offset()));
// R12: ic_data_array with check entries: classes and target functions.
__ leaq(R12, FieldAddress(R12, Array::data_offset()));
// R12: points directly to the first ic data array element.
Label get_class_id_as_smi, no_match, loop, compute_result, found;
__ Bind(&loop);
// Check left.
__ movq(RAX, Address(RSP, 2 * kWordSize));
__ call(&get_class_id_as_smi);
__ movq(R13, Address(R12, 0 * kWordSize));
__ cmpq(RAX, R13); // Class id match?
__ j(NOT_EQUAL, &no_match, Assembler::kNearJump);
// Check right.
__ movq(RAX, Address(RSP, 1 * kWordSize));
__ call(&get_class_id_as_smi);
__ movq(R13, Address(R12, 1 * kWordSize));
__ cmpq(RAX, R13); // Class id match?
__ j(EQUAL, &found, Assembler::kNearJump);
__ Bind(&no_match);
// Next check group.
__ addq(R12, Immediate(
kWordSize * ICData::TestEntryLengthFor(kNumArgsTested)));
__ cmpq(R13, Immediate(Smi::RawValue(kIllegalCid))); // Done?
__ j(NOT_EQUAL, &loop, Assembler::kNearJump);
Label update_ic_data;
__ jmp(&update_ic_data);
__ Bind(&found);
const intptr_t count_offset =
ICData::CountIndexFor(kNumArgsTested) * kWordSize;
__ addq(Address(R12, count_offset), Immediate(Smi::RawValue(1)));
__ j(NO_OVERFLOW, &compute_result);
__ movq(Address(R12, count_offset),
Immediate(Smi::RawValue(Smi::kMaxValue)));
__ Bind(&compute_result);
Label true_label;
__ movq(RAX, Address(RSP, 1 * kWordSize));
__ cmpq(RAX, Address(RSP, 2 * kWordSize));
__ j(EQUAL, &true_label, Assembler::kNearJump);
__ LoadObject(RAX, Bool::False(), PP);
__ ret();
__ Bind(&true_label);
__ LoadObject(RAX, Bool::True(), PP);
__ ret();
__ Bind(&get_class_id_as_smi);
Label not_smi;
// Test if Smi -> load Smi class for comparison.
__ testq(RAX, Immediate(kSmiTagMask));
__ j(NOT_ZERO, &not_smi, Assembler::kNearJump);
__ movq(RAX, Immediate(Smi::RawValue(kSmiCid)));
__ ret();
__ Bind(&not_smi);
__ LoadClassId(RAX, RAX);
__ SmiTag(RAX);
__ ret();
__ Bind(&update_ic_data);
// RBX: ICData
__ movq(RAX, Address(RSP, 1 * kWordSize));
__ movq(R13, Address(RSP, 2 * kWordSize));
__ EnterStubFrameWithPP();
__ pushq(R13); // arg 0
__ pushq(RAX); // arg 1
__ PushObject(Symbols::EqualOperator(), PP); // Target's name.
__ pushq(RBX); // ICData
__ CallRuntime(kUpdateICDataTwoArgsRuntimeEntry, 4);
__ Drop(4);
__ LeaveFrameWithPP();
__ jmp(&compute_result, Assembler::kNearJump);
}
// Calls to the runtime to optimize the given function.
// RDI: function to be reoptimized.
// R10: argument descriptor (preserved).
@@ -0,0 +1,19 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test function equality with null.
import "package:expect/expect.dart";
class A {
foo() { }
}
main() {
var a = new A();
var f = a.foo;
Expect.isFalse(f == null);
Expect.isFalse(null == f);
}
@@ -0,0 +1,59 @@
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "debug_lib.dart";
class MyClass {
operator ==(other) {
print(other);
return true; // Breakpoint #3.
}
}
main() {
if (RunScript(testScript)) return;
var a = new MyClass();
var b = null;
var x = a == b; // Breakpoint #1.
print(x);
b = 123;
a == b; // Breakpoint #2.
print("after 2");
if (a == null) { // Breakpoint #4.
throw "unreachable";
}
print("after 4");
if (null == a) {
throw "unreachable";
}
print("ok");
}
var testScript = [
MatchFrames(["main"]),
SetBreakpoint(18),
SetBreakpoint(21),
SetBreakpoint(10),
SetBreakpoint(23),
Resume(),
MatchFrames(["main"]), // At breakpoint #1.
StepInto(),
MatchFrames(["main"]), // Don't step into == method because of null.
Resume(),
MatchFrames(["main"]), // At breakpoint #2.
StepInto(),
StepInto(),
MatchFrames(["MyClass.==", "main"]), // At MyClass.== entry.
Resume(),
MatchFrames(["MyClass.==", "main"]), // At breakpoint #3.
Resume(),
MatchFrames(["main"]), // At breakpoint #4.
StepInto(),
MatchFrames(["main"]), // After breakpoint #4.
Step(),
MatchFrames(["main"]), // At null == a.
StepInto(),
MatchFrames(["main"]), // After null == a.
Resume()
];