From: fschneider@chromium.org Date: Thu, 17 Nov 2011 13:57:55 +0000 (+0000) Subject: Landing: [hydrogen] optimize switch with string clauses. Patch by Fedor Indutny ... X-Git-Tag: upstream/4.7.83~17892 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=8fbf1d50172c1c380c84ded66664ef006517122e;p=platform%2Fupstream%2Fv8.git Landing: [hydrogen] optimize switch with string clauses. Patch by Fedor Indutny . Original code review: http://codereview.chromium.org/8373029/ Review URL: http://codereview.chromium.org/8589019 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@10019 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- diff --git a/src/arm/lithium-arm.cc b/src/arm/lithium-arm.cc index 62eae3f..cc3f38d 100644 --- a/src/arm/lithium-arm.cc +++ b/src/arm/lithium-arm.cc @@ -228,6 +228,13 @@ void LIsObjectAndBranch::PrintDataTo(StringStream* stream) { } +void LIsStringAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_string("); + InputAt(0)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LIsSmiAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if is_smi("); InputAt(0)->PrintTo(stream); @@ -242,6 +249,14 @@ void LIsUndetectableAndBranch::PrintDataTo(StringStream* stream) { } +void LStringCompareAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if string_compare("); + InputAt(0)->PrintTo(stream); + InputAt(1)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if has_instance_type("); InputAt(0)->PrintTo(stream); @@ -1452,6 +1467,13 @@ LInstruction* LChunkBuilder::DoIsObjectAndBranch(HIsObjectAndBranch* instr) { } +LInstruction* LChunkBuilder::DoIsStringAndBranch(HIsStringAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* temp = TempRegister(); + return new LIsStringAndBranch(UseRegisterAtStart(instr->value()), temp); +} + + LInstruction* LChunkBuilder::DoIsSmiAndBranch(HIsSmiAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); return new LIsSmiAndBranch(Use(instr->value())); @@ -1466,6 +1488,17 @@ LInstruction* LChunkBuilder::DoIsUndetectableAndBranch( } +LInstruction* LChunkBuilder::DoStringCompareAndBranch( + HStringCompareAndBranch* instr) { + ASSERT(instr->left()->representation().IsTagged()); + ASSERT(instr->right()->representation().IsTagged()); + LOperand* left = UseFixed(instr->left(), r1); + LOperand* right = UseFixed(instr->right(), r0); + LStringCompareAndBranch* result = new LStringCompareAndBranch(left, right); + return MarkAsCall(result, instr); +} + + LInstruction* LChunkBuilder::DoHasInstanceTypeAndBranch( HHasInstanceTypeAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); diff --git a/src/arm/lithium-arm.h b/src/arm/lithium-arm.h index 5a73bed..4e1974f 100644 --- a/src/arm/lithium-arm.h +++ b/src/arm/lithium-arm.h @@ -109,8 +109,10 @@ class LCodeGen; V(IsConstructCallAndBranch) \ V(IsNilAndBranch) \ V(IsObjectAndBranch) \ + V(IsStringAndBranch) \ V(IsSmiAndBranch) \ V(IsUndetectableAndBranch) \ + V(StringCompareAndBranch) \ V(JSArrayLength) \ V(Label) \ V(LazyBailout) \ @@ -658,6 +660,20 @@ class LIsObjectAndBranch: public LControlInstruction<1, 1> { }; +class LIsStringAndBranch: public LControlInstruction<1, 1> { + public: + LIsStringAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + DECLARE_CONCRETE_INSTRUCTION(IsStringAndBranch, "is-string-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsStringAndBranch) + + virtual void PrintDataTo(StringStream* stream); +}; + + class LIsSmiAndBranch: public LControlInstruction<1, 0> { public: explicit LIsSmiAndBranch(LOperand* value) { @@ -686,6 +702,23 @@ class LIsUndetectableAndBranch: public LControlInstruction<1, 1> { }; +class LStringCompareAndBranch: public LControlInstruction<2, 0> { + public: + LStringCompareAndBranch(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + DECLARE_CONCRETE_INSTRUCTION(StringCompareAndBranch, + "string-compare-and-branch") + DECLARE_HYDROGEN_ACCESSOR(StringCompareAndBranch) + + Token::Value op() const { return hydrogen()->token(); } + + virtual void PrintDataTo(StringStream* stream); +}; + + class LHasInstanceTypeAndBranch: public LControlInstruction<1, 0> { public: explicit LHasInstanceTypeAndBranch(LOperand* value) { diff --git a/src/arm/lithium-codegen-arm.cc b/src/arm/lithium-codegen-arm.cc index db1a365..61bdf68 100644 --- a/src/arm/lithium-codegen-arm.cc +++ b/src/arm/lithium-codegen-arm.cc @@ -1812,6 +1812,31 @@ void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { } +Condition LCodeGen::EmitIsString(Register input, + Register temp1, + Label* is_not_string) { + __ JumpIfSmi(input, is_not_string); + __ CompareObjectType(input, temp1, temp1, FIRST_NONSTRING_TYPE); + + return lt; +} + + +void LCodeGen::DoIsStringAndBranch(LIsStringAndBranch* instr) { + Register reg = ToRegister(instr->InputAt(0)); + Register temp1 = ToRegister(instr->TempAt(0)); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition true_cond = + EmitIsString(reg, temp1, false_label); + + EmitBranch(true_block, false_block, true_cond); +} + + void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { int true_block = chunk_->LookupDestination(instr->true_block_id()); int false_block = chunk_->LookupDestination(instr->false_block_id()); @@ -1837,6 +1862,41 @@ void LCodeGen::DoIsUndetectableAndBranch(LIsUndetectableAndBranch* instr) { } +static Condition ComputeCompareCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return eq; + case Token::LT: + return lt; + case Token::GT: + return gt; + case Token::LTE: + return le; + case Token::GTE: + return ge; + default: + UNREACHABLE(); + return kNoCondition; + } +} + + +void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) { + Token::Value op = instr->op(); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Handle ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + __ cmp(r0, Operand(0)); // This instruction also signals no smi code inlined. + + Condition condition = ComputeCompareCondition(op); + + EmitBranch(true_block, false_block, condition); +} + + static InstanceType TestType(HHasInstanceTypeAndBranch* instr) { InstanceType from = instr->from(); InstanceType to = instr->to(); @@ -2125,26 +2185,6 @@ void LCodeGen::DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr, } -static Condition ComputeCompareCondition(Token::Value op) { - switch (op) { - case Token::EQ_STRICT: - case Token::EQ: - return eq; - case Token::LT: - return lt; - case Token::GT: - return gt; - case Token::LTE: - return le; - case Token::GTE: - return ge; - default: - UNREACHABLE(); - return kNoCondition; - } -} - - void LCodeGen::DoCmpT(LCmpT* instr) { Token::Value op = instr->op(); diff --git a/src/arm/lithium-codegen-arm.h b/src/arm/lithium-codegen-arm.h index 2b49e19..ac757ea 100644 --- a/src/arm/lithium-codegen-arm.h +++ b/src/arm/lithium-codegen-arm.h @@ -284,6 +284,13 @@ class LCodeGen BASE_EMBEDDED { Label* is_not_object, Label* is_object); + // Emits optimized code for %_IsString(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsString(Register input, + Register temp1, + Label* is_not_string); + // Emits optimized code for %_IsConstructCall(). // Caller should branch on equal condition. void EmitIsConstructCall(Register temp1, Register temp2); diff --git a/src/ast.cc b/src/ast.cc index 1429e2a..91f4d9a 100644 --- a/src/ast.cc +++ b/src/ast.cc @@ -710,6 +710,10 @@ void CaseClause::RecordTypeFeedback(TypeFeedbackOracle* oracle) { TypeInfo info = oracle->SwitchType(this); if (info.IsSmi()) { compare_type_ = SMI_ONLY; + } else if (info.IsSymbol()) { + compare_type_ = SYMBOL_ONLY; + } else if (info.IsNonSymbol()) { + compare_type_ = STRING_ONLY; } else if (info.IsNonPrimitive()) { compare_type_ = OBJECT_ONLY; } else { diff --git a/src/ast.h b/src/ast.h index 71639d5..ab300db 100644 --- a/src/ast.h +++ b/src/ast.h @@ -726,6 +726,8 @@ class CaseClause: public ZoneObject { // Type feedback information. void RecordTypeFeedback(TypeFeedbackOracle* oracle); bool IsSmiCompare() { return compare_type_ == SMI_ONLY; } + bool IsSymbolCompare() { return compare_type_ == SYMBOL_ONLY; } + bool IsStringCompare() { return compare_type_ == STRING_ONLY; } bool IsObjectCompare() { return compare_type_ == OBJECT_ONLY; } private: @@ -733,7 +735,13 @@ class CaseClause: public ZoneObject { Label body_target_; ZoneList* statements_; int position_; - enum CompareTypeFeedback { NONE, SMI_ONLY, OBJECT_ONLY }; + enum CompareTypeFeedback { + NONE, + SMI_ONLY, + SYMBOL_ONLY, + STRING_ONLY, + OBJECT_ONLY + }; CompareTypeFeedback compare_type_; int compare_id_; int entry_id_; diff --git a/src/hydrogen-instructions.cc b/src/hydrogen-instructions.cc index ae62367..bf09dd5 100644 --- a/src/hydrogen-instructions.cc +++ b/src/hydrogen-instructions.cc @@ -1325,6 +1325,13 @@ void HCompareGeneric::PrintDataTo(StringStream* stream) { } +void HStringCompareAndBranch::PrintDataTo(StringStream* stream) { + stream->Add(Token::Name(token())); + stream->Add(" "); + HControlInstruction::PrintDataTo(stream); +} + + void HCompareIDAndBranch::PrintDataTo(StringStream* stream) { stream->Add(Token::Name(token())); stream->Add(" "); diff --git a/src/hydrogen-instructions.h b/src/hydrogen-instructions.h index fce0e30..29744a9 100644 --- a/src/hydrogen-instructions.h +++ b/src/hydrogen-instructions.h @@ -118,8 +118,10 @@ class LChunkBuilder; V(IsConstructCallAndBranch) \ V(IsNilAndBranch) \ V(IsObjectAndBranch) \ + V(IsStringAndBranch) \ V(IsSmiAndBranch) \ V(IsUndetectableAndBranch) \ + V(StringCompareAndBranch) \ V(JSArrayLength) \ V(LeaveInlined) \ V(LoadContextSlot) \ @@ -2716,6 +2718,18 @@ class HIsObjectAndBranch: public HUnaryControlInstruction { DECLARE_CONCRETE_INSTRUCTION(IsObjectAndBranch) }; +class HIsStringAndBranch: public HUnaryControlInstruction { + public: + explicit HIsStringAndBranch(HValue* value) + : HUnaryControlInstruction(value, NULL, NULL) { } + + virtual Representation RequiredInputRepresentation(int index) { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(IsStringAndBranch) +}; + class HIsSmiAndBranch: public HUnaryControlInstruction { public: @@ -2746,6 +2760,42 @@ class HIsUndetectableAndBranch: public HUnaryControlInstruction { }; +class HStringCompareAndBranch: public HTemplateControlInstruction<2, 3> { + public: + HStringCompareAndBranch(HValue* context, + HValue* left, + HValue* right, + Token::Value token) + : token_(token) { + ASSERT(Token::IsCompareOp(token)); + SetOperandAt(0, context); + SetOperandAt(1, left); + SetOperandAt(2, right); + set_representation(Representation::Tagged()); + } + + HValue* context() { return OperandAt(0); } + HValue* left() { return OperandAt(1); } + HValue* right() { return OperandAt(2); } + Token::Value token() const { return token_; } + + virtual void PrintDataTo(StringStream* stream); + + virtual Representation RequiredInputRepresentation(int index) { + return Representation::Tagged(); + } + + Representation GetInputRepresentation() const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(StringCompareAndBranch) + + private: + Token::Value token_; +}; + + class HIsConstructCallAndBranch: public HTemplateControlInstruction<2, 0> { public: virtual Representation RequiredInputRepresentation(int index) { diff --git a/src/hydrogen.cc b/src/hydrogen.cc index d0380c4..108d00c 100644 --- a/src/hydrogen.cc +++ b/src/hydrogen.cc @@ -734,6 +734,7 @@ void HGraph::Postorder(HBasicBlock* block, Postorder(it.Current(), visited, order, block); } } else { + ASSERT(block->IsFinished()); for (HSuccessorIterator it(block->end()); !it.Done(); it.Advance()) { Postorder(it.Current(), visited, order, loop_header); } @@ -2708,43 +2709,95 @@ void HGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { return Bailout("SwitchStatement: too many clauses"); } + HValue* context = environment()->LookupContext(); + CHECK_ALIVE(VisitForValue(stmt->tag())); AddSimulate(stmt->EntryId()); HValue* tag_value = Pop(); HBasicBlock* first_test_block = current_block(); - // 1. Build all the tests, with dangling true branches. Unconditionally - // deoptimize if we encounter a non-smi comparison. + SwitchType switch_type = UNKNOWN_SWITCH; + + // 1. Extract clause type for (int i = 0; i < clause_count; ++i) { CaseClause* clause = clauses->at(i); if (clause->is_default()) continue; - if (!clause->label()->IsSmiLiteral()) { - return Bailout("SwitchStatement: non-literal switch label"); + + if (switch_type == UNKNOWN_SWITCH) { + if (clause->label()->IsSmiLiteral()) { + switch_type = SMI_SWITCH; + } else if (clause->label()->IsStringLiteral()) { + switch_type = STRING_SWITCH; + } else { + return Bailout("SwitchStatement: non-literal switch label"); + } + } else if ((switch_type == STRING_SWITCH && + !clause->label()->IsStringLiteral()) || + (switch_type == SMI_SWITCH && + !clause->label()->IsSmiLiteral())) { + return Bailout("SwitchStatemnt: mixed label types are not supported"); } + } - // Unconditionally deoptimize on the first non-smi compare. - clause->RecordTypeFeedback(oracle()); - if (!clause->IsSmiCompare()) { - // Finish with deoptimize and add uses of enviroment values to - // account for invisible uses. - current_block()->FinishExitWithDeoptimization(HDeoptimize::kUseAll); - set_current_block(NULL); - break; + HUnaryControlInstruction* string_check = NULL; + HBasicBlock* not_string_block = NULL; + + // Test switch's tag value if all clauses are string literals + if (switch_type == STRING_SWITCH) { + string_check = new(zone()) HIsStringAndBranch(tag_value); + first_test_block = graph()->CreateBasicBlock(); + not_string_block = graph()->CreateBasicBlock(); + + string_check->SetSuccessorAt(0, first_test_block); + string_check->SetSuccessorAt(1, not_string_block); + current_block()->Finish(string_check); + + set_current_block(first_test_block); + } + + // 2. Build all the tests, with dangling true branches + for (int i = 0; i < clause_count; ++i) { + CaseClause* clause = clauses->at(i); + if (clause->is_default()) continue; + + if (switch_type == SMI_SWITCH) { + clause->RecordTypeFeedback(oracle()); } - // Otherwise generate a compare and branch. + // Generate a compare and branch. CHECK_ALIVE(VisitForValue(clause->label())); HValue* label_value = Pop(); - HCompareIDAndBranch* compare = - new(zone()) HCompareIDAndBranch(tag_value, - label_value, - Token::EQ_STRICT); - compare->SetInputRepresentation(Representation::Integer32()); - HBasicBlock* body_block = graph()->CreateBasicBlock(); + HBasicBlock* next_test_block = graph()->CreateBasicBlock(); + HBasicBlock* body_block = graph()->CreateBasicBlock(); + + HControlInstruction* compare; + + if (switch_type == SMI_SWITCH) { + if (!clause->IsSmiCompare()) { + // Finish with deoptimize and add uses of enviroment values to + // account for invisible uses. + current_block()->FinishExitWithDeoptimization(HDeoptimize::kUseAll); + set_current_block(NULL); + break; + } + + HCompareIDAndBranch* compare_ = + new(zone()) HCompareIDAndBranch(tag_value, + label_value, + Token::EQ_STRICT); + compare_->SetInputRepresentation(Representation::Integer32()); + compare = compare_; + } else { + compare = new(zone()) HStringCompareAndBranch(context, tag_value, + label_value, + Token::EQ_STRICT); + } + compare->SetSuccessorAt(0, body_block); compare->SetSuccessorAt(1, next_test_block); current_block()->Finish(compare); + set_current_block(next_test_block); } @@ -2752,10 +2805,15 @@ void HGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { // exit. This block is NULL if we deoptimized. HBasicBlock* last_block = current_block(); - // 2. Loop over the clauses and the linked list of tests in lockstep, + if (not_string_block != NULL) { + last_block = CreateJoin(last_block, not_string_block, stmt->ExitId()); + } + + // 3. Loop over the clauses and the linked list of tests in lockstep, // translating the clause bodies. HBasicBlock* curr_test_block = first_test_block; HBasicBlock* fall_through_block = NULL; + BreakAndContinueInfo break_info(stmt); { BreakAndContinueScope push(&break_info, this); for (int i = 0; i < clause_count; ++i) { diff --git a/src/hydrogen.h b/src/hydrogen.h index 2d08dc8..948ea34 100644 --- a/src/hydrogen.h +++ b/src/hydrogen.h @@ -657,6 +657,7 @@ class FunctionState { class HGraphBuilder: public AstVisitor { public: enum BreakType { BREAK, CONTINUE }; + enum SwitchType { UNKNOWN_SWITCH, SMI_SWITCH, STRING_SWITCH }; // A class encapsulating (lazily-allocated) break and continue blocks for // a breakable statement. Separated from BreakAndContinueScope so that it diff --git a/src/ia32/lithium-codegen-ia32.cc b/src/ia32/lithium-codegen-ia32.cc index 7f78d03..97293d5 100644 --- a/src/ia32/lithium-codegen-ia32.cc +++ b/src/ia32/lithium-codegen-ia32.cc @@ -1683,6 +1683,31 @@ void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { } +Condition LCodeGen::EmitIsString(Register input, + Register temp1, + Label* is_not_string) { + __ JumpIfSmi(input, is_not_string); + + Condition cond = masm_->IsObjectStringType(input, temp1, temp1); + + return cond; +} + + +void LCodeGen::DoIsStringAndBranch(LIsStringAndBranch* instr) { + Register reg = ToRegister(instr->InputAt(0)); + Register temp = ToRegister(instr->TempAt(0)); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition true_cond = EmitIsString(reg, temp, false_label); + + EmitBranch(true_block, false_block, true_cond); +} + + void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { Operand input = ToOperand(instr->InputAt(0)); @@ -1710,6 +1735,41 @@ void LCodeGen::DoIsUndetectableAndBranch(LIsUndetectableAndBranch* instr) { } +static Condition ComputeCompareCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return equal; + case Token::LT: + return less; + case Token::GT: + return greater; + case Token::LTE: + return less_equal; + case Token::GTE: + return greater_equal; + default: + UNREACHABLE(); + return no_condition; + } +} + + +void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) { + Token::Value op = instr->op(); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Handle ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + Condition condition = ComputeCompareCondition(op); + __ test(eax, Operand(eax)); + + EmitBranch(true_block, false_block, condition); +} + + static InstanceType TestType(HHasInstanceTypeAndBranch* instr) { InstanceType from = instr->from(); InstanceType to = instr->to(); @@ -1987,26 +2047,6 @@ void LCodeGen::DoDeferredInstanceOfKnownGlobal(LInstanceOfKnownGlobal* instr, } -static Condition ComputeCompareCondition(Token::Value op) { - switch (op) { - case Token::EQ_STRICT: - case Token::EQ: - return equal; - case Token::LT: - return less; - case Token::GT: - return greater; - case Token::LTE: - return less_equal; - case Token::GTE: - return greater_equal; - default: - UNREACHABLE(); - return no_condition; - } -} - - void LCodeGen::DoCmpT(LCmpT* instr) { Token::Value op = instr->op(); diff --git a/src/ia32/lithium-codegen-ia32.h b/src/ia32/lithium-codegen-ia32.h index 1354e1d..ccbf048 100644 --- a/src/ia32/lithium-codegen-ia32.h +++ b/src/ia32/lithium-codegen-ia32.h @@ -280,6 +280,13 @@ class LCodeGen BASE_EMBEDDED { Label* is_not_object, Label* is_object); + // Emits optimized code for %_IsString(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsString(Register input, + Register temp1, + Label* is_not_string); + // Emits optimized code for %_IsConstructCall(). // Caller should branch on equal condition. void EmitIsConstructCall(Register temp); diff --git a/src/ia32/lithium-ia32.cc b/src/ia32/lithium-ia32.cc index 1f01df6..7b5bf1e 100644 --- a/src/ia32/lithium-ia32.cc +++ b/src/ia32/lithium-ia32.cc @@ -225,6 +225,13 @@ void LIsObjectAndBranch::PrintDataTo(StringStream* stream) { } +void LIsStringAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_string("); + InputAt(0)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LIsSmiAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if is_smi("); InputAt(0)->PrintTo(stream); @@ -239,6 +246,14 @@ void LIsUndetectableAndBranch::PrintDataTo(StringStream* stream) { } +void LStringCompareAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if string_compare("); + InputAt(1)->PrintTo(stream); + InputAt(2)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if has_instance_type("); InputAt(0)->PrintTo(stream); @@ -1499,6 +1514,13 @@ LInstruction* LChunkBuilder::DoIsObjectAndBranch(HIsObjectAndBranch* instr) { } +LInstruction* LChunkBuilder::DoIsStringAndBranch(HIsStringAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* temp = TempRegister(); + return new LIsStringAndBranch(UseRegister(instr->value()), temp); +} + + LInstruction* LChunkBuilder::DoIsSmiAndBranch(HIsSmiAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); return new(zone()) LIsSmiAndBranch(Use(instr->value())); @@ -1513,6 +1535,21 @@ LInstruction* LChunkBuilder::DoIsUndetectableAndBranch( } +LInstruction* LChunkBuilder::DoStringCompareAndBranch( + HStringCompareAndBranch* instr) { + ASSERT(instr->left()->representation().IsTagged()); + ASSERT(instr->right()->representation().IsTagged()); + LOperand* context = UseFixed(instr->context(), esi); + LOperand* left = UseFixed(instr->left(), edx); + LOperand* right = UseFixed(instr->right(), eax); + + LStringCompareAndBranch* result = new + LStringCompareAndBranch(context, left, right); + + return MarkAsCall(result, instr); +} + + LInstruction* LChunkBuilder::DoHasInstanceTypeAndBranch( HHasInstanceTypeAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); diff --git a/src/ia32/lithium-ia32.h b/src/ia32/lithium-ia32.h index 2fe55fd..74e53ae 100644 --- a/src/ia32/lithium-ia32.h +++ b/src/ia32/lithium-ia32.h @@ -103,8 +103,10 @@ class LCodeGen; V(IsConstructCallAndBranch) \ V(IsNilAndBranch) \ V(IsObjectAndBranch) \ + V(IsStringAndBranch) \ V(IsSmiAndBranch) \ V(IsUndetectableAndBranch) \ + V(StringCompareAndBranch) \ V(JSArrayLength) \ V(Label) \ V(LazyBailout) \ @@ -633,6 +635,19 @@ class LIsObjectAndBranch: public LControlInstruction<1, 1> { }; +class LIsStringAndBranch: public LControlInstruction<1, 1> { + public: + LIsStringAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + DECLARE_CONCRETE_INSTRUCTION(IsStringAndBranch, "is-string-and-branch") + + virtual void PrintDataTo(StringStream* stream); +}; + + class LIsSmiAndBranch: public LControlInstruction<1, 0> { public: explicit LIsSmiAndBranch(LOperand* value) { @@ -660,6 +675,24 @@ class LIsUndetectableAndBranch: public LControlInstruction<1, 1> { }; +class LStringCompareAndBranch: public LControlInstruction<3, 0> { + public: + LStringCompareAndBranch(LOperand* context, LOperand* left, LOperand* right) { + inputs_[0] = context; + inputs_[1] = left; + inputs_[2] = right; + } + + DECLARE_CONCRETE_INSTRUCTION(StringCompareAndBranch, + "string-compare-and-branch") + DECLARE_HYDROGEN_ACCESSOR(StringCompareAndBranch) + + virtual void PrintDataTo(StringStream* stream); + + Token::Value op() const { return hydrogen()->token(); } +}; + + class LHasInstanceTypeAndBranch: public LControlInstruction<1, 1> { public: LHasInstanceTypeAndBranch(LOperand* value, LOperand* temp) { diff --git a/src/type-info.cc b/src/type-info.cc index afec71a..d522242 100644 --- a/src/type-info.cc +++ b/src/type-info.cc @@ -360,6 +360,10 @@ TypeInfo TypeFeedbackOracle::SwitchType(CaseClause* clause) { return unknown; case CompareIC::SMIS: return TypeInfo::Smi(); + case CompareIC::STRINGS: + return TypeInfo::String(); + case CompareIC::SYMBOLS: + return TypeInfo::Symbol(); case CompareIC::HEAP_NUMBERS: return TypeInfo::Number(); case CompareIC::OBJECTS: diff --git a/src/type-info.h b/src/type-info.h index 2c3543e..7c9c05e 100644 --- a/src/type-info.h +++ b/src/type-info.h @@ -64,6 +64,8 @@ class TypeInfo { static TypeInfo Integer32() { return TypeInfo(kInteger32); } // We know it's a Smi. static TypeInfo Smi() { return TypeInfo(kSmi); } + // We know it's a Symbol. + static TypeInfo Symbol() { return TypeInfo(kSymbol); } // We know it's a heap number. static TypeInfo Double() { return TypeInfo(kDouble); } // We know it's a string. @@ -137,6 +139,16 @@ class TypeInfo { return ((type_ & kSmi) == kSmi); } + inline bool IsSymbol() { + ASSERT(type_ != kUninitialized); + return ((type_ & kSymbol) == kSymbol); + } + + inline bool IsNonSymbol() { + ASSERT(type_ != kUninitialized); + return ((type_ & kSymbol) == kString); + } + inline bool IsInteger32() { ASSERT(type_ != kUninitialized); return ((type_ & kInteger32) == kInteger32); @@ -168,6 +180,7 @@ class TypeInfo { case kNumber: return "Number"; case kInteger32: return "Integer32"; case kSmi: return "Smi"; + case kSymbol: return "Symbol"; case kDouble: return "Double"; case kString: return "String"; case kNonPrimitive: return "Object"; @@ -186,6 +199,7 @@ class TypeInfo { kSmi = 0x17, // 0010111 kDouble = 0x19, // 0011001 kString = 0x30, // 0110000 + kSymbol = 0x32, // 0110010 kNonPrimitive = 0x40, // 1000000 kUninitialized = 0x7f // 1111111 }; diff --git a/src/x64/lithium-codegen-x64.cc b/src/x64/lithium-codegen-x64.cc index 36edfb7..fd88b9d 100644 --- a/src/x64/lithium-codegen-x64.cc +++ b/src/x64/lithium-codegen-x64.cc @@ -1614,6 +1614,30 @@ void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { } +Condition LCodeGen::EmitIsString(Register input, + Register temp1, + Label* is_not_string) { + __ JumpIfSmi(input, is_not_string); + Condition cond = masm_->IsObjectStringType(input, temp1, temp1); + + return cond; +} + + +void LCodeGen::DoIsStringAndBranch(LIsStringAndBranch* instr) { + Register reg = ToRegister(instr->InputAt(0)); + Register temp = ToRegister(instr->TempAt(0)); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition true_cond = EmitIsString(reg, temp, false_label); + + EmitBranch(true_block, false_block, true_cond); +} + + void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { int true_block = chunk_->LookupDestination(instr->true_block_id()); int false_block = chunk_->LookupDestination(instr->false_block_id()); @@ -1645,6 +1669,21 @@ void LCodeGen::DoIsUndetectableAndBranch(LIsUndetectableAndBranch* instr) { } +void LCodeGen::DoStringCompareAndBranch(LStringCompareAndBranch* instr) { + Token::Value op = instr->op(); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Handle ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + Condition condition = TokenToCondition(op, false); + __ testq(rax, rax); + + EmitBranch(true_block, false_block, condition); +} + + static InstanceType TestType(HHasInstanceTypeAndBranch* instr) { InstanceType from = instr->from(); InstanceType to = instr->to(); diff --git a/src/x64/lithium-codegen-x64.h b/src/x64/lithium-codegen-x64.h index 37e426e..ee54466 100644 --- a/src/x64/lithium-codegen-x64.h +++ b/src/x64/lithium-codegen-x64.h @@ -269,6 +269,13 @@ class LCodeGen BASE_EMBEDDED { Label* is_not_object, Label* is_object); + // Emits optimized code for %_IsString(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsString(Register input, + Register temp1, + Label* is_not_string); + // Emits optimized code for %_IsConstructCall(). // Caller should branch on equal condition. void EmitIsConstructCall(Register temp); diff --git a/src/x64/lithium-x64.cc b/src/x64/lithium-x64.cc index e9e7896..a8e29d1 100644 --- a/src/x64/lithium-x64.cc +++ b/src/x64/lithium-x64.cc @@ -230,6 +230,13 @@ void LIsObjectAndBranch::PrintDataTo(StringStream* stream) { } +void LIsStringAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if is_string("); + InputAt(0)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LIsSmiAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if is_smi("); InputAt(0)->PrintTo(stream); @@ -244,6 +251,14 @@ void LIsUndetectableAndBranch::PrintDataTo(StringStream* stream) { } +void LStringCompareAndBranch::PrintDataTo(StringStream* stream) { + stream->Add("if string_compare("); + InputAt(0)->PrintTo(stream); + InputAt(1)->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) { stream->Add("if has_instance_type("); InputAt(0)->PrintTo(stream); @@ -1451,6 +1466,13 @@ LInstruction* LChunkBuilder::DoIsObjectAndBranch(HIsObjectAndBranch* instr) { } +LInstruction* LChunkBuilder::DoIsStringAndBranch(HIsStringAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* temp = TempRegister(); + return new LIsStringAndBranch(UseRegisterAtStart(instr->value()), temp); +} + + LInstruction* LChunkBuilder::DoIsSmiAndBranch(HIsSmiAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); return new LIsSmiAndBranch(Use(instr->value())); @@ -1465,6 +1487,19 @@ LInstruction* LChunkBuilder::DoIsUndetectableAndBranch( } +LInstruction* LChunkBuilder::DoStringCompareAndBranch( + HStringCompareAndBranch* instr) { + + ASSERT(instr->left()->representation().IsTagged()); + ASSERT(instr->right()->representation().IsTagged()); + LOperand* left = UseFixed(instr->left(), rdx); + LOperand* right = UseFixed(instr->right(), rax); + LStringCompareAndBranch* result = new LStringCompareAndBranch(left, right); + + return MarkAsCall(result, instr); +} + + LInstruction* LChunkBuilder::DoHasInstanceTypeAndBranch( HHasInstanceTypeAndBranch* instr) { ASSERT(instr->value()->representation().IsTagged()); diff --git a/src/x64/lithium-x64.h b/src/x64/lithium-x64.h index f5edd62..e60e5d6 100644 --- a/src/x64/lithium-x64.h +++ b/src/x64/lithium-x64.h @@ -109,8 +109,10 @@ class LCodeGen; V(IsConstructCallAndBranch) \ V(IsNilAndBranch) \ V(IsObjectAndBranch) \ + V(IsStringAndBranch) \ V(IsSmiAndBranch) \ V(IsUndetectableAndBranch) \ + V(StringCompareAndBranch) \ V(JSArrayLength) \ V(Label) \ V(LazyBailout) \ @@ -640,6 +642,20 @@ class LIsObjectAndBranch: public LControlInstruction<1, 0> { }; +class LIsStringAndBranch: public LControlInstruction<1, 1> { + public: + explicit LIsStringAndBranch(LOperand* value, LOperand* temp) { + inputs_[0] = value; + temps_[0] = temp; + } + + DECLARE_CONCRETE_INSTRUCTION(IsStringAndBranch, "is-string-and-branch") + DECLARE_HYDROGEN_ACCESSOR(IsStringAndBranch) + + virtual void PrintDataTo(StringStream* stream); +}; + + class LIsSmiAndBranch: public LControlInstruction<1, 0> { public: explicit LIsSmiAndBranch(LOperand* value) { @@ -668,6 +684,23 @@ class LIsUndetectableAndBranch: public LControlInstruction<1, 1> { }; +class LStringCompareAndBranch: public LControlInstruction<2, 0> { + public: + explicit LStringCompareAndBranch(LOperand* left, LOperand* right) { + inputs_[0] = left; + inputs_[1] = right; + } + + DECLARE_CONCRETE_INSTRUCTION(StringCompareAndBranch, + "string-compare-and-branch") + DECLARE_HYDROGEN_ACCESSOR(StringCompareAndBranch) + + virtual void PrintDataTo(StringStream* stream); + + Token::Value op() const { return hydrogen()->token(); } +}; + + class LHasInstanceTypeAndBranch: public LControlInstruction<1, 0> { public: explicit LHasInstanceTypeAndBranch(LOperand* value) { diff --git a/test/mjsunit/switch.js b/test/mjsunit/switch.js index 180f994..6a61fe5 100644 --- a/test/mjsunit/switch.js +++ b/test/mjsunit/switch.js @@ -25,6 +25,8 @@ // (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 + function f0() { switch (0) { // switch deliberately left empty @@ -126,6 +128,42 @@ assertEquals(3, f4(1), "fallthrough-switch.1"); assertEquals(3, f4(2), "fallthrough-switch.2"); assertEquals(5, f4(3), "fallthrough-switch.3"); +function f4_string(tag, x) { + switch(tag) { + case 'zero': + x++; + case 'two': + x++; + } + return x; +} + +// Symbols +assertEquals(2, f4_string('zero', 0), "fallthrough-string-switch.0"); +assertEquals(1, f4_string('one', 1), "fallthrough-string-switch.1"); +assertEquals(3, f4_string('two', 2), "fallthrough-string-switch.2"); + +// Strings +assertEquals(2, f4_string('_zero'.slice(1), 0), "fallthrough-string-switch.3"); +assertEquals(1, f4_string('_one'.slice(1), 1), "fallthrough-string-switch.4"); +assertEquals(3, f4_string('_two'.slice(1), 2), "fallthrough-string-switch.5"); + +// Oddball +assertEquals(3, f4_string(null, 3), "fallthrough-string-switch.6"); + +// Test for regression +function regress_string(value) { + var json = 1; + switch (typeof value) { + case 'object': + break; + + default: + + } + return json; +}; +assertEquals(1, regress_string('object'), 'regression-string'); function f5(x) { switch(x) { @@ -287,3 +325,138 @@ var verylong_size = 1000; var verylong = makeVeryLong(verylong_size); assertEquals(verylong_size * 2 + 1, verylong()); + +// +// Test suite below aims to cover all possible combinations of following: +// +// clauses | tags | type feedback | optimization +// ========================================================= +// strings | symbol | all | on +// smis | string | target | off +// mixed | oddball | non-target | +// | smis | none | +// | heapnum | | +// ========================================================= + +// Function-with-switch generator +var test_id = 0, + clause_values = { + string: ['abc', 'def', 'ghi', 'jkl'], + smi: [1, 2, 3, 4], + mixed: ['abc', 1, 'def', 2, 'ghi', 3, 'jkl', 4] + }; + +function switch_gen(clause_type, feedback, optimize) { + var values = clause_values[clause_type]; + + function opt(fn) { + if (feedback === 'all') { + values.forEach(fn); + } else if (Array.isArray(feedback)) { + // Non-target + values.filter(function(v) { + return feedback.indexOf(v) === -1; + }).forEach(fn); + } else if (feedback !== undefined) { + // Target + fn(feedback); + } else { + // None + } + + if (optimize) %OptimizeFunctionOnNextCall(fn); + + return fn; + }; + + return opt(new Function( + 'tag', + '"' + (test_id++) + '";' + + 'switch(tag) {' + + values.map(function(value) { + return 'case ' + JSON.stringify(value) + ': return' + + JSON.stringify('ok-' + value); + }).join(';') + + '}' + )); +}; + +function test_switch(clause_type, test_type, feedback, optimize) { + var pairs = [], + fn = switch_gen(clause_type, feedback, optimize); + + if (Array.isArray(test_type)) { + pairs = test_type.map(function(v) { + return { + value: v, + expected: 'ok-' + v + }; + }); + } else if (test_type === 'symbols') { + pairs = clause_values.string.map(function(v) { + return { + value: v, + expected: clause_type !== 'smi' ? 'ok-' + v : undefined + }; + }); + } else if (test_type === 'strings') { + pairs = clause_values.string.map(function(v) { + return { + value: ('%%' + v).slice(2), + expected: clause_type !== 'smi' ? 'ok-' + v : undefined + }; + }); + } else if (test_type === 'oddball') { + pairs = [ + { value: null, expected: undefined }, + { value: NaN, expected: undefined }, + { value: undefined, expected: undefined } + ]; + } else if (test_type === 'smi') { + pairs = clause_values.smi.map(function(v) { + return { + value: v, + expected: clause_type !== 'string' ? 'ok-' + v : undefined + }; + }); + } else if (test_type === 'heapnum') { + pairs = clause_values.smi.map(function(v) { + return { + value: ((v * 17)/16) - ((v*17)%16/16), + expected: clause_type !== 'string' ? 'ok-' + v : undefined + }; + }); + } + + pairs.forEach(function(pair) { + assertEquals(fn(pair.value), pair.expected); + }); +}; + +// test_switch(clause_type, test_type, feedback, optimize); + +function test_switches(opt) { + var test_types = ['symbols', 'strings', 'oddball', 'smi', 'heapnum']; + + function test(clause_type) { + var values = clause_values[clause_type]; + + test_types.forEach(function(test_type) { + test_switch(clause_type, test_type, 'all', opt); + test_switch(clause_type, test_type, 'none', opt); + + // Targeting specific clause feedback + values.forEach(function(value) { + test_switch(clause_type, test_type, [value], value, opt); + test_switch(clause_type, test_type, value, value, opt); + }); + }); + }; + + test('string'); + test('smi'); + test('mixed'); +}; + +test_switches(false); +test_switches(true);