From f7138b1427aa300c836e7eb38cffa137b9656e5c Mon Sep 17 00:00:00 2001 From: "svenpanne@chromium.org" Date: Thu, 21 Jul 2011 13:51:04 +0000 Subject: [PATCH] Implement a type recording ToBoolean IC. The IC records the set of types it has seen, e.g. {String} or {Boolean, Undefined}, etc. Note that in theory this could lead to a large number of different ToBoolean ICs (512, to be exact, because we distinguish 9 types), but in practice only a small handful of them are actually generated. Currently the type recording part is only implemented on ia32, other platforms continue to work like they did before, though. Removed some dead code on the way. Review URL: http://codereview.chromium.org/7473028 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@8716 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/code-stubs.cc | 69 +++++++++++++ src/code-stubs.h | 48 ++++++++- src/debug.cc | 3 +- src/ia32/code-stubs-ia32.cc | 188 +++++++++++++++++++++++++++--------- src/ic.cc | 34 +++++-- src/ic.h | 12 ++- src/log.cc | 1 + src/objects-inl.h | 23 ++++- src/objects.cc | 1 + src/objects.h | 28 +++--- src/spaces.cc | 1 + 11 files changed, 334 insertions(+), 74 deletions(-) diff --git a/src/code-stubs.cc b/src/code-stubs.cc index 1d1128f2d1..329969bf7a 100644 --- a/src/code-stubs.cc +++ b/src/code-stubs.cc @@ -329,4 +329,73 @@ void CallFunctionStub::PrintName(StringStream* stream) { stream->Add("CallFunctionStub_Args%d%s%s", argc_, in_loop_name, flags_name); } + +void ToBooleanStub::PrintName(StringStream* stream) { + stream->Add("ToBooleanStub_"); + types_.Print(stream); +} + + +void ToBooleanStub::Types::Print(StringStream* stream) { + if (IsEmpty()) stream->Add("None"); + if (Contains(UNDEFINED)) stream->Add("Undefined"); + if (Contains(BOOLEAN)) stream->Add("Bool"); + if (Contains(SMI)) stream->Add("Smi"); + if (Contains(NULL_TYPE)) stream->Add("Null"); + if (Contains(UNDETECTABLE)) stream->Add("Undetectable"); + if (Contains(SPEC_OBJECT)) stream->Add("SpecObject"); + if (Contains(STRING)) stream->Add("String"); + if (Contains(HEAP_NUMBER)) stream->Add("HeapNumber"); + if (Contains(INTERNAL_OBJECT)) stream->Add("InternalObject"); +} + + +void ToBooleanStub::Types::TraceTransition(Types to) { + if (!FLAG_trace_ic) return; + char buffer[100]; + NoAllocationStringAllocator allocator(buffer, + static_cast(sizeof(buffer))); + StringStream stream(&allocator); + stream.Add("[ToBooleanIC ("); + Print(&stream); + stream.Add("->"); + to.Print(&stream); + stream.Add(")]\n"); + stream.OutputToStdOut(); +} + + +bool ToBooleanStub::Types::Record(Handle object) { + if (object->IsUndefined()) { + Add(UNDEFINED); + return false; + } else if (object->IsBoolean()) { + Add(BOOLEAN); + return object->IsTrue(); + } else if (object->IsNull()) { + Add(NULL_TYPE); + return false; + } else if (object->IsSmi()) { + Add(SMI); + return Smi::cast(*object)->value() != 0; + } else if (object->IsUndetectableObject()) { + Add(UNDETECTABLE); + return false; + } else if (object->IsSpecObject()) { + Add(SPEC_OBJECT); + return true; + } else if (object->IsString()) { + Add(STRING); + return String::cast(*object)->length() != 0; + } else if (object->IsHeapNumber()) { + Add(HEAP_NUMBER); + double value = HeapNumber::cast(*object)->value(); + return value != 0 && !isnan(value); + } else { + Add(INTERNAL_OBJECT); + return true; + } +} + + } } // namespace v8::internal diff --git a/src/code-stubs.h b/src/code-stubs.h index 17c245c80e..1257f0f422 100644 --- a/src/code-stubs.h +++ b/src/code-stubs.h @@ -900,14 +900,56 @@ class KeyedStoreElementStub : public CodeStub { class ToBooleanStub: public CodeStub { public: - explicit ToBooleanStub(Register tos) : tos_(tos) { } + enum Type { + UNDEFINED, + BOOLEAN, + NULL_TYPE, + SMI, + UNDETECTABLE, + SPEC_OBJECT, + STRING, + HEAP_NUMBER, + INTERNAL_OBJECT, + NUMBER_OF_TYPES + }; + + class Types { + public: + Types() {} + explicit Types(int bits) : set_(bits) {} + + bool IsEmpty() const { return set_.IsEmpty(); } + bool Contains(Type type) const { return set_.Contains(type); } + void Add(Type type) { set_.Add(type); } + int ToInt() const { return set_.ToIntegral(); } + void Print(StringStream* stream); + void TraceTransition(Types to); + bool Record(Handle object); + + private: + EnumSet set_; + }; + + explicit ToBooleanStub(Register tos, Types types = Types()) + : tos_(tos), types_(types) { } void Generate(MacroAssembler* masm); + virtual int GetCodeKind() { return Code::TO_BOOLEAN_IC; } + virtual void PrintName(StringStream* stream); private: - Register tos_; Major MajorKey() { return ToBoolean; } - int MinorKey() { return tos_.code(); } + int MinorKey() { return (tos_.code() << NUMBER_OF_TYPES) | types_.ToInt(); } + + void CheckOddball(MacroAssembler* masm, + Type type, + Handle value, + bool result, + Label* patch); + void GenerateTypeTransition(MacroAssembler* masm); + + Register tos_; + Types types_; }; } } // namespace v8::internal diff --git a/src/debug.cc b/src/debug.cc index aecbb463b5..5024bce63f 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -169,7 +169,8 @@ void BreakLocationIterator::Next() { if ((code->is_inline_cache_stub() && !code->is_binary_op_stub() && !code->is_unary_op_stub() && - !code->is_compare_ic_stub()) || + !code->is_compare_ic_stub() && + !code->is_to_boolean_ic_stub()) || RelocInfo::IsConstructCall(rmode())) { break_point_++; return; diff --git a/src/ia32/code-stubs-ia32.cc b/src/ia32/code-stubs-ia32.cc index 71aacf9a3a..b13893e8b1 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -236,69 +236,163 @@ void FastCloneShallowArrayStub::Generate(MacroAssembler* masm) { } -// The stub returns zero for false, and a non-zero value for true. +// The stub expects its argument on the stack and returns its result in tos_: +// zero for false, and a non-zero value for true. void ToBooleanStub::Generate(MacroAssembler* masm) { - Label false_result, true_result, not_string; + Label patch; Factory* factory = masm->isolate()->factory(); + const Register argument = eax; const Register map = edx; - __ mov(eax, Operand(esp, 1 * kPointerSize)); + if (!types_.IsEmpty()) { + __ mov(argument, Operand(esp, 1 * kPointerSize)); + } // undefined -> false - __ cmp(eax, factory->undefined_value()); - __ j(equal, &false_result); + CheckOddball(masm, UNDEFINED, factory->undefined_value(), false, &patch); // Boolean -> its value - __ cmp(eax, factory->false_value()); - __ j(equal, &false_result); - __ cmp(eax, factory->true_value()); - __ j(equal, &true_result); - - // Smis: 0 -> false, all other -> true - __ test(eax, Operand(eax)); - __ j(zero, &false_result); - __ JumpIfSmi(eax, &true_result); + CheckOddball(masm, BOOLEAN, factory->false_value(), false, &patch); + CheckOddball(masm, BOOLEAN, factory->true_value(), true, &patch); // 'null' -> false. - __ cmp(eax, factory->null_value()); - __ j(equal, &false_result, Label::kNear); + CheckOddball(masm, NULL_TYPE, factory->null_value(), false, &patch); - // Get the map of the heap object. - __ mov(map, FieldOperand(eax, HeapObject::kMapOffset)); + bool need_map = + types_.Contains(UNDETECTABLE) | + types_.Contains(SPEC_OBJECT) | + types_.Contains(STRING) | + types_.Contains(HEAP_NUMBER) | + types_.Contains(INTERNAL_OBJECT); - // Undetectable -> false. - __ test_b(FieldOperand(map, Map::kBitFieldOffset), - 1 << Map::kIsUndetectable); - __ j(not_zero, &false_result, Label::kNear); + if (types_.Contains(SMI)) { + // Smis: 0 -> false, all other -> true + Label not_smi; + __ JumpIfNotSmi(argument, ¬_smi, Label::kNear); + // argument contains the correct return value already + if (!tos_.is(argument)) { + __ mov(tos_, argument); + } + __ ret(1 * kPointerSize); + __ bind(¬_smi); + } else if (need_map) { + // If we need a map later and have a Smi -> patch. + __ JumpIfSmi(argument, &patch, Label::kNear); + } - // JavaScript object -> true. - __ CmpInstanceType(map, FIRST_SPEC_OBJECT_TYPE); - __ j(above_equal, &true_result, Label::kNear); + if (need_map) { + __ mov(map, FieldOperand(argument, HeapObject::kMapOffset)); - // String value -> false iff empty. - __ CmpInstanceType(map, FIRST_NONSTRING_TYPE); - __ j(above_equal, ¬_string, Label::kNear); - __ cmp(FieldOperand(eax, String::kLengthOffset), Immediate(0)); - __ j(zero, &false_result, Label::kNear); - __ jmp(&true_result, Label::kNear); + // Everything with a map could be undetectable, so check this now. + __ test_b(FieldOperand(map, Map::kBitFieldOffset), + 1 << Map::kIsUndetectable); + if (types_.Contains(UNDETECTABLE)) { + // Undetectable -> false. + Label not_undetectable; + __ j(zero, ¬_undetectable, Label::kNear); + __ Set(tos_, Immediate(0)); + __ ret(1 * kPointerSize); + __ bind(¬_undetectable); + } else { + // We've seen an undetectable value for the first time -> patch. + __ j(not_zero, &patch, Label::kNear); + } + } - __ bind(¬_string); - // HeapNumber -> false iff +0, -0, or NaN. - __ cmp(map, factory->heap_number_map()); - __ j(not_equal, &true_result, Label::kNear); - __ fldz(); - __ fld_d(FieldOperand(eax, HeapNumber::kValueOffset)); - __ FCmp(); - __ j(zero, &false_result, Label::kNear); - // Fall through to |true_result|. + if (types_.Contains(SPEC_OBJECT)) { + // spec object -> true. + Label not_js_object; + __ CmpInstanceType(map, FIRST_SPEC_OBJECT_TYPE); + __ j(below, ¬_js_object, Label::kNear); + __ Set(tos_, Immediate(1)); + __ ret(1 * kPointerSize); + __ bind(¬_js_object); + } else if (types_.Contains(INTERNAL_OBJECT)) { + // We've seen a spec object for the first time -> patch. + __ CmpInstanceType(map, FIRST_SPEC_OBJECT_TYPE); + __ j(above_equal, &patch, Label::kNear); + } - // Return 1/0 for true/false in tos_. - __ bind(&true_result); - __ mov(tos_, 1); - __ ret(1 * kPointerSize); - __ bind(&false_result); - __ mov(tos_, 0); - __ ret(1 * kPointerSize); + if (types_.Contains(STRING)) { + // String value -> false iff empty. + Label not_string; + __ CmpInstanceType(map, FIRST_NONSTRING_TYPE); + __ j(above_equal, ¬_string, Label::kNear); + __ mov(tos_, FieldOperand(argument, String::kLengthOffset)); + __ ret(1 * kPointerSize); // the string length is OK as the return value + __ bind(¬_string); + } else if (types_.Contains(INTERNAL_OBJECT)) { + // We've seen a string for the first time -> patch + __ CmpInstanceType(map, FIRST_NONSTRING_TYPE); + __ j(below, &patch, Label::kNear); + } + + if (types_.Contains(HEAP_NUMBER)) { + // heap number -> false iff +0, -0, or NaN. + Label not_heap_number, false_result; + __ cmp(map, factory->heap_number_map()); + __ j(not_equal, ¬_heap_number, Label::kNear); + __ fldz(); + __ fld_d(FieldOperand(argument, HeapNumber::kValueOffset)); + __ FCmp(); + __ j(zero, &false_result, Label::kNear); + __ Set(tos_, Immediate(1)); + __ ret(1 * kPointerSize); + __ bind(&false_result); + __ Set(tos_, Immediate(0)); + __ ret(1 * kPointerSize); + __ bind(¬_heap_number); + } else if (types_.Contains(INTERNAL_OBJECT)) { + // We've seen a heap number for the first time -> patch + __ cmp(map, factory->heap_number_map()); + __ j(equal, &patch, Label::kNear); + } + + if (types_.Contains(INTERNAL_OBJECT)) { + // internal objects -> true + __ Set(tos_, Immediate(1)); + __ ret(1 * kPointerSize); + } + + __ bind(&patch); + GenerateTypeTransition(masm); +} + + +void ToBooleanStub::CheckOddball(MacroAssembler* masm, + Type type, + Handle value, + bool result, + Label* patch) { + const Register argument = eax; + if (types_.Contains(type)) { + // If we see an expected oddball, return its ToBoolean value tos_. + Label different_value; + __ cmp(argument, value); + __ j(not_equal, &different_value, Label::kNear); + __ Set(tos_, Immediate(result ? 1 : 0)); + __ ret(1 * kPointerSize); + __ bind(&different_value); + } else if (types_.Contains(INTERNAL_OBJECT)) { + // If we see an unexpected oddball and handle internal objects, we must + // patch because the code for internal objects doesn't handle it explictly. + __ cmp(argument, value); + __ j(equal, patch); + } +} + + +void ToBooleanStub::GenerateTypeTransition(MacroAssembler* masm) { + __ pop(ecx); // Get return address, operand is now on top of stack. + __ push(Immediate(Smi::FromInt(tos_.code()))); + __ push(Immediate(Smi::FromInt(types_.ToInt()))); + __ push(ecx); // Push return address. + // Patch the caller to an appropriate specialized stub and return the + // operation result to the caller of the stub. + __ TailCallExternalReference( + ExternalReference(IC_Utility(IC::kToBoolean_Patch), masm->isolate()), + 3, + 1); } diff --git a/src/ic.cc b/src/ic.cc index f70f75a7f6..c5a9f3d84b 100644 --- a/src/ic.cc +++ b/src/ic.cc @@ -309,6 +309,7 @@ void IC::Clear(Address address) { case Code::UNARY_OP_IC: case Code::BINARY_OP_IC: case Code::COMPARE_IC: + case Code::TO_BOOLEAN_IC: // Clearing these is tricky and does not // make any performance difference. return; @@ -842,14 +843,6 @@ MaybeObject* KeyedCallIC::LoadFunction(State state, } -#ifdef DEBUG -#define TRACE_IC_NAMED(msg, name) \ - if (FLAG_trace_ic) PrintF(msg, *(name)->ToCString()) -#else -#define TRACE_IC_NAMED(msg, name) -#endif - - MaybeObject* LoadIC::Load(State state, Handle object, Handle name) { @@ -2506,6 +2499,31 @@ RUNTIME_FUNCTION(Code*, CompareIC_Miss) { } +RUNTIME_FUNCTION(MaybeObject*, ToBoolean_Patch) { + ASSERT(args.length() == 3); + + HandleScope scope(isolate); + Handle object = args.at(0); + Register tos = Register::from_code(args.smi_at(1)); + ToBooleanStub::Types old_types(args.smi_at(2)); + + ToBooleanStub::Types new_types(old_types); + bool to_boolean_value = new_types.Record(object); + old_types.TraceTransition(new_types); + + ToBooleanStub stub(tos, new_types); + Handle code = stub.GetCode(); + ToBooleanIC ic(isolate); + ic.patch(*code); + return Smi::FromInt(to_boolean_value ? 1 : 0); +} + + +void ToBooleanIC::patch(Code* code) { + set_target(code); +} + + static const Address IC_utilities[] = { #define ADDR(name) FUNCTION_ADDR(name), IC_UTIL_LIST(ADDR) diff --git a/src/ic.h b/src/ic.h index 11c2e3af45..2236ba37b6 100644 --- a/src/ic.h +++ b/src/ic.h @@ -59,7 +59,8 @@ namespace internal { ICU(StoreInterceptorProperty) \ ICU(UnaryOp_Patch) \ ICU(BinaryOp_Patch) \ - ICU(CompareIC_Miss) + ICU(CompareIC_Miss) \ + ICU(ToBoolean_Patch) // // IC is the base class for LoadIC, StoreIC, CallIC, KeyedLoadIC, // and KeyedStoreIC. @@ -720,6 +721,15 @@ class CompareIC: public IC { Token::Value op_; }; + +class ToBooleanIC: public IC { + public: + explicit ToBooleanIC(Isolate* isolate) : IC(NO_EXTRA_FRAME, isolate) { } + + void patch(Code* code); +}; + + // Helper for BinaryOpIC and CompareIC. void PatchInlinedSmiCode(Address address); diff --git a/src/log.cc b/src/log.cc index 04fd22ef5c..dedf7e90c5 100644 --- a/src/log.cc +++ b/src/log.cc @@ -1400,6 +1400,7 @@ void Logger::LogCodeObject(Object* object) { case Code::UNARY_OP_IC: // fall through case Code::BINARY_OP_IC: // fall through case Code::COMPARE_IC: // fall through + case Code::TO_BOOLEAN_IC: // fall through case Code::STUB: description = CodeStub::MajorName(CodeStub::GetMajorKey(code_object), true); diff --git a/src/objects-inl.h b/src/objects-inl.h index 5726b37393..2417c3207b 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -158,6 +158,12 @@ bool Object::IsString() { } +bool Object::IsSpecObject() { + return Object::IsHeapObject() + && HeapObject::cast(this)->map()->instance_type() >= FIRST_SPEC_OBJECT_TYPE; +} + + bool Object::IsSymbol() { if (!this->IsHeapObject()) return false; uint32_t type = HeapObject::cast(this)->map()->instance_type(); @@ -2757,7 +2763,8 @@ int Code::major_key() { ASSERT(kind() == STUB || kind() == UNARY_OP_IC || kind() == BINARY_OP_IC || - kind() == COMPARE_IC); + kind() == COMPARE_IC || + kind() == TO_BOOLEAN_IC); return READ_BYTE_FIELD(this, kStubMajorKeyOffset); } @@ -2766,7 +2773,8 @@ void Code::set_major_key(int major) { ASSERT(kind() == STUB || kind() == UNARY_OP_IC || kind() == BINARY_OP_IC || - kind() == COMPARE_IC); + kind() == COMPARE_IC || + kind() == TO_BOOLEAN_IC); ASSERT(0 <= major && major < 256); WRITE_BYTE_FIELD(this, kStubMajorKeyOffset, major); } @@ -2908,6 +2916,17 @@ void Code::set_compare_state(byte value) { } +byte Code::to_boolean_state() { + ASSERT(is_to_boolean_ic_stub()); + return READ_BYTE_FIELD(this, kToBooleanTypeOffset); +} + + +void Code::set_to_boolean_state(byte value) { + ASSERT(is_to_boolean_ic_stub()); + WRITE_BYTE_FIELD(this, kToBooleanTypeOffset, value); +} + bool Code::is_inline_cache_stub() { Kind kind = this->kind(); return kind >= FIRST_IC_KIND && kind <= LAST_IC_KIND; diff --git a/src/objects.cc b/src/objects.cc index 7495d6e709..e7cdc51898 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -7209,6 +7209,7 @@ const char* Code::Kind2String(Kind kind) { case UNARY_OP_IC: return "UNARY_OP_IC"; case BINARY_OP_IC: return "BINARY_OP_IC"; case COMPARE_IC: return "COMPARE_IC"; + case TO_BOOLEAN_IC: return "TO_BOOLEAN_IC"; } UNREACHABLE(); return NULL; diff --git a/src/objects.h b/src/objects.h index 9b55ea7475..a8fac8c8ff 100644 --- a/src/objects.h +++ b/src/objects.h @@ -790,6 +790,8 @@ class Object : public MaybeObject { STRUCT_LIST(DECLARE_STRUCT_PREDICATE) #undef DECLARE_STRUCT_PREDICATE + INLINE(bool IsSpecObject()); + // Oddball testing. INLINE(bool IsUndefined()); INLINE(bool IsNull()); @@ -3480,13 +3482,14 @@ class Code: public HeapObject { UNARY_OP_IC, BINARY_OP_IC, COMPARE_IC, + TO_BOOLEAN_IC, // No more than 16 kinds. The value currently encoded in four bits in // Flags. // Pseudo-kinds. REGEXP = BUILTIN, FIRST_IC_KIND = LOAD_IC, - LAST_IC_KIND = COMPARE_IC + LAST_IC_KIND = TO_BOOLEAN_IC }; enum { @@ -3552,13 +3555,10 @@ class Code: public HeapObject { inline bool is_keyed_store_stub() { return kind() == KEYED_STORE_IC; } inline bool is_call_stub() { return kind() == CALL_IC; } inline bool is_keyed_call_stub() { return kind() == KEYED_CALL_IC; } - inline bool is_unary_op_stub() { - return kind() == UNARY_OP_IC; - } - inline bool is_binary_op_stub() { - return kind() == BINARY_OP_IC; - } + inline bool is_unary_op_stub() { return kind() == UNARY_OP_IC; } + inline bool is_binary_op_stub() { return kind() == BINARY_OP_IC; } inline bool is_compare_ic_stub() { return kind() == COMPARE_IC; } + inline bool is_to_boolean_ic_stub() { return kind() == TO_BOOLEAN_IC; } // [major_key]: For kind STUB or BINARY_OP_IC, the major key. inline int major_key(); @@ -3600,21 +3600,24 @@ class Code: public HeapObject { inline CheckType check_type(); inline void set_check_type(CheckType value); - // [type-recording unary op type]: For all UNARY_OP_IC. + // [type-recording unary op type]: For kind UNARY_OP_IC. inline byte unary_op_type(); inline void set_unary_op_type(byte value); - // [type-recording binary op type]: For all TYPE_RECORDING_BINARY_OP_IC. + // [type-recording binary op type]: For kind BINARY_OP_IC. inline byte binary_op_type(); inline void set_binary_op_type(byte value); inline byte binary_op_result_type(); inline void set_binary_op_result_type(byte value); - // [compare state]: For kind compare IC stubs, tells what state the - // stub is in. + // [compare state]: For kind COMPARE_IC, tells what state the stub is in. inline byte compare_state(); inline void set_compare_state(byte value); + // [to_boolean_foo]: For kind TO_BOOLEAN_IC tells what state the stub is in. + inline byte to_boolean_state(); + inline void set_to_boolean_state(byte value); + // Get the safepoint entry for the given pc. SafepointEntry GetSafepointEntry(Address pc); @@ -3756,9 +3759,10 @@ class Code: public HeapObject { static const int kStackSlotsOffset = kKindSpecificFlagsOffset; static const int kCheckTypeOffset = kKindSpecificFlagsOffset; - static const int kCompareStateOffset = kStubMajorKeyOffset + 1; static const int kUnaryOpTypeOffset = kStubMajorKeyOffset + 1; static const int kBinaryOpTypeOffset = kStubMajorKeyOffset + 1; + static const int kCompareStateOffset = kStubMajorKeyOffset + 1; + static const int kToBooleanTypeOffset = kStubMajorKeyOffset + 1; static const int kHasDeoptimizationSupportOffset = kOptimizableOffset + 1; static const int kBinaryOpReturnTypeOffset = kBinaryOpTypeOffset + 1; diff --git a/src/spaces.cc b/src/spaces.cc index 0f80496b0a..54361da88a 100644 --- a/src/spaces.cc +++ b/src/spaces.cc @@ -1542,6 +1542,7 @@ static void ReportCodeKindStatistics() { CASE(UNARY_OP_IC); CASE(BINARY_OP_IC); CASE(COMPARE_IC); + CASE(TO_BOOLEAN_IC); } }