diff options
author | Ben Murdoch <benm@google.com> | 2011-05-05 13:52:32 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2011-05-10 15:41:47 +0100 |
commit | b0fe1620dcb4135ac3ab2d66ff93072373911299 (patch) | |
tree | 3487cdc7e01ec56a6f84ea20f4bae596a0b73986 /src | |
parent | df5bff59602802b769e994b0dc1d8869a27fa40c (diff) | |
download | android_external_v8-b0fe1620dcb4135ac3ab2d66ff93072373911299.tar.gz android_external_v8-b0fe1620dcb4135ac3ab2d66ff93072373911299.tar.bz2 android_external_v8-b0fe1620dcb4135ac3ab2d66ff93072373911299.zip |
Update V8 to r6101 as required by WebKit r74534
Change-Id: I7f84af8dd732f11898fd644b2c2b1538914cb78d
Diffstat (limited to 'src')
232 files changed, 51512 insertions, 5583 deletions
diff --git a/src/SConscript b/src/SConscript index 89536987..dfa099cb 100755 --- a/src/SConscript +++ b/src/SConscript @@ -40,6 +40,7 @@ SOURCES = { api.cc assembler.cc ast.cc + atomicops_internals_x86_gcc.cc bignum.cc bignum-dtoa.cc bootstrapper.cc @@ -59,6 +60,7 @@ SOURCES = { dateparser.cc debug-agent.cc debug.cc + deoptimizer.cc disassembler.cc diy-fp.cc dtoa.cc @@ -76,10 +78,13 @@ SOURCES = { hashmap.cc heap-profiler.cc heap.cc + hydrogen.cc + hydrogen-instructions.cc ic.cc interpreter-irregexp.cc jsregexp.cc jump-target.cc + lithium-allocator.cc liveedit.cc log-utils.cc log.cc @@ -99,6 +104,8 @@ SOURCES = { register-allocator.cc rewriter.cc runtime.cc + runtime-profiler.cc + safepoint-table.cc scanner-base.cc scanner.cc scopeinfo.cc @@ -134,11 +141,14 @@ SOURCES = { arm/constants-arm.cc arm/cpu-arm.cc arm/debug-arm.cc + arm/deoptimizer-arm.cc arm/disasm-arm.cc arm/frames-arm.cc arm/full-codegen-arm.cc arm/ic-arm.cc arm/jump-target-arm.cc + arm/lithium-arm.cc + arm/lithium-codegen-arm.cc arm/macro-assembler-arm.cc arm/regexp-macro-assembler-arm.cc arm/register-allocator-arm.cc @@ -172,11 +182,14 @@ SOURCES = { ia32/codegen-ia32.cc ia32/cpu-ia32.cc ia32/debug-ia32.cc + ia32/deoptimizer-ia32.cc ia32/disasm-ia32.cc ia32/frames-ia32.cc ia32/full-codegen-ia32.cc ia32/ic-ia32.cc ia32/jump-target-ia32.cc + ia32/lithium-codegen-ia32.cc + ia32/lithium-ia32.cc ia32/macro-assembler-ia32.cc ia32/regexp-macro-assembler-ia32.cc ia32/register-allocator-ia32.cc @@ -192,6 +205,7 @@ SOURCES = { x64/codegen-x64.cc x64/cpu-x64.cc x64/debug-x64.cc + x64/deoptimizer-x64.cc x64/disasm-x64.cc x64/frames-x64.cc x64/full-codegen-x64.cc @@ -216,7 +230,8 @@ SOURCES = { 'mode:release': [], 'mode:debug': [ 'objects-debug.cc', 'prettyprinter.cc', 'regexp-macro-assembler-tracer.cc' - ] + ], + 'objectprint:on': ['objects-debug.cc'] } diff --git a/src/accessors.cc b/src/accessors.cc index 08ef41b9..43d54fe4 100644 --- a/src/accessors.cc +++ b/src/accessors.cc @@ -28,8 +28,11 @@ #include "v8.h" #include "accessors.h" +#include "ast.h" +#include "deoptimizer.h" #include "execution.h" #include "factory.h" +#include "safepoint-table.h" #include "scopeinfo.h" #include "top.h" @@ -503,11 +506,9 @@ MaybeObject* Accessors::FunctionGetLength(Object* object, void*) { // If the function isn't compiled yet, the length is not computed // correctly yet. Compile it now and return the right length. HandleScope scope; - Handle<SharedFunctionInfo> shared(function->shared()); - if (!CompileLazyShared(shared, KEEP_EXCEPTION)) { - return Failure::Exception(); - } - return Smi::FromInt(shared->length()); + Handle<JSFunction> handle(function); + if (!CompileLazy(handle, KEEP_EXCEPTION)) return Failure::Exception(); + return Smi::FromInt(handle->shared()->length()); } else { return Smi::FromInt(function->shared()->length()); } @@ -545,6 +546,208 @@ const AccessorDescriptor Accessors::FunctionName = { // Accessors::FunctionArguments // +static Address SlotAddress(JavaScriptFrame* frame, int slot_index) { + if (slot_index >= 0) { + const int offset = JavaScriptFrameConstants::kLocal0Offset; + return frame->fp() + offset - (slot_index * kPointerSize); + } else { + const int offset = JavaScriptFrameConstants::kReceiverOffset; + return frame->caller_sp() + offset + (slot_index * kPointerSize); + } +} + + +// We can't intermix stack decoding and allocations because +// deoptimization infrastracture is not GC safe. +// Thus we build a temporary structure in malloced space. +class SlotRef BASE_EMBEDDED { + public: + enum SlotRepresentation { + UNKNOWN, + TAGGED, + INT32, + DOUBLE, + LITERAL + }; + + SlotRef() + : addr_(NULL), representation_(UNKNOWN) { } + + SlotRef(Address addr, SlotRepresentation representation) + : addr_(addr), representation_(representation) { } + + explicit SlotRef(Object* literal) + : literal_(literal), representation_(LITERAL) { } + + Handle<Object> GetValue() { + switch (representation_) { + case TAGGED: + return Handle<Object>(Memory::Object_at(addr_)); + + case INT32: { + int value = Memory::int32_at(addr_); + if (Smi::IsValid(value)) { + return Handle<Object>(Smi::FromInt(value)); + } else { + return Factory::NewNumberFromInt(value); + } + } + + case DOUBLE: { + double value = Memory::double_at(addr_); + return Factory::NewNumber(value); + } + + case LITERAL: + return literal_; + + default: + UNREACHABLE(); + return Handle<Object>::null(); + } + } + + private: + Address addr_; + Handle<Object> literal_; + SlotRepresentation representation_; +}; + + +static SlotRef ComputeSlotForNextArgument(TranslationIterator* iterator, + DeoptimizationInputData* data, + JavaScriptFrame* frame) { + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator->Next()); + + switch (opcode) { + case Translation::BEGIN: + case Translation::FRAME: + // Peeled off before getting here. + break; + + case Translation::ARGUMENTS_OBJECT: + // This can be only emitted for local slots not for argument slots. + break; + + case Translation::REGISTER: + case Translation::INT32_REGISTER: + case Translation::DOUBLE_REGISTER: + case Translation::DUPLICATE: + // We are at safepoint which corresponds to call. All registers are + // saved by caller so there would be no live registers at this + // point. Thus these translation commands should not be used. + break; + + case Translation::STACK_SLOT: { + int slot_index = iterator->Next(); + Address slot_addr = SlotAddress(frame, slot_index); + return SlotRef(slot_addr, SlotRef::TAGGED); + } + + case Translation::INT32_STACK_SLOT: { + int slot_index = iterator->Next(); + Address slot_addr = SlotAddress(frame, slot_index); + return SlotRef(slot_addr, SlotRef::INT32); + } + + case Translation::DOUBLE_STACK_SLOT: { + int slot_index = iterator->Next(); + Address slot_addr = SlotAddress(frame, slot_index); + return SlotRef(slot_addr, SlotRef::DOUBLE); + } + + case Translation::LITERAL: { + int literal_index = iterator->Next(); + return SlotRef(data->LiteralArray()->get(literal_index)); + } + } + + UNREACHABLE(); + return SlotRef(); +} + + + + + +static void ComputeSlotMappingForArguments(JavaScriptFrame* frame, + int inlined_frame_index, + Vector<SlotRef>* args_slots) { + AssertNoAllocation no_gc; + + int deopt_index = AstNode::kNoNumber; + + DeoptimizationInputData* data = + static_cast<OptimizedFrame*>(frame)->GetDeoptimizationData(&deopt_index); + + TranslationIterator it(data->TranslationByteArray(), + data->TranslationIndex(deopt_index)->value()); + + Translation::Opcode opcode = static_cast<Translation::Opcode>(it.Next()); + ASSERT(opcode == Translation::BEGIN); + int frame_count = it.Next(); + + USE(frame_count); + ASSERT(frame_count > inlined_frame_index); + + int frames_to_skip = inlined_frame_index; + while (true) { + opcode = static_cast<Translation::Opcode>(it.Next()); + + // Skip over operands to advance to the next opcode. + it.Skip(Translation::NumberOfOperandsFor(opcode)); + + if (opcode == Translation::FRAME) { + if (frames_to_skip == 0) { + // We reached frame corresponding to inlined function in question. + // Process translation commands for arguments. + + // Skip translation command for receiver. + it.Skip(Translation::NumberOfOperandsFor( + static_cast<Translation::Opcode>(it.Next()))); + + // Compute slots for arguments. + for (int i = 0; i < args_slots->length(); ++i) { + (*args_slots)[i] = ComputeSlotForNextArgument(&it, data, frame); + } + + return; + } + + frames_to_skip--; + } + } + + UNREACHABLE(); +} + + +static MaybeObject* ConstructArgumentsObjectForInlinedFunction( + JavaScriptFrame* frame, + Handle<JSFunction> inlined_function, + int inlined_frame_index) { + + int args_count = inlined_function->shared()->formal_parameter_count(); + + ScopedVector<SlotRef> args_slots(args_count); + + ComputeSlotMappingForArguments(frame, inlined_frame_index, &args_slots); + + Handle<JSObject> arguments = + Factory::NewArgumentsObject(inlined_function, args_count); + + Handle<FixedArray> array = Factory::NewFixedArray(args_count); + for (int i = 0; i < args_count; ++i) { + Handle<Object> value = args_slots[i].GetValue(); + array->set(i, *value); + } + arguments->set_elements(*array); + + // Return the freshly allocated arguments object. + return *arguments; +} + MaybeObject* Accessors::FunctionGetArguments(Object* object, void*) { HandleScope scope; @@ -554,38 +757,50 @@ MaybeObject* Accessors::FunctionGetArguments(Object* object, void*) { Handle<JSFunction> function(holder); // Find the top invocation of the function by traversing frames. + List<JSFunction*> functions(2); for (JavaScriptFrameIterator it; !it.done(); it.Advance()) { - // Skip all frames that aren't invocations of the given function. JavaScriptFrame* frame = it.frame(); - if (frame->function() != *function) continue; - - // If there is an arguments variable in the stack, we return that. - int index = function->shared()->scope_info()-> - StackSlotIndex(Heap::arguments_symbol()); - if (index >= 0) { - Handle<Object> arguments = Handle<Object>(frame->GetExpression(index)); - if (!arguments->IsTheHole()) return *arguments; + frame->GetFunctions(&functions); + for (int i = functions.length() - 1; i >= 0; i--) { + // Skip all frames that aren't invocations of the given function. + if (functions[i] != *function) continue; + + if (i > 0) { + // Function in question was inlined. + return ConstructArgumentsObjectForInlinedFunction(frame, function, i); + } else { + // If there is an arguments variable in the stack, we return that. + int index = function->shared()->scope_info()-> + StackSlotIndex(Heap::arguments_symbol()); + if (index >= 0) { + Handle<Object> arguments = + Handle<Object>(frame->GetExpression(index)); + if (!arguments->IsTheHole()) return *arguments; + } + + // If there isn't an arguments variable in the stack, we need to + // find the frame that holds the actual arguments passed to the + // function on the stack. + it.AdvanceToArgumentsFrame(); + frame = it.frame(); + + // Get the number of arguments and construct an arguments object + // mirror for the right frame. + const int length = frame->GetProvidedParametersCount(); + Handle<JSObject> arguments = Factory::NewArgumentsObject(function, + length); + Handle<FixedArray> array = Factory::NewFixedArray(length); + + // Copy the parameters to the arguments object. + ASSERT(array->length() == length); + for (int i = 0; i < length; i++) array->set(i, frame->GetParameter(i)); + arguments->set_elements(*array); + + // Return the freshly allocated arguments object. + return *arguments; + } } - - // If there isn't an arguments variable in the stack, we need to - // find the frame that holds the actual arguments passed to the - // function on the stack. - it.AdvanceToArgumentsFrame(); - frame = it.frame(); - - // Get the number of arguments and construct an arguments object - // mirror for the right frame. - const int length = frame->GetProvidedParametersCount(); - Handle<JSObject> arguments = Factory::NewArgumentsObject(function, length); - Handle<FixedArray> array = Factory::NewFixedArray(length); - - // Copy the parameters to the arguments object. - ASSERT(array->length() == length); - for (int i = 0; i < length; i++) array->set(i, frame->GetParameter(i)); - arguments->set_elements(*array); - - // Return the freshly allocated arguments object. - return *arguments; + functions.Rewind(0); } // No frame corresponding to the given function found. Return null. @@ -613,19 +828,34 @@ MaybeObject* Accessors::FunctionGetCaller(Object* object, void*) { if (!found_it) return Heap::undefined_value(); Handle<JSFunction> function(holder); - // Find the top invocation of the function by traversing frames. + List<JSFunction*> functions(2); for (JavaScriptFrameIterator it; !it.done(); it.Advance()) { - // Skip all frames that aren't invocations of the given function. - if (it.frame()->function() != *function) continue; - // Once we have found the frame, we need to go to the caller - // frame. This may require skipping through a number of top-level - // frames, e.g. frames for scripts not functions. - while (true) { - it.Advance(); - if (it.done()) return Heap::null_value(); - JSFunction* caller = JSFunction::cast(it.frame()->function()); - if (!caller->shared()->is_toplevel()) return caller; + JavaScriptFrame* frame = it.frame(); + frame->GetFunctions(&functions); + for (int i = functions.length() - 1; i >= 0; i--) { + if (functions[i] == *function) { + // Once we have found the frame, we need to go to the caller + // frame. This may require skipping through a number of top-level + // frames, e.g. frames for scripts not functions. + if (i > 0) { + ASSERT(!functions[i - 1]->shared()->is_toplevel()); + return functions[i - 1]; + } else { + for (it.Advance(); !it.done(); it.Advance()) { + frame = it.frame(); + functions.Rewind(0); + frame->GetFunctions(&functions); + if (!functions.last()->shared()->is_toplevel()) { + return functions.last(); + } + ASSERT(functions.length() == 1); + } + if (it.done()) return Heap::null_value(); + break; + } + } } + functions.Rewind(0); } // No frame corresponding to the given function found. Return null. diff --git a/src/accessors.h b/src/accessors.h index 96d742ef..14ccc8fb 100644 --- a/src/accessors.h +++ b/src/accessors.h @@ -78,13 +78,14 @@ class Accessors : public AllStatic { MUST_USE_RESULT static MaybeObject* FunctionGetPrototype(Object* object, void*); MUST_USE_RESULT static MaybeObject* FunctionSetPrototype(JSObject* object, - Object* value, - void*); + Object* value, + void*); + static MaybeObject* FunctionGetArguments(Object* object, void*); + private: // Accessor functions only used through the descriptor. static MaybeObject* FunctionGetLength(Object* object, void*); static MaybeObject* FunctionGetName(Object* object, void*); - static MaybeObject* FunctionGetArguments(Object* object, void*); static MaybeObject* FunctionGetCaller(Object* object, void*); MUST_USE_RESULT static MaybeObject* ArraySetLength(JSObject* object, Object* value, void*); diff --git a/src/allocation.h b/src/allocation.h index 6f4bd2fb..394366ea 100644 --- a/src/allocation.h +++ b/src/allocation.h @@ -28,6 +28,9 @@ #ifndef V8_ALLOCATION_H_ #define V8_ALLOCATION_H_ +#include "checks.h" +#include "globals.h" + namespace v8 { namespace internal { @@ -33,6 +33,7 @@ #include "bootstrapper.h" #include "compiler.h" #include "debug.h" +#include "deoptimizer.h" #include "execution.h" #include "global-handles.h" #include "heap-profiler.h" @@ -40,18 +41,21 @@ #include "parser.h" #include "platform.h" #include "profile-generator-inl.h" +#include "runtime-profiler.h" #include "serialize.h" #include "snapshot.h" #include "top.h" #include "v8threads.h" #include "version.h" +#include "vm-state-inl.h" #include "../include/v8-profiler.h" +#include "../include/v8-testing.h" #define LOG_API(expr) LOG(ApiEntryCall(expr)) #ifdef ENABLE_VMSTATE_TRACKING -#define ENTER_V8 i::VMState __state__(i::OTHER) +#define ENTER_V8 ASSERT(i::V8::IsRunning()); i::VMState __state__(i::OTHER) #define LEAVE_V8 i::VMState __state__(i::EXTERNAL) #else #define ENTER_V8 ((void) 0) @@ -97,6 +101,7 @@ namespace v8 { } \ } while (false) + // --- D a t a t h a t i s s p e c i f i c t o a t h r e a d --- @@ -1160,14 +1165,22 @@ void ObjectTemplate::SetInternalFieldCount(int value) { ScriptData* ScriptData::PreCompile(const char* input, int length) { - unibrow::Utf8InputBuffer<> buf(input, length); - return i::ParserApi::PreParse(i::Handle<i::String>(), &buf, NULL); + i::Utf8ToUC16CharacterStream stream( + reinterpret_cast<const unsigned char*>(input), length); + return i::ParserApi::PreParse(&stream, NULL); } ScriptData* ScriptData::PreCompile(v8::Handle<String> source) { i::Handle<i::String> str = Utils::OpenHandle(*source); - return i::ParserApi::PreParse(str, NULL, NULL); + if (str->IsExternalTwoByteString()) { + i::ExternalTwoByteStringUC16CharacterStream stream( + i::Handle<i::ExternalTwoByteString>::cast(str), 0, str->length()); + return i::ParserApi::PreParse(&stream, NULL); + } else { + i::GenericStringUC16CharacterStream stream(str, 0, str->length()); + return i::ParserApi::PreParse(&stream, NULL); + } } @@ -2312,6 +2325,11 @@ bool v8::Object::ForceDelete(v8::Handle<Value> key) { HandleScope scope; i::Handle<i::JSObject> self = Utils::OpenHandle(this); i::Handle<i::Object> key_obj = Utils::OpenHandle(*key); + + // When turning on access checks for a global object deoptimize all functions + // as optimized code does not always handle access checks. + i::Deoptimizer::DeoptimizeGlobalObject(*self); + EXCEPTION_PREAMBLE(); i::Handle<i::Object> obj = i::ForceDeleteProperty(self, key_obj); has_pending_exception = obj.is_null(); @@ -2598,6 +2616,10 @@ void v8::Object::TurnOnAccessCheck() { HandleScope scope; i::Handle<i::JSObject> obj = Utils::OpenHandle(this); + // When turning on access checks for a global object deoptimize all functions + // as optimized code does not always handle access checks. + i::Deoptimizer::DeoptimizeGlobalObject(*obj); + i::Handle<i::Map> new_map = i::Factory::CopyMapDropTransitions(i::Handle<i::Map>(obj->map())); new_map->set_is_access_check_needed(true); @@ -3105,14 +3127,15 @@ int String::Write(uint16_t* buffer, // using StringInputBuffer or Get(i) to access the characters. str->TryFlatten(); } - int end = length; - if ( (length == -1) || (length > str->length() - start) ) - end = str->length() - start; + int end = start + length; + if ((length == -1) || (length > str->length() - start) ) + end = str->length(); if (end < 0) return 0; i::String::WriteToFlat(*str, buffer, start, end); - if (length == -1 || end < length) - buffer[end] = '\0'; - return end; + if (length == -1 || end - start < length) { + buffer[end - start] = '\0'; + } + return end - start; } @@ -3243,35 +3266,18 @@ void v8::Object::SetInternalField(int index, v8::Handle<Value> value) { } -static bool CanBeEncodedAsSmi(void* ptr) { - const uintptr_t address = reinterpret_cast<uintptr_t>(ptr); - return ((address & i::kEncodablePointerMask) == 0); -} - - -static i::Smi* EncodeAsSmi(void* ptr) { - ASSERT(CanBeEncodedAsSmi(ptr)); - const uintptr_t address = reinterpret_cast<uintptr_t>(ptr); - i::Smi* result = reinterpret_cast<i::Smi*>(address << i::kPointerToSmiShift); - ASSERT(i::Internals::HasSmiTag(result)); - ASSERT_EQ(result, i::Smi::FromInt(result->value())); - ASSERT_EQ(ptr, i::Internals::GetExternalPointerFromSmi(result)); - return result; -} - - void v8::Object::SetPointerInInternalField(int index, void* value) { ENTER_V8; - if (CanBeEncodedAsSmi(value)) { - Utils::OpenHandle(this)->SetInternalField(index, EncodeAsSmi(value)); - } else { - HandleScope scope; - i::Handle<i::Proxy> proxy = - i::Factory::NewProxy(reinterpret_cast<i::Address>(value), i::TENURED); - if (!proxy.is_null()) - Utils::OpenHandle(this)->SetInternalField(index, *proxy); + i::Object* as_object = reinterpret_cast<i::Object*>(value); + if (as_object->IsSmi()) { + Utils::OpenHandle(this)->SetInternalField(index, as_object); + return; } - ASSERT_EQ(value, GetPointerFromInternalField(index)); + HandleScope scope; + i::Handle<i::Proxy> proxy = + i::Factory::NewProxy(reinterpret_cast<i::Address>(value), i::TENURED); + if (!proxy.is_null()) + Utils::OpenHandle(this)->SetInternalField(index, *proxy); } @@ -3279,7 +3285,6 @@ void v8::Object::SetPointerInInternalField(int index, void* value) { bool v8::V8::Initialize() { if (i::V8::IsRunning()) return true; - ENTER_V8; HandleScope scope; if (i::Snapshot::Initialize()) return true; return i::V8::Initialize(NULL); @@ -3403,6 +3408,7 @@ Persistent<Context> v8::Context::New( global_constructor->set_needs_access_check( proxy_constructor->needs_access_check()); } + i::RuntimeProfiler::Reset(); } // Leave V8. @@ -3554,13 +3560,11 @@ Local<Value> v8::External::Wrap(void* data) { LOG_API("External::Wrap"); EnsureInitialized("v8::External::Wrap()"); ENTER_V8; - - v8::Local<v8::Value> result = CanBeEncodedAsSmi(data) - ? Utils::ToLocal(i::Handle<i::Object>(EncodeAsSmi(data))) - : v8::Local<v8::Value>(ExternalNewImpl(data)); - - ASSERT_EQ(data, Unwrap(result)); - return result; + i::Object* as_object = reinterpret_cast<i::Object*>(data); + if (as_object->IsSmi()) { + return Utils::ToLocal(i::Handle<i::Object>(as_object)); + } + return ExternalNewImpl(data); } @@ -3568,7 +3572,7 @@ void* v8::Object::SlowGetPointerFromInternalField(int index) { i::Handle<i::JSObject> obj = Utils::OpenHandle(this); i::Object* value = obj->GetInternalField(index); if (value->IsSmi()) { - return i::Internals::GetExternalPointerFromSmi(value); + return value; } else if (value->IsProxy()) { return reinterpret_cast<void*>(i::Proxy::cast(value)->proxy()); } else { @@ -3582,7 +3586,8 @@ void* v8::External::FullUnwrap(v8::Handle<v8::Value> wrapper) { i::Handle<i::Object> obj = Utils::OpenHandle(*wrapper); void* result; if (obj->IsSmi()) { - result = i::Internals::GetExternalPointerFromSmi(*obj); + // The external value was an aligned pointer. + result = *obj; } else if (obj->IsProxy()) { result = ExternalValueImpl(obj); } else { @@ -3797,6 +3802,35 @@ double v8::Date::NumberValue() const { } +void v8::Date::DateTimeConfigurationChangeNotification() { + ON_BAILOUT("v8::Date::DateTimeConfigurationChangeNotification()", return); + LOG_API("Date::DateTimeConfigurationChangeNotification"); + ENTER_V8; + + HandleScope scope; + + // Get the function ResetDateCache (defined in date-delay.js). + i::Handle<i::String> func_name_str = + i::Factory::LookupAsciiSymbol("ResetDateCache"); + i::MaybeObject* result = i::Top::builtins()->GetProperty(*func_name_str); + i::Object* object_func; + if (!result->ToObject(&object_func)) { + return; + } + + if (object_func->IsJSFunction()) { + i::Handle<i::JSFunction> func = + i::Handle<i::JSFunction>(i::JSFunction::cast(object_func)); + + // Call ResetDateCache(0 but expect no exceptions: + bool caught_exception = false; + i::Handle<i::Object> result = + i::Execution::TryCall(func, i::Top::builtins(), 0, NULL, + &caught_exception); + } +} + + static i::Handle<i::String> RegExpFlagsToString(RegExp::Flags flags) { char flags_buf[3]; int num_flags = 0; @@ -4890,6 +4924,13 @@ const HeapGraphNode* HeapSnapshot::GetRoot() const { } +const HeapGraphNode* HeapSnapshot::GetNodeById(uint64_t id) const { + IsDeadCheck("v8::HeapSnapshot::GetNodeById"); + return reinterpret_cast<const HeapGraphNode*>( + ToInternal(this)->GetEntryById(id)); +} + + const HeapSnapshotsDiff* HeapSnapshot::CompareWith( const HeapSnapshot* snapshot) const { IsDeadCheck("v8::HeapSnapshot::CompareWith"); @@ -4936,7 +4977,8 @@ const HeapSnapshot* HeapProfiler::FindSnapshot(unsigned uid) { const HeapSnapshot* HeapProfiler::TakeSnapshot(Handle<String> title, - HeapSnapshot::Type type) { + HeapSnapshot::Type type, + ActivityControl* control) { IsDeadCheck("v8::HeapProfiler::TakeSnapshot"); i::HeapSnapshot::Type internal_type = i::HeapSnapshot::kFull; switch (type) { @@ -4950,12 +4992,74 @@ const HeapSnapshot* HeapProfiler::TakeSnapshot(Handle<String> title, UNREACHABLE(); } return reinterpret_cast<const HeapSnapshot*>( - i::HeapProfiler::TakeSnapshot(*Utils::OpenHandle(*title), internal_type)); + i::HeapProfiler::TakeSnapshot( + *Utils::OpenHandle(*title), internal_type, control)); } #endif // ENABLE_LOGGING_AND_PROFILING +v8::Testing::StressType internal::Testing::stress_type_ = + v8::Testing::kStressTypeOpt; + + +void Testing::SetStressRunType(Testing::StressType type) { + internal::Testing::set_stress_type(type); +} + +int Testing::GetStressRuns() { + if (internal::FLAG_stress_runs != 0) return internal::FLAG_stress_runs; +#ifdef DEBUG + // In debug mode the code runs much slower so stressing will only make two + // runs. + return 2; +#else + return 5; +#endif +} + + +static void SetFlagsFromString(const char* flags) { + V8::SetFlagsFromString(flags, i::StrLength(flags)); +} + + +void Testing::PrepareStressRun(int run) { + static const char* kLazyOptimizations = + "--prepare-always-opt --nolimit-inlining " + "--noalways-opt --noopt-eagerly"; + static const char* kEagerOptimizations = "--opt-eagerly"; + static const char* kForcedOptimizations = "--always-opt"; + + // If deoptimization stressed turn on frequent deoptimization. If no value + // is spefified through --deopt-every-n-times use a default default value. + static const char* kDeoptEvery13Times = "--deopt-every-n-times=13"; + if (internal::Testing::stress_type() == Testing::kStressTypeDeopt && + internal::FLAG_deopt_every_n_times == 0) { + SetFlagsFromString(kDeoptEvery13Times); + } + +#ifdef DEBUG + // As stressing in debug mode only make two runs skip the deopt stressing + // here. + if (run == GetStressRuns() - 1) { + SetFlagsFromString(kForcedOptimizations); + } else { + SetFlagsFromString(kEagerOptimizations); + SetFlagsFromString(kLazyOptimizations); + } +#else + if (run == GetStressRuns() - 1) { + SetFlagsFromString(kForcedOptimizations); + } else if (run == GetStressRuns() - 2) { + SetFlagsFromString(kEagerOptimizations); + } else { + SetFlagsFromString(kLazyOptimizations); + } +#endif +} + + namespace internal { @@ -31,6 +31,8 @@ #include "apiutils.h" #include "factory.h" +#include "../include/v8-testing.h" + namespace v8 { // Constants used in the implementation of the API. The most natural thing @@ -489,6 +491,18 @@ void HandleScopeImplementer::DeleteExtensions(internal::Object** prev_limit) { (!blocks_.is_empty() && prev_limit != NULL)); } + +class Testing { + public: + static v8::Testing::StressType stress_type() { return stress_type_; } + static void set_stress_type(v8::Testing::StressType stress_type) { + stress_type_ = stress_type; + } + + private: + static v8::Testing::StressType stress_type_; +}; + } } // namespace v8::internal #endif // V8_API_H_ diff --git a/src/arm/assembler-arm-inl.h b/src/arm/assembler-arm-inl.h index 15720c95..68d32f1e 100644 --- a/src/arm/assembler-arm-inl.h +++ b/src/arm/assembler-arm-inl.h @@ -110,6 +110,30 @@ Address* RelocInfo::target_reference_address() { } +Handle<JSGlobalPropertyCell> RelocInfo::target_cell_handle() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + return Handle<JSGlobalPropertyCell>( + reinterpret_cast<JSGlobalPropertyCell**>(address)); +} + + +JSGlobalPropertyCell* RelocInfo::target_cell() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + Object* object = HeapObject::FromAddress( + address - JSGlobalPropertyCell::kValueOffset); + return reinterpret_cast<JSGlobalPropertyCell*>(object); +} + + +void RelocInfo::set_target_cell(JSGlobalPropertyCell* cell) { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = cell->address() + JSGlobalPropertyCell::kValueOffset; + Memory::Address_at(pc_) = address; +} + + Address RelocInfo::call_address() { // The 2 instructions offset assumes patched debug break slot or return // sequence. diff --git a/src/arm/assembler-arm.cc b/src/arm/assembler-arm.cc index cfdd1649..8fdcf182 100644 --- a/src/arm/assembler-arm.cc +++ b/src/arm/assembler-arm.cc @@ -70,7 +70,7 @@ static uint64_t CpuFeaturesImpliedByCompiler() { #endif // def __arm__ -void CpuFeatures::Probe() { +void CpuFeatures::Probe(bool portable) { #ifndef __arm__ // For the simulator=arm build, use VFP when FLAG_enable_vfp3 is enabled. if (FLAG_enable_vfp3) { @@ -81,7 +81,7 @@ void CpuFeatures::Probe() { supported_ |= 1u << ARMv7; } #else // def __arm__ - if (Serializer::enabled()) { + if (portable && Serializer::enabled()) { supported_ |= OS::CpuFeaturesImpliedByPlatform(); supported_ |= CpuFeaturesImpliedByCompiler(); return; // No features if we might serialize. @@ -98,6 +98,8 @@ void CpuFeatures::Probe() { supported_ |= 1u << ARMv7; found_by_runtime_probing_ |= 1u << ARMv7; } + + if (!portable) found_by_runtime_probing_ = 0; #endif } @@ -318,7 +320,10 @@ static const int kMinimalBufferSize = 4*KB; static byte* spare_buffer_ = NULL; Assembler::Assembler(void* buffer, int buffer_size) - : positions_recorder_(this) { + : positions_recorder_(this), + allow_peephole_optimization_(false) { + // BUG(3245989): disable peephole optimization if crankshaft is enabled. + allow_peephole_optimization_ = FLAG_peephole_optimization; if (buffer == NULL) { // Do our own buffer management. if (buffer_size <= kMinimalBufferSize) { @@ -987,6 +992,7 @@ void Assembler::b(int branch_offset, Condition cond) { void Assembler::bl(int branch_offset, Condition cond) { + positions_recorder()->WriteRecordedPositions(); ASSERT((branch_offset & 3) == 0); int imm24 = branch_offset >> 2; ASSERT(is_int24(imm24)); @@ -1650,9 +1656,10 @@ void Assembler::stop(const char* msg, Condition cond, int32_t code) { emit(reinterpret_cast<Instr>(msg)); #else // def __arm__ #ifdef CAN_USE_ARMV5_INSTRUCTIONS + ASSERT(cond == al); bkpt(0); #else // ndef CAN_USE_ARMV5_INSTRUCTIONS - svc(0x9f0001); + svc(0x9f0001, cond); #endif // ndef CAN_USE_ARMV5_INSTRUCTIONS #endif // def __arm__ } @@ -1826,13 +1833,18 @@ void Assembler::vldr(const DwVfpRegister dst, const Condition cond) { // Ddst = MEM(Rbase + offset). // Instruction details available in ARM DDI 0406A, A8-628. - // cond(31-28) | 1101(27-24)| 1001(23-20) | Rbase(19-16) | + // cond(31-28) | 1101(27-24)| U001(23-20) | Rbase(19-16) | // Vdst(15-12) | 1011(11-8) | offset ASSERT(CpuFeatures::IsEnabled(VFP3)); + int u = 1; + if (offset < 0) { + offset = -offset; + u = 0; + } ASSERT(offset % 4 == 0); ASSERT((offset / 4) < 256); ASSERT(offset >= 0); - emit(cond | 0xD9*B20 | base.code()*B16 | dst.code()*B12 | + emit(cond | u*B23 | 0xD1*B20 | base.code()*B16 | dst.code()*B12 | 0xB*B8 | ((offset / 4) & 255)); } @@ -1843,15 +1855,20 @@ void Assembler::vldr(const SwVfpRegister dst, const Condition cond) { // Sdst = MEM(Rbase + offset). // Instruction details available in ARM DDI 0406A, A8-628. - // cond(31-28) | 1101(27-24)| 1001(23-20) | Rbase(19-16) | + // cond(31-28) | 1101(27-24)| U001(23-20) | Rbase(19-16) | // Vdst(15-12) | 1010(11-8) | offset ASSERT(CpuFeatures::IsEnabled(VFP3)); + int u = 1; + if (offset < 0) { + offset = -offset; + u = 0; + } ASSERT(offset % 4 == 0); ASSERT((offset / 4) < 256); ASSERT(offset >= 0); int sd, d; dst.split_code(&sd, &d); - emit(cond | d*B22 | 0xD9*B20 | base.code()*B16 | sd*B12 | + emit(cond | u*B23 | d*B22 | 0xD1*B20 | base.code()*B16 | sd*B12 | 0xA*B8 | ((offset / 4) & 255)); } @@ -1862,13 +1879,18 @@ void Assembler::vstr(const DwVfpRegister src, const Condition cond) { // MEM(Rbase + offset) = Dsrc. // Instruction details available in ARM DDI 0406A, A8-786. - // cond(31-28) | 1101(27-24)| 1000(23-20) | | Rbase(19-16) | + // cond(31-28) | 1101(27-24)| U000(23-20) | | Rbase(19-16) | // Vsrc(15-12) | 1011(11-8) | (offset/4) ASSERT(CpuFeatures::IsEnabled(VFP3)); + int u = 1; + if (offset < 0) { + offset = -offset; + u = 0; + } ASSERT(offset % 4 == 0); ASSERT((offset / 4) < 256); ASSERT(offset >= 0); - emit(cond | 0xD8*B20 | base.code()*B16 | src.code()*B12 | + emit(cond | u*B23 | 0xD0*B20 | base.code()*B16 | src.code()*B12 | 0xB*B8 | ((offset / 4) & 255)); } @@ -1879,15 +1901,20 @@ void Assembler::vstr(const SwVfpRegister src, const Condition cond) { // MEM(Rbase + offset) = SSrc. // Instruction details available in ARM DDI 0406A, A8-786. - // cond(31-28) | 1101(27-24)| 1000(23-20) | Rbase(19-16) | + // cond(31-28) | 1101(27-24)| U000(23-20) | Rbase(19-16) | // Vdst(15-12) | 1010(11-8) | (offset/4) ASSERT(CpuFeatures::IsEnabled(VFP3)); + int u = 1; + if (offset < 0) { + offset = -offset; + u = 0; + } ASSERT(offset % 4 == 0); ASSERT((offset / 4) < 256); ASSERT(offset >= 0); int sd, d; src.split_code(&sd, &d); - emit(cond | d*B22 | 0xD8*B20 | base.code()*B16 | sd*B12 | + emit(cond | u*B23 | d*B22 | 0xD0*B20 | base.code()*B16 | sd*B12 | 0xA*B8 | ((offset / 4) & 255)); } @@ -2411,7 +2438,7 @@ void Assembler::RecordDebugBreakSlot() { void Assembler::RecordComment(const char* msg) { - if (FLAG_debug_code) { + if (FLAG_code_comments) { CheckBuffer(); RecordRelocInfo(RelocInfo::COMMENT, reinterpret_cast<intptr_t>(msg)); } @@ -2469,6 +2496,20 @@ void Assembler::GrowBuffer() { } +void Assembler::db(uint8_t data) { + CheckBuffer(); + *reinterpret_cast<uint8_t*>(pc_) = data; + pc_ += sizeof(uint8_t); +} + + +void Assembler::dd(uint32_t data) { + CheckBuffer(); + *reinterpret_cast<uint32_t*>(pc_) = data; + pc_ += sizeof(uint32_t); +} + + void Assembler::RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data) { RelocInfo rinfo(pc_, rmode, data); // we do not try to reuse pool constants if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::DEBUG_BREAK_SLOT) { diff --git a/src/arm/assembler-arm.h b/src/arm/assembler-arm.h index ee4c9aa5..36f7507f 100644 --- a/src/arm/assembler-arm.h +++ b/src/arm/assembler-arm.h @@ -69,7 +69,39 @@ namespace internal { // // Core register struct Register { - bool is_valid() const { return 0 <= code_ && code_ < 16; } + static const int kNumRegisters = 16; + static const int kNumAllocatableRegisters = 8; + + static int ToAllocationIndex(Register reg) { + return reg.code(); + } + + static Register FromAllocationIndex(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + return from_code(index); + } + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "r0", + "r1", + "r2", + "r3", + "r4", + "r5", + "r6", + "r7", + }; + return names[index]; + } + + static Register from_code(int code) { + Register r = { code }; + return r; + } + + bool is_valid() const { return 0 <= code_ && code_ < kNumRegisters; } bool is(Register reg) const { return code_ == reg.code_; } int code() const { ASSERT(is_valid()); @@ -132,6 +164,48 @@ struct SwVfpRegister { // Double word VFP register. struct DwVfpRegister { + // d0 has been excluded from allocation. This is following ia32 + // where xmm0 is excluded. This should be revisited. + static const int kNumRegisters = 16; + static const int kNumAllocatableRegisters = 15; + + static int ToAllocationIndex(DwVfpRegister reg) { + ASSERT(reg.code() != 0); + return reg.code() - 1; + } + + static DwVfpRegister FromAllocationIndex(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + return from_code(index + 1); + } + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "d1", + "d2", + "d3", + "d4", + "d5", + "d6", + "d7", + "d8", + "d9", + "d10", + "d11", + "d12", + "d13", + "d14", + "d15" + }; + return names[index]; + } + + static DwVfpRegister from_code(int code) { + DwVfpRegister r = { code }; + return r; + } + // Supporting d0 to d15, can be later extended to d31. bool is_valid() const { return 0 <= code_ && code_ < 16; } bool is(DwVfpRegister reg) const { return code_ == reg.code_; } @@ -167,6 +241,9 @@ struct DwVfpRegister { }; +typedef DwVfpRegister DoubleRegister; + + // Support for the VFP registers s0 to s31 (d0 to d15). // Note that "s(N):s(N+1)" is the same as "d(N/2)". const SwVfpRegister s0 = { 0 }; @@ -286,6 +363,9 @@ enum Coprocessor { // Condition field in instructions. enum Condition { + // any value < 0 is considered no_condition + no_condition = -1, + eq = 0 << 28, // Z set equal. ne = 1 << 28, // Z clear not equal. nz = 1 << 28, // Z clear not zero. @@ -527,7 +607,7 @@ class CpuFeatures : public AllStatic { public: // Detect features of the target CPU. Set safe defaults if the serializer // is enabled (snapshots must be portable). - static void Probe(); + static void Probe(bool portable); // Check whether a feature is supported by the target CPU. static bool IsSupported(CpuFeature f) { @@ -1148,15 +1228,20 @@ class Assembler : public Malloced { void RecordDebugBreakSlot(); // Record a comment relocation entry that can be used by a disassembler. - // Use --debug_code to enable. + // Use --code-comments to enable. void RecordComment(const char* msg); + // Writes a single byte or word of data in the code stream. Used for + // inline tables, e.g., jump-tables. + void db(uint8_t data); + void dd(uint32_t data); + int pc_offset() const { return pc_ - buffer_; } PositionsRecorder* positions_recorder() { return &positions_recorder_; } bool can_peephole_optimize(int instructions) { - if (!FLAG_peephole_optimization) return false; + if (!allow_peephole_optimization_) return false; if (last_bound_pos_ > pc_offset() - instructions * kInstrSize) return false; return reloc_info_writer.last_pc() <= pc_ - instructions * kInstrSize; } @@ -1185,6 +1270,8 @@ class Assembler : public Malloced { static bool IsLdrPcImmediateOffset(Instr instr); static bool IsNop(Instr instr, int type = NON_MARKING_NOP); + // Check if is time to emit a constant pool for pending reloc info entries + void CheckConstPool(bool force_emit, bool require_jump); protected: int buffer_space() const { return reloc_info_writer.pos() - pc_; } @@ -1201,9 +1288,6 @@ class Assembler : public Malloced { // Patch branch instruction at pos to branch to given branch target pos void target_at_put(int pos, int target_pos); - // Check if is time to emit a constant pool for pending reloc info entries - void CheckConstPool(bool force_emit, bool require_jump); - // Block the emission of the constant pool before pc_offset void BlockConstPoolBefore(int pc_offset) { if (no_const_pool_before_ < pc_offset) no_const_pool_before_ = pc_offset; @@ -1317,6 +1401,7 @@ class Assembler : public Malloced { friend class BlockConstPoolScope; PositionsRecorder positions_recorder_; + bool allow_peephole_optimization_; friend class PositionsRecorder; friend class EnsureSpace; }; diff --git a/src/arm/builtins-arm.cc b/src/arm/builtins-arm.cc index 862ef395..6480a916 100644 --- a/src/arm/builtins-arm.cc +++ b/src/arm/builtins-arm.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -31,6 +31,8 @@ #include "codegen-inl.h" #include "debug.h" +#include "deoptimizer.h" +#include "full-codegen.h" #include "runtime.h" namespace v8 { @@ -1089,6 +1091,80 @@ void Builtins::Generate_LazyCompile(MacroAssembler* masm) { } +void Builtins::Generate_LazyRecompile(MacroAssembler* masm) { + // Enter an internal frame. + __ EnterInternalFrame(); + + // Preserve the function. + __ push(r1); + + // Push the function on the stack as the argument to the runtime function. + __ push(r1); + __ CallRuntime(Runtime::kLazyRecompile, 1); + // Calculate the entry point. + __ add(r2, r0, Operand(Code::kHeaderSize - kHeapObjectTag)); + // Restore saved function. + __ pop(r1); + + // Tear down temporary frame. + __ LeaveInternalFrame(); + + // Do a tail-call of the compiled function. + __ Jump(r2); +} + + +static void Generate_NotifyDeoptimizedHelper(MacroAssembler* masm, + Deoptimizer::BailoutType type) { + __ EnterInternalFrame(); + // Pass the function and deoptimization type to the runtime system. + __ mov(r0, Operand(Smi::FromInt(static_cast<int>(type)))); + __ push(r0); + __ CallRuntime(Runtime::kNotifyDeoptimized, 1); + __ LeaveInternalFrame(); + + // Get the full codegen state from the stack and untag it -> r6. + __ ldr(r6, MemOperand(sp, 0 * kPointerSize)); + __ SmiUntag(r6); + // Switch on the state. + Label with_tos_register, unknown_state; + __ cmp(r6, Operand(FullCodeGenerator::NO_REGISTERS)); + __ b(ne, &with_tos_register); + __ add(sp, sp, Operand(1 * kPointerSize)); // Remove state. + __ Ret(); + + __ bind(&with_tos_register); + __ ldr(r0, MemOperand(sp, 1 * kPointerSize)); + __ cmp(r6, Operand(FullCodeGenerator::TOS_REG)); + __ b(ne, &unknown_state); + __ add(sp, sp, Operand(2 * kPointerSize)); // Remove state. + __ Ret(); + + __ bind(&unknown_state); + __ stop("no cases left"); +} + + +void Builtins::Generate_NotifyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::EAGER); +} + + +void Builtins::Generate_NotifyLazyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::LAZY); +} + + +void Builtins::Generate_NotifyOSR(MacroAssembler* masm) { + __ stop("builtins-arm.cc: NotifyOSR"); +} + + +void Builtins::Generate_OnStackReplacement(MacroAssembler* masm) { + __ stop("builtins-arm.cc: OnStackReplacement"); +} + + void Builtins::Generate_FunctionCall(MacroAssembler* masm) { // 1. Make sure we have at least one argument. // r0: actual number of arguments diff --git a/src/arm/code-stubs-arm.cc b/src/arm/code-stubs-arm.cc index 76a610b7..5ec8584f 100644 --- a/src/arm/code-stubs-arm.cc +++ b/src/arm/code-stubs-arm.cc @@ -82,12 +82,15 @@ void FastNewClosureStub::Generate(MacroAssembler* masm) { // write barrier because the allocated object is in new space. __ LoadRoot(r1, Heap::kEmptyFixedArrayRootIndex); __ LoadRoot(r2, Heap::kTheHoleValueRootIndex); + __ LoadRoot(r4, Heap::kUndefinedValueRootIndex); __ str(r1, FieldMemOperand(r0, JSObject::kPropertiesOffset)); __ str(r1, FieldMemOperand(r0, JSObject::kElementsOffset)); __ str(r2, FieldMemOperand(r0, JSFunction::kPrototypeOrInitialMapOffset)); __ str(r3, FieldMemOperand(r0, JSFunction::kSharedFunctionInfoOffset)); __ str(cp, FieldMemOperand(r0, JSFunction::kContextOffset)); __ str(r1, FieldMemOperand(r0, JSFunction::kLiteralsOffset)); + __ str(r4, FieldMemOperand(r0, JSFunction::kNextFunctionLinkOffset)); + // Initialize the code pointer in the function to be the one // found in the shared function info object. @@ -1088,6 +1091,10 @@ void ToBooleanStub::Generate(MacroAssembler* masm) { Label not_heap_number; Register scratch = r7; + __ LoadRoot(ip, Heap::kNullValueRootIndex); + __ cmp(tos_, ip); + __ b(eq, &false_result); + // HeapNumber => false iff +0, -0, or NaN. __ ldr(scratch, FieldMemOperand(tos_, HeapObject::kMapOffset)); __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex); @@ -2200,6 +2207,14 @@ Handle<Code> GetBinaryOpStub(int key, BinaryOpIC::TypeInfo type_info) { } +Handle<Code> GetTypeRecordingBinaryOpStub(int key, + TRBinaryOpIC::TypeInfo type_info, + TRBinaryOpIC::TypeInfo result_type_info) { + UNIMPLEMENTED(); + return Handle<Code>::null(); +} + + void TranscendentalCacheStub::Generate(MacroAssembler* masm) { // Argument is a number and is on stack and in r0. Label runtime_call; @@ -2290,6 +2305,7 @@ Runtime::FunctionId TranscendentalCacheStub::RuntimeFunction() { // Add more cases when necessary. case TranscendentalCache::SIN: return Runtime::kMath_sin; case TranscendentalCache::COS: return Runtime::kMath_cos; + case TranscendentalCache::LOG: return Runtime::kMath_log; default: UNIMPLEMENTED(); return Runtime::kAbort; @@ -2640,7 +2656,7 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, // r0:r1: result // sp: stack pointer // fp: frame pointer - __ LeaveExitFrame(); + __ LeaveExitFrame(save_doubles_); // check if we should retry or throw exception Label retry; @@ -2689,7 +2705,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { // builtin once. // Enter the exit frame that transitions from JavaScript to C++. - __ EnterExitFrame(); + __ EnterExitFrame(save_doubles_); // r4: number of arguments (C callee-saved) // r5: pointer to builtin function (C callee-saved) @@ -2777,6 +2793,15 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { // Setup frame pointer for the frame to be pushed. __ add(fp, sp, Operand(-EntryFrameConstants::kCallerFPOffset)); +#ifdef ENABLE_LOGGING_AND_PROFILING + // If this is the outermost JS call, set js_entry_sp value. + ExternalReference js_entry_sp(Top::k_js_entry_sp_address); + __ mov(r5, Operand(ExternalReference(js_entry_sp))); + __ ldr(r6, MemOperand(r5)); + __ cmp(r6, Operand(0, RelocInfo::NONE)); + __ str(fp, MemOperand(r5), eq); +#endif + // Call a faked try-block that does the invoke. __ bl(&invoke); @@ -2839,6 +2864,15 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { // No need to restore registers __ add(sp, sp, Operand(StackHandlerConstants::kSize)); +#ifdef ENABLE_LOGGING_AND_PROFILING + // If current FP value is the same as js_entry_sp value, it means that + // the current function is the outermost. + __ mov(r5, Operand(ExternalReference(js_entry_sp))); + __ ldr(r6, MemOperand(r5)); + __ cmp(fp, Operand(r6)); + __ mov(r6, Operand(0, RelocInfo::NONE), LeaveCC, eq); + __ str(r6, MemOperand(r5), eq); +#endif __ bind(&exit); // r0 holds result // Restore the top frame descriptors from the stack. @@ -2859,80 +2893,97 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { } -// This stub performs an instanceof, calling the builtin function if -// necessary. Uses r1 for the object, r0 for the function that it may -// be an instance of (these are fetched from the stack). +// Uses registers r0 to r4. Expected input is +// function in r0 (or at sp+1*ptrsz) and object in +// r1 (or at sp), depending on whether or not +// args_in_registers() is true. void InstanceofStub::Generate(MacroAssembler* masm) { - // Get the object - slow case for smis (we may need to throw an exception - // depending on the rhs). - Label slow, loop, is_instance, is_not_instance; - __ ldr(r0, MemOperand(sp, 1 * kPointerSize)); - __ BranchOnSmi(r0, &slow); - - // Check that the left hand is a JS object and put map in r3. - __ CompareObjectType(r0, r3, r2, FIRST_JS_OBJECT_TYPE); - __ b(lt, &slow); - __ cmp(r2, Operand(LAST_JS_OBJECT_TYPE)); - __ b(gt, &slow); + // Fixed register usage throughout the stub: + const Register object = r1; // Object (lhs). + const Register map = r3; // Map of the object. + const Register function = r0; // Function (rhs). + const Register prototype = r4; // Prototype of the function. + const Register scratch = r2; + Label slow, loop, is_instance, is_not_instance, not_js_object; + if (!args_in_registers()) { + __ ldr(function, MemOperand(sp, 1 * kPointerSize)); + __ ldr(object, MemOperand(sp, 0)); + } - // Get the prototype of the function (r4 is result, r2 is scratch). - __ ldr(r1, MemOperand(sp, 0)); - // r1 is function, r3 is map. + // Check that the left hand is a JS object and load map. + __ BranchOnSmi(object, &slow); + __ IsObjectJSObjectType(object, map, scratch, &slow); // Look up the function and the map in the instanceof cache. Label miss; __ LoadRoot(ip, Heap::kInstanceofCacheFunctionRootIndex); - __ cmp(r1, ip); + __ cmp(object, ip); __ b(ne, &miss); __ LoadRoot(ip, Heap::kInstanceofCacheMapRootIndex); - __ cmp(r3, ip); + __ cmp(map, ip); __ b(ne, &miss); - __ LoadRoot(r0, Heap::kInstanceofCacheAnswerRootIndex); - __ pop(); - __ pop(); - __ mov(pc, Operand(lr)); + __ LoadRoot(function, Heap::kInstanceofCacheAnswerRootIndex); + __ Ret(args_in_registers() ? 0 : 2); __ bind(&miss); - __ TryGetFunctionPrototype(r1, r4, r2, &slow); + __ TryGetFunctionPrototype(object, prototype, scratch, &slow); // Check that the function prototype is a JS object. - __ BranchOnSmi(r4, &slow); - __ CompareObjectType(r4, r5, r5, FIRST_JS_OBJECT_TYPE); - __ b(lt, &slow); - __ cmp(r5, Operand(LAST_JS_OBJECT_TYPE)); - __ b(gt, &slow); + __ BranchOnSmi(prototype, &slow); + __ IsObjectJSObjectType(prototype, scratch, scratch, &slow); - __ StoreRoot(r1, Heap::kInstanceofCacheFunctionRootIndex); - __ StoreRoot(r3, Heap::kInstanceofCacheMapRootIndex); + __ StoreRoot(object, Heap::kInstanceofCacheFunctionRootIndex); + __ StoreRoot(map, Heap::kInstanceofCacheMapRootIndex); // Register mapping: r3 is object map and r4 is function prototype. // Get prototype of object into r2. - __ ldr(r2, FieldMemOperand(r3, Map::kPrototypeOffset)); + __ ldr(scratch, FieldMemOperand(map, Map::kPrototypeOffset)); // Loop through the prototype chain looking for the function prototype. __ bind(&loop); - __ cmp(r2, Operand(r4)); + __ cmp(scratch, Operand(prototype)); __ b(eq, &is_instance); __ LoadRoot(ip, Heap::kNullValueRootIndex); - __ cmp(r2, ip); + __ cmp(scratch, ip); __ b(eq, &is_not_instance); - __ ldr(r2, FieldMemOperand(r2, HeapObject::kMapOffset)); - __ ldr(r2, FieldMemOperand(r2, Map::kPrototypeOffset)); + __ ldr(scratch, FieldMemOperand(scratch, HeapObject::kMapOffset)); + __ ldr(scratch, FieldMemOperand(scratch, Map::kPrototypeOffset)); __ jmp(&loop); __ bind(&is_instance); __ mov(r0, Operand(Smi::FromInt(0))); __ StoreRoot(r0, Heap::kInstanceofCacheAnswerRootIndex); - __ pop(); - __ pop(); - __ mov(pc, Operand(lr)); // Return. + __ Ret(args_in_registers() ? 0 : 2); __ bind(&is_not_instance); __ mov(r0, Operand(Smi::FromInt(1))); - __ StoreRoot(r0, Heap::kInstanceofCacheAnswerRootIndex); - __ pop(); - __ pop(); - __ mov(pc, Operand(lr)); // Return. + __ Ret(args_in_registers() ? 0 : 2); + + Label object_not_null, object_not_null_or_smi; + __ bind(¬_js_object); + // Before null, smi and string value checks, check that the rhs is a function + // as for a non-function rhs an exception needs to be thrown. + __ BranchOnSmi(function, &slow); + __ CompareObjectType(function, map, scratch, JS_FUNCTION_TYPE); + __ b(ne, &slow); + + // Null is not instance of anything. + __ cmp(scratch, Operand(Factory::null_value())); + __ b(ne, &object_not_null); + __ mov(r0, Operand(Smi::FromInt(1))); + __ Ret(args_in_registers() ? 0 : 2); + + __ bind(&object_not_null); + // Smi values are not instances of anything. + __ BranchOnNotSmi(object, &object_not_null_or_smi); + __ mov(r0, Operand(Smi::FromInt(1))); + __ Ret(args_in_registers() ? 0 : 2); + + __ bind(&object_not_null_or_smi); + // String values are not instances of anything. + __ IsObjectJSStringType(object, scratch, &slow); + __ mov(r0, Operand(Smi::FromInt(1))); + __ Ret(args_in_registers() ? 0 : 2); // Slow-case. Tail call builtin. __ bind(&slow); @@ -3429,6 +3480,95 @@ void RegExpExecStub::Generate(MacroAssembler* masm) { } +void RegExpConstructResultStub::Generate(MacroAssembler* masm) { + const int kMaxInlineLength = 100; + Label slowcase; + Label done; + __ ldr(r1, MemOperand(sp, kPointerSize * 2)); + STATIC_ASSERT(kSmiTag == 0); + STATIC_ASSERT(kSmiTagSize == 1); + __ tst(r1, Operand(kSmiTagMask)); + __ b(ne, &slowcase); + __ cmp(r1, Operand(Smi::FromInt(kMaxInlineLength))); + __ b(hi, &slowcase); + // Smi-tagging is equivalent to multiplying by 2. + // Allocate RegExpResult followed by FixedArray with size in ebx. + // JSArray: [Map][empty properties][Elements][Length-smi][index][input] + // Elements: [Map][Length][..elements..] + // Size of JSArray with two in-object properties and the header of a + // FixedArray. + int objects_size = + (JSRegExpResult::kSize + FixedArray::kHeaderSize) / kPointerSize; + __ mov(r5, Operand(r1, LSR, kSmiTagSize + kSmiShiftSize)); + __ add(r2, r5, Operand(objects_size)); + __ AllocateInNewSpace( + r2, // In: Size, in words. + r0, // Out: Start of allocation (tagged). + r3, // Scratch register. + r4, // Scratch register. + &slowcase, + static_cast<AllocationFlags>(TAG_OBJECT | SIZE_IN_WORDS)); + // r0: Start of allocated area, object-tagged. + // r1: Number of elements in array, as smi. + // r5: Number of elements, untagged. + + // Set JSArray map to global.regexp_result_map(). + // Set empty properties FixedArray. + // Set elements to point to FixedArray allocated right after the JSArray. + // Interleave operations for better latency. + __ ldr(r2, ContextOperand(cp, Context::GLOBAL_INDEX)); + __ add(r3, r0, Operand(JSRegExpResult::kSize)); + __ mov(r4, Operand(Factory::empty_fixed_array())); + __ ldr(r2, FieldMemOperand(r2, GlobalObject::kGlobalContextOffset)); + __ str(r3, FieldMemOperand(r0, JSObject::kElementsOffset)); + __ ldr(r2, ContextOperand(r2, Context::REGEXP_RESULT_MAP_INDEX)); + __ str(r4, FieldMemOperand(r0, JSObject::kPropertiesOffset)); + __ str(r2, FieldMemOperand(r0, HeapObject::kMapOffset)); + + // Set input, index and length fields from arguments. + __ ldr(r1, MemOperand(sp, kPointerSize * 0)); + __ str(r1, FieldMemOperand(r0, JSRegExpResult::kInputOffset)); + __ ldr(r1, MemOperand(sp, kPointerSize * 1)); + __ str(r1, FieldMemOperand(r0, JSRegExpResult::kIndexOffset)); + __ ldr(r1, MemOperand(sp, kPointerSize * 2)); + __ str(r1, FieldMemOperand(r0, JSArray::kLengthOffset)); + + // Fill out the elements FixedArray. + // r0: JSArray, tagged. + // r3: FixedArray, tagged. + // r5: Number of elements in array, untagged. + + // Set map. + __ mov(r2, Operand(Factory::fixed_array_map())); + __ str(r2, FieldMemOperand(r3, HeapObject::kMapOffset)); + // Set FixedArray length. + __ mov(r6, Operand(r5, LSL, kSmiTagSize)); + __ str(r6, FieldMemOperand(r3, FixedArray::kLengthOffset)); + // Fill contents of fixed-array with the-hole. + __ mov(r2, Operand(Factory::the_hole_value())); + __ add(r3, r3, Operand(FixedArray::kHeaderSize - kHeapObjectTag)); + // Fill fixed array elements with hole. + // r0: JSArray, tagged. + // r2: the hole. + // r3: Start of elements in FixedArray. + // r5: Number of elements to fill. + Label loop; + __ tst(r5, Operand(r5)); + __ bind(&loop); + __ b(le, &done); // Jump if r1 is negative or zero. + __ sub(r5, r5, Operand(1), SetCC); + __ str(r2, MemOperand(r3, r5, LSL, kPointerSizeLog2)); + __ jmp(&loop); + + __ bind(&done); + __ add(sp, sp, Operand(3 * kPointerSize)); + __ Ret(); + + __ bind(&slowcase); + __ TailCallRuntime(Runtime::kRegExpConstructResult, 3, 1); +} + + void CallFunctionStub::Generate(MacroAssembler* masm) { Label slow; @@ -4721,6 +4861,123 @@ void StringAddStub::Generate(MacroAssembler* masm) { } +void ICCompareStub::GenerateSmis(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::SMIS); + Label miss; + __ orr(r2, r1, r0); + __ tst(r2, Operand(kSmiTagMask)); + __ b(ne, &miss); + + if (GetCondition() == eq) { + // For equality we do not care about the sign of the result. + __ sub(r0, r0, r1, SetCC); + } else { + __ sub(r1, r1, r0, SetCC); + // Correct sign of result in case of overflow. + __ rsb(r1, r1, Operand(0), SetCC, vs); + __ mov(r0, r1); + } + __ Ret(); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateHeapNumbers(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::HEAP_NUMBERS); + + Label generic_stub; + Label unordered; + Label miss; + __ and_(r2, r1, Operand(r0)); + __ tst(r2, Operand(kSmiTagMask)); + __ b(eq, &generic_stub); + + __ CompareObjectType(r0, r2, r2, HEAP_NUMBER_TYPE); + __ b(ne, &miss); + __ CompareObjectType(r1, r2, r2, HEAP_NUMBER_TYPE); + __ b(ne, &miss); + + // Inlining the double comparison and falling back to the general compare + // stub if NaN is involved or VFP3 is unsupported. + if (CpuFeatures::IsSupported(VFP3)) { + CpuFeatures::Scope scope(VFP3); + + // Load left and right operand + __ sub(r2, r1, Operand(kHeapObjectTag)); + __ vldr(d0, r2, HeapNumber::kValueOffset); + __ sub(r2, r0, Operand(kHeapObjectTag)); + __ vldr(d1, r2, HeapNumber::kValueOffset); + + // Compare operands + __ vcmp(d0, d1); + __ vmrs(pc); // Move vector status bits to normal status bits. + + // Don't base result on status bits when a NaN is involved. + __ b(vs, &unordered); + + // Return a result of -1, 0, or 1, based on status bits. + __ mov(r0, Operand(EQUAL), LeaveCC, eq); + __ mov(r0, Operand(LESS), LeaveCC, lt); + __ mov(r0, Operand(GREATER), LeaveCC, gt); + __ Ret(); + + __ bind(&unordered); + } + + CompareStub stub(GetCondition(), strict(), NO_COMPARE_FLAGS, r1, r0); + __ bind(&generic_stub); + __ Jump(stub.GetCode(), RelocInfo::CODE_TARGET); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateObjects(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::OBJECTS); + Label miss; + __ and_(r2, r1, Operand(r0)); + __ tst(r2, Operand(kSmiTagMask)); + __ b(eq, &miss); + + __ CompareObjectType(r0, r2, r2, JS_OBJECT_TYPE); + __ b(ne, &miss); + __ CompareObjectType(r1, r2, r2, JS_OBJECT_TYPE); + __ b(ne, &miss); + + ASSERT(GetCondition() == eq); + __ sub(r0, r0, Operand(r1)); + __ Ret(); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateMiss(MacroAssembler* masm) { + __ Push(r1, r0); + __ push(lr); + + // Call the runtime system in a fresh internal frame. + ExternalReference miss = ExternalReference(IC_Utility(IC::kCompareIC_Miss)); + __ EnterInternalFrame(); + __ Push(r1, r0); + __ mov(ip, Operand(Smi::FromInt(op_))); + __ push(ip); + __ CallExternalReference(miss, 3); + __ LeaveInternalFrame(); + // Compute the entry point of the rewritten stub. + __ add(r2, r0, Operand(Code::kHeaderSize - kHeapObjectTag)); + // Restore registers. + __ pop(lr); + __ pop(r0); + __ pop(r1); + __ Jump(r2); +} + + #undef __ } } // namespace v8::internal diff --git a/src/arm/code-stubs-arm.h b/src/arm/code-stubs-arm.h index 2e07e3b5..8ffca773 100644 --- a/src/arm/code-stubs-arm.h +++ b/src/arm/code-stubs-arm.h @@ -106,9 +106,9 @@ class GenericBinaryOpStub : public CodeStub { // Minor key encoding in 17 bits. class ModeBits: public BitField<OverwriteMode, 0, 2> {}; class OpBits: public BitField<Token::Value, 2, 6> {}; - class TypeInfoBits: public BitField<int, 8, 2> {}; - class RegisterBits: public BitField<bool, 10, 1> {}; - class KnownIntBits: public BitField<int, 11, kKnownRhsKeyBits> {}; + class TypeInfoBits: public BitField<int, 8, 3> {}; + class RegisterBits: public BitField<bool, 11, 1> {}; + class KnownIntBits: public BitField<int, 12, kKnownRhsKeyBits> {}; Major MajorKey() { return GenericBinaryOp; } int MinorKey() { @@ -196,6 +196,10 @@ class GenericBinaryOpStub : public CodeStub { const char* GetName(); + virtual void FinishCode(Code* code) { + code->set_binary_op_type(runtime_operands_type_); + } + #ifdef DEBUG void Print() { if (!specialized_on_rhs_) { diff --git a/src/arm/codegen-arm.cc b/src/arm/codegen-arm.cc index 469d41f5..59bc14e7 100644 --- a/src/arm/codegen-arm.cc +++ b/src/arm/codegen-arm.cc @@ -36,7 +36,7 @@ #include "debug.h" #include "ic-inl.h" #include "jsregexp.h" -#include "jump-target-light-inl.h" +#include "jump-target-inl.h" #include "parser.h" #include "regexp-macro-assembler.h" #include "regexp-stack.h" @@ -79,12 +79,12 @@ void VirtualFrameRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { } -void ICRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { masm->EnterInternalFrame(); } -void ICRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { masm->LeaveInternalFrame(); } @@ -165,6 +165,9 @@ void CodeGenerator::Generate(CompilationInfo* info) { int slots = scope()->num_parameters() + scope()->num_stack_slots(); ScopedVector<TypeInfo> type_info_array(slots); + for (int i = 0; i < slots; i++) { + type_info_array[i] = TypeInfo::Unknown(); + } type_info_ = &type_info_array; ASSERT(allocator_ == NULL); @@ -1150,7 +1153,7 @@ void DeferredInlineSmiOperation::GenerateNonSmiInput() { } // Check that the *signed* result fits in a smi. Not necessary for AND, SAR // if the shift if more than 0 or SHR if the shit is more than 1. - if (!( (op_ == Token::AND && value_ >= 0) || + if (!( (op_ == Token::AND) || ((op_ == Token::SAR) && (shift_value > 0)) || ((op_ == Token::SHR) && (shift_value > 1)))) { __ add(r3, int32, Operand(0x40000000), SetCC); @@ -1411,10 +1414,8 @@ void CodeGenerator::SmiOperation(Token::Value op, default: UNREACHABLE(); } deferred->BindExit(); - TypeInfo result_type = TypeInfo::Integer32(); - if (op == Token::BIT_AND && int_value >= 0) { - result_type = TypeInfo::Smi(); - } + TypeInfo result_type = + (op == Token::BIT_AND) ? TypeInfo::Smi() : TypeInfo::Integer32(); frame_->EmitPush(tos, result_type); } break; @@ -5163,11 +5164,11 @@ class DeferredIsStringWrapperSafeForDefaultValueOf : public DeferredCode { // Set the bit in the map to indicate that it has been checked safe for // default valueOf and set true result. - __ ldrb(scratch1_, FieldMemOperand(map_result_, Map::kBitField2Offset)); + __ ldr(scratch1_, FieldMemOperand(map_result_, Map::kBitField2Offset)); __ orr(scratch1_, scratch1_, Operand(1 << Map::kStringWrapperSafeForDefaultValueOf)); - __ strb(scratch1_, FieldMemOperand(map_result_, Map::kBitField2Offset)); + __ str(scratch1_, FieldMemOperand(map_result_, Map::kBitField2Offset)); __ mov(map_result_, Operand(1)); __ jmp(exit_label()); __ bind(&false_result); @@ -5418,97 +5419,14 @@ void CodeGenerator::GenerateRegExpExec(ZoneList<Expression*>* args) { void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) { - // No stub. This code only occurs a few times in regexp.js. - const int kMaxInlineLength = 100; ASSERT_EQ(3, args->length()); + Load(args->at(0)); // Size of array, smi. Load(args->at(1)); // "index" property value. Load(args->at(2)); // "input" property value. - { - VirtualFrame::SpilledScope spilled_scope(frame_); - Label slowcase; - Label done; - __ ldr(r1, MemOperand(sp, kPointerSize * 2)); - STATIC_ASSERT(kSmiTag == 0); - STATIC_ASSERT(kSmiTagSize == 1); - __ tst(r1, Operand(kSmiTagMask)); - __ b(ne, &slowcase); - __ cmp(r1, Operand(Smi::FromInt(kMaxInlineLength))); - __ b(hi, &slowcase); - // Smi-tagging is equivalent to multiplying by 2. - // Allocate RegExpResult followed by FixedArray with size in ebx. - // JSArray: [Map][empty properties][Elements][Length-smi][index][input] - // Elements: [Map][Length][..elements..] - // Size of JSArray with two in-object properties and the header of a - // FixedArray. - int objects_size = - (JSRegExpResult::kSize + FixedArray::kHeaderSize) / kPointerSize; - __ mov(r5, Operand(r1, LSR, kSmiTagSize + kSmiShiftSize)); - __ add(r2, r5, Operand(objects_size)); - __ AllocateInNewSpace( - r2, // In: Size, in words. - r0, // Out: Start of allocation (tagged). - r3, // Scratch register. - r4, // Scratch register. - &slowcase, - static_cast<AllocationFlags>(TAG_OBJECT | SIZE_IN_WORDS)); - // r0: Start of allocated area, object-tagged. - // r1: Number of elements in array, as smi. - // r5: Number of elements, untagged. - - // Set JSArray map to global.regexp_result_map(). - // Set empty properties FixedArray. - // Set elements to point to FixedArray allocated right after the JSArray. - // Interleave operations for better latency. - __ ldr(r2, ContextOperand(cp, Context::GLOBAL_INDEX)); - __ add(r3, r0, Operand(JSRegExpResult::kSize)); - __ mov(r4, Operand(Factory::empty_fixed_array())); - __ ldr(r2, FieldMemOperand(r2, GlobalObject::kGlobalContextOffset)); - __ str(r3, FieldMemOperand(r0, JSObject::kElementsOffset)); - __ ldr(r2, ContextOperand(r2, Context::REGEXP_RESULT_MAP_INDEX)); - __ str(r4, FieldMemOperand(r0, JSObject::kPropertiesOffset)); - __ str(r2, FieldMemOperand(r0, HeapObject::kMapOffset)); - - // Set input, index and length fields from arguments. - __ ldm(ia_w, sp, static_cast<RegList>(r2.bit() | r4.bit())); - __ str(r1, FieldMemOperand(r0, JSArray::kLengthOffset)); - __ add(sp, sp, Operand(kPointerSize)); - __ str(r4, FieldMemOperand(r0, JSRegExpResult::kIndexOffset)); - __ str(r2, FieldMemOperand(r0, JSRegExpResult::kInputOffset)); - - // Fill out the elements FixedArray. - // r0: JSArray, tagged. - // r3: FixedArray, tagged. - // r5: Number of elements in array, untagged. - - // Set map. - __ mov(r2, Operand(Factory::fixed_array_map())); - __ str(r2, FieldMemOperand(r3, HeapObject::kMapOffset)); - // Set FixedArray length. - __ mov(r6, Operand(r5, LSL, kSmiTagSize)); - __ str(r6, FieldMemOperand(r3, FixedArray::kLengthOffset)); - // Fill contents of fixed-array with the-hole. - __ mov(r2, Operand(Factory::the_hole_value())); - __ add(r3, r3, Operand(FixedArray::kHeaderSize - kHeapObjectTag)); - // Fill fixed array elements with hole. - // r0: JSArray, tagged. - // r2: the hole. - // r3: Start of elements in FixedArray. - // r5: Number of elements to fill. - Label loop; - __ tst(r5, Operand(r5)); - __ bind(&loop); - __ b(le, &done); // Jump if r1 is negative or zero. - __ sub(r5, r5, Operand(1), SetCC); - __ str(r2, MemOperand(r3, r5, LSL, kPointerSizeLog2)); - __ jmp(&loop); - - __ bind(&slowcase); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); - - __ bind(&done); - } - frame_->Forget(3); + RegExpConstructResultStub stub; + frame_->SpillAll(); + frame_->CallStub(&stub, 3); frame_->EmitPush(r0); } @@ -5758,6 +5676,20 @@ void CodeGenerator::GenerateMathCos(ZoneList<Expression*>* args) { } +void CodeGenerator::GenerateMathLog(ZoneList<Expression*>* args) { + ASSERT_EQ(args->length(), 1); + Load(args->at(0)); + if (CpuFeatures::IsSupported(VFP3)) { + TranscendentalCacheStub stub(TranscendentalCache::LOG); + frame_->SpillAllButCopyTOSToR0(); + frame_->CallStub(&stub, 1); + } else { + frame_->CallRuntime(Runtime::kMath_log, 1); + } + frame_->EmitPush(r0); +} + + void CodeGenerator::GenerateObjectEquals(ZoneList<Expression*>* args) { ASSERT(args->length() == 2); @@ -6537,7 +6469,7 @@ void CodeGenerator::VisitCompareOperation(CompareOperation* node) { case Token::INSTANCEOF: { Load(left); Load(right); - InstanceofStub stub; + InstanceofStub stub(InstanceofStub::kNoFlags); frame_->CallStub(&stub, 2); // At this point if instanceof succeeded then r0 == 0. __ tst(r0, Operand(r0)); diff --git a/src/arm/codegen-arm.h b/src/arm/codegen-arm.h index 1930f5e1..589e704b 100644 --- a/src/arm/codegen-arm.h +++ b/src/arm/codegen-arm.h @@ -209,6 +209,9 @@ class CodeGenerator: public AstVisitor { Code::Flags flags, CompilationInfo* info); + // Print the code after compiling it. + static void PrintCode(Handle<Code> code, CompilationInfo* info); + #ifdef ENABLE_LOGGING_AND_PROFILING static bool ShouldGenerateLog(Expression* type); #endif @@ -305,8 +308,9 @@ class CodeGenerator: public AstVisitor { // Node visitors. void VisitStatements(ZoneList<Statement*>* statements); + virtual void VisitSlot(Slot* node); #define DEF_VISIT(type) \ - void Visit##type(type* node); + virtual void Visit##type(type* node); AST_NODE_LIST(DEF_VISIT) #undef DEF_VISIT @@ -516,6 +520,7 @@ class CodeGenerator: public AstVisitor { void GenerateMathSin(ZoneList<Expression*>* args); void GenerateMathCos(ZoneList<Expression*>* args); void GenerateMathSqrt(ZoneList<Expression*>* args); + void GenerateMathLog(ZoneList<Expression*>* args); void GenerateIsRegExpEquivalent(ZoneList<Expression*>* args); @@ -578,6 +583,7 @@ class CodeGenerator: public AstVisitor { friend class FastCodeGenerator; friend class FullCodeGenerator; friend class FullCodeGenSyntaxChecker; + friend class LCodeGen; DISALLOW_COPY_AND_ASSIGN(CodeGenerator); }; diff --git a/src/arm/cpu-arm.cc b/src/arm/cpu-arm.cc index e998b6f5..b359dce6 100644 --- a/src/arm/cpu-arm.cc +++ b/src/arm/cpu-arm.cc @@ -42,7 +42,10 @@ namespace v8 { namespace internal { void CPU::Setup() { - CpuFeatures::Probe(); + CpuFeatures::Probe(true); + if (!CpuFeatures::IsSupported(VFP3) || Serializer::enabled()) { + V8::DisableCrankshaft(); + } } diff --git a/src/arm/deoptimizer-arm.cc b/src/arm/deoptimizer-arm.cc new file mode 100644 index 00000000..3917d6df --- /dev/null +++ b/src/arm/deoptimizer-arm.cc @@ -0,0 +1,503 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "codegen.h" +#include "deoptimizer.h" +#include "full-codegen.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + +int Deoptimizer::table_entry_size_ = 16; + +void Deoptimizer::DeoptimizeFunction(JSFunction* function) { + AssertNoAllocation no_allocation; + + if (!function->IsOptimized()) return; + + // Get the optimized code. + Code* code = function->code(); + + // Invalidate the relocation information, as it will become invalid by the + // code patching below, and is not needed any more. + code->InvalidateRelocation(); + + // For each return after a safepoint insert an absolute call to the + // corresponding deoptimization entry. + unsigned last_pc_offset = 0; + SafepointTable table(function->code()); + for (unsigned i = 0; i < table.length(); i++) { + unsigned pc_offset = table.GetPcOffset(i); + int deoptimization_index = table.GetDeoptimizationIndex(i); + int gap_code_size = table.GetGapCodeSize(i); + // Check that we did not shoot past next safepoint. + // TODO(srdjan): How do we guarantee that safepoint code does not + // overlap other safepoint patching code? + CHECK(pc_offset >= last_pc_offset); +#ifdef DEBUG + // Destroy the code which is not supposed to be run again. + int instructions = (pc_offset - last_pc_offset) / Assembler::kInstrSize; + CodePatcher destroyer(code->instruction_start() + last_pc_offset, + instructions); + for (int x = 0; x < instructions; x++) { + destroyer.masm()->bkpt(0); + } +#endif + last_pc_offset = pc_offset; + if (deoptimization_index != Safepoint::kNoDeoptimizationIndex) { + const int kCallInstructionSizeInWords = 3; + CodePatcher patcher(code->instruction_start() + pc_offset + gap_code_size, + kCallInstructionSizeInWords); + Address deoptimization_entry = Deoptimizer::GetDeoptimizationEntry( + deoptimization_index, Deoptimizer::LAZY); + patcher.masm()->Call(deoptimization_entry, RelocInfo::NONE); + last_pc_offset += + gap_code_size + kCallInstructionSizeInWords * Assembler::kInstrSize; + } + } + + +#ifdef DEBUG + // Destroy the code which is not supposed to be run again. + int instructions = + (code->safepoint_table_start() - last_pc_offset) / Assembler::kInstrSize; + CodePatcher destroyer(code->instruction_start() + last_pc_offset, + instructions); + for (int x = 0; x < instructions; x++) { + destroyer.masm()->bkpt(0); + } +#endif + + // Add the deoptimizing code to the list. + DeoptimizingCodeListNode* node = new DeoptimizingCodeListNode(code); + node->set_next(deoptimizing_code_list_); + deoptimizing_code_list_ = node; + + // Set the code for the function to non-optimized version. + function->ReplaceCode(function->shared()->code()); + + if (FLAG_trace_deopt) { + PrintF("[forced deoptimization: "); + function->PrintName(); + PrintF(" / %x]\n", reinterpret_cast<uint32_t>(function)); + } +} + + +void Deoptimizer::PatchStackCheckCode(RelocInfo* rinfo, + Code* replacement_code) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::RevertStackCheckCode(RelocInfo* rinfo, Code* check_code) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::DoComputeOsrOutputFrame() { + UNIMPLEMENTED(); +} + + +// This code is very similar to ia32 code, but relies on register names (fp, sp) +// and how the frame is laid out. +void Deoptimizer::DoComputeFrame(TranslationIterator* iterator, + int frame_index) { + // Read the ast node id, function, and frame height for this output frame. + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator->Next()); + USE(opcode); + ASSERT(Translation::FRAME == opcode); + int node_id = iterator->Next(); + JSFunction* function = JSFunction::cast(ComputeLiteral(iterator->Next())); + unsigned height = iterator->Next(); + unsigned height_in_bytes = height * kPointerSize; + if (FLAG_trace_deopt) { + PrintF(" translating "); + function->PrintName(); + PrintF(" => node=%d, height=%d\n", node_id, height_in_bytes); + } + + // The 'fixed' part of the frame consists of the incoming parameters and + // the part described by JavaScriptFrameConstants. + unsigned fixed_frame_size = ComputeFixedSize(function); + unsigned input_frame_size = input_->GetFrameSize(); + unsigned output_frame_size = height_in_bytes + fixed_frame_size; + + // Allocate and store the output frame description. + FrameDescription* output_frame = + new(output_frame_size) FrameDescription(output_frame_size, function); + + bool is_bottommost = (0 == frame_index); + bool is_topmost = (output_count_ - 1 == frame_index); + ASSERT(frame_index >= 0 && frame_index < output_count_); + ASSERT(output_[frame_index] == NULL); + output_[frame_index] = output_frame; + + // The top address for the bottommost output frame can be computed from + // the input frame pointer and the output frame's height. For all + // subsequent output frames, it can be computed from the previous one's + // top address and the current frame's size. + uint32_t top_address; + if (is_bottommost) { + // 2 = context and function in the frame. + top_address = + input_->GetRegister(fp.code()) - (2 * kPointerSize) - height_in_bytes; + } else { + top_address = output_[frame_index - 1]->GetTop() - output_frame_size; + } + output_frame->SetTop(top_address); + + // Compute the incoming parameter translation. + int parameter_count = function->shared()->formal_parameter_count() + 1; + unsigned output_offset = output_frame_size; + unsigned input_offset = input_frame_size; + for (int i = 0; i < parameter_count; ++i) { + output_offset -= kPointerSize; + DoTranslateCommand(iterator, frame_index, output_offset); + } + input_offset -= (parameter_count * kPointerSize); + + // There are no translation commands for the caller's pc and fp, the + // context, and the function. Synthesize their values and set them up + // explicitly. + // + // The caller's pc for the bottommost output frame is the same as in the + // input frame. For all subsequent output frames, it can be read from the + // previous one. This frame's pc can be computed from the non-optimized + // function code and AST id of the bailout. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + intptr_t value; + if (is_bottommost) { + value = input_->GetFrameSlot(input_offset); + } else { + value = output_[frame_index - 1]->GetPc(); + } + output_frame->SetFrameSlot(output_offset, value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; caller's pc\n", + top_address + output_offset, output_offset, value); + } + + // The caller's frame pointer for the bottommost output frame is the same + // as in the input frame. For all subsequent output frames, it can be + // read from the previous one. Also compute and set this frame's frame + // pointer. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + if (is_bottommost) { + value = input_->GetFrameSlot(input_offset); + } else { + value = output_[frame_index - 1]->GetFp(); + } + output_frame->SetFrameSlot(output_offset, value); + intptr_t fp_value = top_address + output_offset; + ASSERT(!is_bottommost || input_->GetRegister(fp.code()) == fp_value); + output_frame->SetFp(fp_value); + if (is_topmost) { + output_frame->SetRegister(fp.code(), fp_value); + } + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; caller's fp\n", + fp_value, output_offset, value); + } + + // The context can be gotten from the function so long as we don't + // optimize functions that need local contexts. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + value = reinterpret_cast<intptr_t>(function->context()); + // The context for the bottommost output frame should also agree with the + // input frame. + ASSERT(!is_bottommost || input_->GetFrameSlot(input_offset) == value); + output_frame->SetFrameSlot(output_offset, value); + if (is_topmost) { + output_frame->SetRegister(cp.code(), value); + } + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; context\n", + top_address + output_offset, output_offset, value); + } + + // The function was mentioned explicitly in the BEGIN_FRAME. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + value = reinterpret_cast<uint32_t>(function); + // The function for the bottommost output frame should also agree with the + // input frame. + ASSERT(!is_bottommost || input_->GetFrameSlot(input_offset) == value); + output_frame->SetFrameSlot(output_offset, value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; function\n", + top_address + output_offset, output_offset, value); + } + + // Translate the rest of the frame. + for (unsigned i = 0; i < height; ++i) { + output_offset -= kPointerSize; + DoTranslateCommand(iterator, frame_index, output_offset); + } + ASSERT(0 == output_offset); + + // Compute this frame's PC, state, and continuation. + Code* non_optimized_code = function->shared()->code(); + FixedArray* raw_data = non_optimized_code->deoptimization_data(); + DeoptimizationOutputData* data = DeoptimizationOutputData::cast(raw_data); + Address start = non_optimized_code->instruction_start(); + unsigned pc_and_state = GetOutputInfo(data, node_id, function->shared()); + unsigned pc_offset = FullCodeGenerator::PcField::decode(pc_and_state); + uint32_t pc_value = reinterpret_cast<uint32_t>(start + pc_offset); + output_frame->SetPc(pc_value); + if (is_topmost) { + output_frame->SetRegister(pc.code(), pc_value); + } + + FullCodeGenerator::State state = + FullCodeGenerator::StateField::decode(pc_and_state); + output_frame->SetState(Smi::FromInt(state)); + + // Set the continuation for the topmost frame. + if (is_topmost) { + Code* continuation = (bailout_type_ == EAGER) + ? Builtins::builtin(Builtins::NotifyDeoptimized) + : Builtins::builtin(Builtins::NotifyLazyDeoptimized); + output_frame->SetContinuation( + reinterpret_cast<uint32_t>(continuation->entry())); + } + + if (output_count_ - 1 == frame_index) iterator->Done(); +} + + +#define __ masm()-> + + +// This code tries to be close to ia32 code so that any changes can be +// easily ported. +void Deoptimizer::EntryGenerator::Generate() { + GeneratePrologue(); + // TOS: bailout-id; TOS+1: return address if not EAGER. + CpuFeatures::Scope scope(VFP3); + // Save all general purpose registers before messing with them. + const int kNumberOfRegisters = Register::kNumRegisters; + + // Everything but pc, lr and ip which will be saved but not restored. + RegList restored_regs = kJSCallerSaved | kCalleeSaved | ip.bit(); + + const int kDoubleRegsSize = + kDoubleSize * DwVfpRegister::kNumAllocatableRegisters; + + // Save all general purpose registers before messing with them. + __ sub(sp, sp, Operand(kDoubleRegsSize)); + for (int i = 0; i < DwVfpRegister::kNumAllocatableRegisters; ++i) { + DwVfpRegister vfp_reg = DwVfpRegister::FromAllocationIndex(i); + int offset = i * kDoubleSize; + __ vstr(vfp_reg, sp, offset); + } + + // Push all 16 registers (needed to populate FrameDescription::registers_). + __ stm(db_w, sp, restored_regs | sp.bit() | lr.bit() | pc.bit()); + + const int kSavedRegistersAreaSize = + (kNumberOfRegisters * kPointerSize) + kDoubleRegsSize; + + // Get the bailout id from the stack. + __ ldr(r2, MemOperand(sp, kSavedRegistersAreaSize)); + + // Get the address of the location in the code object if possible (r3) (return + // address for lazy deoptimization) and compute the fp-to-sp delta in + // register r4. + if (type() == EAGER) { + __ mov(r3, Operand(0)); + // Correct one word for bailout id. + __ add(r4, sp, Operand(kSavedRegistersAreaSize + (1 * kPointerSize))); + } else { + __ mov(r3, lr); + // Correct two words for bailout id and return address. + __ add(r4, sp, Operand(kSavedRegistersAreaSize + (2 * kPointerSize))); + } + __ sub(r4, fp, r4); + + // Allocate a new deoptimizer object. + // Pass four arguments in r0 to r3 and fifth argument on stack. + __ PrepareCallCFunction(5, r5); + __ ldr(r0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset)); + __ mov(r1, Operand(type())); // bailout type, + // r2: bailout id already loaded. + // r3: code address or 0 already loaded. + __ str(r4, MemOperand(sp, 0 * kPointerSize)); // Fp-to-sp delta. + // Call Deoptimizer::New(). + __ CallCFunction(ExternalReference::new_deoptimizer_function(), 5); + + // Preserve "deoptimizer" object in register r0 and get the input + // frame descriptor pointer to r1 (deoptimizer->input_); + __ ldr(r1, MemOperand(r0, Deoptimizer::input_offset())); + + + // Copy core registers into FrameDescription::registers_[kNumRegisters]. + ASSERT(Register::kNumRegisters == kNumberOfRegisters); + for (int i = 0; i < kNumberOfRegisters; i++) { + int offset = (i * kIntSize) + FrameDescription::registers_offset(); + __ ldr(r2, MemOperand(sp, i * kPointerSize)); + __ str(r2, MemOperand(r1, offset)); + } + + // Copy VFP registers to + // double_registers_[DoubleRegister::kNumAllocatableRegisters] + int double_regs_offset = FrameDescription::double_registers_offset(); + for (int i = 0; i < DwVfpRegister::kNumAllocatableRegisters; ++i) { + int dst_offset = i * kDoubleSize + double_regs_offset; + int src_offset = i * kDoubleSize + kNumberOfRegisters * kPointerSize; + __ vldr(d0, sp, src_offset); + __ vstr(d0, r1, dst_offset); + } + + // Remove the bailout id, eventually return address, and the saved registers + // from the stack. + if (type() == EAGER) { + __ add(sp, sp, Operand(kSavedRegistersAreaSize + (1 * kPointerSize))); + } else { + __ add(sp, sp, Operand(kSavedRegistersAreaSize + (2 * kPointerSize))); + } + + // Compute a pointer to the unwinding limit in register r2; that is + // the first stack slot not part of the input frame. + __ ldr(r2, MemOperand(r1, FrameDescription::frame_size_offset())); + __ add(r2, r2, sp); + + // Unwind the stack down to - but not including - the unwinding + // limit and copy the contents of the activation frame to the input + // frame description. + __ add(r3, r1, Operand(FrameDescription::frame_content_offset())); + Label pop_loop; + __ bind(&pop_loop); + __ pop(r4); + __ str(r4, MemOperand(r3, 0)); + __ add(r3, r3, Operand(sizeof(uint32_t))); + __ cmp(r2, sp); + __ b(ne, &pop_loop); + + // Compute the output frame in the deoptimizer. + __ push(r0); // Preserve deoptimizer object across call. + // r0: deoptimizer object; r1: scratch. + __ PrepareCallCFunction(1, r1); + // Call Deoptimizer::ComputeOutputFrames(). + __ CallCFunction(ExternalReference::compute_output_frames_function(), 1); + __ pop(r0); // Restore deoptimizer object (class Deoptimizer). + + // Replace the current (input) frame with the output frames. + Label outer_push_loop, inner_push_loop; + // Outer loop state: r0 = current "FrameDescription** output_", + // r1 = one past the last FrameDescription**. + __ ldr(r1, MemOperand(r0, Deoptimizer::output_count_offset())); + __ ldr(r0, MemOperand(r0, Deoptimizer::output_offset())); // r0 is output_. + __ add(r1, r0, Operand(r1, LSL, 2)); + __ bind(&outer_push_loop); + // Inner loop state: r2 = current FrameDescription*, r3 = loop index. + __ ldr(r2, MemOperand(r0, 0)); // output_[ix] + __ ldr(r3, MemOperand(r2, FrameDescription::frame_size_offset())); + __ bind(&inner_push_loop); + __ sub(r3, r3, Operand(sizeof(uint32_t))); + // __ add(r6, r2, Operand(r3, LSL, 1)); + __ add(r6, r2, Operand(r3)); + __ ldr(r7, MemOperand(r6, FrameDescription::frame_content_offset())); + __ push(r7); + __ cmp(r3, Operand(0)); + __ b(ne, &inner_push_loop); // test for gt? + __ add(r0, r0, Operand(kPointerSize)); + __ cmp(r0, r1); + __ b(lt, &outer_push_loop); + + // In case of OSR, we have to restore the XMM registers. + if (type() == OSR) { + UNIMPLEMENTED(); + } + + // Push state, pc, and continuation from the last output frame. + if (type() != OSR) { + __ ldr(r6, MemOperand(r2, FrameDescription::state_offset())); + __ push(r6); + } + + __ ldr(r6, MemOperand(r2, FrameDescription::pc_offset())); + __ push(r6); + __ ldr(r6, MemOperand(r2, FrameDescription::continuation_offset())); + __ push(r6); + + // Push the registers from the last output frame. + for (int i = kNumberOfRegisters - 1; i >= 0; i--) { + int offset = (i * kIntSize) + FrameDescription::registers_offset(); + __ ldr(r6, MemOperand(r2, offset)); + __ push(r6); + } + + // Restore the registers from the stack. + __ ldm(ia_w, sp, restored_regs); // all but pc registers. + __ pop(ip); // remove sp + __ pop(ip); // remove lr + + // Set up the roots register. + ExternalReference roots_address = ExternalReference::roots_address(); + __ mov(r10, Operand(roots_address)); + + __ pop(ip); // remove pc + __ pop(r7); // get continuation, leave pc on stack + __ pop(lr); + __ Jump(r7); + __ stop("Unreachable."); +} + + +void Deoptimizer::TableEntryGenerator::GeneratePrologue() { + // Create a sequence of deoptimization entries. Note that any + // registers may be still live. + Label done; + for (int i = 0; i < count(); i++) { + int start = masm()->pc_offset(); + USE(start); + if (type() == EAGER) { + __ nop(); + } else { + // Emulate ia32 like call by pushing return address to stack. + __ push(lr); + } + __ mov(ip, Operand(i)); + __ push(ip); + __ b(&done); + ASSERT(masm()->pc_offset() - start == table_entry_size_); + } + __ bind(&done); +} + +#undef __ + +} } // namespace v8::internal diff --git a/src/arm/frames-arm.cc b/src/arm/frames-arm.cc index b0c09903..d2726cfc 100644 --- a/src/arm/frames-arm.cc +++ b/src/arm/frames-arm.cc @@ -38,7 +38,12 @@ namespace internal { Address ExitFrame::ComputeStackPointer(Address fp) { - return fp + ExitFrameConstants::kSPOffset; + Address marker = Memory::Address_at(fp + ExitFrameConstants::kMarkerOffset); + Address sp = fp + ExitFrameConstants::kSPOffset; + if (marker == NULL) { + sp -= DwVfpRegister::kNumRegisters * kDoubleSize + 2 * kPointerSize; + } + return sp; } diff --git a/src/arm/frames-arm.h b/src/arm/frames-arm.h index 5847a6a2..00c20efa 100644 --- a/src/arm/frames-arm.h +++ b/src/arm/frames-arm.h @@ -74,6 +74,18 @@ static const RegList kCalleeSaved = static const int kNumCalleeSaved = 7 + kR9Available; +// Number of registers for which space is reserved in safepoints. Must be a +// multiple of 8. +// TODO(regis): Only 8 registers may actually be sufficient. Revisit. +static const int kNumSafepointRegisters = 16; + +// Define the list of registers actually saved at safepoints. +// Note that the number of saved registers may be smaller than the reserved +// space, i.e. kNumSafepointSavedRegisters <= kNumSafepointRegisters. +static const RegList kSafepointSavedRegisters = kJSCallerSaved | kCalleeSaved; +static const int kNumSafepointSavedRegisters = + kNumJSCallerSaved + kNumCalleeSaved; + // ---------------------------------------------------- @@ -99,7 +111,9 @@ class ExitFrameConstants : public AllStatic { static const int kCodeOffset = -1 * kPointerSize; static const int kSPOffset = -1 * kPointerSize; - static const int kSavedRegistersOffset = 0 * kPointerSize; + // TODO(regis): Use a patched sp value on the stack instead. + // A marker of 0 indicates that double registers are saved. + static const int kMarkerOffset = -2 * kPointerSize; // The caller fields are below the frame pointer on the stack. static const int kCallerFPOffset = +0 * kPointerSize; diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 0b6e13dc..d2549183 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -171,21 +171,20 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { } } + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + // Check the stack for overflow or break request. { Comment cmnt(masm_, "[ Stack check"); - __ LoadRoot(r2, Heap::kStackLimitRootIndex); - __ cmp(sp, Operand(r2)); + PrepareForBailout(info->function(), NO_REGISTERS); + Label ok; + __ LoadRoot(ip, Heap::kStackLimitRootIndex); + __ cmp(sp, Operand(ip)); + __ b(hs, &ok); StackCheckStub stub; - __ mov(ip, - Operand(reinterpret_cast<intptr_t>(stub.GetCode().location()), - RelocInfo::CODE_TARGET), - LeaveCC, - lo); - __ Call(ip, lo); - } - - if (FLAG_trace) { - __ CallRuntime(Runtime::kTraceEnter, 0); + __ CallStub(&stub); + __ bind(&ok); } { Comment cmnt(masm_, "[ Body"); @@ -200,6 +199,10 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { __ LoadRoot(r0, Heap::kUndefinedValueRootIndex); } EmitReturnSequence(); + + // Force emit the constant pool, so it doesn't get emitted in the middle + // of the stack check table. + masm()->CheckConstPool(true, false); } @@ -208,6 +211,21 @@ void FullCodeGenerator::ClearAccumulator() { } +void FullCodeGenerator::EmitStackCheck(IterationStatement* stmt) { + Comment cmnt(masm_, "[ Stack check"); + Label ok; + __ LoadRoot(ip, Heap::kStackLimitRootIndex); + __ cmp(sp, Operand(ip)); + __ b(hs, &ok); + StackCheckStub stub; + __ CallStub(&stub); + __ bind(&ok); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + PrepareForBailoutForId(stmt->OsrEntryId(), NO_REGISTERS); + RecordStackCheck(stmt->OsrEntryId()); +} + + void FullCodeGenerator::EmitReturnSequence() { Comment cmnt(masm_, "[ Return sequence"); if (return_label_.is_bound()) { @@ -280,6 +298,7 @@ void FullCodeGenerator::StackValueContext::Plug(Slot* slot) const { void FullCodeGenerator::TestContext::Plug(Slot* slot) const { // For simplicity we always test the accumulator register. codegen()->Move(result_register(), slot); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } @@ -302,12 +321,16 @@ void FullCodeGenerator::StackValueContext::Plug( void FullCodeGenerator::TestContext::Plug(Heap::RootListIndex index) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); if (index == Heap::kUndefinedValueRootIndex || index == Heap::kNullValueRootIndex || index == Heap::kFalseValueRootIndex) { - __ b(false_label_); + if (false_label_ != fall_through_) __ b(false_label_); } else if (index == Heap::kTrueValueRootIndex) { - __ b(true_label_); + if (true_label_ != fall_through_) __ b(true_label_); } else { __ LoadRoot(result_register(), index); codegen()->DoTest(true_label_, false_label_, fall_through_); @@ -326,29 +349,34 @@ void FullCodeGenerator::AccumulatorValueContext::Plug( void FullCodeGenerator::StackValueContext::Plug(Handle<Object> lit) const { - // Immediates can be pushed directly. + // Immediates cannot be pushed directly. __ mov(result_register(), Operand(lit)); __ push(result_register()); } void FullCodeGenerator::TestContext::Plug(Handle<Object> lit) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); ASSERT(!lit->IsUndetectableObject()); // There are no undetectable literals. if (lit->IsUndefined() || lit->IsNull() || lit->IsFalse()) { - __ b(false_label_); + if (false_label_ != fall_through_) __ b(false_label_); } else if (lit->IsTrue() || lit->IsJSObject()) { - __ b(true_label_); + if (true_label_ != fall_through_) __ b(true_label_); } else if (lit->IsString()) { if (String::cast(*lit)->length() == 0) { + if (false_label_ != fall_through_) __ b(false_label_); __ b(false_label_); } else { - __ b(true_label_); + if (true_label_ != fall_through_) __ b(true_label_); } } else if (lit->IsSmi()) { if (Smi::cast(*lit)->value() == 0) { - __ b(false_label_); + if (false_label_ != fall_through_) __ b(false_label_); } else { - __ b(true_label_); + if (true_label_ != fall_through_) __ b(true_label_); } } else { // For simplicity we always test the accumulator register. @@ -388,13 +416,14 @@ void FullCodeGenerator::TestContext::DropAndPlug(int count, // For simplicity we always test the accumulator register. __ Drop(count); __ Move(result_register(), reg); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } void FullCodeGenerator::EffectContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT_EQ(materialize_true, materialize_false); + ASSERT(materialize_true == materialize_false); __ bind(materialize_true); } @@ -429,8 +458,8 @@ void FullCodeGenerator::StackValueContext::Plug( void FullCodeGenerator::TestContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT(materialize_false == false_label_); ASSERT(materialize_true == true_label_); + ASSERT(materialize_false == false_label_); } @@ -454,6 +483,10 @@ void FullCodeGenerator::StackValueContext::Plug(bool flag) const { void FullCodeGenerator::TestContext::Plug(bool flag) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); if (flag) { if (true_label_ != fall_through_) __ b(true_label_); } else { @@ -534,6 +567,33 @@ void FullCodeGenerator::Move(Slot* dst, } +void FullCodeGenerator::PrepareForBailoutBeforeSplit(State state, + bool should_normalize, + Label* if_true, + Label* if_false) { + // Only prepare for bailouts before splits if we're in a test + // context. Otherwise, we let the Visit function deal with the + // preparation to avoid preparing with the same AST id twice. + if (!context()->IsTest() || !info_->IsOptimizable()) return; + + Label skip; + if (should_normalize) __ b(&skip); + + ForwardBailoutStack* current = forward_bailout_stack_; + while (current != NULL) { + PrepareForBailout(current->expr(), state); + current = current->parent(); + } + + if (should_normalize) { + __ LoadRoot(ip, Heap::kTrueValueRootIndex); + __ cmp(r0, ip); + Split(eq, if_true, if_false, NULL); + __ bind(&skip); + } +} + + void FullCodeGenerator::EmitDeclaration(Variable* variable, Variable::Mode mode, FunctionLiteral* function) { @@ -656,6 +716,8 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { // Keep the switch value on the stack until a case matches. VisitForStackValue(stmt->tag()); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + ZoneList<CaseClause*>* clauses = stmt->cases(); CaseClause* default_clause = NULL; // Can occur anywhere in the list. @@ -721,6 +783,7 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { } __ bind(nested_statement.break_target()); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); } @@ -832,28 +895,22 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { __ bind(&update_each); __ mov(result_register(), r3); // Perform the assignment as if via '='. - EmitAssignment(stmt->each()); + { EffectContext context(this); + EmitAssignment(stmt->each(), stmt->AssignmentId()); + } // Generate code for the body of the loop. - Label stack_limit_hit, stack_check_done; Visit(stmt->body()); - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_done); - // Generate code for the going to the next element by incrementing // the index (smi) stored on top of the stack. __ bind(loop_statement.continue_target()); __ pop(r0); __ add(r0, r0, Operand(Smi::FromInt(1))); __ push(r0); - __ b(&loop); - // Slow case for the stack limit check. - StackCheckStub stack_check_stub; - __ bind(&stack_limit_hit); - __ CallStub(&stack_check_stub); - __ b(&stack_check_done); + EmitStackCheck(stmt); + __ b(&loop); // Remove the pointers stored on the stack. __ bind(loop_statement.break_target()); @@ -1200,12 +1257,15 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { // Fall through. case ObjectLiteral::Property::COMPUTED: if (key->handle()->IsSymbol()) { - VisitForAccumulatorValue(value); - __ mov(r2, Operand(key->handle())); - __ ldr(r1, MemOperand(sp)); if (property->emit_store()) { + VisitForAccumulatorValue(value); + __ mov(r2, Operand(key->handle())); + __ ldr(r1, MemOperand(sp)); Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(key->id(), NO_REGISTERS); + } else { + VisitForEffect(value); } break; } @@ -1300,6 +1360,8 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { // Update the write barrier for the array store with r0 as the scratch // register. __ RecordWrite(r1, Operand(offset), r2, result_register()); + + PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS); } if (result_saved) { @@ -1346,13 +1408,27 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { break; case KEYED_PROPERTY: if (expr->is_compound()) { - VisitForStackValue(property->obj()); - VisitForAccumulatorValue(property->key()); + if (property->is_arguments_access()) { + VariableProxy* obj_proxy = property->obj()->AsVariableProxy(); + __ ldr(r0, EmitSlotSearch(obj_proxy->var()->AsSlot(), r0)); + __ push(r0); + __ mov(r0, Operand(property->key()->AsLiteral()->handle())); + } else { + VisitForStackValue(property->obj()); + VisitForAccumulatorValue(property->key()); + } __ ldr(r1, MemOperand(sp, 0)); __ push(r0); } else { - VisitForStackValue(property->obj()); - VisitForStackValue(property->key()); + if (property->is_arguments_access()) { + VariableProxy* obj_proxy = property->obj()->AsVariableProxy(); + __ ldr(r1, EmitSlotSearch(obj_proxy->var()->AsSlot(), r0)); + __ mov(r0, Operand(property->key()->AsLiteral()->handle())); + __ Push(r1, r0); + } else { + VisitForStackValue(property->obj()); + VisitForStackValue(property->key()); + } } break; } @@ -1372,6 +1448,12 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { } } + // For property compound assignments we need another deoptimization + // point after the property load. + if (property != NULL) { + PrepareForBailoutForId(expr->CompoundLoadId(), TOS_REG); + } + Token::Value op = expr->binary_op(); ConstantOperand constant = ShouldInlineSmiCase(op) ? GetConstantOperand(op, expr->target(), expr->value()) @@ -1397,6 +1479,9 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { } else { EmitBinaryOp(op, mode); } + + // Deoptimization point in case the binary operation may have side effects. + PrepareForBailout(expr->binary_operation(), TOS_REG); } else { VisitForAccumulatorValue(expr->value()); } @@ -1409,6 +1494,8 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { case VARIABLE: EmitVariableAssignment(expr->target()->AsVariableProxy()->var(), expr->op()); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(r0); break; case NAMED_PROPERTY: EmitNamedPropertyAssignment(expr); @@ -1458,7 +1545,7 @@ void FullCodeGenerator::EmitBinaryOp(Token::Value op, } -void FullCodeGenerator::EmitAssignment(Expression* expr) { +void FullCodeGenerator::EmitAssignment(Expression* expr, int bailout_ast_id) { // Invalid left-hand sides are rewritten to have a 'throw // ReferenceError' on the left-hand side. if (!expr->IsValidLeftHandSide()) { @@ -1506,6 +1593,8 @@ void FullCodeGenerator::EmitAssignment(Expression* expr) { break; } } + PrepareForBailoutForId(bailout_ast_id, TOS_REG); + context()->Plug(r0); } @@ -1579,8 +1668,6 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&done); } - - context()->Plug(result_register()); } @@ -1623,10 +1710,10 @@ void FullCodeGenerator::EmitNamedPropertyAssignment(Assignment* expr) { __ push(ip); __ CallRuntime(Runtime::kToFastProperties, 1); __ pop(r0); - context()->DropAndPlug(1, r0); - } else { - context()->Plug(r0); + __ Drop(1); } + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(r0); } @@ -1667,10 +1754,10 @@ void FullCodeGenerator::EmitKeyedPropertyAssignment(Assignment* expr) { __ push(ip); __ CallRuntime(Runtime::kToFastProperties, 1); __ pop(r0); - context()->DropAndPlug(1, r0); - } else { - context()->Plug(r0); + __ Drop(1); } + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(r0); } @@ -1681,13 +1768,14 @@ void FullCodeGenerator::VisitProperty(Property* expr) { if (key->IsPropertyName()) { VisitForAccumulatorValue(expr->obj()); EmitNamedPropertyLoad(expr); + context()->Plug(r0); } else { VisitForStackValue(expr->obj()); VisitForAccumulatorValue(expr->key()); __ pop(r1); EmitKeyedPropertyLoad(expr); + context()->Plug(r0); } - context()->Plug(r0); } void FullCodeGenerator::EmitCallWithIC(Call* expr, @@ -1696,18 +1784,19 @@ void FullCodeGenerator::EmitCallWithIC(Call* expr, // Code common for calls using the IC. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } __ mov(r2, Operand(name)); } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); // Call the IC initialization code. InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeCallInitialize(arg_count, in_loop); EmitCallIC(ic, mode); + RecordJSReturnSite(expr); // Restore context register. __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); context()->Plug(r0); @@ -1729,18 +1818,19 @@ void FullCodeGenerator::EmitKeyedCallWithIC(Call* expr, // Code common for calls using the IC. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); // Call the IC initialization code. InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeKeyedCallInitialize(arg_count, in_loop); __ ldr(r2, MemOperand(sp, (arg_count + 1) * kPointerSize)); // Key. EmitCallIC(ic, mode); + RecordJSReturnSite(expr); // Restore context register. __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, r0); // Drop the key still on the stack. @@ -1751,16 +1841,17 @@ void FullCodeGenerator::EmitCallWithStub(Call* expr) { // Code common for calls using the call stub. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); + RecordJSReturnSite(expr); // Restore context register. __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, r0); @@ -1768,6 +1859,12 @@ void FullCodeGenerator::EmitCallWithStub(Call* expr) { void FullCodeGenerator::VisitCall(Call* expr) { +#ifdef DEBUG + // We want to verify that RecordJSReturnSite gets called on all paths + // through this function. Avoid early returns. + expr->return_is_recorded_ = false; +#endif + Comment cmnt(masm_, "[ Call"); Expression* fun = expr->expression(); Variable* var = fun->AsVariableProxy()->AsVariable(); @@ -1780,7 +1877,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope pos_scope(masm()->positions_recorder()); + { PreservePositionScope pos_scope(masm()->positions_recorder()); VisitForStackValue(fun); __ LoadRoot(r2, Heap::kUndefinedValueRootIndex); __ push(r2); // Reserved receiver slot. @@ -1815,10 +1912,11 @@ void FullCodeGenerator::VisitCall(Call* expr) { } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); + RecordJSReturnSite(expr); // Restore context register. __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, r0); @@ -1832,7 +1930,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a lookup slot (dynamically introduced variable). Label slow, done; - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); // Generate code for loading from variables potentially shadowed // by eval-introduced variables. EmitDynamicLoadFromSlotFastCase(var->AsSlot(), @@ -1873,7 +1971,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { Literal* key = prop->key()->AsLiteral(); if (key != NULL && key->handle()->IsSymbol()) { // Call to a named property, use call IC. - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(prop->obj()); } EmitCallWithIC(expr, key->handle(), RelocInfo::CODE_TARGET); @@ -1881,15 +1979,15 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a keyed property. // For a synthetic property use keyed load IC followed by function call, // for a regular property use keyed CallIC. - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(prop->obj()); } if (prop->is_synthetic()) { - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForAccumulatorValue(prop->key()); } // Record source code position for IC call. - SetSourcePosition(prop->position(), FORCED_POSITION); + SetSourcePosition(prop->position()); __ pop(r1); // We do not need to keep the receiver. Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); @@ -1913,7 +2011,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { lit->set_try_full_codegen(true); } - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(fun); } // Load global receiver object. @@ -1923,6 +2021,11 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Emit function call. EmitCallWithStub(expr); } + +#ifdef DEBUG + // RecordJSReturnSite should have been called. + ASSERT(expr->return_is_recorded_); +#endif } @@ -1970,8 +2073,9 @@ void FullCodeGenerator::EmitIsSmi(ZoneList<Expression*>* args) { context()->PrepareTest(&materialize_true, &materialize_false, &if_true, &if_false, &fall_through); - __ BranchOnSmi(r0, if_true); - __ b(if_false); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); + __ tst(r0, Operand(kSmiTagMask)); + Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); } @@ -1989,6 +2093,7 @@ void FullCodeGenerator::EmitIsNonNegativeSmi(ZoneList<Expression*>* args) { context()->PrepareTest(&materialize_true, &materialize_false, &if_true, &if_false, &fall_through); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ tst(r0, Operand(kSmiTagMask | 0x80000000)); Split(eq, if_true, if_false, fall_through); @@ -2021,6 +2126,7 @@ void FullCodeGenerator::EmitIsObject(ZoneList<Expression*>* args) { __ cmp(r1, Operand(FIRST_JS_OBJECT_TYPE)); __ b(lt, if_false); __ cmp(r1, Operand(LAST_JS_OBJECT_TYPE)); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(le, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2041,6 +2147,7 @@ void FullCodeGenerator::EmitIsSpecObject(ZoneList<Expression*>* args) { __ BranchOnSmi(r0, if_false); __ CompareObjectType(r0, r1, r1, FIRST_JS_OBJECT_TYPE); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(ge, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2063,6 +2170,7 @@ void FullCodeGenerator::EmitIsUndetectableObject(ZoneList<Expression*>* args) { __ ldr(r1, FieldMemOperand(r0, HeapObject::kMapOffset)); __ ldrb(r1, FieldMemOperand(r1, Map::kBitFieldOffset)); __ tst(r1, Operand(1 << Map::kIsUndetectable)); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(ne, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2086,6 +2194,7 @@ void FullCodeGenerator::EmitIsStringWrapperSafeForDefaultValueOf( // Just indicate false, as %_IsStringWrapperSafeForDefaultValueOf() is only // used in a few functions in runtime.js which should not normally be hit by // this compiler. + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ jmp(if_false); context()->Plug(if_true, if_false); } @@ -2105,6 +2214,7 @@ void FullCodeGenerator::EmitIsFunction(ZoneList<Expression*>* args) { __ BranchOnSmi(r0, if_false); __ CompareObjectType(r0, r1, r1, JS_FUNCTION_TYPE); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2125,6 +2235,7 @@ void FullCodeGenerator::EmitIsArray(ZoneList<Expression*>* args) { __ BranchOnSmi(r0, if_false); __ CompareObjectType(r0, r1, r1, JS_ARRAY_TYPE); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2145,6 +2256,7 @@ void FullCodeGenerator::EmitIsRegExp(ZoneList<Expression*>* args) { __ BranchOnSmi(r0, if_false); __ CompareObjectType(r0, r1, r1, JS_REGEXP_TYPE); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2176,6 +2288,7 @@ void FullCodeGenerator::EmitIsConstructCall(ZoneList<Expression*>* args) { __ bind(&check_frame_marker); __ ldr(r1, MemOperand(r2, StandardFrameConstants::kMarkerOffset)); __ cmp(r1, Operand(Smi::FromInt(StackFrame::CONSTRUCT))); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2198,6 +2311,7 @@ void FullCodeGenerator::EmitObjectEquals(ZoneList<Expression*>* args) { __ pop(r1); __ cmp(r0, r1); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2622,6 +2736,15 @@ void FullCodeGenerator::EmitMathSqrt(ZoneList<Expression*>* args) { } +void FullCodeGenerator::EmitMathLog(ZoneList<Expression*>* args) { + // Load the argument on the stack and call the runtime function. + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallRuntime(Runtime::kMath_log, 1); + context()->Plug(r0); +} + + void FullCodeGenerator::EmitCallFunction(ZoneList<Expression*>* args) { ASSERT(args->length() >= 2); @@ -2642,11 +2765,12 @@ void FullCodeGenerator::EmitCallFunction(ZoneList<Expression*>* args) { void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) { + RegExpConstructResultStub stub; ASSERT(args->length() == 3); VisitForStackValue(args->at(0)); VisitForStackValue(args->at(1)); VisitForStackValue(args->at(2)); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); + __ CallStub(&stub); context()->Plug(r0); } @@ -2765,9 +2889,8 @@ void FullCodeGenerator::EmitHasCachedArrayIndex(ZoneList<Expression*>* args) { __ ldr(r0, FieldMemOperand(r0, String::kHashFieldOffset)); __ tst(r0, Operand(String::kContainsCachedArrayIndexMask)); - - __ b(eq, if_true); - __ b(if_false); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); + Split(eq, if_true, if_false, fall_through); context()->Plug(if_true, if_false); } @@ -2890,6 +3013,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { // Notice that the labels are swapped. context()->PrepareTest(&materialize_true, &materialize_false, &if_false, &if_true, &fall_through); + if (context()->IsTest()) ForwardBailoutToChild(expr); VisitForControl(expr->expression(), if_true, if_false, fall_through); context()->Plug(if_false, if_true); // Labels swapped. break; @@ -3009,14 +3133,25 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ push(r0); EmitNamedPropertyLoad(prop); } else { - VisitForStackValue(prop->obj()); - VisitForAccumulatorValue(prop->key()); + if (prop->is_arguments_access()) { + VariableProxy* obj_proxy = prop->obj()->AsVariableProxy(); + __ ldr(r0, EmitSlotSearch(obj_proxy->var()->AsSlot(), r0)); + __ push(r0); + __ mov(r0, Operand(prop->key()->AsLiteral()->handle())); + } else { + VisitForStackValue(prop->obj()); + VisitForAccumulatorValue(prop->key()); + } __ ldr(r1, MemOperand(sp, 0)); __ push(r0); EmitKeyedPropertyLoad(prop); } } + // We need a second deoptimization point after loading the value + // in case evaluating the property load my have a side effect. + PrepareForBailout(expr->increment(), TOS_REG); + // Call ToNumber only if operand is not a smi. Label no_conversion; __ BranchOnSmi(r0, &no_conversion); @@ -3059,6 +3194,10 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ sub(r0, r0, Operand(Smi::FromInt(count_value))); } __ mov(r1, Operand(Smi::FromInt(count_value))); + + // Record position before stub call. + SetSourcePosition(expr->position()); + GenericBinaryOpStub stub(Token::ADD, NO_OVERWRITE, r1, r0); __ CallStub(&stub); __ bind(&done); @@ -3070,6 +3209,8 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { { EffectContext context(this); EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context.Plug(r0); } // For all contexts except EffectConstant We have the result on // top of the stack. @@ -3079,6 +3220,8 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { } else { EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(r0); } break; case NAMED_PROPERTY: { @@ -3086,6 +3229,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ pop(r1); Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); if (expr->is_postfix()) { if (!context()->IsEffect()) { context()->PlugTOS(); @@ -3100,6 +3244,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ pop(r2); // Receiver. Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); if (expr->is_postfix()) { if (!context()->IsEffect()) { context()->PlugTOS(); @@ -3125,6 +3270,7 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) { // Use a regular load, not a contextual load, to avoid a reference // error. EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailout(expr, TOS_REG); context()->Plug(r0); } else if (proxy != NULL && proxy->var()->AsSlot() != NULL && @@ -3140,12 +3286,13 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) { __ mov(r0, Operand(proxy->name())); __ Push(cp, r0); __ CallRuntime(Runtime::kLoadContextSlotNoReferenceError, 2); + PrepareForBailout(expr, TOS_REG); __ bind(&done); context()->Plug(r0); } else { // This expression cannot throw a reference error at the top level. - Visit(expr); + context()->HandleExpression(expr); } } @@ -3170,6 +3317,8 @@ bool FullCodeGenerator::TryLiteralCompare(Token::Value op, { AccumulatorValueContext context(this); VisitForTypeofValue(left_unary->expression()); } + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); + if (check->Equals(Heap::number_symbol())) { __ tst(r0, Operand(kSmiTagMask)); __ b(eq, if_true); @@ -3273,6 +3422,7 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { case Token::IN: VisitForStackValue(expr->right()); __ InvokeBuiltin(Builtins::IN, CALL_JS); + PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); __ LoadRoot(ip, Heap::kTrueValueRootIndex); __ cmp(r0, ip); Split(eq, if_true, if_false, fall_through); @@ -3280,8 +3430,9 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { case Token::INSTANCEOF: { VisitForStackValue(expr->right()); - InstanceofStub stub; + InstanceofStub stub(InstanceofStub::kNoFlags); __ CallStub(&stub); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); // The stub returns 0 for true. __ tst(r0, r0); Split(eq, if_true, if_false, fall_through); @@ -3340,6 +3491,7 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { : NO_COMPARE_FLAGS; CompareStub stub(cc, strict, flags, r1, r0); __ CallStub(&stub); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ cmp(r0, Operand(0, RelocInfo::NONE)); Split(cc, if_true, if_false, fall_through); } @@ -3361,6 +3513,7 @@ void FullCodeGenerator::VisitCompareToNull(CompareToNull* expr) { &if_true, &if_false, &fall_through); VisitForAccumulatorValue(expr->expression()); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ LoadRoot(r1, Heap::kNullValueRootIndex); __ cmp(r0, r1); if (expr->is_strict()) { diff --git a/src/arm/ic-arm.cc b/src/arm/ic-arm.cc index 5e36d2ce..e5a1bae9 100644 --- a/src/arm/ic-arm.cc +++ b/src/arm/ic-arm.cc @@ -115,9 +115,6 @@ static void GenerateStringDictionaryProbes(MacroAssembler* masm, Register name, Register scratch1, Register scratch2) { - // Assert that name contains a string. - if (FLAG_debug_code) __ AbortIfNotString(name); - // Compute the capacity mask. const int kCapacityOffset = StringDictionary::kHeaderSize + StringDictionary::kCapacityIndex * kPointerSize; @@ -841,15 +838,7 @@ void KeyedCallIC::GenerateNormal(MacroAssembler* masm, int argc) { // -- lr : return address // ----------------------------------- - // Check if the name is a string. - Label miss; - __ tst(r2, Operand(kSmiTagMask)); - __ b(eq, &miss); - Condition cond = masm->IsObjectStringType(r2, r0); - __ b(NegateCondition(cond), &miss); - GenerateCallNormal(masm, argc); - __ bind(&miss); GenerateMiss(masm, argc); } @@ -918,6 +907,8 @@ void LoadIC::GenerateMiss(MacroAssembler* masm) { // Returns the code marker, or the 0 if the code is not marked. static inline int InlinedICSiteMarker(Address address, Address* inline_end_address) { + if (V8::UseCrankshaft()) return false; + // If the instruction after the call site is not the pseudo instruction nop1 // then this is not related to an inlined in-object property load. The nop1 // instruction is located just after the call to the IC in the deferred code @@ -951,6 +942,8 @@ static inline int InlinedICSiteMarker(Address address, bool LoadIC::PatchInlinedLoad(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // Find the end of the inlined code for handling the load if this is an // inlined IC call site. Address inline_end_address; @@ -1030,6 +1023,8 @@ bool LoadIC::PatchInlinedContextualLoad(Address address, bool StoreIC::PatchInlinedStore(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // Find the end of the inlined code for the store if there is an // inlined version of the store. Address inline_end_address; @@ -1080,6 +1075,8 @@ bool StoreIC::PatchInlinedStore(Address address, Object* map, int offset) { bool KeyedLoadIC::PatchInlinedLoad(Address address, Object* map) { + if (V8::UseCrankshaft()) return false; + Address inline_end_address; if (InlinedICSiteMarker(address, &inline_end_address) != Assembler::PROPERTY_ACCESS_INLINED) { @@ -1098,6 +1095,8 @@ bool KeyedLoadIC::PatchInlinedLoad(Address address, Object* map) { bool KeyedStoreIC::PatchInlinedStore(Address address, Object* map) { + if (V8::UseCrankshaft()) return false; + // Find the end of the inlined code for handling the store if this is an // inlined IC call site. Address inline_end_address; @@ -1326,7 +1325,7 @@ void KeyedLoadIC::GenerateString(MacroAssembler* masm) { char_at_generator.GenerateFast(masm); __ Ret(); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm, call_helper); __ bind(&miss); @@ -2318,9 +2317,76 @@ void StoreIC::GenerateNormal(MacroAssembler* masm) { } +void StoreIC::GenerateGlobalProxy(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- r0 : value + // -- r1 : receiver + // -- r2 : name + // -- lr : return address + // ----------------------------------- + + __ Push(r1, r2, r0); + + // Do tail-call to runtime routine. + __ TailCallRuntime(Runtime::kSetProperty, 3, 1); +} + + #undef __ +Condition CompareIC::ComputeCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return eq; + case Token::LT: + return lt; + case Token::GT: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return lt; + case Token::LTE: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return ge; + case Token::GTE: + return ge; + default: + UNREACHABLE(); + return no_condition; + } +} + + +void CompareIC::UpdateCaches(Handle<Object> x, Handle<Object> y) { + HandleScope scope; + Handle<Code> rewritten; + State previous_state = GetState(); + State state = TargetState(previous_state, false, x, y); + if (state == GENERIC) { + CompareStub stub(GetCondition(), strict(), NO_COMPARE_FLAGS, r1, r0); + rewritten = stub.GetCode(); + } else { + ICCompareStub stub(op_, state); + rewritten = stub.GetCode(); + } + set_target(*rewritten); + +#ifdef DEBUG + if (FLAG_trace_ic) { + PrintF("[CompareIC (%s->%s)#%s]\n", + GetStateName(previous_state), + GetStateName(state), + Token::Name(op_)); + } +#endif +} + + +void PatchInlinedSmiCode(Address address) { + UNIMPLEMENTED(); +} + + } } // namespace v8::internal #endif // V8_TARGET_ARCH_ARM diff --git a/src/arm/jump-target-arm.cc b/src/arm/jump-target-arm.cc index 16d4e50d..c6eb6289 100644 --- a/src/arm/jump-target-arm.cc +++ b/src/arm/jump-target-arm.cc @@ -143,16 +143,6 @@ void JumpTarget::DoBind() { entry_frame_set_ = true; } else { cgen()->frame()->MergeTo(&entry_frame_); - // On fall through we may have to merge both ways. - if (direction_ != FORWARD_ONLY) { - // This will not need to adjust the virtual frame entries that are - // register allocated since that was done above and they now match. - // But it does need to adjust the entry_frame_ of this jump target - // to make it potentially less optimistic. Later code can branch back - // to this jump target and we need to assert that that code does not - // have weaker assumptions about types. - entry_frame_.MergeTo(cgen()->frame()); - } } } else { // If there is no current frame we must have an entry frame which we can diff --git a/src/arm/lithium-arm.cc b/src/arm/lithium-arm.cc new file mode 100644 index 00000000..e31d2e1d --- /dev/null +++ b/src/arm/lithium-arm.cc @@ -0,0 +1,2122 @@ +// Copyright 2010 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. + +#include "arm/lithium-arm.h" +#include "arm/lithium-codegen-arm.h" + +namespace v8 { +namespace internal { + +#define DEFINE_COMPILE(type) \ + void L##type::CompileToNative(LCodeGen* generator) { \ + generator->Do##type(this); \ + } +LITHIUM_CONCRETE_INSTRUCTION_LIST(DEFINE_COMPILE) +#undef DEFINE_COMPILE + +LOsrEntry::LOsrEntry() { + for (int i = 0; i < Register::kNumAllocatableRegisters; ++i) { + register_spills_[i] = NULL; + } + for (int i = 0; i < DoubleRegister::kNumAllocatableRegisters; ++i) { + double_register_spills_[i] = NULL; + } +} + + +void LOsrEntry::MarkSpilledRegister(int allocation_index, + LOperand* spill_operand) { + ASSERT(spill_operand->IsStackSlot()); + ASSERT(register_spills_[allocation_index] == NULL); + register_spills_[allocation_index] = spill_operand; +} + + +void LOsrEntry::MarkSpilledDoubleRegister(int allocation_index, + LOperand* spill_operand) { + ASSERT(spill_operand->IsDoubleStackSlot()); + ASSERT(double_register_spills_[allocation_index] == NULL); + double_register_spills_[allocation_index] = spill_operand; +} + + +void LInstruction::PrintTo(StringStream* stream) const { + stream->Add("%s ", this->Mnemonic()); + if (HasResult()) { + result()->PrintTo(stream); + stream->Add(" "); + } + PrintDataTo(stream); + + if (HasEnvironment()) { + stream->Add(" "); + environment()->PrintTo(stream); + } + + if (HasPointerMap()) { + stream->Add(" "); + pointer_map()->PrintTo(stream); + } +} + + +void LLabel::PrintDataTo(StringStream* stream) const { + LGap::PrintDataTo(stream); + LLabel* rep = replacement(); + if (rep != NULL) { + stream->Add(" Dead block replaced with B%d", rep->block_id()); + } +} + + +bool LParallelMove::IsRedundant() const { + for (int i = 0; i < move_operands_.length(); ++i) { + if (!move_operands_[i].IsRedundant()) return false; + } + return true; +} + + +void LParallelMove::PrintDataTo(StringStream* stream) const { + for (int i = move_operands_.length() - 1; i >= 0; --i) { + if (!move_operands_[i].IsEliminated()) { + LOperand* from = move_operands_[i].from(); + LOperand* to = move_operands_[i].to(); + if (from->Equals(to)) { + to->PrintTo(stream); + } else { + to->PrintTo(stream); + stream->Add(" = "); + from->PrintTo(stream); + } + stream->Add("; "); + } + } +} + + +bool LGap::IsRedundant() const { + for (int i = 0; i < 4; i++) { + if (parallel_moves_[i] != NULL && !parallel_moves_[i]->IsRedundant()) { + return false; + } + } + + return true; +} + + +void LGap::PrintDataTo(StringStream* stream) const { + for (int i = 0; i < 4; i++) { + stream->Add("("); + if (parallel_moves_[i] != NULL) { + parallel_moves_[i]->PrintDataTo(stream); + } + stream->Add(") "); + } +} + + +const char* LArithmeticD::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-d"; + case Token::SUB: return "sub-d"; + case Token::MUL: return "mul-d"; + case Token::DIV: return "div-d"; + case Token::MOD: return "mod-d"; + default: + UNREACHABLE(); + return NULL; + } +} + + +const char* LArithmeticT::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-t"; + case Token::SUB: return "sub-t"; + case Token::MUL: return "mul-t"; + case Token::MOD: return "mod-t"; + case Token::DIV: return "div-t"; + default: + UNREACHABLE(); + return NULL; + } +} + + + +void LBinaryOperation::PrintDataTo(StringStream* stream) const { + stream->Add("= "); + left()->PrintTo(stream); + stream->Add(" "); + right()->PrintTo(stream); +} + + +void LGoto::PrintDataTo(StringStream* stream) const { + stream->Add("B%d", block_id()); +} + + +void LBranch::PrintDataTo(StringStream* stream) const { + stream->Add("B%d | B%d on ", true_block_id(), false_block_id()); + input()->PrintTo(stream); +} + + +void LCmpIDAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if "); + left()->PrintTo(stream); + stream->Add(" %s ", Token::String(op())); + right()->PrintTo(stream); + stream->Add(" then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsNullAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if "); + input()->PrintTo(stream); + stream->Add(is_strict() ? " === null" : " == null"); + stream->Add(" then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsObjectAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if is_object("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsSmiAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if is_smi("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if has_instance_type("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LHasCachedArrayIndexAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if has_cached_array_index("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LClassOfTestAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if class_of_test("); + input()->PrintTo(stream); + stream->Add(", \"%o\") then B%d else B%d", + *hydrogen()->class_name(), + true_block_id(), + false_block_id()); +} + + +void LTypeofIs::PrintDataTo(StringStream* stream) const { + input()->PrintTo(stream); + stream->Add(" == \"%s\"", *hydrogen()->type_literal()->ToCString()); +} + + +void LTypeofIsAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if typeof "); + input()->PrintTo(stream); + stream->Add(" == \"%s\" then B%d else B%d", + *hydrogen()->type_literal()->ToCString(), + true_block_id(), false_block_id()); +} + + +void LCallConstantFunction::PrintDataTo(StringStream* stream) const { + stream->Add("#%d / ", arity()); +} + + +void LUnaryMathOperation::PrintDataTo(StringStream* stream) const { + stream->Add("/%s ", hydrogen()->OpName()); + input()->PrintTo(stream); +} + + +void LCallKeyed::PrintDataTo(StringStream* stream) const { + stream->Add("[r2] #%d / ", arity()); +} + + +void LCallNamed::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s #%d / ", *name_string, arity()); +} + + +void LCallGlobal::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s #%d / ", *name_string, arity()); +} + + +void LCallKnownGlobal::PrintDataTo(StringStream* stream) const { + stream->Add("#%d / ", arity()); +} + + +void LCallNew::PrintDataTo(StringStream* stream) const { + LUnaryOperation::PrintDataTo(stream); + stream->Add(" #%d / ", arity()); +} + + +void LClassOfTest::PrintDataTo(StringStream* stream) const { + stream->Add("= class_of_test("); + input()->PrintTo(stream); + stream->Add(", \"%o\")", *hydrogen()->class_name()); +} + + +void LUnaryOperation::PrintDataTo(StringStream* stream) const { + stream->Add("= "); + input()->PrintTo(stream); +} + + +void LAccessArgumentsAt::PrintDataTo(StringStream* stream) const { + arguments()->PrintTo(stream); + + stream->Add(" length "); + length()->PrintTo(stream); + + stream->Add(" index "); + index()->PrintTo(stream); +} + + +LChunk::LChunk(HGraph* graph) + : spill_slot_count_(0), + graph_(graph), + instructions_(32), + pointer_maps_(8), + inlined_closures_(1) { +} + + +void LChunk::Verify() const { + // TODO(twuerthinger): Implement verification for chunk. +} + + +int LChunk::GetNextSpillIndex(bool is_double) { + // Skip a slot if for a double-width slot. + if (is_double) spill_slot_count_++; + return spill_slot_count_++; +} + + +LOperand* LChunk::GetNextSpillSlot(bool is_double) { + int index = GetNextSpillIndex(is_double); + if (is_double) { + return LDoubleStackSlot::Create(index); + } else { + return LStackSlot::Create(index); + } +} + + +void LChunk::MarkEmptyBlocks() { + HPhase phase("Mark empty blocks", this); + for (int i = 0; i < graph()->blocks()->length(); ++i) { + HBasicBlock* block = graph()->blocks()->at(i); + int first = block->first_instruction_index(); + int last = block->last_instruction_index(); + LInstruction* first_instr = instructions()->at(first); + LInstruction* last_instr = instructions()->at(last); + + LLabel* label = LLabel::cast(first_instr); + if (last_instr->IsGoto()) { + LGoto* goto_instr = LGoto::cast(last_instr); + if (!goto_instr->include_stack_check() && + label->IsRedundant() && + !label->is_loop_header()) { + bool can_eliminate = true; + for (int i = first + 1; i < last && can_eliminate; ++i) { + LInstruction* cur = instructions()->at(i); + if (cur->IsGap()) { + LGap* gap = LGap::cast(cur); + if (!gap->IsRedundant()) { + can_eliminate = false; + } + } else { + can_eliminate = false; + } + } + + if (can_eliminate) { + label->set_replacement(GetLabel(goto_instr->block_id())); + } + } + } + } +} + + +void LStoreNamed::PrintDataTo(StringStream* stream) const { + object()->PrintTo(stream); + stream->Add("."); + stream->Add(*String::cast(*name())->ToCString()); + stream->Add(" <- "); + value()->PrintTo(stream); +} + + +void LStoreKeyed::PrintDataTo(StringStream* stream) const { + object()->PrintTo(stream); + stream->Add("["); + key()->PrintTo(stream); + stream->Add("] <- "); + value()->PrintTo(stream); +} + + +int LChunk::AddInstruction(LInstruction* instr, HBasicBlock* block) { + LGap* gap = new LGap(block); + int index = -1; + if (instr->IsControl()) { + instructions_.Add(gap); + index = instructions_.length(); + instructions_.Add(instr); + } else { + index = instructions_.length(); + instructions_.Add(instr); + instructions_.Add(gap); + } + if (instr->HasPointerMap()) { + pointer_maps_.Add(instr->pointer_map()); + instr->pointer_map()->set_lithium_position(index); + } + return index; +} + + +LConstantOperand* LChunk::DefineConstantOperand(HConstant* constant) { + return LConstantOperand::Create(constant->id()); +} + + +int LChunk::GetParameterStackSlot(int index) const { + // The receiver is at index 0, the first parameter at index 1, so we + // shift all parameter indexes down by the number of parameters, and + // make sure they end up negative so they are distinguishable from + // spill slots. + int result = index - graph()->info()->scope()->num_parameters() - 1; + ASSERT(result < 0); + return result; +} + +// A parameter relative to ebp in the arguments stub. +int LChunk::ParameterAt(int index) { + ASSERT(-1 <= index); // -1 is the receiver. + return (1 + graph()->info()->scope()->num_parameters() - index) * + kPointerSize; +} + + +LGap* LChunk::GetGapAt(int index) const { + return LGap::cast(instructions_[index]); +} + + +bool LChunk::IsGapAt(int index) const { + return instructions_[index]->IsGap(); +} + + +int LChunk::NearestGapPos(int index) const { + while (!IsGapAt(index)) index--; + return index; +} + + +void LChunk::AddGapMove(int index, LOperand* from, LOperand* to) { + GetGapAt(index)->GetOrCreateParallelMove(LGap::START)->AddMove(from, to); +} + + +class LGapNode: public ZoneObject { + public: + explicit LGapNode(LOperand* operand) + : operand_(operand), resolved_(false), visited_id_(-1) { } + + LOperand* operand() const { return operand_; } + bool IsResolved() const { return !IsAssigned() || resolved_; } + void MarkResolved() { + ASSERT(!IsResolved()); + resolved_ = true; + } + int visited_id() const { return visited_id_; } + void set_visited_id(int id) { + ASSERT(id > visited_id_); + visited_id_ = id; + } + + bool IsAssigned() const { return assigned_from_.is_set(); } + LGapNode* assigned_from() const { return assigned_from_.get(); } + void set_assigned_from(LGapNode* n) { assigned_from_.set(n); } + + private: + LOperand* operand_; + SetOncePointer<LGapNode> assigned_from_; + bool resolved_; + int visited_id_; +}; + + +LGapResolver::LGapResolver(const ZoneList<LMoveOperands>* moves, + LOperand* marker_operand) + : nodes_(4), + identified_cycles_(4), + result_(4), + marker_operand_(marker_operand), + next_visited_id_(0) { + for (int i = 0; i < moves->length(); ++i) { + LMoveOperands move = moves->at(i); + if (!move.IsRedundant()) RegisterMove(move); + } +} + + +const ZoneList<LMoveOperands>* LGapResolver::ResolveInReverseOrder() { + for (int i = 0; i < identified_cycles_.length(); ++i) { + ResolveCycle(identified_cycles_[i]); + } + + int unresolved_nodes; + do { + unresolved_nodes = 0; + for (int j = 0; j < nodes_.length(); j++) { + LGapNode* node = nodes_[j]; + if (!node->IsResolved() && node->assigned_from()->IsResolved()) { + AddResultMove(node->assigned_from(), node); + node->MarkResolved(); + } + if (!node->IsResolved()) ++unresolved_nodes; + } + } while (unresolved_nodes > 0); + return &result_; +} + + +void LGapResolver::AddResultMove(LGapNode* from, LGapNode* to) { + AddResultMove(from->operand(), to->operand()); +} + + +void LGapResolver::AddResultMove(LOperand* from, LOperand* to) { + result_.Add(LMoveOperands(from, to)); +} + + +void LGapResolver::ResolveCycle(LGapNode* start) { + ZoneList<LOperand*> circle_operands(8); + circle_operands.Add(marker_operand_); + LGapNode* cur = start; + do { + cur->MarkResolved(); + circle_operands.Add(cur->operand()); + cur = cur->assigned_from(); + } while (cur != start); + circle_operands.Add(marker_operand_); + + for (int i = circle_operands.length() - 1; i > 0; --i) { + LOperand* from = circle_operands[i]; + LOperand* to = circle_operands[i - 1]; + AddResultMove(from, to); + } +} + + +bool LGapResolver::CanReach(LGapNode* a, LGapNode* b, int visited_id) { + ASSERT(a != b); + LGapNode* cur = a; + while (cur != b && cur->visited_id() != visited_id && cur->IsAssigned()) { + cur->set_visited_id(visited_id); + cur = cur->assigned_from(); + } + + return cur == b; +} + + +bool LGapResolver::CanReach(LGapNode* a, LGapNode* b) { + ASSERT(a != b); + return CanReach(a, b, next_visited_id_++); +} + + +void LGapResolver::RegisterMove(LMoveOperands move) { + if (move.from()->IsConstantOperand()) { + // Constant moves should be last in the machine code. Therefore add them + // first to the result set. + AddResultMove(move.from(), move.to()); + } else { + LGapNode* from = LookupNode(move.from()); + LGapNode* to = LookupNode(move.to()); + if (to->IsAssigned() && to->assigned_from() == from) { + move.Eliminate(); + return; + } + ASSERT(!to->IsAssigned()); + if (CanReach(from, to)) { + // This introduces a circle. Save. + identified_cycles_.Add(from); + } + to->set_assigned_from(from); + } +} + + +LGapNode* LGapResolver::LookupNode(LOperand* operand) { + for (int i = 0; i < nodes_.length(); ++i) { + if (nodes_[i]->operand()->Equals(operand)) return nodes_[i]; + } + + // No node found => create a new one. + LGapNode* result = new LGapNode(operand); + nodes_.Add(result); + return result; +} + + +Handle<Object> LChunk::LookupLiteral(LConstantOperand* operand) const { + return HConstant::cast(graph_->LookupValue(operand->index()))->handle(); +} + + +Representation LChunk::LookupLiteralRepresentation( + LConstantOperand* operand) const { + return graph_->LookupValue(operand->index())->representation(); +} + + +LChunk* LChunkBuilder::Build() { + ASSERT(is_unused()); + chunk_ = new LChunk(graph()); + HPhase phase("Building chunk", chunk_); + status_ = BUILDING; + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int i = 0; i < blocks->length(); i++) { + HBasicBlock* next = NULL; + if (i < blocks->length() - 1) next = blocks->at(i + 1); + DoBasicBlock(blocks->at(i), next); + if (is_aborted()) return NULL; + } + status_ = DONE; + return chunk_; +} + + +void LChunkBuilder::Abort(const char* format, ...) { + if (FLAG_trace_bailout) { + SmartPointer<char> debug_name = graph()->debug_name()->ToCString(); + PrintF("Aborting LChunk building in @\"%s\": ", *debug_name); + va_list arguments; + va_start(arguments, format); + OS::VPrint(format, arguments); + va_end(arguments); + PrintF("\n"); + } + status_ = ABORTED; +} + + +LRegister* LChunkBuilder::ToOperand(Register reg) { + return LRegister::Create(Register::ToAllocationIndex(reg)); +} + + +LUnallocated* LChunkBuilder::ToUnallocated(Register reg) { + return new LUnallocated(LUnallocated::FIXED_REGISTER, + Register::ToAllocationIndex(reg)); +} + + +LUnallocated* LChunkBuilder::ToUnallocated(DoubleRegister reg) { + return new LUnallocated(LUnallocated::FIXED_DOUBLE_REGISTER, + DoubleRegister::ToAllocationIndex(reg)); +} + + +LOperand* LChunkBuilder::UseFixed(HValue* value, Register fixed_register) { + return Use(value, ToUnallocated(fixed_register)); +} + + +LOperand* LChunkBuilder::UseFixedDouble(HValue* value, DoubleRegister reg) { + return Use(value, ToUnallocated(reg)); +} + + +LOperand* LChunkBuilder::UseRegister(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LOperand* LChunkBuilder::UseRegisterAtStart(HValue* value) { + return Use(value, + new LUnallocated(LUnallocated::MUST_HAVE_REGISTER, + LUnallocated::USED_AT_START)); +} + + +LOperand* LChunkBuilder::UseTempRegister(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::WRITABLE_REGISTER)); +} + + +LOperand* LChunkBuilder::Use(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::NONE)); +} + + +LOperand* LChunkBuilder::UseAtStart(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::NONE, + LUnallocated::USED_AT_START)); +} + + +LOperand* LChunkBuilder::UseOrConstant(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : Use(value); +} + + +LOperand* LChunkBuilder::UseOrConstantAtStart(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseAtStart(value); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstant(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseRegister(value); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstantAtStart(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseRegisterAtStart(value); +} + + +LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) { + if (value->EmitAtUses()) { + HInstruction* instr = HInstruction::cast(value); + VisitInstruction(instr); + } + allocator_->RecordUse(value, operand); + return operand; +} + + +LInstruction* LChunkBuilder::Define(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::NONE)); +} + + +LInstruction* LChunkBuilder::DefineAsRegister(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LInstruction* LChunkBuilder::DefineAsSpilled(LInstruction* instr, int index) { + return Define(instr, new LUnallocated(LUnallocated::FIXED_SLOT, index)); +} + + +LInstruction* LChunkBuilder::DefineSameAsAny(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::SAME_AS_ANY_INPUT)); +} + + +LInstruction* LChunkBuilder::DefineSameAsFirst(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::SAME_AS_FIRST_INPUT)); +} + + +LInstruction* LChunkBuilder::DefineFixed(LInstruction* instr, Register reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::DefineFixedDouble(LInstruction* instr, + DoubleRegister reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::AssignEnvironment(LInstruction* instr) { + HEnvironment* hydrogen_env = current_block_->last_environment(); + instr->set_environment(CreateEnvironment(hydrogen_env)); + return instr; +} + + +LInstruction* LChunkBuilder::SetInstructionPendingDeoptimizationEnvironment( + LInstruction* instr, int ast_id) { + ASSERT(instructions_pending_deoptimization_environment_ == NULL); + ASSERT(pending_deoptimization_ast_id_ == AstNode::kNoNumber); + instructions_pending_deoptimization_environment_ = instr; + pending_deoptimization_ast_id_ = ast_id; + return instr; +} + + +void LChunkBuilder::ClearInstructionPendingDeoptimizationEnvironment() { + instructions_pending_deoptimization_environment_ = NULL; + pending_deoptimization_ast_id_ = AstNode::kNoNumber; +} + + +LInstruction* LChunkBuilder::MarkAsCall(LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize) { + allocator_->MarkAsCall(); + instr = AssignPointerMap(instr); + + if (hinstr->HasSideEffects()) { + ASSERT(hinstr->next()->IsSimulate()); + HSimulate* sim = HSimulate::cast(hinstr->next()); + instr = SetInstructionPendingDeoptimizationEnvironment( + instr, sim->ast_id()); + } + + // If instruction does not have side-effects lazy deoptimization + // after the call will try to deoptimize to the point before the call. + // Thus we still need to attach environment to this call even if + // call sequence can not deoptimize eagerly. + bool needs_environment = + (can_deoptimize == CAN_DEOPTIMIZE_EAGERLY) || !hinstr->HasSideEffects(); + if (needs_environment && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + } + + return instr; +} + + +LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) { + ASSERT(!instr->HasPointerMap()); + instr->set_pointer_map(new LPointerMap(position_)); + return instr; +} + + +LInstruction* LChunkBuilder::Define(LInstruction* instr, LUnallocated* result) { + allocator_->RecordDefinition(current_instruction_, result); + instr->set_result(result); + return instr; +} + + +LOperand* LChunkBuilder::Temp() { + LUnallocated* operand = new LUnallocated(LUnallocated::NONE); + allocator_->RecordTemporary(operand); + return operand; +} + + +LUnallocated* LChunkBuilder::TempRegister() { + LUnallocated* operand = new LUnallocated(LUnallocated::MUST_HAVE_REGISTER); + allocator_->RecordTemporary(operand); + return operand; +} + + +LOperand* LChunkBuilder::FixedTemp(Register reg) { + LUnallocated* operand = ToUnallocated(reg); + allocator_->RecordTemporary(operand); + return operand; +} + + +LOperand* LChunkBuilder::FixedTemp(DoubleRegister reg) { + LUnallocated* operand = ToUnallocated(reg); + allocator_->RecordTemporary(operand); + return operand; +} + + +LInstruction* LChunkBuilder::DoBlockEntry(HBlockEntry* instr) { + return new LLabel(instr->block()); +} + + +LInstruction* LChunkBuilder::DoDeoptimize(HDeoptimize* instr) { + return AssignEnvironment(new LDeoptimize); +} + + +LInstruction* LChunkBuilder::DoBit(Token::Value op, + HBitwiseBinaryOperation* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + return DefineSameAsFirst(new LBitI(op, left, right)); +} + + +LInstruction* LChunkBuilder::DoShift(Token::Value op, + HBitwiseBinaryOperation* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->OperandAt(0)->representation().IsInteger32()); + ASSERT(instr->OperandAt(1)->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->OperandAt(0)); + + HValue* right_value = instr->OperandAt(1); + LOperand* right = NULL; + int constant_value = 0; + if (right_value->IsConstant()) { + HConstant* constant = HConstant::cast(right_value); + right = chunk_->DefineConstantOperand(constant); + constant_value = constant->Integer32Value() & 0x1f; + } else { + right = UseRegister(right_value); + } + + // Shift operations can only deoptimize if we do a logical shift + // by 0 and the result cannot be truncated to int32. + bool can_deopt = (op == Token::SHR && constant_value == 0); + if (can_deopt) { + bool can_truncate = true; + for (int i = 0; i < instr->uses()->length(); i++) { + if (!instr->uses()->at(i)->CheckFlag(HValue::kTruncatingToInt32)) { + can_truncate = false; + break; + } + } + can_deopt = !can_truncate; + } + + LInstruction* result = + DefineSameAsFirst(new LShiftI(op, left, right, can_deopt)); + if (can_deopt) AssignEnvironment(result); + return result; +} + + +LInstruction* LChunkBuilder::DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr) { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->left()->representation().IsDouble()); + ASSERT(instr->right()->representation().IsDouble()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + LArithmeticD* result = new LArithmeticD(op, left, right); + return DefineSameAsFirst(result); +} + + +LInstruction* LChunkBuilder::DoArithmeticT(Token::Value op, + HArithmeticBinaryOperation* instr) { + ASSERT(op == Token::ADD || + op == Token::DIV || + op == Token::MOD || + op == Token::MUL || + op == Token::SUB); + HValue* left = instr->left(); + HValue* right = instr->right(); + ASSERT(left->representation().IsTagged()); + ASSERT(right->representation().IsTagged()); + LOperand* left_operand = UseFixed(left, r1); + LOperand* right_operand = UseFixed(right, r0); + LInstruction* result = new LArithmeticT(op, left_operand, right_operand); + return MarkAsCall(DefineFixed(result, r0), instr); +} + +void LChunkBuilder::DoBasicBlock(HBasicBlock* block, HBasicBlock* next_block) { + ASSERT(is_building()); + current_block_ = block; + next_block_ = next_block; + if (block->IsStartBlock()) { + block->UpdateEnvironment(graph_->start_environment()); + argument_count_ = 0; + } else if (block->predecessors()->length() == 1) { + // We have a single predecessor => copy environment and outgoing + // argument count from the predecessor. + ASSERT(block->phis()->length() == 0); + HBasicBlock* pred = block->predecessors()->at(0); + HEnvironment* last_environment = pred->last_environment(); + ASSERT(last_environment != NULL); + // Only copy the environment, if it is later used again. + if (pred->end()->SecondSuccessor() == NULL) { + ASSERT(pred->end()->FirstSuccessor() == block); + } else { + if (pred->end()->FirstSuccessor()->block_id() > block->block_id() || + pred->end()->SecondSuccessor()->block_id() > block->block_id()) { + last_environment = last_environment->Copy(); + } + } + block->UpdateEnvironment(last_environment); + ASSERT(pred->argument_count() >= 0); + argument_count_ = pred->argument_count(); + } else { + // We are at a state join => process phis. + HBasicBlock* pred = block->predecessors()->at(0); + // No need to copy the environment, it cannot be used later. + HEnvironment* last_environment = pred->last_environment(); + for (int i = 0; i < block->phis()->length(); ++i) { + HPhi* phi = block->phis()->at(i); + last_environment->SetValueAt(phi->merged_index(), phi); + } + for (int i = 0; i < block->deleted_phis()->length(); ++i) { + last_environment->SetValueAt(block->deleted_phis()->at(i), + graph_->GetConstantUndefined()); + } + block->UpdateEnvironment(last_environment); + // Pick up the outgoing argument count of one of the predecessors. + argument_count_ = pred->argument_count(); + } + HInstruction* current = block->first(); + int start = chunk_->instructions()->length(); + while (current != NULL && !is_aborted()) { + if (FLAG_trace_environment) { + PrintF("Process instruction %d\n", current->id()); + } + // Code for constants in registers is generated lazily. + if (!current->EmitAtUses()) { + VisitInstruction(current); + } + current = current->next(); + } + int end = chunk_->instructions()->length() - 1; + if (end >= start) { + block->set_first_instruction_index(start); + block->set_last_instruction_index(end); + } + block->set_argument_count(argument_count_); + next_block_ = NULL; + current_block_ = NULL; +} + + +void LChunkBuilder::VisitInstruction(HInstruction* current) { + HInstruction* old_current = current_instruction_; + current_instruction_ = current; + allocator_->BeginInstruction(); + if (current->has_position()) position_ = current->position(); + LInstruction* instr = current->CompileToLithium(this); + + if (instr != NULL) { + if (FLAG_stress_pointer_maps && !instr->HasPointerMap()) { + instr = AssignPointerMap(instr); + } + if (FLAG_stress_environments && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + } + if (current->IsBranch()) { + instr->set_hydrogen_value(HBranch::cast(current)->value()); + } else { + instr->set_hydrogen_value(current); + } + + int index = chunk_->AddInstruction(instr, current_block_); + allocator_->SummarizeInstruction(index); + } else { + // This instruction should be omitted. + allocator_->OmitInstruction(); + } + current_instruction_ = old_current; +} + + +void LEnvironment::WriteTranslation(LCodeGen* cgen, + Translation* translation) const { + if (this == NULL) return; + + // The translation includes one command per value in the environment. + int translation_size = values()->length(); + // The output frame height does not include the parameters. + int height = translation_size - parameter_count(); + + outer()->WriteTranslation(cgen, translation); + int closure_id = cgen->DefineDeoptimizationLiteral(closure()); + translation->BeginFrame(ast_id(), closure_id, height); + for (int i = 0; i < translation_size; ++i) { + LOperand* value = values()->at(i); + // spilled_registers_ and spilled_double_registers_ are either + // both NULL or both set. + if (spilled_registers_ != NULL && value != NULL) { + if (value->IsRegister() && + spilled_registers_[value->index()] != NULL) { + translation->MarkDuplicate(); + cgen->AddToTranslation(translation, + spilled_registers_[value->index()], + HasTaggedValueAt(i)); + } else if (value->IsDoubleRegister() && + spilled_double_registers_[value->index()] != NULL) { + translation->MarkDuplicate(); + cgen->AddToTranslation(translation, + spilled_double_registers_[value->index()], + false); + } + } + + cgen->AddToTranslation(translation, value, HasTaggedValueAt(i)); + } +} + + +void LEnvironment::PrintTo(StringStream* stream) const { + stream->Add("[id=%d|", ast_id()); + stream->Add("[parameters=%d|", parameter_count()); + stream->Add("[arguments_stack_height=%d|", arguments_stack_height()); + for (int i = 0; i < values_.length(); ++i) { + if (i != 0) stream->Add(";"); + if (values_[i] == NULL) { + stream->Add("[hole]"); + } else { + values_[i]->PrintTo(stream); + } + } + stream->Add("]"); +} + + +LEnvironment* LChunkBuilder::CreateEnvironment(HEnvironment* hydrogen_env) { + if (hydrogen_env == NULL) return NULL; + + LEnvironment* outer = CreateEnvironment(hydrogen_env->outer()); + int ast_id = hydrogen_env->ast_id(); + ASSERT(ast_id != AstNode::kNoNumber); + int value_count = hydrogen_env->values()->length(); + LEnvironment* result = new LEnvironment(hydrogen_env->closure(), + ast_id, + hydrogen_env->parameter_count(), + argument_count_, + value_count, + outer); + int argument_index = 0; + for (int i = 0; i < value_count; ++i) { + HValue* value = hydrogen_env->values()->at(i); + LOperand* op = NULL; + if (value->IsArgumentsObject()) { + op = NULL; + } else if (value->IsPushArgument()) { + op = new LArgument(argument_index++); + } else { + op = UseOrConstant(value); + if (op->IsUnallocated()) { + LUnallocated* unalloc = LUnallocated::cast(op); + unalloc->set_policy(LUnallocated::ANY); + } + } + result->AddValue(op, value->representation()); + } + + return result; +} + + +LInstruction* LChunkBuilder::DoGoto(HGoto* instr) { + LInstruction* result = new LGoto(instr->FirstSuccessor()->block_id(), + instr->include_stack_check()); + if (instr->include_stack_check()) result = AssignPointerMap(result); + return result; +} + + +LInstruction* LChunkBuilder::DoBranch(HBranch* instr) { + HValue* v = instr->value(); + HBasicBlock* first = instr->FirstSuccessor(); + HBasicBlock* second = instr->SecondSuccessor(); + ASSERT(first != NULL && second != NULL); + int first_id = first->block_id(); + int second_id = second->block_id(); + + if (v->EmitAtUses()) { + if (v->IsClassOfTest()) { + HClassOfTest* compare = HClassOfTest::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LClassOfTestAndBranch(UseTempRegister(compare->value()), + TempRegister(), + TempRegister(), + first_id, + second_id); + } else if (v->IsCompare()) { + HCompare* compare = HCompare::cast(v); + Token::Value op = compare->token(); + HValue* left = compare->left(); + HValue* right = compare->right(); + if (left->representation().IsInteger32()) { + ASSERT(right->representation().IsInteger32()); + return new LCmpIDAndBranch(op, + UseRegisterAtStart(left), + UseOrConstantAtStart(right), + first_id, + second_id, + false); + } else if (left->representation().IsDouble()) { + ASSERT(right->representation().IsDouble()); + return new LCmpIDAndBranch(op, + UseRegisterAtStart(left), + UseRegisterAtStart(right), + first_id, + second_id, + true); + } else { + ASSERT(left->representation().IsTagged()); + ASSERT(right->representation().IsTagged()); + bool reversed = op == Token::GT || op == Token::LTE; + LOperand* left_operand = UseFixed(left, reversed ? r0 : r1); + LOperand* right_operand = UseFixed(right, reversed ? r1 : r0); + LInstruction* result = new LCmpTAndBranch(left_operand, + right_operand, + first_id, + second_id); + return MarkAsCall(result, instr); + } + } else if (v->IsIsSmi()) { + HIsSmi* compare = HIsSmi::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LIsSmiAndBranch(Use(compare->value()), + first_id, + second_id); + } else if (v->IsHasInstanceType()) { + HHasInstanceType* compare = HHasInstanceType::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LHasInstanceTypeAndBranch(UseRegisterAtStart(compare->value()), + TempRegister(), + first_id, + second_id); + } else if (v->IsHasCachedArrayIndex()) { + HHasCachedArrayIndex* compare = HHasCachedArrayIndex::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LHasCachedArrayIndexAndBranch( + UseRegisterAtStart(compare->value()), first_id, second_id); + } else if (v->IsIsNull()) { + HIsNull* compare = HIsNull::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + // We only need a temp register for non-strict compare. + LOperand* temp = compare->is_strict() ? NULL : TempRegister(); + return new LIsNullAndBranch(UseRegisterAtStart(compare->value()), + compare->is_strict(), + temp, + first_id, + second_id); + } else if (v->IsIsObject()) { + HIsObject* compare = HIsObject::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + return new LIsObjectAndBranch(UseRegisterAtStart(compare->value()), + temp1, + temp2, + first_id, + second_id); + } else if (v->IsCompareJSObjectEq()) { + HCompareJSObjectEq* compare = HCompareJSObjectEq::cast(v); + return new LCmpJSObjectEqAndBranch(UseRegisterAtStart(compare->left()), + UseRegisterAtStart(compare->right()), + first_id, + second_id); + } else if (v->IsInstanceOf()) { + HInstanceOf* instance_of = HInstanceOf::cast(v); + LInstruction* result = + new LInstanceOfAndBranch(Use(instance_of->left()), + Use(instance_of->right()), + first_id, + second_id); + return MarkAsCall(result, instr); + } else if (v->IsTypeofIs()) { + HTypeofIs* typeof_is = HTypeofIs::cast(v); + return new LTypeofIsAndBranch(UseTempRegister(typeof_is->value()), + first_id, + second_id); + } else { + if (v->IsConstant()) { + if (HConstant::cast(v)->handle()->IsTrue()) { + return new LGoto(first_id); + } else if (HConstant::cast(v)->handle()->IsFalse()) { + return new LGoto(second_id); + } + } + Abort("Undefined compare before branch"); + return NULL; + } + } + return new LBranch(UseRegisterAtStart(v), first_id, second_id); +} + + +LInstruction* LChunkBuilder::DoCompareMapAndBranch( + HCompareMapAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + HBasicBlock* first = instr->FirstSuccessor(); + HBasicBlock* second = instr->SecondSuccessor(); + return new LCmpMapAndBranch(value, + instr->map(), + first->block_id(), + second->block_id()); +} + + +LInstruction* LChunkBuilder::DoArgumentsLength(HArgumentsLength* length) { + return DefineAsRegister(new LArgumentsLength(Use(length->value()))); +} + + +LInstruction* LChunkBuilder::DoArgumentsElements(HArgumentsElements* elems) { + return DefineAsRegister(new LArgumentsElements); +} + + +LInstruction* LChunkBuilder::DoInstanceOf(HInstanceOf* instr) { + LInstruction* result = + new LInstanceOf(UseFixed(instr->left(), r1), + UseFixed(instr->right(), r0)); + return MarkAsCall(DefineFixed(result, r0), instr); +} + + +LInstruction* LChunkBuilder::DoApplyArguments(HApplyArguments* instr) { + LOperand* function = UseFixed(instr->function(), r1); + LOperand* receiver = UseFixed(instr->receiver(), r0); + LOperand* length = UseRegisterAtStart(instr->length()); + LOperand* elements = UseRegisterAtStart(instr->elements()); + LInstruction* result = new LApplyArguments(function, + receiver, + length, + elements); + return MarkAsCall(DefineFixed(result, r0), instr, CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoPushArgument(HPushArgument* instr) { + ++argument_count_; + LOperand* argument = Use(instr->argument()); + return new LPushArgument(argument); +} + + +LInstruction* LChunkBuilder::DoGlobalObject(HGlobalObject* instr) { + return DefineAsRegister(new LGlobalObject); +} + + +LInstruction* LChunkBuilder::DoGlobalReceiver(HGlobalReceiver* instr) { + return DefineAsRegister(new LGlobalReceiver); +} + + +LInstruction* LChunkBuilder::DoCallConstantFunction( + HCallConstantFunction* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallConstantFunction, r0), instr); +} + + +LInstruction* LChunkBuilder::DoUnaryMathOperation(HUnaryMathOperation* instr) { + BuiltinFunctionId op = instr->op(); + LOperand* input = UseRegisterAtStart(instr->value()); + LInstruction* result = new LUnaryMathOperation(input); + switch (op) { + case kMathAbs: + return AssignEnvironment(AssignPointerMap(DefineSameAsFirst(result))); + case kMathFloor: + return AssignEnvironment(DefineAsRegister(result)); + case kMathSqrt: + return DefineSameAsFirst(result); + case kMathPowHalf: + Abort("MathPowHalf LUnaryMathOperation not implemented"); + return NULL; + case kMathLog: + Abort("MathLog LUnaryMathOperation not implemented"); + return NULL; + case kMathCos: + Abort("MathCos LUnaryMathOperation not implemented"); + return NULL; + case kMathSin: + Abort("MathSin LUnaryMathOperation not implemented"); + return NULL; + default: + UNREACHABLE(); + return NULL; + } +} + + +LInstruction* LChunkBuilder::DoCallKeyed(HCallKeyed* instr) { + ASSERT(instr->key()->representation().IsTagged()); + argument_count_ -= instr->argument_count(); + UseFixed(instr->key(), r2); + return MarkAsCall(DefineFixed(new LCallKeyed, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallNamed(HCallNamed* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallNamed, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallGlobal(HCallGlobal* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallGlobal, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallKnownGlobal(HCallKnownGlobal* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallKnownGlobal, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallNew(HCallNew* instr) { + LOperand* constructor = UseFixed(instr->constructor(), r1); + argument_count_ -= instr->argument_count(); + LInstruction* result = new LCallNew(constructor); + return MarkAsCall(DefineFixed(result, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallFunction(HCallFunction* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallFunction, r0), instr); +} + + +LInstruction* LChunkBuilder::DoCallRuntime(HCallRuntime* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallRuntime, r0), instr); +} + + +LInstruction* LChunkBuilder::DoShr(HShr* instr) { + return DoShift(Token::SHR, instr); +} + + +LInstruction* LChunkBuilder::DoSar(HSar* instr) { + return DoShift(Token::SAR, instr); +} + + +LInstruction* LChunkBuilder::DoShl(HShl* instr) { + return DoShift(Token::SHL, instr); +} + + +LInstruction* LChunkBuilder::DoBitAnd(HBitAnd* instr) { + return DoBit(Token::BIT_AND, instr); +} + + +LInstruction* LChunkBuilder::DoBitNot(HBitNot* instr) { + ASSERT(instr->value()->representation().IsInteger32()); + ASSERT(instr->representation().IsInteger32()); + return DefineSameAsFirst(new LBitNotI(UseRegisterAtStart(instr->value()))); +} + + +LInstruction* LChunkBuilder::DoBitOr(HBitOr* instr) { + return DoBit(Token::BIT_OR, instr); +} + + +LInstruction* LChunkBuilder::DoBitXor(HBitXor* instr) { + return DoBit(Token::BIT_XOR, instr); +} + + +LInstruction* LChunkBuilder::DoDiv(HDiv* instr) { + if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::DIV, instr); + } else if (instr->representation().IsInteger32()) { + // The temporary operand is necessary to ensure that right is not allocated + // into edx. + FixedTemp(r1); + LOperand* value = UseFixed(instr->left(), r0); + LOperand* divisor = UseRegister(instr->right()); + return AssignEnvironment(DefineFixed(new LDivI(value, divisor), r0)); + } else { + return DoArithmeticT(Token::DIV, instr); + } +} + + +LInstruction* LChunkBuilder::DoMod(HMod* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + // The temporary operand is necessary to ensure that right is not allocated + // into edx. + FixedTemp(r1); + LOperand* value = UseFixed(instr->left(), r0); + LOperand* divisor = UseRegister(instr->right()); + LInstruction* result = DefineFixed(new LModI(value, divisor), r1); + if (instr->CheckFlag(HValue::kBailoutOnMinusZero) || + instr->CheckFlag(HValue::kCanBeDivByZero)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsTagged()) { + return DoArithmeticT(Token::MOD, instr); + } else { + ASSERT(instr->representation().IsDouble()); + // We call a C function for double modulo. It can't trigger a GC. + // We need to use fixed result register for the call. + // TODO(fschneider): Allow any register as input registers. + LOperand* left = UseFixedDouble(instr->left(), d1); + LOperand* right = UseFixedDouble(instr->right(), d2); + LArithmeticD* result = new LArithmeticD(Token::MOD, left, right); + return MarkAsCall(DefineFixedDouble(result, d1), instr); + } +} + + +LInstruction* LChunkBuilder::DoMul(HMul* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstant(instr->MostConstantOperand()); + LOperand* temp = NULL; + if (instr->CheckFlag(HValue::kBailoutOnMinusZero)) { + temp = TempRegister(); + } + LMulI* mul = new LMulI(left, right, temp); + return AssignEnvironment(DefineSameAsFirst(mul)); + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::MUL, instr); + } else { + return DoArithmeticT(Token::MUL, instr); + } +} + + +LInstruction* LChunkBuilder::DoSub(HSub* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + LSubI* sub = new LSubI(left, right); + LInstruction* result = DefineSameAsFirst(sub); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::SUB, instr); + } else { + return DoArithmeticT(Token::SUB, instr); + } +} + + +LInstruction* LChunkBuilder::DoAdd(HAdd* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + LAddI* add = new LAddI(left, right); + LInstruction* result = DefineSameAsFirst(add); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::ADD, instr); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::ADD, instr); + } +} + + +LInstruction* LChunkBuilder::DoPower(HPower* instr) { + Abort("LPower instruction not implemented on ARM"); + return NULL; +} + + +LInstruction* LChunkBuilder::DoCompare(HCompare* instr) { + Token::Value op = instr->token(); + if (instr->left()->representation().IsInteger32()) { + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseOrConstantAtStart(instr->right()); + return DefineAsRegister(new LCmpID(op, left, right, false)); + } else if (instr->left()->representation().IsDouble()) { + ASSERT(instr->right()->representation().IsDouble()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + return DefineAsRegister(new LCmpID(op, left, right, true)); + } else { + bool reversed = (op == Token::GT || op == Token::LTE); + LOperand* left = UseFixed(instr->left(), reversed ? r0 : r1); + LOperand* right = UseFixed(instr->right(), reversed ? r1 : r0); + LInstruction* result = new LCmpT(left, right); + return MarkAsCall(DefineFixed(result, r0), instr); + } +} + + +LInstruction* LChunkBuilder::DoCompareJSObjectEq( + HCompareJSObjectEq* instr) { + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + LInstruction* result = new LCmpJSObjectEq(left, right); + return DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoIsNull(HIsNull* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + + return DefineAsRegister(new LIsNull(value, + instr->is_strict())); +} + + +LInstruction* LChunkBuilder::DoIsObject(HIsObject* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + + return DefineAsRegister(new LIsObject(value, TempRegister())); +} + + +LInstruction* LChunkBuilder::DoIsSmi(HIsSmi* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseAtStart(instr->value()); + + return DefineAsRegister(new LIsSmi(value)); +} + + +LInstruction* LChunkBuilder::DoHasInstanceType(HHasInstanceType* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + + return DefineAsRegister(new LHasInstanceType(value)); +} + + +LInstruction* LChunkBuilder::DoHasCachedArrayIndex( + HHasCachedArrayIndex* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegister(instr->value()); + + return DefineAsRegister(new LHasCachedArrayIndex(value)); +} + + +LInstruction* LChunkBuilder::DoClassOfTest(HClassOfTest* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseTempRegister(instr->value()); + + return DefineSameAsFirst(new LClassOfTest(value, TempRegister())); +} + + +LInstruction* LChunkBuilder::DoArrayLength(HArrayLength* instr) { + LOperand* array = NULL; + LOperand* temporary = NULL; + + if (instr->value()->IsLoadElements()) { + array = UseRegisterAtStart(instr->value()); + } else { + array = UseRegister(instr->value()); + temporary = TempRegister(); + } + + LInstruction* result = new LArrayLength(array, temporary); + return AssignEnvironment(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoValueOf(HValueOf* instr) { + LOperand* object = UseRegister(instr->value()); + LInstruction* result = new LValueOf(object, TempRegister()); + return AssignEnvironment(DefineSameAsFirst(result)); +} + + +LInstruction* LChunkBuilder::DoBoundsCheck(HBoundsCheck* instr) { + return AssignEnvironment(new LBoundsCheck(UseRegisterAtStart(instr->index()), + Use(instr->length()))); +} + + +LInstruction* LChunkBuilder::DoThrow(HThrow* instr) { + LOperand* value = UseFixed(instr->value(), r0); + return MarkAsCall(new LThrow(value), instr); +} + + +LInstruction* LChunkBuilder::DoChange(HChange* instr) { + Representation from = instr->from(); + Representation to = instr->to(); + if (from.IsTagged()) { + if (to.IsDouble()) { + LOperand* value = UseRegister(instr->value()); + LInstruction* res = new LNumberUntagD(value); + return AssignEnvironment(DefineAsRegister(res)); + } else { + ASSERT(to.IsInteger32()); + LOperand* value = UseRegister(instr->value()); + bool needs_check = !instr->value()->type().IsSmi(); + LInstruction* res = NULL; + if (needs_check) { + res = DefineSameAsFirst(new LTaggedToI(value, FixedTemp(d1))); + } else { + res = DefineSameAsFirst(new LSmiUntag(value, needs_check)); + } + if (needs_check) { + res = AssignEnvironment(res); + } + return res; + } + } else if (from.IsDouble()) { + if (to.IsTagged()) { + LOperand* value = UseRegister(instr->value()); + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + + // Make sure that the temp and result_temp registers are + // different. + LUnallocated* result_temp = TempRegister(); + LInstruction* result = new LNumberTagD(value, temp1, temp2); + Define(result, result_temp); + return AssignPointerMap(result); + } else { + ASSERT(to.IsInteger32()); + LOperand* value = UseRegister(instr->value()); + LInstruction* res = new LDoubleToI(value); + return AssignEnvironment(DefineAsRegister(res)); + } + } else if (from.IsInteger32()) { + if (to.IsTagged()) { + HValue* val = instr->value(); + LOperand* value = UseRegister(val); + if (val->HasRange() && val->range()->IsInSmiRange()) { + return DefineSameAsFirst(new LSmiTag(value)); + } else { + LInstruction* result = new LNumberTagI(value); + return AssignEnvironment(AssignPointerMap(DefineSameAsFirst(result))); + } + } else { + ASSERT(to.IsDouble()); + LOperand* value = Use(instr->value()); + return DefineAsRegister(new LInteger32ToDouble(value)); + } + } + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoCheckNonSmi(HCheckNonSmi* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckSmi(value, eq)); +} + + +LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + LInstruction* result = new LCheckInstanceType(value, temp); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoCheckPrototypeMaps(HCheckPrototypeMaps* instr) { + LOperand* temp = TempRegister(); + LInstruction* result = + new LCheckPrototypeMaps(temp, + instr->holder(), + instr->receiver_map()); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoCheckSmi(HCheckSmi* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckSmi(value, ne)); +} + + +LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckFunction(value)); +} + + +LInstruction* LChunkBuilder::DoCheckMap(HCheckMap* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LInstruction* result = new LCheckMap(value); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoReturn(HReturn* instr) { + return new LReturn(UseFixed(instr->value(), r0)); +} + + +LInstruction* LChunkBuilder::DoConstant(HConstant* instr) { + Representation r = instr->representation(); + if (r.IsInteger32()) { + int32_t value = instr->Integer32Value(); + return DefineAsRegister(new LConstantI(value)); + } else if (r.IsDouble()) { + double value = instr->DoubleValue(); + return DefineAsRegister(new LConstantD(value)); + } else if (r.IsTagged()) { + return DefineAsRegister(new LConstantT(instr->handle())); + } else { + Abort("unsupported constant of type double"); + return NULL; + } +} + + +LInstruction* LChunkBuilder::DoLoadGlobal(HLoadGlobal* instr) { + LInstruction* result = new LLoadGlobal(); + return instr->check_hole_value() + ? AssignEnvironment(DefineAsRegister(result)) + : DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoStoreGlobal(HStoreGlobal* instr) { + return new LStoreGlobal(UseRegisterAtStart(instr->value())); +} + + +LInstruction* LChunkBuilder::DoLoadNamedField(HLoadNamedField* instr) { + return DefineAsRegister( + new LLoadNamedField(UseRegisterAtStart(instr->object()))); +} + + +LInstruction* LChunkBuilder::DoLoadNamedGeneric(HLoadNamedGeneric* instr) { + LOperand* object = UseFixed(instr->object(), r0); + LInstruction* result = DefineFixed(new LLoadNamedGeneric(object), r0); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoLoadElements(HLoadElements* instr) { + LOperand* input = UseRegisterAtStart(instr->value()); + return DefineSameAsFirst(new LLoadElements(input)); +} + + +LInstruction* LChunkBuilder::DoLoadKeyedFastElement( + HLoadKeyedFastElement* instr) { + Representation r = instr->representation(); + LOperand* obj = UseRegisterAtStart(instr->object()); + ASSERT(instr->key()->representation().IsInteger32()); + LOperand* key = UseRegisterAtStart(instr->key()); + LOperand* load_result = NULL; + // Double needs an extra temp, because the result is converted from heap + // number to a double register. + if (r.IsDouble()) load_result = TempRegister(); + LInstruction* result = new LLoadKeyedFastElement(obj, + key, + load_result); + if (r.IsDouble()) { + result = DefineAsRegister(result); + } else { + result = DefineSameAsFirst(result); + } + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoLoadKeyedGeneric(HLoadKeyedGeneric* instr) { + LOperand* object = UseFixed(instr->object(), r1); + LOperand* key = UseFixed(instr->key(), r0); + + LInstruction* result = + DefineFixed(new LLoadKeyedGeneric(object, key), r0); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoStoreKeyedFastElement( + HStoreKeyedFastElement* instr) { + bool needs_write_barrier = instr->NeedsWriteBarrier(); + ASSERT(instr->value()->representation().IsTagged()); + ASSERT(instr->object()->representation().IsTagged()); + ASSERT(instr->key()->representation().IsInteger32()); + + LOperand* obj = UseTempRegister(instr->object()); + LOperand* val = needs_write_barrier + ? UseTempRegister(instr->value()) + : UseRegisterAtStart(instr->value()); + LOperand* key = needs_write_barrier + ? UseTempRegister(instr->key()) + : UseRegisterOrConstantAtStart(instr->key()); + + return AssignEnvironment(new LStoreKeyedFastElement(obj, key, val)); +} + + +LInstruction* LChunkBuilder::DoStoreKeyedGeneric(HStoreKeyedGeneric* instr) { + LOperand* obj = UseFixed(instr->object(), r2); + LOperand* key = UseFixed(instr->key(), r1); + LOperand* val = UseFixed(instr->value(), r0); + + ASSERT(instr->object()->representation().IsTagged()); + ASSERT(instr->key()->representation().IsTagged()); + ASSERT(instr->value()->representation().IsTagged()); + + return MarkAsCall(new LStoreKeyedGeneric(obj, key, val), instr); +} + + +LInstruction* LChunkBuilder::DoStoreNamedField(HStoreNamedField* instr) { + bool needs_write_barrier = !instr->value()->type().IsSmi(); + + LOperand* obj = needs_write_barrier + ? UseTempRegister(instr->object()) + : UseRegisterAtStart(instr->object()); + + LOperand* val = needs_write_barrier + ? UseTempRegister(instr->value()) + : UseRegister(instr->value()); + + // We only need a scratch register if we have a write barrier or we + // have a store into the properties array (not in-object-property). + LOperand* temp = (!instr->is_in_object() || needs_write_barrier) + ? TempRegister() : NULL; + + return new LStoreNamedField(obj, + instr->name(), + val, + instr->is_in_object(), + instr->offset(), + temp, + needs_write_barrier, + instr->transition()); +} + + +LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) { + LOperand* obj = UseFixed(instr->object(), r1); + LOperand* val = UseFixed(instr->value(), r0); + + LInstruction* result = new LStoreNamedGeneric(obj, instr->name(), val); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoArrayLiteral(HArrayLiteral* instr) { + return MarkAsCall(DefineFixed(new LArrayLiteral, r0), instr); +} + + +LInstruction* LChunkBuilder::DoObjectLiteral(HObjectLiteral* instr) { + return MarkAsCall(DefineFixed(new LObjectLiteral, r0), instr); +} + + +LInstruction* LChunkBuilder::DoRegExpLiteral(HRegExpLiteral* instr) { + return MarkAsCall(DefineFixed(new LRegExpLiteral, r0), instr); +} + + +LInstruction* LChunkBuilder::DoFunctionLiteral(HFunctionLiteral* instr) { + return MarkAsCall(DefineFixed(new LFunctionLiteral, r0), instr); +} + + +LInstruction* LChunkBuilder::DoDeleteProperty(HDeleteProperty* instr) { + LInstruction* result = new LDeleteProperty(Use(instr->object()), + UseOrConstant(instr->key())); + return MarkAsCall(DefineFixed(result, r0), instr); +} + + +LInstruction* LChunkBuilder::DoOsrEntry(HOsrEntry* instr) { + allocator_->MarkAsOsrEntry(); + current_block_->last_environment()->set_ast_id(instr->ast_id()); + return AssignEnvironment(new LOsrEntry); +} + + +LInstruction* LChunkBuilder::DoParameter(HParameter* instr) { + int spill_index = chunk()->GetParameterStackSlot(instr->index()); + return DefineAsSpilled(new LParameter, spill_index); +} + + +LInstruction* LChunkBuilder::DoUnknownOSRValue(HUnknownOSRValue* instr) { + int spill_index = chunk()->GetNextSpillIndex(false); // Not double-width. + return DefineAsSpilled(new LUnknownOSRValue, spill_index); +} + + +LInstruction* LChunkBuilder::DoCallStub(HCallStub* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallStub, r0), instr); +} + + +LInstruction* LChunkBuilder::DoArgumentsObject(HArgumentsObject* instr) { + // There are no real uses of the arguments object (we bail out in all other + // cases). + return NULL; +} + + +LInstruction* LChunkBuilder::DoAccessArgumentsAt(HAccessArgumentsAt* instr) { + LOperand* arguments = UseRegister(instr->arguments()); + LOperand* length = UseTempRegister(instr->length()); + LOperand* index = Use(instr->index()); + LInstruction* result = new LAccessArgumentsAt(arguments, length, index); + return DefineAsRegister(AssignEnvironment(result)); +} + + +LInstruction* LChunkBuilder::DoTypeof(HTypeof* instr) { + LInstruction* result = new LTypeof(Use(instr->value())); + return MarkAsCall(DefineFixed(result, r0), instr); +} + + +LInstruction* LChunkBuilder::DoTypeofIs(HTypeofIs* instr) { + return DefineSameAsFirst(new LTypeofIs(UseRegister(instr->value()))); +} + +LInstruction* LChunkBuilder::DoSimulate(HSimulate* instr) { + HEnvironment* env = current_block_->last_environment(); + ASSERT(env != NULL); + + env->set_ast_id(instr->ast_id()); + + env->Drop(instr->pop_count()); + for (int i = 0; i < instr->values()->length(); ++i) { + HValue* value = instr->values()->at(i); + if (instr->HasAssignedIndexAt(i)) { + env->Bind(instr->GetAssignedIndexAt(i), value); + } else { + env->Push(value); + } + } + + if (FLAG_trace_environment) { + PrintF("Reconstructed environment ast_id=%d, instr_id=%d\n", + instr->ast_id(), + instr->id()); + env->PrintToStd(); + } + ASSERT(env->values()->length() == instr->environment_height()); + + // 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()) { + LInstruction* result = new LLazyBailout; + result = AssignEnvironment(result); + instructions_pending_deoptimization_environment_-> + set_deoptimization_environment(result->environment()); + ClearInstructionPendingDeoptimizationEnvironment(); + return result; + } + + return NULL; +} + + +LInstruction* LChunkBuilder::DoStackCheck(HStackCheck* instr) { + return MarkAsCall(new LStackCheck, instr); +} + + +LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) { + HEnvironment* outer = current_block_->last_environment(); + HConstant* undefined = graph()->GetConstantUndefined(); + HEnvironment* inner = outer->CopyForInlining(instr->closure(), + instr->function(), + false, + undefined); + current_block_->UpdateEnvironment(inner); + chunk_->AddInlinedClosure(instr->closure()); + return NULL; +} + + +LInstruction* LChunkBuilder::DoLeaveInlined(HLeaveInlined* instr) { + HEnvironment* outer = current_block_->last_environment()->outer(); + current_block_->UpdateEnvironment(outer); + return NULL; +} + + +void LPointerMap::RecordPointer(LOperand* op) { + // Do not record arguments as pointers. + if (op->IsStackSlot() && op->index() < 0) return; + ASSERT(!op->IsDoubleRegister() && !op->IsDoubleStackSlot()); + pointer_operands_.Add(op); +} + + +void LPointerMap::PrintTo(StringStream* stream) const { + stream->Add("{"); + for (int i = 0; i < pointer_operands_.length(); ++i) { + if (i != 0) stream->Add(";"); + pointer_operands_[i]->PrintTo(stream); + } + stream->Add("} @%d", position()); +} + +} } // namespace v8::internal diff --git a/src/arm/lithium-arm.h b/src/arm/lithium-arm.h new file mode 100644 index 00000000..41209c67 --- /dev/null +++ b/src/arm/lithium-arm.h @@ -0,0 +1,2115 @@ +// Copyright 2010 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. + +#ifndef V8_ARM_LITHIUM_ARM_H_ +#define V8_ARM_LITHIUM_ARM_H_ + +#include "hydrogen.h" +#include "lithium-allocator.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LCodeGen; +class LEnvironment; +class Translation; + + +// Type hierarchy: +// +// LInstruction +// LAccessArgumentsAt +// LArgumentsElements +// LArgumentsLength +// LBinaryOperation +// LAddI +// LApplyArguments +// LArithmeticD +// LArithmeticT +// LBitI +// LBoundsCheck +// LCmpID +// LCmpIDAndBranch +// LCmpJSObjectEq +// LCmpJSObjectEqAndBranch +// LCmpT +// LDivI +// LInstanceOf +// LInstanceOfAndBranch +// LLoadKeyedFastElement +// LLoadKeyedGeneric +// LModI +// LMulI +// LShiftI +// LSubI +// LCallConstantFunction +// LCallFunction +// LCallGlobal +// LCallKeyed +// LCallKnownGlobal +// LCallNamed +// LCallRuntime +// LCallStub +// LConstant +// LConstantD +// LConstantI +// LConstantT +// LDeoptimize +// LFunctionLiteral +// LGlobalObject +// LGlobalReceiver +// LLabel +// LLayzBailout +// LLoadGlobal +// LMaterializedLiteral +// LArrayLiteral +// LObjectLiteral +// LRegExpLiteral +// LOsrEntry +// LParameter +// LStackCheck +// LStoreKeyed +// LStoreKeyedFastElement +// LStoreKeyedGeneric +// LStoreNamed +// LStoreNamedField +// LStoreNamedGeneric +// LUnaryOperation +// LArrayLength +// LBitNotI +// LBranch +// LCallNew +// LCheckFunction +// LCheckInstanceType +// LCheckMap +// LCheckPrototypeMaps +// LCheckSmi +// LClassOfTest +// LClassOfTestAndBranch +// LDeleteProperty +// LDoubleToI +// LHasCachedArrayIndex +// LHasCachedArrayIndexAndBranch +// LHasInstanceType +// LHasInstanceTypeAndBranch +// LInteger32ToDouble +// LIsNull +// LIsNullAndBranch +// LIsObject +// LIsObjectAndBranch +// LIsSmi +// LIsSmiAndBranch +// LLoadNamedField +// LLoadNamedGeneric +// LNumberTagD +// LNumberTagI +// LPushArgument +// LReturn +// LSmiTag +// LStoreGlobal +// LTaggedToI +// LThrow +// LTypeof +// LTypeofIs +// LTypeofIsAndBranch +// LUnaryMathOperation +// LValueOf +// LUnknownOSRValue + +#define LITHIUM_ALL_INSTRUCTION_LIST(V) \ + V(BinaryOperation) \ + V(Constant) \ + V(Call) \ + V(MaterializedLiteral) \ + V(StoreKeyed) \ + V(StoreNamed) \ + V(UnaryOperation) \ + LITHIUM_CONCRETE_INSTRUCTION_LIST(V) + + +#define LITHIUM_CONCRETE_INSTRUCTION_LIST(V) \ + V(AccessArgumentsAt) \ + V(AddI) \ + V(ApplyArguments) \ + V(ArgumentsElements) \ + V(ArgumentsLength) \ + V(ArithmeticD) \ + V(ArithmeticT) \ + V(ArrayLength) \ + V(ArrayLiteral) \ + V(BitI) \ + V(BitNotI) \ + V(BoundsCheck) \ + V(Branch) \ + V(CallConstantFunction) \ + V(CallFunction) \ + V(CallGlobal) \ + V(CallKeyed) \ + V(CallKnownGlobal) \ + V(CallNamed) \ + V(CallNew) \ + V(CallRuntime) \ + V(CallStub) \ + V(CheckFunction) \ + V(CheckInstanceType) \ + V(CheckMap) \ + V(CheckPrototypeMaps) \ + V(CheckSmi) \ + V(CmpID) \ + V(CmpIDAndBranch) \ + V(CmpJSObjectEq) \ + V(CmpJSObjectEqAndBranch) \ + V(CmpMapAndBranch) \ + V(CmpT) \ + V(CmpTAndBranch) \ + V(ConstantD) \ + V(ConstantI) \ + V(ConstantT) \ + V(DeleteProperty) \ + V(Deoptimize) \ + V(DivI) \ + V(DoubleToI) \ + V(FunctionLiteral) \ + V(Gap) \ + V(GlobalObject) \ + V(GlobalReceiver) \ + V(Goto) \ + V(InstanceOf) \ + V(InstanceOfAndBranch) \ + V(Integer32ToDouble) \ + V(IsNull) \ + V(IsNullAndBranch) \ + V(IsObject) \ + V(IsObjectAndBranch) \ + V(IsSmi) \ + V(IsSmiAndBranch) \ + V(HasInstanceType) \ + V(HasInstanceTypeAndBranch) \ + V(HasCachedArrayIndex) \ + V(HasCachedArrayIndexAndBranch) \ + V(ClassOfTest) \ + V(ClassOfTestAndBranch) \ + V(Label) \ + V(LazyBailout) \ + V(LoadElements) \ + V(LoadGlobal) \ + V(LoadKeyedFastElement) \ + V(LoadKeyedGeneric) \ + V(LoadNamedField) \ + V(LoadNamedGeneric) \ + V(ModI) \ + V(MulI) \ + V(NumberTagD) \ + V(NumberTagI) \ + V(NumberUntagD) \ + V(ObjectLiteral) \ + V(OsrEntry) \ + V(Parameter) \ + V(PushArgument) \ + V(RegExpLiteral) \ + V(Return) \ + V(ShiftI) \ + V(SmiTag) \ + V(SmiUntag) \ + V(StackCheck) \ + V(StoreGlobal) \ + V(StoreKeyedFastElement) \ + V(StoreKeyedGeneric) \ + V(StoreNamedField) \ + V(StoreNamedGeneric) \ + V(SubI) \ + V(TaggedToI) \ + V(Throw) \ + V(Typeof) \ + V(TypeofIs) \ + V(TypeofIsAndBranch) \ + V(UnaryMathOperation) \ + V(UnknownOSRValue) \ + V(ValueOf) + + +#define DECLARE_INSTRUCTION(type) \ + virtual bool Is##type() const { return true; } \ + static L##type* cast(LInstruction* instr) { \ + ASSERT(instr->Is##type()); \ + return reinterpret_cast<L##type*>(instr); \ + } + + +#define DECLARE_CONCRETE_INSTRUCTION(type, mnemonic) \ + virtual void CompileToNative(LCodeGen* generator); \ + virtual const char* Mnemonic() const { return mnemonic; } \ + DECLARE_INSTRUCTION(type) + + +#define DECLARE_HYDROGEN_ACCESSOR(type) \ + H##type* hydrogen() const { \ + return H##type::cast(hydrogen_value()); \ + } + + +class LInstruction: public ZoneObject { + public: + LInstruction() + : hydrogen_value_(NULL) { } + virtual ~LInstruction() { } + + virtual void CompileToNative(LCodeGen* generator) = 0; + virtual const char* Mnemonic() const = 0; + virtual void PrintTo(StringStream* stream) const; + virtual void PrintDataTo(StringStream* stream) const { } + + // Declare virtual type testers. +#define DECLARE_DO(type) virtual bool Is##type() const { return false; } + LITHIUM_ALL_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + virtual bool IsControl() const { return false; } + + void set_environment(LEnvironment* env) { environment_.set(env); } + LEnvironment* environment() const { return environment_.get(); } + bool HasEnvironment() const { return environment_.is_set(); } + + void set_pointer_map(LPointerMap* p) { pointer_map_.set(p); } + LPointerMap* pointer_map() const { return pointer_map_.get(); } + bool HasPointerMap() const { return pointer_map_.is_set(); } + + void set_result(LOperand* operand) { result_.set(operand); } + LOperand* result() const { return result_.get(); } + bool HasResult() const { return result_.is_set(); } + + void set_hydrogen_value(HValue* value) { hydrogen_value_ = value; } + HValue* hydrogen_value() const { return hydrogen_value_; } + + void set_deoptimization_environment(LEnvironment* env) { + deoptimization_environment_.set(env); + } + LEnvironment* deoptimization_environment() const { + return deoptimization_environment_.get(); + } + bool HasDeoptimizationEnvironment() const { + return deoptimization_environment_.is_set(); + } + + private: + SetOncePointer<LEnvironment> environment_; + SetOncePointer<LPointerMap> pointer_map_; + SetOncePointer<LOperand> result_; + HValue* hydrogen_value_; + SetOncePointer<LEnvironment> deoptimization_environment_; +}; + + +class LGapNode; + + +class LGapResolver BASE_EMBEDDED { + public: + LGapResolver(const ZoneList<LMoveOperands>* moves, LOperand* marker_operand); + const ZoneList<LMoveOperands>* ResolveInReverseOrder(); + + private: + LGapNode* LookupNode(LOperand* operand); + bool CanReach(LGapNode* a, LGapNode* b, int visited_id); + bool CanReach(LGapNode* a, LGapNode* b); + void RegisterMove(LMoveOperands move); + void AddResultMove(LOperand* from, LOperand* to); + void AddResultMove(LGapNode* from, LGapNode* to); + void ResolveCycle(LGapNode* start); + + ZoneList<LGapNode*> nodes_; + ZoneList<LGapNode*> identified_cycles_; + ZoneList<LMoveOperands> result_; + LOperand* marker_operand_; + int next_visited_id_; + int bailout_after_ast_id_; +}; + + +class LParallelMove : public ZoneObject { + public: + LParallelMove() : move_operands_(4) { } + + void AddMove(LOperand* from, LOperand* to) { + move_operands_.Add(LMoveOperands(from, to)); + } + + bool IsRedundant() const; + + const ZoneList<LMoveOperands>* move_operands() const { + return &move_operands_; + } + + void PrintDataTo(StringStream* stream) const; + + private: + ZoneList<LMoveOperands> move_operands_; +}; + + +class LGap: public LInstruction { + public: + explicit LGap(HBasicBlock* block) + : block_(block) { + parallel_moves_[BEFORE] = NULL; + parallel_moves_[START] = NULL; + parallel_moves_[END] = NULL; + parallel_moves_[AFTER] = NULL; + } + + DECLARE_CONCRETE_INSTRUCTION(Gap, "gap") + virtual void PrintDataTo(StringStream* stream) const; + + bool IsRedundant() const; + + HBasicBlock* block() const { return block_; } + + enum InnerPosition { + BEFORE, + START, + END, + AFTER, + FIRST_INNER_POSITION = BEFORE, + LAST_INNER_POSITION = AFTER + }; + + LParallelMove* GetOrCreateParallelMove(InnerPosition pos) { + if (parallel_moves_[pos] == NULL) parallel_moves_[pos] = new LParallelMove; + return parallel_moves_[pos]; + } + + LParallelMove* GetParallelMove(InnerPosition pos) { + return parallel_moves_[pos]; + } + + private: + LParallelMove* parallel_moves_[LAST_INNER_POSITION + 1]; + HBasicBlock* block_; +}; + + +class LGoto: public LInstruction { + public: + LGoto(int block_id, bool include_stack_check = false) + : block_id_(block_id), include_stack_check_(include_stack_check) { } + + DECLARE_CONCRETE_INSTRUCTION(Goto, "goto") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int block_id() const { return block_id_; } + bool include_stack_check() const { return include_stack_check_; } + + private: + int block_id_; + bool include_stack_check_; +}; + + +class LLazyBailout: public LInstruction { + public: + LLazyBailout() : gap_instructions_size_(0) { } + + DECLARE_CONCRETE_INSTRUCTION(LazyBailout, "lazy-bailout") + + void set_gap_instructions_size(int gap_instructions_size) { + gap_instructions_size_ = gap_instructions_size; + } + int gap_instructions_size() { return gap_instructions_size_; } + + private: + int gap_instructions_size_; +}; + + +class LDeoptimize: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(Deoptimize, "deoptimize") +}; + + +class LLabel: public LGap { + public: + explicit LLabel(HBasicBlock* block) + : LGap(block), replacement_(NULL) { } + + DECLARE_CONCRETE_INSTRUCTION(Label, "label") + + virtual void PrintDataTo(StringStream* stream) const; + + int block_id() const { return block()->block_id(); } + bool is_loop_header() const { return block()->IsLoopHeader(); } + Label* label() { return &label_; } + LLabel* replacement() const { return replacement_; } + void set_replacement(LLabel* label) { replacement_ = label; } + bool HasReplacement() const { return replacement_ != NULL; } + + private: + Label label_; + LLabel* replacement_; +}; + + +class LParameter: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(Parameter, "parameter") +}; + + +class LCallStub: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallStub, "call-stub") + DECLARE_HYDROGEN_ACCESSOR(CallStub) +}; + + +class LUnknownOSRValue: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(UnknownOSRValue, "unknown-osr-value") +}; + + +class LUnaryOperation: public LInstruction { + public: + explicit LUnaryOperation(LOperand* input) : input_(input) { } + + DECLARE_INSTRUCTION(UnaryOperation) + + LOperand* input() const { return input_; } + + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* input_; +}; + + +class LBinaryOperation: public LInstruction { + public: + LBinaryOperation(LOperand* left, LOperand* right) + : left_(left), right_(right) { } + + DECLARE_INSTRUCTION(BinaryOperation) + + LOperand* left() const { return left_; } + LOperand* right() const { return right_; } + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* left_; + LOperand* right_; +}; + + +class LApplyArguments: public LBinaryOperation { + public: + LApplyArguments(LOperand* function, + LOperand* receiver, + LOperand* length, + LOperand* elements) + : LBinaryOperation(function, receiver), + length_(length), + elements_(elements) { } + + DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments") + + LOperand* function() const { return left(); } + LOperand* receiver() const { return right(); } + LOperand* length() const { return length_; } + LOperand* elements() const { return elements_; } + + private: + LOperand* length_; + LOperand* elements_; +}; + + +class LAccessArgumentsAt: public LInstruction { + public: + LAccessArgumentsAt(LOperand* arguments, LOperand* length, LOperand* index) + : arguments_(arguments), length_(length), index_(index) { } + + DECLARE_CONCRETE_INSTRUCTION(AccessArgumentsAt, "access-arguments-at") + + LOperand* arguments() const { return arguments_; } + LOperand* length() const { return length_; } + LOperand* index() const { return index_; } + + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* arguments_; + LOperand* length_; + LOperand* index_; +}; + + +class LArgumentsLength: public LUnaryOperation { + public: + explicit LArgumentsLength(LOperand* elements) : LUnaryOperation(elements) {} + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsLength, "arguments-length") +}; + + +class LArgumentsElements: public LInstruction { + public: + LArgumentsElements() { } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsElements, "arguments-elements") +}; + + +class LModI: public LBinaryOperation { + public: + LModI(LOperand* left, LOperand* right) : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(ModI, "mod-i") + DECLARE_HYDROGEN_ACCESSOR(Mod) +}; + + +class LDivI: public LBinaryOperation { + public: + LDivI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(DivI, "div-i") + DECLARE_HYDROGEN_ACCESSOR(Div) +}; + + +class LMulI: public LBinaryOperation { + public: + LMulI(LOperand* left, LOperand* right, LOperand* temp) + : LBinaryOperation(left, right), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(MulI, "mul-i") + DECLARE_HYDROGEN_ACCESSOR(Mul) + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LCmpID: public LBinaryOperation { + public: + LCmpID(Token::Value op, LOperand* left, LOperand* right, bool is_double) + : LBinaryOperation(left, right), op_(op), is_double_(is_double) { } + + Token::Value op() const { return op_; } + bool is_double() const { return is_double_; } + + DECLARE_CONCRETE_INSTRUCTION(CmpID, "cmp-id") + + private: + Token::Value op_; + bool is_double_; +}; + + +class LCmpIDAndBranch: public LCmpID { + public: + LCmpIDAndBranch(Token::Value op, + LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id, + bool is_double) + : LCmpID(op, left, right, is_double), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpIDAndBranch, "cmp-id-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LUnaryMathOperation: public LUnaryOperation { + public: + explicit LUnaryMathOperation(LOperand* value) + : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(UnaryMathOperation, "unary-math-operation") + DECLARE_HYDROGEN_ACCESSOR(UnaryMathOperation) + + virtual void PrintDataTo(StringStream* stream) const; + BuiltinFunctionId op() const { return hydrogen()->op(); } +}; + + +class LCmpJSObjectEq: public LBinaryOperation { + public: + LCmpJSObjectEq(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) {} + + DECLARE_CONCRETE_INSTRUCTION(CmpJSObjectEq, "cmp-jsobject-eq") +}; + + +class LCmpJSObjectEqAndBranch: public LCmpJSObjectEq { + public: + LCmpJSObjectEqAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LCmpJSObjectEq(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpJSObjectEqAndBranch, + "cmp-jsobject-eq-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LIsNull: public LUnaryOperation { + public: + LIsNull(LOperand* value, bool is_strict) + : LUnaryOperation(value), is_strict_(is_strict) {} + + DECLARE_CONCRETE_INSTRUCTION(IsNull, "is-null") + + bool is_strict() const { return is_strict_; } + + private: + bool is_strict_; +}; + + +class LIsNullAndBranch: public LIsNull { + public: + LIsNullAndBranch(LOperand* value, + bool is_strict, + LOperand* temp, + int true_block_id, + int false_block_id) + : LIsNull(value, is_strict), + temp_(temp), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsNullAndBranch, "is-null-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; + int true_block_id_; + int false_block_id_; +}; + + +class LIsObject: public LUnaryOperation { + public: + LIsObject(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temp_(temp) {} + + DECLARE_CONCRETE_INSTRUCTION(IsObject, "is-object") + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LIsObjectAndBranch: public LIsObject { + public: + LIsObjectAndBranch(LOperand* value, + LOperand* temp, + LOperand* temp2, + int true_block_id, + int false_block_id) + : LIsObject(value, temp), + temp2_(temp2), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsObjectAndBranch, "is-object-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp2() const { return temp2_; } + + private: + LOperand* temp2_; + int true_block_id_; + int false_block_id_; +}; + + +class LIsSmi: public LUnaryOperation { + public: + explicit LIsSmi(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(IsSmi, "is-smi") + DECLARE_HYDROGEN_ACCESSOR(IsSmi) +}; + + +class LIsSmiAndBranch: public LIsSmi { + public: + LIsSmiAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LIsSmi(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsSmiAndBranch, "is-smi-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LHasInstanceType: public LUnaryOperation { + public: + explicit LHasInstanceType(LOperand* value) + : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceType, "has-instance-type") + DECLARE_HYDROGEN_ACCESSOR(HasInstanceType) + + InstanceType TestType(); // The type to test against when generating code. + Condition BranchCondition(); // The branch condition for 'true'. +}; + + +class LHasInstanceTypeAndBranch: public LHasInstanceType { + public: + LHasInstanceTypeAndBranch(LOperand* value, + LOperand* temporary, + int true_block_id, + int false_block_id) + : LHasInstanceType(value), + temp_(temporary), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceTypeAndBranch, + "has-instance-type-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp() { return temp_; } + + private: + LOperand* temp_; + int true_block_id_; + int false_block_id_; +}; + + +class LHasCachedArrayIndex: public LUnaryOperation { + public: + explicit LHasCachedArrayIndex(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndex, "has-cached-array-index") + DECLARE_HYDROGEN_ACCESSOR(HasCachedArrayIndex) +}; + + +class LHasCachedArrayIndexAndBranch: public LHasCachedArrayIndex { + public: + LHasCachedArrayIndexAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LHasCachedArrayIndex(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndexAndBranch, + "has-cached-array-index-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LClassOfTest: public LUnaryOperation { + public: + LClassOfTest(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temporary_(temp) {} + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTest, "class-of-test") + DECLARE_HYDROGEN_ACCESSOR(ClassOfTest) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* temporary() { return temporary_; } + + private: + LOperand *temporary_; +}; + + +class LClassOfTestAndBranch: public LClassOfTest { + public: + LClassOfTestAndBranch(LOperand* value, + LOperand* temporary, + LOperand* temporary2, + int true_block_id, + int false_block_id) + : LClassOfTest(value, temporary), + temporary2_(temporary2), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTestAndBranch, + "class-of-test-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + LOperand* temporary2() { return temporary2_; } + + private: + LOperand* temporary2_; + int true_block_id_; + int false_block_id_; +}; + + +class LCmpT: public LBinaryOperation { + public: + LCmpT(LOperand* left, LOperand* right) : LBinaryOperation(left, right) {} + + DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t") + DECLARE_HYDROGEN_ACCESSOR(Compare) + + Token::Value op() const { return hydrogen()->token(); } +}; + + +class LCmpTAndBranch: public LCmpT { + public: + LCmpTAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LCmpT(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpTAndBranch, "cmp-t-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LInstanceOf: public LBinaryOperation { + public: + LInstanceOf(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOf, "instance-of") +}; + + +class LInstanceOfAndBranch: public LInstanceOf { + public: + LInstanceOfAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LInstanceOf(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOfAndBranch, "instance-of-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LBoundsCheck: public LBinaryOperation { + public: + LBoundsCheck(LOperand* index, LOperand* length) + : LBinaryOperation(index, length) { } + + LOperand* index() const { return left(); } + LOperand* length() const { return right(); } + + DECLARE_CONCRETE_INSTRUCTION(BoundsCheck, "bounds-check") +}; + + +class LBitI: public LBinaryOperation { + public: + LBitI(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + Token::Value op() const { return op_; } + + DECLARE_CONCRETE_INSTRUCTION(BitI, "bit-i") + + private: + Token::Value op_; +}; + + +class LShiftI: public LBinaryOperation { + public: + LShiftI(Token::Value op, LOperand* left, LOperand* right, bool can_deopt) + : LBinaryOperation(left, right), op_(op), can_deopt_(can_deopt) { } + + Token::Value op() const { return op_; } + + bool can_deopt() const { return can_deopt_; } + + DECLARE_CONCRETE_INSTRUCTION(ShiftI, "shift-i") + + private: + Token::Value op_; + bool can_deopt_; +}; + + +class LSubI: public LBinaryOperation { + public: + LSubI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(SubI, "sub-i") + DECLARE_HYDROGEN_ACCESSOR(Sub) +}; + + +class LConstant: public LInstruction { + DECLARE_INSTRUCTION(Constant) +}; + + +class LConstantI: public LConstant { + public: + explicit LConstantI(int32_t value) : value_(value) { } + int32_t value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantI, "constant-i") + + private: + int32_t value_; +}; + + +class LConstantD: public LConstant { + public: + explicit LConstantD(double value) : value_(value) { } + double value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantD, "constant-d") + + private: + double value_; +}; + + +class LConstantT: public LConstant { + public: + explicit LConstantT(Handle<Object> value) : value_(value) { } + Handle<Object> value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantT, "constant-t") + + private: + Handle<Object> value_; +}; + + +class LBranch: public LUnaryOperation { + public: + LBranch(LOperand* input, int true_block_id, int false_block_id) + : LUnaryOperation(input), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(Branch, "branch") + DECLARE_HYDROGEN_ACCESSOR(Value) + + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LCmpMapAndBranch: public LUnaryOperation { + public: + LCmpMapAndBranch(LOperand* value, + Handle<Map> map, + int true_block_id, + int false_block_id) + : LUnaryOperation(value), + map_(map), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpMapAndBranch, "cmp-map-and-branch") + + virtual bool IsControl() const { return true; } + + Handle<Map> map() const { return map_; } + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + Handle<Map> map_; + int true_block_id_; + int false_block_id_; +}; + + +class LArrayLength: public LUnaryOperation { + public: + LArrayLength(LOperand* input, LOperand* temporary) + : LUnaryOperation(input), temporary_(temporary) { } + + LOperand* temporary() const { return temporary_; } + + DECLARE_CONCRETE_INSTRUCTION(ArrayLength, "array-length") + DECLARE_HYDROGEN_ACCESSOR(ArrayLength) + + private: + LOperand* temporary_; +}; + + +class LValueOf: public LUnaryOperation { + public: + LValueOf(LOperand* input, LOperand* temporary) + : LUnaryOperation(input), temporary_(temporary) { } + + LOperand* temporary() const { return temporary_; } + + DECLARE_CONCRETE_INSTRUCTION(ValueOf, "value-of") + DECLARE_HYDROGEN_ACCESSOR(ValueOf) + + private: + LOperand* temporary_; +}; + + +class LThrow: public LUnaryOperation { + public: + explicit LThrow(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(Throw, "throw") +}; + + +class LBitNotI: public LUnaryOperation { + public: + explicit LBitNotI(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(BitNotI, "bit-not-i") +}; + + +class LAddI: public LBinaryOperation { + public: + LAddI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(AddI, "add-i") + DECLARE_HYDROGEN_ACCESSOR(Add) +}; + + +class LArithmeticD: public LBinaryOperation { + public: + LArithmeticD(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + Token::Value op() const { return op_; } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const; + + private: + Token::Value op_; +}; + + +class LArithmeticT: public LBinaryOperation { + public: + LArithmeticT(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const; + + Token::Value op() const { return op_; } + + private: + Token::Value op_; +}; + + +class LReturn: public LUnaryOperation { + public: + explicit LReturn(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(Return, "return") +}; + + +class LLoadNamedField: public LUnaryOperation { + public: + explicit LLoadNamedField(LOperand* object) : LUnaryOperation(object) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedField, "load-named-field") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedField) +}; + + +class LLoadNamedGeneric: public LUnaryOperation { + public: + explicit LLoadNamedGeneric(LOperand* object) : LUnaryOperation(object) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedGeneric, "load-named-generic") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedGeneric) + + LOperand* object() const { return input(); } + Handle<Object> name() const { return hydrogen()->name(); } +}; + + +class LLoadElements: public LUnaryOperation { + public: + explicit LLoadElements(LOperand* obj) : LUnaryOperation(obj) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadElements, "load-elements") +}; + + +class LLoadKeyedFastElement: public LBinaryOperation { + public: + LLoadKeyedFastElement(LOperand* elements, + LOperand* key, + LOperand* load_result) + : LBinaryOperation(elements, key), + load_result_(load_result) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedFastElement, "load-keyed-fast-element") + DECLARE_HYDROGEN_ACCESSOR(LoadKeyedFastElement) + + LOperand* elements() const { return left(); } + LOperand* key() const { return right(); } + LOperand* load_result() const { return load_result_; } + + private: + LOperand* load_result_; +}; + + +class LLoadKeyedGeneric: public LBinaryOperation { + public: + LLoadKeyedGeneric(LOperand* obj, LOperand* key) + : LBinaryOperation(obj, key) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedGeneric, "load-keyed-generic") + + LOperand* object() const { return left(); } + LOperand* key() const { return right(); } +}; + + +class LLoadGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(LoadGlobal, "load-global") + DECLARE_HYDROGEN_ACCESSOR(LoadGlobal) +}; + + +class LStoreGlobal: public LUnaryOperation { + public: + explicit LStoreGlobal(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(StoreGlobal, "store-global") + DECLARE_HYDROGEN_ACCESSOR(StoreGlobal) +}; + + +class LPushArgument: public LUnaryOperation { + public: + explicit LPushArgument(LOperand* argument) : LUnaryOperation(argument) {} + + DECLARE_CONCRETE_INSTRUCTION(PushArgument, "push-argument") +}; + + +class LGlobalObject: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(GlobalObject, "global-object") +}; + + +class LGlobalReceiver: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(GlobalReceiver, "global-receiver") +}; + + +class LCallConstantFunction: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction, "call-constant-function") + DECLARE_HYDROGEN_ACCESSOR(CallConstantFunction) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<JSFunction> function() const { return hydrogen()->function(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallKeyed: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallKeyed, "call-keyed") + DECLARE_HYDROGEN_ACCESSOR(CallKeyed) + + virtual void PrintDataTo(StringStream* stream) const; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNamed: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallNamed, "call-named") + DECLARE_HYDROGEN_ACCESSOR(CallNamed) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const { return hydrogen()->name(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallFunction: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallFunction, "call-function") + DECLARE_HYDROGEN_ACCESSOR(CallFunction) + + int arity() const { return hydrogen()->argument_count() - 2; } +}; + + +class LCallGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallGlobal, "call-global") + DECLARE_HYDROGEN_ACCESSOR(CallGlobal) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const {return hydrogen()->name(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallKnownGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal, "call-known-global") + DECLARE_HYDROGEN_ACCESSOR(CallKnownGlobal) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<JSFunction> target() const { return hydrogen()->target(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNew: public LUnaryOperation { + public: + explicit LCallNew(LOperand* constructor) : LUnaryOperation(constructor) { } + + DECLARE_CONCRETE_INSTRUCTION(CallNew, "call-new") + DECLARE_HYDROGEN_ACCESSOR(CallNew) + + virtual void PrintDataTo(StringStream* stream) const; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallRuntime: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallRuntime, "call-runtime") + DECLARE_HYDROGEN_ACCESSOR(CallRuntime) + + Runtime::Function* function() const { return hydrogen()->function(); } + int arity() const { return hydrogen()->argument_count(); } +}; + + +class LInteger32ToDouble: public LUnaryOperation { + public: + explicit LInteger32ToDouble(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(Integer32ToDouble, "int32-to-double") +}; + + +class LNumberTagI: public LUnaryOperation { + public: + explicit LNumberTagI(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagI, "number-tag-i") +}; + + +class LNumberTagD: public LUnaryOperation { + public: + LNumberTagD(LOperand* value, LOperand* temp1, LOperand* temp2) + : LUnaryOperation(value), temp1_(temp1), temp2_(temp2) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagD, "number-tag-d") + + LOperand* temp1() const { return temp1_; } + LOperand* temp2() const { return temp2_; } + + private: + LOperand* temp1_; + LOperand* temp2_; +}; + + +// Sometimes truncating conversion from a tagged value to an int32. +class LDoubleToI: public LUnaryOperation { + public: + explicit LDoubleToI(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(DoubleToI, "double-to-i") + DECLARE_HYDROGEN_ACCESSOR(Change) + + bool truncating() { return hydrogen()->CanTruncateToInt32(); } +}; + + +// Truncating conversion from a tagged value to an int32. +class LTaggedToI: public LUnaryOperation { + public: + LTaggedToI(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(TaggedToI, "tagged-to-i") + DECLARE_HYDROGEN_ACCESSOR(Change) + + bool truncating() { return hydrogen()->CanTruncateToInt32(); } + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LSmiTag: public LUnaryOperation { + public: + explicit LSmiTag(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(SmiTag, "smi-tag") +}; + + +class LNumberUntagD: public LUnaryOperation { + public: + explicit LNumberUntagD(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberUntagD, "double-untag") +}; + + +class LSmiUntag: public LUnaryOperation { + public: + LSmiUntag(LOperand* use, bool needs_check) + : LUnaryOperation(use), needs_check_(needs_check) { } + + DECLARE_CONCRETE_INSTRUCTION(SmiUntag, "smi-untag") + + bool needs_check() const { return needs_check_; } + + private: + bool needs_check_; +}; + + +class LStoreNamed: public LInstruction { + public: + LStoreNamed(LOperand* obj, Handle<Object> name, LOperand* val) + : object_(obj), name_(name), value_(val) { } + + DECLARE_INSTRUCTION(StoreNamed) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* object() const { return object_; } + Handle<Object> name() const { return name_; } + LOperand* value() const { return value_; } + + private: + LOperand* object_; + Handle<Object> name_; + LOperand* value_; +}; + + +class LStoreNamedField: public LStoreNamed { + public: + LStoreNamedField(LOperand* obj, + Handle<Object> name, + LOperand* val, + bool in_object, + int offset, + LOperand* temp, + bool needs_write_barrier, + Handle<Map> transition) + : LStoreNamed(obj, name, val), + is_in_object_(in_object), + offset_(offset), + temp_(temp), + needs_write_barrier_(needs_write_barrier), + transition_(transition) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedField, "store-named-field") + + bool is_in_object() { return is_in_object_; } + int offset() { return offset_; } + LOperand* temp() { return temp_; } + bool needs_write_barrier() { return needs_write_barrier_; } + Handle<Map> transition() const { return transition_; } + void set_transition(Handle<Map> map) { transition_ = map; } + + private: + bool is_in_object_; + int offset_; + LOperand* temp_; + bool needs_write_barrier_; + Handle<Map> transition_; +}; + + +class LStoreNamedGeneric: public LStoreNamed { + public: + LStoreNamedGeneric(LOperand* obj, + Handle<Object> name, + LOperand* val) + : LStoreNamed(obj, name, val) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedGeneric, "store-named-generic") +}; + + +class LStoreKeyed: public LInstruction { + public: + LStoreKeyed(LOperand* obj, LOperand* key, LOperand* val) + : object_(obj), key_(key), value_(val) { } + + DECLARE_INSTRUCTION(StoreKeyed) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* object() const { return object_; } + LOperand* key() const { return key_; } + LOperand* value() const { return value_; } + + private: + LOperand* object_; + LOperand* key_; + LOperand* value_; +}; + + +class LStoreKeyedFastElement: public LStoreKeyed { + public: + LStoreKeyedFastElement(LOperand* obj, LOperand* key, LOperand* val) + : LStoreKeyed(obj, key, val) {} + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFastElement, + "store-keyed-fast-element") + DECLARE_HYDROGEN_ACCESSOR(StoreKeyedFastElement) +}; + + +class LStoreKeyedGeneric: public LStoreKeyed { + public: + LStoreKeyedGeneric(LOperand* obj, LOperand* key, LOperand* val) + : LStoreKeyed(obj, key, val) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedGeneric, "store-keyed-generic") +}; + + +class LCheckFunction: public LUnaryOperation { + public: + explicit LCheckFunction(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckFunction, "check-function") + DECLARE_HYDROGEN_ACCESSOR(CheckFunction) +}; + + +class LCheckInstanceType: public LUnaryOperation { + public: + LCheckInstanceType(LOperand* use, LOperand* temp) + : LUnaryOperation(use), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckInstanceType, "check-instance-type") + DECLARE_HYDROGEN_ACCESSOR(CheckInstanceType) + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LCheckMap: public LUnaryOperation { + public: + explicit LCheckMap(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckMap, "check-map") + DECLARE_HYDROGEN_ACCESSOR(CheckMap) +}; + + +class LCheckPrototypeMaps: public LInstruction { + public: + LCheckPrototypeMaps(LOperand* temp, + Handle<JSObject> holder, + Handle<Map> receiver_map) + : temp_(temp), + holder_(holder), + receiver_map_(receiver_map) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckPrototypeMaps, "check-prototype-maps") + + LOperand* temp() const { return temp_; } + Handle<JSObject> holder() const { return holder_; } + Handle<Map> receiver_map() const { return receiver_map_; } + + private: + LOperand* temp_; + Handle<JSObject> holder_; + Handle<Map> receiver_map_; +}; + + +class LCheckSmi: public LUnaryOperation { + public: + LCheckSmi(LOperand* use, Condition condition) + : LUnaryOperation(use), condition_(condition) { } + + Condition condition() const { return condition_; } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const { + return (condition_ == eq) ? "check-non-smi" : "check-smi"; + } + + private: + Condition condition_; +}; + + +class LMaterializedLiteral: public LInstruction { + public: + DECLARE_INSTRUCTION(MaterializedLiteral) +}; + + +class LArrayLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(ArrayLiteral, "array-literal") + DECLARE_HYDROGEN_ACCESSOR(ArrayLiteral) +}; + + +class LObjectLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(ObjectLiteral, "object-literal") + DECLARE_HYDROGEN_ACCESSOR(ObjectLiteral) +}; + + +class LRegExpLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(RegExpLiteral, "regexp-literal") + DECLARE_HYDROGEN_ACCESSOR(RegExpLiteral) +}; + + +class LFunctionLiteral: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(FunctionLiteral, "function-literal") + DECLARE_HYDROGEN_ACCESSOR(FunctionLiteral) + + Handle<SharedFunctionInfo> shared_info() { return hydrogen()->shared_info(); } +}; + + +class LTypeof: public LUnaryOperation { + public: + explicit LTypeof(LOperand* input) : LUnaryOperation(input) { } + + DECLARE_CONCRETE_INSTRUCTION(Typeof, "typeof") +}; + + +class LTypeofIs: public LUnaryOperation { + public: + explicit LTypeofIs(LOperand* input) : LUnaryOperation(input) { } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(TypeofIs, "typeof-is") + DECLARE_HYDROGEN_ACCESSOR(TypeofIs) + + Handle<String> type_literal() { return hydrogen()->type_literal(); } +}; + + +class LTypeofIsAndBranch: public LTypeofIs { + public: + LTypeofIsAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LTypeofIs(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(TypeofIsAndBranch, "typeof-is-and-branch") + + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LDeleteProperty: public LBinaryOperation { + public: + LDeleteProperty(LOperand* obj, LOperand* key) : LBinaryOperation(obj, key) {} + + DECLARE_CONCRETE_INSTRUCTION(DeleteProperty, "delete-property") + + LOperand* object() const { return left(); } + LOperand* key() const { return right(); } +}; + + +class LOsrEntry: public LInstruction { + public: + LOsrEntry(); + + DECLARE_CONCRETE_INSTRUCTION(OsrEntry, "osr-entry") + + LOperand** SpilledRegisterArray() { return register_spills_; } + LOperand** SpilledDoubleRegisterArray() { return double_register_spills_; } + + void MarkSpilledRegister(int allocation_index, LOperand* spill_operand); + void MarkSpilledDoubleRegister(int allocation_index, + LOperand* spill_operand); + + private: + // Arrays of spill slot operands for registers with an assigned spill + // slot, i.e., that must also be restored to the spill slot on OSR entry. + // NULL if the register has no assigned spill slot. Indexed by allocation + // index. + LOperand* register_spills_[Register::kNumAllocatableRegisters]; + LOperand* double_register_spills_[DoubleRegister::kNumAllocatableRegisters]; +}; + + +class LStackCheck: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(StackCheck, "stack-check") +}; + + +class LPointerMap: public ZoneObject { + public: + explicit LPointerMap(int position) + : pointer_operands_(8), position_(position), lithium_position_(-1) { } + + const ZoneList<LOperand*>* operands() const { return &pointer_operands_; } + int position() const { return position_; } + int lithium_position() const { return lithium_position_; } + + void set_lithium_position(int pos) { + ASSERT(lithium_position_ == -1); + lithium_position_ = pos; + } + + void RecordPointer(LOperand* op); + void PrintTo(StringStream* stream) const; + + private: + ZoneList<LOperand*> pointer_operands_; + int position_; + int lithium_position_; +}; + + +class LEnvironment: public ZoneObject { + public: + LEnvironment(Handle<JSFunction> closure, + int ast_id, + int parameter_count, + int argument_count, + int value_count, + LEnvironment* outer) + : closure_(closure), + arguments_stack_height_(argument_count), + deoptimization_index_(Safepoint::kNoDeoptimizationIndex), + translation_index_(-1), + ast_id_(ast_id), + parameter_count_(parameter_count), + values_(value_count), + representations_(value_count), + spilled_registers_(NULL), + spilled_double_registers_(NULL), + outer_(outer) { + } + + Handle<JSFunction> closure() const { return closure_; } + int arguments_stack_height() const { return arguments_stack_height_; } + int deoptimization_index() const { return deoptimization_index_; } + int translation_index() const { return translation_index_; } + int ast_id() const { return ast_id_; } + int parameter_count() const { return parameter_count_; } + const ZoneList<LOperand*>* values() const { return &values_; } + LEnvironment* outer() const { return outer_; } + + void AddValue(LOperand* operand, Representation representation) { + values_.Add(operand); + representations_.Add(representation); + } + + bool HasTaggedValueAt(int index) const { + return representations_[index].IsTagged(); + } + + void Register(int deoptimization_index, int translation_index) { + ASSERT(!HasBeenRegistered()); + deoptimization_index_ = deoptimization_index; + translation_index_ = translation_index; + } + bool HasBeenRegistered() const { + return deoptimization_index_ != Safepoint::kNoDeoptimizationIndex; + } + + void SetSpilledRegisters(LOperand** registers, + LOperand** double_registers) { + spilled_registers_ = registers; + spilled_double_registers_ = double_registers; + } + + // Emit frame translation commands for this environment. + void WriteTranslation(LCodeGen* cgen, Translation* translation) const; + + void PrintTo(StringStream* stream) const; + + private: + Handle<JSFunction> closure_; + int arguments_stack_height_; + int deoptimization_index_; + int translation_index_; + int ast_id_; + int parameter_count_; + ZoneList<LOperand*> values_; + ZoneList<Representation> representations_; + + // Allocation index indexed arrays of spill slot operands for registers + // that are also in spill slots at an OSR entry. NULL for environments + // that do not correspond to an OSR entry. + LOperand** spilled_registers_; + LOperand** spilled_double_registers_; + + LEnvironment* outer_; +}; + +class LChunkBuilder; +class LChunk: public ZoneObject { + public: + explicit LChunk(HGraph* graph); + + int AddInstruction(LInstruction* instruction, HBasicBlock* block); + LConstantOperand* DefineConstantOperand(HConstant* constant); + Handle<Object> LookupLiteral(LConstantOperand* operand) const; + Representation LookupLiteralRepresentation(LConstantOperand* operand) const; + + int GetNextSpillIndex(bool is_double); + LOperand* GetNextSpillSlot(bool is_double); + + int ParameterAt(int index); + int GetParameterStackSlot(int index) const; + int spill_slot_count() const { return spill_slot_count_; } + HGraph* graph() const { return graph_; } + const ZoneList<LInstruction*>* instructions() const { return &instructions_; } + void AddGapMove(int index, LOperand* from, LOperand* to); + LGap* GetGapAt(int index) const; + bool IsGapAt(int index) const; + int NearestGapPos(int index) const; + void MarkEmptyBlocks(); + const ZoneList<LPointerMap*>* pointer_maps() const { return &pointer_maps_; } + LLabel* GetLabel(int block_id) const { + HBasicBlock* block = graph_->blocks()->at(block_id); + int first_instruction = block->first_instruction_index(); + return LLabel::cast(instructions_[first_instruction]); + } + int LookupDestination(int block_id) const { + LLabel* cur = GetLabel(block_id); + while (cur->replacement() != NULL) { + cur = cur->replacement(); + } + return cur->block_id(); + } + Label* GetAssemblyLabel(int block_id) const { + LLabel* label = GetLabel(block_id); + ASSERT(!label->HasReplacement()); + return label->label(); + } + + const ZoneList<Handle<JSFunction> >* inlined_closures() const { + return &inlined_closures_; + } + + void AddInlinedClosure(Handle<JSFunction> closure) { + inlined_closures_.Add(closure); + } + + void Verify() const; + + private: + int spill_slot_count_; + HGraph* const graph_; + ZoneList<LInstruction*> instructions_; + ZoneList<LPointerMap*> pointer_maps_; + ZoneList<Handle<JSFunction> > inlined_closures_; +}; + + +class LChunkBuilder BASE_EMBEDDED { + public: + LChunkBuilder(HGraph* graph, LAllocator* allocator) + : chunk_(NULL), + graph_(graph), + status_(UNUSED), + current_instruction_(NULL), + current_block_(NULL), + next_block_(NULL), + argument_count_(0), + allocator_(allocator), + position_(RelocInfo::kNoPosition), + instructions_pending_deoptimization_environment_(NULL), + pending_deoptimization_ast_id_(AstNode::kNoNumber) { } + + // Build the sequence for the graph. + LChunk* Build(); + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) LInstruction* Do##type(H##type* node); + HYDROGEN_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + private: + enum Status { + UNUSED, + BUILDING, + DONE, + ABORTED + }; + + LChunk* chunk() const { return chunk_; } + HGraph* graph() const { return graph_; } + + bool is_unused() const { return status_ == UNUSED; } + bool is_building() const { return status_ == BUILDING; } + bool is_done() const { return status_ == DONE; } + bool is_aborted() const { return status_ == ABORTED; } + + void Abort(const char* format, ...); + + // Methods for getting operands for Use / Define / Temp. + LRegister* ToOperand(Register reg); + LUnallocated* ToUnallocated(Register reg); + LUnallocated* ToUnallocated(DoubleRegister reg); + + // Methods for setting up define-use relationships. + LOperand* Use(HValue* value, LUnallocated* operand); + LOperand* UseFixed(HValue* value, Register fixed_register); + LOperand* UseFixedDouble(HValue* value, DoubleRegister fixed_register); + + // A value that is guaranteed to be allocated to a register. + // Operand created by UseRegister is guaranteed to be live until the end of + // instruction. This means that register allocator will not reuse it's + // register for any other operand inside instruction. + // Operand created by UseRegisterAtStart is guaranteed to be live only at + // instruction start. Register allocator is free to assign the same register + // to some other operand used inside instruction (i.e. temporary or + // output). + LOperand* UseRegister(HValue* value); + LOperand* UseRegisterAtStart(HValue* value); + + // A value in a register that may be trashed. + LOperand* UseTempRegister(HValue* value); + LOperand* Use(HValue* value); + LOperand* UseAtStart(HValue* value); + LOperand* UseOrConstant(HValue* value); + LOperand* UseOrConstantAtStart(HValue* value); + LOperand* UseRegisterOrConstant(HValue* value); + LOperand* UseRegisterOrConstantAtStart(HValue* value); + + // Methods for setting up define-use relationships. + // Return the same instruction that they are passed. + LInstruction* Define(LInstruction* instr, LUnallocated* result); + LInstruction* Define(LInstruction* instr); + LInstruction* DefineAsRegister(LInstruction* instr); + LInstruction* DefineAsSpilled(LInstruction* instr, int index); + LInstruction* DefineSameAsAny(LInstruction* instr); + LInstruction* DefineSameAsFirst(LInstruction* instr); + LInstruction* DefineFixed(LInstruction* instr, Register reg); + LInstruction* DefineFixedDouble(LInstruction* instr, DoubleRegister reg); + LInstruction* AssignEnvironment(LInstruction* instr); + LInstruction* AssignPointerMap(LInstruction* instr); + + enum CanDeoptimize { CAN_DEOPTIMIZE_EAGERLY, CANNOT_DEOPTIMIZE_EAGERLY }; + + // By default we assume that instruction sequences generated for calls + // cannot deoptimize eagerly and we do not attach environment to this + // instruction. + LInstruction* MarkAsCall( + LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize = CANNOT_DEOPTIMIZE_EAGERLY); + + LInstruction* SetInstructionPendingDeoptimizationEnvironment( + LInstruction* instr, int ast_id); + void ClearInstructionPendingDeoptimizationEnvironment(); + + LEnvironment* CreateEnvironment(HEnvironment* hydrogen_env); + + // Temporary operand that may be a memory location. + LOperand* Temp(); + // Temporary operand that must be in a register. + LUnallocated* TempRegister(); + LOperand* FixedTemp(Register reg); + LOperand* FixedTemp(DoubleRegister reg); + + void VisitInstruction(HInstruction* current); + + void DoBasicBlock(HBasicBlock* block, HBasicBlock* next_block); + LInstruction* DoBit(Token::Value op, HBitwiseBinaryOperation* instr); + LInstruction* DoShift(Token::Value op, HBitwiseBinaryOperation* instr); + LInstruction* DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr); + LInstruction* DoArithmeticT(Token::Value op, + HArithmeticBinaryOperation* instr); + + LChunk* chunk_; + HGraph* const graph_; + Status status_; + HInstruction* current_instruction_; + HBasicBlock* current_block_; + HBasicBlock* next_block_; + int argument_count_; + LAllocator* allocator_; + int position_; + LInstruction* instructions_pending_deoptimization_environment_; + int pending_deoptimization_ast_id_; + + DISALLOW_COPY_AND_ASSIGN(LChunkBuilder); +}; + +#undef DECLARE_HYDROGEN_ACCESSOR +#undef DECLARE_INSTRUCTION +#undef DECLARE_CONCRETE_INSTRUCTION + +} } // namespace v8::internal + +#endif // V8_ARM_LITHIUM_ARM_H_ diff --git a/src/arm/lithium-codegen-arm.cc b/src/arm/lithium-codegen-arm.cc new file mode 100644 index 00000000..dfc48917 --- /dev/null +++ b/src/arm/lithium-codegen-arm.cc @@ -0,0 +1,2172 @@ +// Copyright 2010 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. + +#include "arm/lithium-codegen-arm.h" +#include "code-stubs.h" +#include "stub-cache.h" + +namespace v8 { +namespace internal { + + +class SafepointGenerator : public PostCallGenerator { + public: + SafepointGenerator(LCodeGen* codegen, + LPointerMap* pointers, + int deoptimization_index) + : codegen_(codegen), + pointers_(pointers), + deoptimization_index_(deoptimization_index) { } + virtual ~SafepointGenerator() { } + + virtual void Generate() { + codegen_->RecordSafepoint(pointers_, deoptimization_index_); + } + + private: + LCodeGen* codegen_; + LPointerMap* pointers_; + int deoptimization_index_; +}; + + +#define __ masm()-> + +bool LCodeGen::GenerateCode() { + HPhase phase("Code generation", chunk()); + ASSERT(is_unused()); + status_ = GENERATING; + CpuFeatures::Scope scope1(VFP3); + CpuFeatures::Scope scope2(ARMv7); + return GeneratePrologue() && + GenerateBody() && + GenerateDeferredCode() && + GenerateSafepointTable(); +} + + +void LCodeGen::FinishCode(Handle<Code> code) { + ASSERT(is_done()); + code->set_stack_slots(StackSlotCount()); + code->set_safepoint_table_start(safepoints_.GetCodeOffset()); + PopulateDeoptimizationData(code); +} + + +void LCodeGen::Abort(const char* format, ...) { + if (FLAG_trace_bailout) { + SmartPointer<char> debug_name = graph()->debug_name()->ToCString(); + PrintF("Aborting LCodeGen in @\"%s\": ", *debug_name); + va_list arguments; + va_start(arguments, format); + OS::VPrint(format, arguments); + va_end(arguments); + PrintF("\n"); + } + status_ = ABORTED; +} + + +void LCodeGen::Comment(const char* format, ...) { + if (!FLAG_code_comments) return; + char buffer[4 * KB]; + StringBuilder builder(buffer, ARRAY_SIZE(buffer)); + va_list arguments; + va_start(arguments, format); + builder.AddFormattedList(format, arguments); + va_end(arguments); + + // Copy the string before recording it in the assembler to avoid + // issues when the stack allocated buffer goes out of scope. + size_t length = builder.position(); + Vector<char> copy = Vector<char>::New(length + 1); + memcpy(copy.start(), builder.Finalize(), copy.length()); + masm()->RecordComment(copy.start()); +} + + +bool LCodeGen::GeneratePrologue() { + ASSERT(is_generating()); + +#ifdef DEBUG + if (strlen(FLAG_stop_at) > 0 && + info_->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) { + __ stop("stop_at"); + } +#endif + + // r1: Callee's JS function. + // cp: Callee's context. + // fp: Caller's frame pointer. + // lr: Caller's pc. + + __ stm(db_w, sp, r1.bit() | cp.bit() | fp.bit() | lr.bit()); + __ add(fp, sp, Operand(2 * kPointerSize)); // Adjust FP to point to saved FP. + + // Reserve space for the stack slots needed by the code. + int slots = StackSlotCount(); + if (slots > 0) { + if (FLAG_debug_code) { + __ mov(r0, Operand(slots)); + __ mov(r2, Operand(kSlotsZapValue)); + Label loop; + __ bind(&loop); + __ push(r2); + __ sub(r0, r0, Operand(1), SetCC); + __ b(ne, &loop); + } else { + __ sub(sp, sp, Operand(slots * kPointerSize)); + } + } + + // Trace the call. + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + return !is_aborted(); +} + + +bool LCodeGen::GenerateBody() { + ASSERT(is_generating()); + bool emit_instructions = true; + for (current_instruction_ = 0; + !is_aborted() && current_instruction_ < instructions_->length(); + current_instruction_++) { + LInstruction* instr = instructions_->at(current_instruction_); + if (instr->IsLabel()) { + LLabel* label = LLabel::cast(instr); + emit_instructions = !label->HasReplacement(); + } + + if (emit_instructions) { + Comment(";;; @%d: %s.", current_instruction_, instr->Mnemonic()); + instr->CompileToNative(this); + } + } + return !is_aborted(); +} + + +LInstruction* LCodeGen::GetNextInstruction() { + if (current_instruction_ < instructions_->length() - 1) { + return instructions_->at(current_instruction_ + 1); + } else { + return NULL; + } +} + + +bool LCodeGen::GenerateDeferredCode() { + ASSERT(is_generating()); + for (int i = 0; !is_aborted() && i < deferred_.length(); i++) { + LDeferredCode* code = deferred_[i]; + __ bind(code->entry()); + code->Generate(); + __ jmp(code->exit()); + } + + // Deferred code is the last part of the instruction sequence. Mark + // the generated code as done unless we bailed out. + if (!is_aborted()) status_ = DONE; + return !is_aborted(); +} + + +bool LCodeGen::GenerateSafepointTable() { + ASSERT(is_done()); + safepoints_.Emit(masm(), StackSlotCount()); + return !is_aborted(); +} + + +Register LCodeGen::ToRegister(int index) const { + return Register::FromAllocationIndex(index); +} + + +DoubleRegister LCodeGen::ToDoubleRegister(int index) const { + return DoubleRegister::FromAllocationIndex(index); +} + + +Register LCodeGen::ToRegister(LOperand* op) const { + ASSERT(op->IsRegister()); + return ToRegister(op->index()); +} + + +Register LCodeGen::EmitLoadRegister(LOperand* op, Register scratch) { + if (op->IsRegister()) { + return ToRegister(op->index()); + } else if (op->IsConstantOperand()) { + __ mov(scratch, ToOperand(op)); + return scratch; + } else if (op->IsStackSlot() || op->IsArgument()) { + __ ldr(scratch, ToMemOperand(op)); + return scratch; + } + UNREACHABLE(); + return scratch; +} + + +DoubleRegister LCodeGen::ToDoubleRegister(LOperand* op) const { + ASSERT(op->IsDoubleRegister()); + return ToDoubleRegister(op->index()); +} + + +DoubleRegister LCodeGen::EmitLoadDoubleRegister(LOperand* op, + SwVfpRegister flt_scratch, + DoubleRegister dbl_scratch) { + if (op->IsDoubleRegister()) { + return ToDoubleRegister(op->index()); + } else if (op->IsConstantOperand()) { + LConstantOperand* const_op = LConstantOperand::cast(op); + Handle<Object> literal = chunk_->LookupLiteral(const_op); + Representation r = chunk_->LookupLiteralRepresentation(const_op); + if (r.IsInteger32()) { + ASSERT(literal->IsNumber()); + __ mov(ip, Operand(static_cast<int32_t>(literal->Number()))); + __ vmov(flt_scratch, ip); + __ vcvt_f64_s32(dbl_scratch, flt_scratch); + return dbl_scratch; + } else if (r.IsDouble()) { + Abort("unsupported double immediate"); + } else if (r.IsTagged()) { + Abort("unsupported tagged immediate"); + } + } else if (op->IsStackSlot() || op->IsArgument()) { + // TODO(regis): Why is vldr not taking a MemOperand? + // __ vldr(dbl_scratch, ToMemOperand(op)); + MemOperand mem_op = ToMemOperand(op); + __ vldr(dbl_scratch, mem_op.rn(), mem_op.offset()); + return dbl_scratch; + } + UNREACHABLE(); + return dbl_scratch; +} + + +int LCodeGen::ToInteger32(LConstantOperand* op) const { + Handle<Object> value = chunk_->LookupLiteral(op); + ASSERT(chunk_->LookupLiteralRepresentation(op).IsInteger32()); + ASSERT(static_cast<double>(static_cast<int32_t>(value->Number())) == + value->Number()); + return static_cast<int32_t>(value->Number()); +} + + +Operand LCodeGen::ToOperand(LOperand* op) { + if (op->IsConstantOperand()) { + LConstantOperand* const_op = LConstantOperand::cast(op); + Handle<Object> literal = chunk_->LookupLiteral(const_op); + Representation r = chunk_->LookupLiteralRepresentation(const_op); + if (r.IsInteger32()) { + ASSERT(literal->IsNumber()); + return Operand(static_cast<int32_t>(literal->Number())); + } else if (r.IsDouble()) { + Abort("ToOperand Unsupported double immediate."); + } + ASSERT(r.IsTagged()); + return Operand(literal); + } else if (op->IsRegister()) { + return Operand(ToRegister(op)); + } else if (op->IsDoubleRegister()) { + Abort("ToOperand IsDoubleRegister unimplemented"); + return Operand(0); + } + // Stack slots not implemented, use ToMemOperand instead. + UNREACHABLE(); + return Operand(0); +} + + +MemOperand LCodeGen::ToMemOperand(LOperand* op) const { + // TODO(regis): Revisit. + ASSERT(!op->IsRegister()); + ASSERT(!op->IsDoubleRegister()); + ASSERT(op->IsStackSlot() || op->IsDoubleStackSlot()); + int index = op->index(); + if (index >= 0) { + // Local or spill slot. Skip the frame pointer, function, and + // context in the fixed part of the frame. + return MemOperand(fp, -(index + 3) * kPointerSize); + } else { + // Incoming parameter. Skip the return address. + return MemOperand(fp, -(index - 1) * kPointerSize); + } +} + + +void LCodeGen::AddToTranslation(Translation* translation, + LOperand* op, + bool is_tagged) { + if (op == NULL) { + // TODO(twuerthinger): Introduce marker operands to indicate that this value + // is not present and must be reconstructed from the deoptimizer. Currently + // this is only used for the arguments object. + translation->StoreArgumentsObject(); + } else if (op->IsStackSlot()) { + if (is_tagged) { + translation->StoreStackSlot(op->index()); + } else { + translation->StoreInt32StackSlot(op->index()); + } + } else if (op->IsDoubleStackSlot()) { + translation->StoreDoubleStackSlot(op->index()); + } else if (op->IsArgument()) { + ASSERT(is_tagged); + int src_index = StackSlotCount() + op->index(); + translation->StoreStackSlot(src_index); + } else if (op->IsRegister()) { + Register reg = ToRegister(op); + if (is_tagged) { + translation->StoreRegister(reg); + } else { + translation->StoreInt32Register(reg); + } + } else if (op->IsDoubleRegister()) { + DoubleRegister reg = ToDoubleRegister(op); + translation->StoreDoubleRegister(reg); + } else if (op->IsConstantOperand()) { + Handle<Object> literal = chunk()->LookupLiteral(LConstantOperand::cast(op)); + int src_index = DefineDeoptimizationLiteral(literal); + translation->StoreLiteral(src_index); + } else { + UNREACHABLE(); + } +} + + +void LCodeGen::CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr) { + if (instr != NULL) { + LPointerMap* pointers = instr->pointer_map(); + RecordPosition(pointers->position()); + __ Call(code, mode); + RegisterLazyDeoptimization(instr); + } else { + LPointerMap no_pointers(0); + RecordPosition(no_pointers.position()); + __ Call(code, mode); + RecordSafepoint(&no_pointers, Safepoint::kNoDeoptimizationIndex); + } +} + + +void LCodeGen::CallRuntime(Runtime::Function* function, + int num_arguments, + LInstruction* instr) { + ASSERT(instr != NULL); + LPointerMap* pointers = instr->pointer_map(); + ASSERT(pointers != NULL); + RecordPosition(pointers->position()); + + __ CallRuntime(function, num_arguments); + // Runtime calls to Throw are not supposed to ever return at the + // call site, so don't register lazy deoptimization for these. We do + // however have to record a safepoint since throwing exceptions can + // cause garbage collections. + if (!instr->IsThrow()) { + RegisterLazyDeoptimization(instr); + } else { + RecordSafepoint(instr->pointer_map(), Safepoint::kNoDeoptimizationIndex); + } +} + + +void LCodeGen::RegisterLazyDeoptimization(LInstruction* instr) { + // Create the environment to bailout to. If the call has side effects + // execution has to continue after the call otherwise execution can continue + // from a previous bailout point repeating the call. + LEnvironment* deoptimization_environment; + if (instr->HasDeoptimizationEnvironment()) { + deoptimization_environment = instr->deoptimization_environment(); + } else { + deoptimization_environment = instr->environment(); + } + + RegisterEnvironmentForDeoptimization(deoptimization_environment); + RecordSafepoint(instr->pointer_map(), + deoptimization_environment->deoptimization_index()); +} + + +void LCodeGen::RegisterEnvironmentForDeoptimization(LEnvironment* environment) { + if (!environment->HasBeenRegistered()) { + // Physical stack frame layout: + // -x ............. -4 0 ..................................... y + // [incoming arguments] [spill slots] [pushed outgoing arguments] + + // Layout of the environment: + // 0 ..................................................... size-1 + // [parameters] [locals] [expression stack including arguments] + + // Layout of the translation: + // 0 ........................................................ size - 1 + 4 + // [expression stack including arguments] [locals] [4 words] [parameters] + // |>------------ translation_size ------------<| + + int frame_count = 0; + for (LEnvironment* e = environment; e != NULL; e = e->outer()) { + ++frame_count; + } + Translation translation(&translations_, frame_count); + environment->WriteTranslation(this, &translation); + int deoptimization_index = deoptimizations_.length(); + environment->Register(deoptimization_index, translation.index()); + deoptimizations_.Add(environment); + } +} + + +void LCodeGen::DeoptimizeIf(Condition cc, LEnvironment* environment) { + RegisterEnvironmentForDeoptimization(environment); + ASSERT(environment->HasBeenRegistered()); + int id = environment->deoptimization_index(); + Address entry = Deoptimizer::GetDeoptimizationEntry(id, Deoptimizer::EAGER); + ASSERT(entry != NULL); + if (entry == NULL) { + Abort("bailout was not prepared"); + return; + } + + ASSERT(FLAG_deopt_every_n_times < 2); // Other values not supported on ARM. + + if (FLAG_deopt_every_n_times == 1 && + info_->shared_info()->opt_count() == id) { + __ Jump(entry, RelocInfo::RUNTIME_ENTRY); + return; + } + + if (cc == no_condition) { + if (FLAG_trap_on_deopt) __ stop("trap_on_deopt"); + __ Jump(entry, RelocInfo::RUNTIME_ENTRY); + } else { + if (FLAG_trap_on_deopt) { + Label done; + __ b(&done, NegateCondition(cc)); + __ stop("trap_on_deopt"); + __ Jump(entry, RelocInfo::RUNTIME_ENTRY); + __ bind(&done); + } else { + __ Jump(entry, RelocInfo::RUNTIME_ENTRY, cc); + } + } +} + + +void LCodeGen::PopulateDeoptimizationData(Handle<Code> code) { + int length = deoptimizations_.length(); + if (length == 0) return; + ASSERT(FLAG_deopt); + Handle<DeoptimizationInputData> data = + Factory::NewDeoptimizationInputData(length, TENURED); + + data->SetTranslationByteArray(*translations_.CreateByteArray()); + data->SetInlinedFunctionCount(Smi::FromInt(inlined_function_count_)); + + Handle<FixedArray> literals = + Factory::NewFixedArray(deoptimization_literals_.length(), TENURED); + for (int i = 0; i < deoptimization_literals_.length(); i++) { + literals->set(i, *deoptimization_literals_[i]); + } + data->SetLiteralArray(*literals); + + data->SetOsrAstId(Smi::FromInt(info_->osr_ast_id())); + data->SetOsrPcOffset(Smi::FromInt(osr_pc_offset_)); + + // Populate the deoptimization entries. + for (int i = 0; i < length; i++) { + LEnvironment* env = deoptimizations_[i]; + data->SetAstId(i, Smi::FromInt(env->ast_id())); + data->SetTranslationIndex(i, Smi::FromInt(env->translation_index())); + data->SetArgumentsStackHeight(i, + Smi::FromInt(env->arguments_stack_height())); + } + code->set_deoptimization_data(*data); +} + + +int LCodeGen::DefineDeoptimizationLiteral(Handle<Object> literal) { + int result = deoptimization_literals_.length(); + for (int i = 0; i < deoptimization_literals_.length(); ++i) { + if (deoptimization_literals_[i].is_identical_to(literal)) return i; + } + deoptimization_literals_.Add(literal); + return result; +} + + +void LCodeGen::PopulateDeoptimizationLiteralsWithInlinedFunctions() { + ASSERT(deoptimization_literals_.length() == 0); + + const ZoneList<Handle<JSFunction> >* inlined_closures = + chunk()->inlined_closures(); + + for (int i = 0, length = inlined_closures->length(); + i < length; + i++) { + DefineDeoptimizationLiteral(inlined_closures->at(i)); + } + + inlined_function_count_ = deoptimization_literals_.length(); +} + + +void LCodeGen::RecordSafepoint(LPointerMap* pointers, + int deoptimization_index) { + const ZoneList<LOperand*>* operands = pointers->operands(); + Safepoint safepoint = safepoints_.DefineSafepoint(masm(), + deoptimization_index); + for (int i = 0; i < operands->length(); i++) { + LOperand* pointer = operands->at(i); + if (pointer->IsStackSlot()) { + safepoint.DefinePointerSlot(pointer->index()); + } + } +} + + +void LCodeGen::RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + int deoptimization_index) { + const ZoneList<LOperand*>* operands = pointers->operands(); + Safepoint safepoint = + safepoints_.DefineSafepointWithRegisters( + masm(), arguments, deoptimization_index); + for (int i = 0; i < operands->length(); i++) { + LOperand* pointer = operands->at(i); + if (pointer->IsStackSlot()) { + safepoint.DefinePointerSlot(pointer->index()); + } else if (pointer->IsRegister()) { + safepoint.DefinePointerRegister(ToRegister(pointer)); + } + } + // Register cp always contains a pointer to the context. + safepoint.DefinePointerRegister(cp); +} + + +void LCodeGen::RecordPosition(int position) { + if (!FLAG_debug_info || position == RelocInfo::kNoPosition) return; + masm()->positions_recorder()->RecordPosition(position); +} + + +void LCodeGen::DoLabel(LLabel* label) { + if (label->is_loop_header()) { + Comment(";;; B%d - LOOP entry", label->block_id()); + } else { + Comment(";;; B%d", label->block_id()); + } + __ bind(label->label()); + current_block_ = label->block_id(); + LCodeGen::DoGap(label); +} + + +void LCodeGen::DoParallelMove(LParallelMove* move) { + // d0 must always be a scratch register. + DoubleRegister dbl_scratch = d0; + LUnallocated marker_operand(LUnallocated::NONE); + + Register core_scratch = r9; + bool destroys_core_scratch = false; + + LGapResolver resolver(move->move_operands(), &marker_operand); + const ZoneList<LMoveOperands>* moves = resolver.ResolveInReverseOrder(); + for (int i = moves->length() - 1; i >= 0; --i) { + LMoveOperands move = moves->at(i); + LOperand* from = move.from(); + LOperand* to = move.to(); + ASSERT(!from->IsDoubleRegister() || + !ToDoubleRegister(from).is(dbl_scratch)); + ASSERT(!to->IsDoubleRegister() || !ToDoubleRegister(to).is(dbl_scratch)); + ASSERT(!from->IsRegister() || !ToRegister(from).is(core_scratch)); + ASSERT(!to->IsRegister() || !ToRegister(to).is(core_scratch)); + if (from == &marker_operand) { + if (to->IsRegister()) { + __ mov(ToRegister(to), core_scratch); + ASSERT(destroys_core_scratch); + } else if (to->IsStackSlot()) { + __ str(core_scratch, ToMemOperand(to)); + ASSERT(destroys_core_scratch); + } else if (to->IsDoubleRegister()) { + __ vmov(ToDoubleRegister(to), dbl_scratch); + } else { + ASSERT(to->IsDoubleStackSlot()); + // TODO(regis): Why is vstr not taking a MemOperand? + // __ vstr(dbl_scratch, ToMemOperand(to)); + MemOperand to_operand = ToMemOperand(to); + __ vstr(dbl_scratch, to_operand.rn(), to_operand.offset()); + } + } else if (to == &marker_operand) { + if (from->IsRegister() || from->IsConstantOperand()) { + __ mov(core_scratch, ToOperand(from)); + destroys_core_scratch = true; + } else if (from->IsStackSlot()) { + __ ldr(core_scratch, ToMemOperand(from)); + destroys_core_scratch = true; + } else if (from->IsDoubleRegister()) { + __ vmov(dbl_scratch, ToDoubleRegister(from)); + } else { + ASSERT(from->IsDoubleStackSlot()); + // TODO(regis): Why is vldr not taking a MemOperand? + // __ vldr(dbl_scratch, ToMemOperand(from)); + MemOperand from_operand = ToMemOperand(from); + __ vldr(dbl_scratch, from_operand.rn(), from_operand.offset()); + } + } else if (from->IsConstantOperand()) { + if (to->IsRegister()) { + __ mov(ToRegister(to), ToOperand(from)); + } else { + ASSERT(to->IsStackSlot()); + __ mov(ip, ToOperand(from)); + __ str(ip, ToMemOperand(to)); + } + } else if (from->IsRegister()) { + if (to->IsRegister()) { + __ mov(ToRegister(to), ToOperand(from)); + } else { + ASSERT(to->IsStackSlot()); + __ str(ToRegister(from), ToMemOperand(to)); + } + } else if (to->IsRegister()) { + ASSERT(from->IsStackSlot()); + __ ldr(ToRegister(to), ToMemOperand(from)); + } else if (from->IsStackSlot()) { + ASSERT(to->IsStackSlot()); + __ ldr(ip, ToMemOperand(from)); + __ str(ip, ToMemOperand(to)); + } else if (from->IsDoubleRegister()) { + if (to->IsDoubleRegister()) { + __ vmov(ToDoubleRegister(to), ToDoubleRegister(from)); + } else { + ASSERT(to->IsDoubleStackSlot()); + // TODO(regis): Why is vstr not taking a MemOperand? + // __ vstr(dbl_scratch, ToMemOperand(to)); + MemOperand to_operand = ToMemOperand(to); + __ vstr(ToDoubleRegister(from), to_operand.rn(), to_operand.offset()); + } + } else if (to->IsDoubleRegister()) { + ASSERT(from->IsDoubleStackSlot()); + // TODO(regis): Why is vldr not taking a MemOperand? + // __ vldr(ToDoubleRegister(to), ToMemOperand(from)); + MemOperand from_operand = ToMemOperand(from); + __ vldr(ToDoubleRegister(to), from_operand.rn(), from_operand.offset()); + } else { + ASSERT(to->IsDoubleStackSlot() && from->IsDoubleStackSlot()); + // TODO(regis): Why is vldr not taking a MemOperand? + // __ vldr(dbl_scratch, ToMemOperand(from)); + MemOperand from_operand = ToMemOperand(from); + __ vldr(dbl_scratch, from_operand.rn(), from_operand.offset()); + // TODO(regis): Why is vstr not taking a MemOperand? + // __ vstr(dbl_scratch, ToMemOperand(to)); + MemOperand to_operand = ToMemOperand(to); + __ vstr(dbl_scratch, to_operand.rn(), to_operand.offset()); + } + } + + if (destroys_core_scratch) { + __ ldr(core_scratch, MemOperand(fp, -kPointerSize)); + } + + LInstruction* next = GetNextInstruction(); + if (next != NULL && next->IsLazyBailout()) { + int pc = masm()->pc_offset(); + safepoints_.SetPcAfterGap(pc); + } +} + + +void LCodeGen::DoGap(LGap* gap) { + for (int i = LGap::FIRST_INNER_POSITION; + i <= LGap::LAST_INNER_POSITION; + i++) { + LGap::InnerPosition inner_pos = static_cast<LGap::InnerPosition>(i); + LParallelMove* move = gap->GetParallelMove(inner_pos); + if (move != NULL) DoParallelMove(move); + } + + LInstruction* next = GetNextInstruction(); + if (next != NULL && next->IsLazyBailout()) { + int pc = masm()->pc_offset(); + safepoints_.SetPcAfterGap(pc); + } +} + + +void LCodeGen::DoParameter(LParameter* instr) { + // Nothing to do. +} + + +void LCodeGen::DoCallStub(LCallStub* instr) { + Abort("DoCallStub unimplemented."); +} + + +void LCodeGen::DoUnknownOSRValue(LUnknownOSRValue* instr) { + // Nothing to do. +} + + +void LCodeGen::DoModI(LModI* instr) { + Abort("DoModI unimplemented."); +} + + +void LCodeGen::DoDivI(LDivI* instr) { + Abort("DoDivI unimplemented."); +} + + +void LCodeGen::DoMulI(LMulI* instr) { + Register left = ToRegister(instr->left()); + Register scratch = r9; + Register right = EmitLoadRegister(instr->right(), scratch); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero) && + !instr->right()->IsConstantOperand()) { + __ orr(ToRegister(instr->temp()), left, right); + } + + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + // scratch:left = left * right. + __ smull(scratch, left, left, right); + __ mov(ip, Operand(left, ASR, 31)); + __ cmp(ip, Operand(scratch)); + DeoptimizeIf(ne, instr->environment()); + } else { + __ mul(left, left, right); + } + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + // Bail out if the result is supposed to be negative zero. + Label done; + __ tst(left, Operand(left)); + __ b(ne, &done); + if (instr->right()->IsConstantOperand()) { + if (ToInteger32(LConstantOperand::cast(instr->right())) < 0) { + DeoptimizeIf(no_condition, instr->environment()); + } + } else { + // Test the non-zero operand for negative sign. + __ cmp(ToRegister(instr->temp()), Operand(0)); + DeoptimizeIf(mi, instr->environment()); + } + __ bind(&done); + } +} + + +void LCodeGen::DoBitI(LBitI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + ASSERT(left->IsRegister()); + Register result = ToRegister(left); + Register right_reg = EmitLoadRegister(right, ip); + switch (instr->op()) { + case Token::BIT_AND: + __ and_(result, ToRegister(left), Operand(right_reg)); + break; + case Token::BIT_OR: + __ orr(result, ToRegister(left), Operand(right_reg)); + break; + case Token::BIT_XOR: + __ eor(result, ToRegister(left), Operand(right_reg)); + break; + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoShiftI(LShiftI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + ASSERT(left->IsRegister()); + Register result = ToRegister(left); + if (right->IsRegister()) { + // Mask the right operand. + __ and_(r9, ToRegister(right), Operand(0x1F)); + switch (instr->op()) { + case Token::SAR: + __ mov(result, Operand(result, ASR, r9)); + break; + case Token::SHR: + if (instr->can_deopt()) { + __ mov(result, Operand(result, LSR, r9), SetCC); + DeoptimizeIf(mi, instr->environment()); + } else { + __ mov(result, Operand(result, LSR, r9)); + } + break; + case Token::SHL: + __ mov(result, Operand(result, LSL, r9)); + break; + default: + UNREACHABLE(); + break; + } + } else { + int value = ToInteger32(LConstantOperand::cast(right)); + uint8_t shift_count = static_cast<uint8_t>(value & 0x1F); + switch (instr->op()) { + case Token::SAR: + if (shift_count != 0) { + __ mov(result, Operand(result, ASR, shift_count)); + } + break; + case Token::SHR: + if (shift_count == 0 && instr->can_deopt()) { + __ tst(result, Operand(0x80000000)); + DeoptimizeIf(ne, instr->environment()); + } else { + __ mov(result, Operand(result, LSR, shift_count)); + } + break; + case Token::SHL: + if (shift_count != 0) { + __ mov(result, Operand(result, LSL, shift_count)); + } + break; + default: + UNREACHABLE(); + break; + } + } +} + + +void LCodeGen::DoSubI(LSubI* instr) { + Register left = ToRegister(instr->left()); + Register right = EmitLoadRegister(instr->right(), ip); + ASSERT(instr->left()->Equals(instr->result())); + __ sub(left, left, right, SetCC); + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + DeoptimizeIf(vs, instr->environment()); + } +} + + +void LCodeGen::DoConstantI(LConstantI* instr) { + ASSERT(instr->result()->IsRegister()); + __ mov(ToRegister(instr->result()), Operand(instr->value())); +} + + +void LCodeGen::DoConstantD(LConstantD* instr) { + Abort("DoConstantD unimplemented."); +} + + +void LCodeGen::DoConstantT(LConstantT* instr) { + ASSERT(instr->result()->IsRegister()); + __ mov(ToRegister(instr->result()), Operand(instr->value())); +} + + +void LCodeGen::DoArrayLength(LArrayLength* instr) { + Register result = ToRegister(instr->result()); + + if (instr->hydrogen()->value()->IsLoadElements()) { + // We load the length directly from the elements array. + Register elements = ToRegister(instr->input()); + __ ldr(result, FieldMemOperand(elements, FixedArray::kLengthOffset)); + } else { + // Check that the receiver really is an array. + Register array = ToRegister(instr->input()); + Register temporary = ToRegister(instr->temporary()); + __ CompareObjectType(array, temporary, temporary, JS_ARRAY_TYPE); + DeoptimizeIf(ne, instr->environment()); + + // Load length directly from the array. + __ ldr(result, FieldMemOperand(array, JSArray::kLengthOffset)); + } + Abort("DoArrayLength untested."); +} + + +void LCodeGen::DoValueOf(LValueOf* instr) { + Abort("DoValueOf unimplemented."); +} + + +void LCodeGen::DoBitNotI(LBitNotI* instr) { + LOperand* input = instr->input(); + ASSERT(input->Equals(instr->result())); + __ mvn(ToRegister(input), Operand(ToRegister(input))); + Abort("DoBitNotI untested."); +} + + +void LCodeGen::DoThrow(LThrow* instr) { + Register input_reg = EmitLoadRegister(instr->input(), ip); + __ push(input_reg); + CallRuntime(Runtime::kThrow, 1, instr); + + if (FLAG_debug_code) { + __ stop("Unreachable code."); + } +} + + +void LCodeGen::DoAddI(LAddI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + + Register right_reg = EmitLoadRegister(right, ip); + __ add(ToRegister(left), ToRegister(left), Operand(right_reg), SetCC); + + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + DeoptimizeIf(vs, instr->environment()); + } +} + + +void LCodeGen::DoArithmeticD(LArithmeticD* instr) { + DoubleRegister left = ToDoubleRegister(instr->left()); + DoubleRegister right = ToDoubleRegister(instr->right()); + switch (instr->op()) { + case Token::ADD: + __ vadd(left, left, right); + break; + case Token::SUB: + __ vsub(left, left, right); + break; + case Token::MUL: + __ vmul(left, left, right); + break; + case Token::DIV: + __ vdiv(left, left, right); + break; + case Token::MOD: { + Abort("DoArithmeticD unimplemented for MOD."); + break; + } + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoArithmeticT(LArithmeticT* instr) { + ASSERT(ToRegister(instr->left()).is(r1)); + ASSERT(ToRegister(instr->right()).is(r0)); + ASSERT(ToRegister(instr->result()).is(r0)); + + // TODO(regis): Implement TypeRecordingBinaryOpStub and replace current + // GenericBinaryOpStub: + // TypeRecordingBinaryOpStub stub(instr->op(), NO_OVERWRITE); + GenericBinaryOpStub stub(instr->op(), NO_OVERWRITE, r1, r0); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +int LCodeGen::GetNextEmittedBlock(int block) { + for (int i = block + 1; i < graph()->blocks()->length(); ++i) { + LLabel* label = chunk_->GetLabel(i); + if (!label->HasReplacement()) return i; + } + return -1; +} + + +void LCodeGen::EmitBranch(int left_block, int right_block, Condition cc) { + int next_block = GetNextEmittedBlock(current_block_); + right_block = chunk_->LookupDestination(right_block); + left_block = chunk_->LookupDestination(left_block); + + if (right_block == left_block) { + EmitGoto(left_block); + } else if (left_block == next_block) { + __ b(NegateCondition(cc), chunk_->GetAssemblyLabel(right_block)); + } else if (right_block == next_block) { + __ b(cc, chunk_->GetAssemblyLabel(left_block)); + } else { + __ b(cc, chunk_->GetAssemblyLabel(left_block)); + __ b(chunk_->GetAssemblyLabel(right_block)); + } +} + + +void LCodeGen::DoBranch(LBranch* instr) { + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Representation r = instr->hydrogen()->representation(); + if (r.IsInteger32()) { + Register reg = ToRegister(instr->input()); + __ cmp(reg, Operand(0)); + EmitBranch(true_block, false_block, nz); + } else if (r.IsDouble()) { + DoubleRegister reg = ToDoubleRegister(instr->input()); + __ vcmp(reg, 0.0); + EmitBranch(true_block, false_block, ne); + } else { + ASSERT(r.IsTagged()); + Register reg = ToRegister(instr->input()); + if (instr->hydrogen()->type().IsBoolean()) { + __ LoadRoot(ip, Heap::kTrueValueRootIndex); + __ cmp(reg, ip); + EmitBranch(true_block, false_block, eq); + } else { + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); + __ cmp(reg, ip); + __ b(eq, false_label); + __ LoadRoot(ip, Heap::kTrueValueRootIndex); + __ cmp(reg, ip); + __ b(eq, true_label); + __ LoadRoot(ip, Heap::kFalseValueRootIndex); + __ cmp(reg, ip); + __ b(eq, false_label); + __ cmp(reg, Operand(0)); + __ b(eq, false_label); + __ tst(reg, Operand(kSmiTagMask)); + __ b(eq, true_label); + + // Test for double values. Zero is false. + Label call_stub; + DoubleRegister dbl_scratch = d0; + Register core_scratch = r9; + ASSERT(!reg.is(core_scratch)); + __ ldr(core_scratch, FieldMemOperand(reg, HeapObject::kMapOffset)); + __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex); + __ cmp(core_scratch, Operand(ip)); + __ b(ne, &call_stub); + __ sub(ip, reg, Operand(kHeapObjectTag)); + __ vldr(dbl_scratch, ip, HeapNumber::kValueOffset); + __ vcmp(dbl_scratch, 0.0); + __ b(eq, false_label); + __ b(true_label); + + // The conversion stub doesn't cause garbage collections so it's + // safe to not record a safepoint after the call. + __ bind(&call_stub); + ToBooleanStub stub(reg); + RegList saved_regs = kJSCallerSaved | kCalleeSaved; + __ stm(db_w, sp, saved_regs); + __ CallStub(&stub); + __ cmp(reg, Operand(0)); + __ ldm(ia_w, sp, saved_regs); + EmitBranch(true_block, false_block, nz); + } + } +} + + +void LCodeGen::EmitGoto(int block, LDeferredCode* deferred_stack_check) { + // TODO(srdjan): Perform stack overflow check if this goto needs it + // before jumping. + block = chunk_->LookupDestination(block); + int next_block = GetNextEmittedBlock(current_block_); + if (block != next_block) { + __ jmp(chunk_->GetAssemblyLabel(block)); + } +} + + +void LCodeGen::DoDeferredStackCheck(LGoto* instr) { + UNIMPLEMENTED(); +} + + +void LCodeGen::DoGoto(LGoto* instr) { + // TODO(srdjan): Implement deferred stack check. + EmitGoto(instr->block_id(), NULL); +} + + +Condition LCodeGen::TokenToCondition(Token::Value op, bool is_unsigned) { + Condition cond = no_condition; + switch (op) { + case Token::EQ: + case Token::EQ_STRICT: + cond = eq; + break; + case Token::LT: + cond = is_unsigned ? lo : lt; + break; + case Token::GT: + cond = is_unsigned ? hi : gt; + break; + case Token::LTE: + cond = is_unsigned ? ls : le; + break; + case Token::GTE: + cond = is_unsigned ? hs : ge; + break; + case Token::IN: + case Token::INSTANCEOF: + default: + UNREACHABLE(); + } + return cond; +} + + +void LCodeGen::EmitCmpI(LOperand* left, LOperand* right) { + __ cmp(ToRegister(left), ToOperand(right)); + Abort("EmitCmpI untested."); +} + + +void LCodeGen::DoCmpID(LCmpID* instr) { + Abort("DoCmpID unimplemented."); +} + + +void LCodeGen::DoCmpIDAndBranch(LCmpIDAndBranch* instr) { + Abort("DoCmpIDAndBranch unimplemented."); +} + + +void LCodeGen::DoCmpJSObjectEq(LCmpJSObjectEq* instr) { + Register left = ToRegister(instr->left()); + Register right = ToRegister(instr->right()); + Register result = ToRegister(instr->result()); + + __ cmp(left, Operand(right)); + __ LoadRoot(result, Heap::kTrueValueRootIndex, eq); + __ LoadRoot(result, Heap::kFalseValueRootIndex, ne); + Abort("DoCmpJSObjectEq untested."); +} + + +void LCodeGen::DoCmpJSObjectEqAndBranch(LCmpJSObjectEqAndBranch* instr) { + Abort("DoCmpJSObjectEqAndBranch unimplemented."); +} + + +void LCodeGen::DoIsNull(LIsNull* instr) { + Abort("DoIsNull unimplemented."); +} + + +void LCodeGen::DoIsNullAndBranch(LIsNullAndBranch* instr) { + Register reg = ToRegister(instr->input()); + + // TODO(fsc): If the expression is known to be a smi, then it's + // definitely not null. Jump to the false block. + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + __ LoadRoot(ip, Heap::kNullValueRootIndex); + __ cmp(reg, ip); + if (instr->is_strict()) { + EmitBranch(true_block, false_block, eq); + } else { + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + __ b(eq, true_label); + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); + __ cmp(reg, ip); + __ b(eq, true_label); + __ tst(reg, Operand(kSmiTagMask)); + __ b(eq, false_label); + // Check for undetectable objects by looking in the bit field in + // the map. The object has already been smi checked. + Register scratch = ToRegister(instr->temp()); + __ ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset)); + __ ldrb(scratch, FieldMemOperand(scratch, Map::kBitFieldOffset)); + __ tst(scratch, Operand(1 << Map::kIsUndetectable)); + EmitBranch(true_block, false_block, ne); + } +} + + +Condition LCodeGen::EmitIsObject(Register input, + Register temp1, + Register temp2, + Label* is_not_object, + Label* is_object) { + Abort("EmitIsObject unimplemented."); + return ne; +} + + +void LCodeGen::DoIsObject(LIsObject* instr) { + Abort("DoIsObject unimplemented."); +} + + +void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { + Abort("DoIsObjectAndBranch unimplemented."); +} + + +void LCodeGen::DoIsSmi(LIsSmi* instr) { + ASSERT(instr->hydrogen()->value()->representation().IsTagged()); + Register result = ToRegister(instr->result()); + Register input_reg = EmitLoadRegister(instr->input(), ip); + __ tst(input_reg, Operand(kSmiTagMask)); + __ LoadRoot(result, Heap::kTrueValueRootIndex); + Label done; + __ b(eq, &done); + __ LoadRoot(result, Heap::kFalseValueRootIndex); + __ bind(&done); +} + + +void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Register input_reg = EmitLoadRegister(instr->input(), ip); + __ tst(input_reg, Operand(kSmiTagMask)); + EmitBranch(true_block, false_block, eq); +} + + +InstanceType LHasInstanceType::TestType() { + InstanceType from = hydrogen()->from(); + InstanceType to = hydrogen()->to(); + if (from == FIRST_TYPE) return to; + ASSERT(from == to || to == LAST_TYPE); + return from; +} + + +Condition LHasInstanceType::BranchCondition() { + InstanceType from = hydrogen()->from(); + InstanceType to = hydrogen()->to(); + if (from == to) return eq; + if (to == LAST_TYPE) return hs; + if (from == FIRST_TYPE) return ls; + UNREACHABLE(); + return eq; +} + + +void LCodeGen::DoHasInstanceType(LHasInstanceType* instr) { + Abort("DoHasInstanceType unimplemented."); +} + + +void LCodeGen::DoHasInstanceTypeAndBranch(LHasInstanceTypeAndBranch* instr) { + Register input = ToRegister(instr->input()); + Register temp = ToRegister(instr->temp()); + + 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); + + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, false_label); + + __ CompareObjectType(input, temp, temp, instr->TestType()); + EmitBranch(true_block, false_block, instr->BranchCondition()); +} + + +void LCodeGen::DoHasCachedArrayIndex(LHasCachedArrayIndex* instr) { + Abort("DoHasCachedArrayIndex unimplemented."); +} + + +void LCodeGen::DoHasCachedArrayIndexAndBranch( + LHasCachedArrayIndexAndBranch* instr) { + Abort("DoHasCachedArrayIndexAndBranch unimplemented."); +} + + +// Branches to a label or falls through with the answer in the z flag. Trashes +// the temp registers, but not the input. Only input and temp2 may alias. +void LCodeGen::EmitClassOfTest(Label* is_true, + Label* is_false, + Handle<String>class_name, + Register input, + Register temp, + Register temp2) { + Abort("EmitClassOfTest unimplemented."); +} + + +void LCodeGen::DoClassOfTest(LClassOfTest* instr) { + Abort("DoClassOfTest unimplemented."); +} + + +void LCodeGen::DoClassOfTestAndBranch(LClassOfTestAndBranch* instr) { + Abort("DoClassOfTestAndBranch unimplemented."); +} + + +void LCodeGen::DoCmpMapAndBranch(LCmpMapAndBranch* instr) { + Abort("DoCmpMapAndBranch unimplemented."); +} + + +void LCodeGen::DoInstanceOf(LInstanceOf* instr) { + // We expect object and function in registers r1 and r0. + InstanceofStub stub(InstanceofStub::kArgsInRegisters); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + + Label true_value, done; + __ tst(r0, r0); + __ mov(r0, Operand(Factory::false_value()), LeaveCC, eq); + __ mov(r0, Operand(Factory::true_value()), LeaveCC, ne); +} + + +void LCodeGen::DoInstanceOfAndBranch(LInstanceOfAndBranch* instr) { + Abort("DoInstanceOfAndBranch unimplemented."); +} + + + +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 no_condition; + } +} + + +void LCodeGen::DoCmpT(LCmpT* instr) { + Token::Value op = instr->op(); + + Handle<Code> ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + Condition condition = ComputeCompareCondition(op); + if (op == Token::GT || op == Token::LTE) { + condition = ReverseCondition(condition); + } + __ cmp(r0, Operand(0)); + __ LoadRoot(ToRegister(instr->result()), Heap::kTrueValueRootIndex, + condition); + __ LoadRoot(ToRegister(instr->result()), Heap::kFalseValueRootIndex, + NegateCondition(condition)); +} + + +void LCodeGen::DoCmpTAndBranch(LCmpTAndBranch* instr) { + Abort("DoCmpTAndBranch unimplemented."); +} + + +void LCodeGen::DoReturn(LReturn* instr) { + if (FLAG_trace) { + // Push the return value on the stack as the parameter. + // Runtime::TraceExit returns its parameter in r0. + __ push(r0); + __ CallRuntime(Runtime::kTraceExit, 1); + } + int32_t sp_delta = (ParameterCount() + 1) * kPointerSize; + __ mov(sp, fp); + __ ldm(ia_w, sp, fp.bit() | lr.bit()); + __ add(sp, sp, Operand(sp_delta)); + __ Jump(lr); +} + + +void LCodeGen::DoLoadGlobal(LLoadGlobal* instr) { + Register result = ToRegister(instr->result()); + __ mov(ip, Operand(Handle<Object>(instr->hydrogen()->cell()))); + __ ldr(result, FieldMemOperand(ip, JSGlobalPropertyCell::kValueOffset)); + if (instr->hydrogen()->check_hole_value()) { + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ cmp(result, ip); + DeoptimizeIf(eq, instr->environment()); + } +} + + +void LCodeGen::DoStoreGlobal(LStoreGlobal* instr) { + Register value = ToRegister(instr->input()); + __ mov(ip, Operand(Handle<Object>(instr->hydrogen()->cell()))); + __ str(value, FieldMemOperand(ip, JSGlobalPropertyCell::kValueOffset)); +} + + +void LCodeGen::DoLoadNamedField(LLoadNamedField* instr) { + Abort("DoLoadNamedField unimplemented."); +} + + +void LCodeGen::DoLoadNamedGeneric(LLoadNamedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(r0)); + ASSERT(ToRegister(instr->result()).is(r0)); + + // Name is always in r2. + __ mov(r2, Operand(instr->name())); + Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoLoadElements(LLoadElements* instr) { + Abort("DoLoadElements unimplemented."); +} + + +void LCodeGen::DoAccessArgumentsAt(LAccessArgumentsAt* instr) { + Abort("DoAccessArgumentsAt unimplemented."); +} + + +void LCodeGen::DoLoadKeyedFastElement(LLoadKeyedFastElement* instr) { + Abort("DoLoadKeyedFastElement unimplemented."); +} + + +void LCodeGen::DoLoadKeyedGeneric(LLoadKeyedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(r1)); + ASSERT(ToRegister(instr->key()).is(r0)); + + Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoArgumentsElements(LArgumentsElements* instr) { + Abort("DoArgumentsElements unimplemented."); +} + + +void LCodeGen::DoArgumentsLength(LArgumentsLength* instr) { + Abort("DoArgumentsLength unimplemented."); +} + + +void LCodeGen::DoApplyArguments(LApplyArguments* instr) { + Abort("DoApplyArguments unimplemented."); +} + + +void LCodeGen::DoPushArgument(LPushArgument* instr) { + LOperand* argument = instr->input(); + if (argument->IsDoubleRegister() || argument->IsDoubleStackSlot()) { + Abort("DoPushArgument not implemented for double type."); + } else { + Register argument_reg = EmitLoadRegister(argument, ip); + __ push(argument_reg); + } +} + + +void LCodeGen::DoGlobalObject(LGlobalObject* instr) { + Register result = ToRegister(instr->result()); + __ ldr(result, ContextOperand(cp, Context::GLOBAL_INDEX)); +} + + +void LCodeGen::DoGlobalReceiver(LGlobalReceiver* instr) { + Register result = ToRegister(instr->result()); + __ ldr(result, ContextOperand(cp, Context::GLOBAL_INDEX)); + __ ldr(result, FieldMemOperand(result, GlobalObject::kGlobalReceiverOffset)); +} + + +void LCodeGen::CallKnownFunction(Handle<JSFunction> function, + int arity, + LInstruction* instr) { + // Change context if needed. + bool change_context = + (graph()->info()->closure()->context() != function->context()) || + scope()->contains_with() || + (scope()->num_heap_slots() > 0); + if (change_context) { + __ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset)); + } + + // Set r0 to arguments count if adaption is not needed. Assumes that r0 + // is available to write to at this point. + if (!function->NeedsArgumentsAdaption()) { + __ mov(r0, Operand(arity)); + } + + LPointerMap* pointers = instr->pointer_map(); + RecordPosition(pointers->position()); + + // Invoke function. + __ ldr(ip, FieldMemOperand(r1, JSFunction::kCodeEntryOffset)); + __ Call(ip); + + // Setup deoptimization. + RegisterLazyDeoptimization(instr); + + // Restore context. + __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallConstantFunction(LCallConstantFunction* instr) { + Abort("DoCallConstantFunction unimplemented."); +} + + +void LCodeGen::DoDeferredMathAbsTaggedHeapNumber(LUnaryMathOperation* instr) { + Abort("DoDeferredMathAbsTaggedHeapNumber unimplemented."); +} + + +void LCodeGen::DoMathAbs(LUnaryMathOperation* instr) { + Abort("DoMathAbs unimplemented."); +} + + +void LCodeGen::DoMathFloor(LUnaryMathOperation* instr) { + Abort("DoMathFloor unimplemented."); +} + + +void LCodeGen::DoMathSqrt(LUnaryMathOperation* instr) { + Abort("DoMathSqrt unimplemented."); +} + + +void LCodeGen::DoUnaryMathOperation(LUnaryMathOperation* instr) { + switch (instr->op()) { + case kMathAbs: + DoMathAbs(instr); + break; + case kMathFloor: + DoMathFloor(instr); + break; + case kMathSqrt: + DoMathSqrt(instr); + break; + default: + Abort("Unimplemented type of LUnaryMathOperation."); + UNREACHABLE(); + } +} + + +void LCodeGen::DoCallKeyed(LCallKeyed* instr) { + Abort("DoCallKeyed unimplemented."); +} + + +void LCodeGen::DoCallNamed(LCallNamed* instr) { + ASSERT(ToRegister(instr->result()).is(r0)); + + int arity = instr->arity(); + Handle<Code> ic = StubCache::ComputeCallInitialize(arity, NOT_IN_LOOP); + __ mov(r2, Operand(instr->name())); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + // Restore context register. + __ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallFunction(LCallFunction* instr) { + Abort("DoCallFunction unimplemented."); +} + + +void LCodeGen::DoCallGlobal(LCallGlobal* instr) { + Abort("DoCallGlobal unimplemented."); +} + + +void LCodeGen::DoCallKnownGlobal(LCallKnownGlobal* instr) { + ASSERT(ToRegister(instr->result()).is(r0)); + __ mov(r1, Operand(instr->target())); + CallKnownFunction(instr->target(), instr->arity(), instr); +} + + +void LCodeGen::DoCallNew(LCallNew* instr) { + ASSERT(ToRegister(instr->input()).is(r1)); + ASSERT(ToRegister(instr->result()).is(r0)); + + Handle<Code> builtin(Builtins::builtin(Builtins::JSConstructCall)); + __ mov(r0, Operand(instr->arity())); + CallCode(builtin, RelocInfo::CONSTRUCT_CALL, instr); +} + + +void LCodeGen::DoCallRuntime(LCallRuntime* instr) { + CallRuntime(instr->function(), instr->arity(), instr); +} + + +void LCodeGen::DoStoreNamedField(LStoreNamedField* instr) { + Abort("DoStoreNamedField unimplemented."); +} + + +void LCodeGen::DoStoreNamedGeneric(LStoreNamedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(r1)); + ASSERT(ToRegister(instr->value()).is(r0)); + + // Name is always in r2. + __ mov(r2, Operand(instr->name())); + Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) { + Abort("DoBoundsCheck unimplemented."); +} + + +void LCodeGen::DoStoreKeyedFastElement(LStoreKeyedFastElement* instr) { + Abort("DoStoreKeyedFastElement unimplemented."); +} + + +void LCodeGen::DoStoreKeyedGeneric(LStoreKeyedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(r2)); + ASSERT(ToRegister(instr->key()).is(r1)); + ASSERT(ToRegister(instr->value()).is(r0)); + + Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoInteger32ToDouble(LInteger32ToDouble* instr) { + Abort("DoInteger32ToDouble unimplemented."); +} + + +void LCodeGen::DoNumberTagI(LNumberTagI* instr) { + class DeferredNumberTagI: public LDeferredCode { + public: + DeferredNumberTagI(LCodeGen* codegen, LNumberTagI* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredNumberTagI(instr_); } + private: + LNumberTagI* instr_; + }; + + LOperand* input = instr->input(); + ASSERT(input->IsRegister() && input->Equals(instr->result())); + Register reg = ToRegister(input); + + DeferredNumberTagI* deferred = new DeferredNumberTagI(this, instr); + __ SmiTag(reg, SetCC); + __ b(vs, deferred->entry()); + __ bind(deferred->exit()); +} + + +void LCodeGen::DoDeferredNumberTagI(LNumberTagI* instr) { + Label slow; + Register reg = ToRegister(instr->input()); + DoubleRegister dbl_scratch = d0; + SwVfpRegister flt_scratch = s0; + + // Preserve the value of all registers. + __ PushSafepointRegisters(); + + // There was overflow, so bits 30 and 31 of the original integer + // disagree. Try to allocate a heap number in new space and store + // the value in there. If that fails, call the runtime system. + Label done; + __ SmiUntag(reg); + __ eor(reg, reg, Operand(0x80000000)); + __ vmov(flt_scratch, reg); + __ vcvt_f64_s32(dbl_scratch, flt_scratch); + if (FLAG_inline_new) { + __ LoadRoot(r6, Heap::kHeapNumberMapRootIndex); + __ AllocateHeapNumber(r5, r3, r4, r6, &slow); + if (!reg.is(r5)) __ mov(reg, r5); + __ b(&done); + } + + // Slow case: Call the runtime system to do the number allocation. + __ bind(&slow); + + // TODO(3095996): Put a valid pointer value in the stack slot where the result + // register is stored, as this register is in the pointer map, but contains an + // integer value. + __ mov(ip, Operand(0)); + int reg_stack_index = __ SafepointRegisterStackIndex(reg.code()); + __ str(ip, MemOperand(sp, reg_stack_index * kPointerSize)); + + __ CallRuntimeSaveDoubles(Runtime::kAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + if (!reg.is(r0)) __ mov(reg, r0); + + // Done. Put the value in dbl_scratch into the value of the allocated heap + // number. + __ bind(&done); + __ sub(ip, reg, Operand(kHeapObjectTag)); + __ vstr(dbl_scratch, ip, HeapNumber::kValueOffset); + __ str(reg, MemOperand(sp, reg_stack_index * kPointerSize)); + __ PopSafepointRegisters(); +} + + +void LCodeGen::DoNumberTagD(LNumberTagD* instr) { + class DeferredNumberTagD: public LDeferredCode { + public: + DeferredNumberTagD(LCodeGen* codegen, LNumberTagD* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredNumberTagD(instr_); } + private: + LNumberTagD* instr_; + }; + + DoubleRegister input_reg = ToDoubleRegister(instr->input()); + Register reg = ToRegister(instr->result()); + Register temp1 = ToRegister(instr->temp1()); + Register temp2 = ToRegister(instr->temp2()); + Register scratch = r9; + + DeferredNumberTagD* deferred = new DeferredNumberTagD(this, instr); + if (FLAG_inline_new) { + __ LoadRoot(scratch, Heap::kHeapNumberMapRootIndex); + __ AllocateHeapNumber(reg, temp1, temp2, scratch, deferred->entry()); + } else { + __ jmp(deferred->entry()); + } + __ bind(deferred->exit()); + __ sub(ip, reg, Operand(kHeapObjectTag)); + __ vstr(input_reg, ip, HeapNumber::kValueOffset); +} + + +void LCodeGen::DoDeferredNumberTagD(LNumberTagD* instr) { + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + Register reg = ToRegister(instr->result()); + __ mov(reg, Operand(0)); + + __ PushSafepointRegisters(); + __ CallRuntimeSaveDoubles(Runtime::kAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + int reg_stack_index = __ SafepointRegisterStackIndex(reg.code()); + __ str(r0, MemOperand(sp, reg_stack_index * kPointerSize)); + __ PopSafepointRegisters(); +} + + +void LCodeGen::DoSmiTag(LSmiTag* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister() && input->Equals(instr->result())); + ASSERT(!instr->hydrogen_value()->CheckFlag(HValue::kCanOverflow)); + __ SmiTag(ToRegister(input)); +} + + +void LCodeGen::DoSmiUntag(LSmiUntag* instr) { + Abort("DoSmiUntag unimplemented."); +} + + +void LCodeGen::EmitNumberUntagD(Register input_reg, + DoubleRegister result_reg, + LEnvironment* env) { + Register core_scratch = r9; + ASSERT(!input_reg.is(core_scratch)); + SwVfpRegister flt_scratch = s0; + ASSERT(!result_reg.is(d0)); + + Label load_smi, heap_number, done; + + // Smi check. + __ tst(input_reg, Operand(kSmiTagMask)); + __ b(eq, &load_smi); + + // Heap number map check. + __ ldr(core_scratch, FieldMemOperand(input_reg, HeapObject::kMapOffset)); + __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex); + __ cmp(core_scratch, Operand(ip)); + __ b(eq, &heap_number); + + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); + __ cmp(input_reg, Operand(ip)); + DeoptimizeIf(ne, env); + + // Convert undefined to NaN. + __ LoadRoot(ip, Heap::kNanValueRootIndex); + __ sub(ip, ip, Operand(kHeapObjectTag)); + __ vldr(result_reg, ip, HeapNumber::kValueOffset); + __ jmp(&done); + + // Heap number to double register conversion. + __ bind(&heap_number); + __ sub(ip, input_reg, Operand(kHeapObjectTag)); + __ vldr(result_reg, ip, HeapNumber::kValueOffset); + __ jmp(&done); + + // Smi to double register conversion + __ bind(&load_smi); + __ SmiUntag(input_reg); // Untag smi before converting to float. + __ vmov(flt_scratch, input_reg); + __ vcvt_f64_s32(result_reg, flt_scratch); + __ SmiTag(input_reg); // Retag smi. + __ bind(&done); +} + + +class DeferredTaggedToI: public LDeferredCode { + public: + DeferredTaggedToI(LCodeGen* codegen, LTaggedToI* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredTaggedToI(instr_); } + private: + LTaggedToI* instr_; +}; + + +void LCodeGen::DoDeferredTaggedToI(LTaggedToI* instr) { + Label done; + Register input_reg = ToRegister(instr->input()); + Register core_scratch = r9; + ASSERT(!input_reg.is(core_scratch)); + DoubleRegister dbl_scratch = d0; + SwVfpRegister flt_scratch = s0; + DoubleRegister dbl_tmp = ToDoubleRegister(instr->temp()); + + // Heap number map check. + __ ldr(core_scratch, FieldMemOperand(input_reg, HeapObject::kMapOffset)); + __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex); + __ cmp(core_scratch, Operand(ip)); + + if (instr->truncating()) { + Label heap_number; + __ b(eq, &heap_number); + // Check for undefined. Undefined is converted to zero for truncating + // conversions. + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); + __ cmp(input_reg, Operand(ip)); + DeoptimizeIf(ne, instr->environment()); + __ mov(input_reg, Operand(0)); + __ b(&done); + + __ bind(&heap_number); + __ sub(ip, input_reg, Operand(kHeapObjectTag)); + __ vldr(dbl_tmp, ip, HeapNumber::kValueOffset); + __ vcmp(dbl_tmp, 0.0); // Sets overflow bit if NaN. + __ vcvt_s32_f64(flt_scratch, dbl_tmp); + __ vmov(input_reg, flt_scratch); // 32-bit result of conversion. + __ vmrs(pc); // Move vector status bits to normal status bits. + // Overflow bit is set if dbl_tmp is Nan. + __ cmn(input_reg, Operand(1), vc); // 0x7fffffff + 1 -> overflow. + __ cmp(input_reg, Operand(1), vc); // 0x80000000 - 1 -> overflow. + DeoptimizeIf(vs, instr->environment()); // Saturation may have occured. + + } else { + // Deoptimize if we don't have a heap number. + DeoptimizeIf(ne, instr->environment()); + + __ sub(ip, input_reg, Operand(kHeapObjectTag)); + __ vldr(dbl_tmp, ip, HeapNumber::kValueOffset); + __ vcvt_s32_f64(flt_scratch, dbl_tmp); + __ vmov(input_reg, flt_scratch); // 32-bit result of conversion. + // Non-truncating conversion means that we cannot lose bits, so we convert + // back to check; note that using non-overlapping s and d regs would be + // slightly faster. + __ vcvt_f64_s32(dbl_scratch, flt_scratch); + __ vcmp(dbl_scratch, dbl_tmp); + __ vmrs(pc); // Move vector status bits to normal status bits. + DeoptimizeIf(ne, instr->environment()); // Not equal or unordered. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ tst(input_reg, Operand(input_reg)); + __ b(ne, &done); + __ vmov(lr, ip, dbl_tmp); + __ tst(ip, Operand(1 << 31)); // Test sign bit. + DeoptimizeIf(ne, instr->environment()); + } + } + __ bind(&done); +} + + +void LCodeGen::DoTaggedToI(LTaggedToI* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + ASSERT(input->Equals(instr->result())); + + Register input_reg = ToRegister(input); + + DeferredTaggedToI* deferred = new DeferredTaggedToI(this, instr); + + // Smi check. + __ tst(input_reg, Operand(kSmiTagMask)); + __ b(ne, deferred->entry()); + + // Smi to int32 conversion + __ SmiUntag(input_reg); // Untag smi. + + __ bind(deferred->exit()); +} + + +void LCodeGen::DoNumberUntagD(LNumberUntagD* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + LOperand* result = instr->result(); + ASSERT(result->IsDoubleRegister()); + + Register input_reg = ToRegister(input); + DoubleRegister result_reg = ToDoubleRegister(result); + + EmitNumberUntagD(input_reg, result_reg, instr->environment()); +} + + +void LCodeGen::DoDoubleToI(LDoubleToI* instr) { + Abort("DoDoubleToI unimplemented."); +} + + +void LCodeGen::DoCheckSmi(LCheckSmi* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + __ tst(ToRegister(input), Operand(kSmiTagMask)); + DeoptimizeIf(instr->condition(), instr->environment()); +} + + +void LCodeGen::DoCheckInstanceType(LCheckInstanceType* instr) { + Abort("DoCheckInstanceType unimplemented."); +} + + +void LCodeGen::DoCheckFunction(LCheckFunction* instr) { + ASSERT(instr->input()->IsRegister()); + Register reg = ToRegister(instr->input()); + __ cmp(reg, Operand(instr->hydrogen()->target())); + DeoptimizeIf(ne, instr->environment()); +} + + +void LCodeGen::DoCheckMap(LCheckMap* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + Register reg = ToRegister(input); + __ ldr(r9, FieldMemOperand(reg, HeapObject::kMapOffset)); + __ cmp(r9, Operand(instr->hydrogen()->map())); + DeoptimizeIf(ne, instr->environment()); +} + + +void LCodeGen::LoadPrototype(Register result, + Handle<JSObject> prototype) { + Abort("LoadPrototype unimplemented."); +} + + +void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) { + Abort("DoCheckPrototypeMaps unimplemented."); +} + + +void LCodeGen::DoArrayLiteral(LArrayLiteral* instr) { + Abort("DoArrayLiteral unimplemented."); +} + + +void LCodeGen::DoObjectLiteral(LObjectLiteral* instr) { + Abort("DoObjectLiteral unimplemented."); +} + + +void LCodeGen::DoRegExpLiteral(LRegExpLiteral* instr) { + Abort("DoRegExpLiteral unimplemented."); +} + + +void LCodeGen::DoFunctionLiteral(LFunctionLiteral* instr) { + Abort("DoFunctionLiteral unimplemented."); +} + + +void LCodeGen::DoTypeof(LTypeof* instr) { + Abort("DoTypeof unimplemented."); +} + + +void LCodeGen::DoTypeofIs(LTypeofIs* instr) { + Abort("DoTypeofIs unimplemented."); +} + + +void LCodeGen::DoTypeofIsAndBranch(LTypeofIsAndBranch* instr) { + Register input = ToRegister(instr->input()); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition final_branch_condition = EmitTypeofIs(true_label, + false_label, + input, + instr->type_literal()); + + EmitBranch(true_block, false_block, final_branch_condition); +} + + +Condition LCodeGen::EmitTypeofIs(Label* true_label, + Label* false_label, + Register input, + Handle<String> type_name) { + Condition final_branch_condition = no_condition; + Register core_scratch = r9; + ASSERT(!input.is(core_scratch)); + if (type_name->Equals(Heap::number_symbol())) { + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, true_label); + __ ldr(input, FieldMemOperand(input, HeapObject::kMapOffset)); + __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex); + __ cmp(input, Operand(ip)); + final_branch_condition = eq; + + } else if (type_name->Equals(Heap::string_symbol())) { + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, false_label); + __ ldr(input, FieldMemOperand(input, HeapObject::kMapOffset)); + __ ldrb(ip, FieldMemOperand(input, Map::kBitFieldOffset)); + __ tst(ip, Operand(1 << Map::kIsUndetectable)); + __ b(ne, false_label); + __ CompareInstanceType(input, core_scratch, FIRST_NONSTRING_TYPE); + final_branch_condition = lo; + + } else if (type_name->Equals(Heap::boolean_symbol())) { + __ LoadRoot(ip, Heap::kTrueValueRootIndex); + __ cmp(input, ip); + __ b(eq, true_label); + __ LoadRoot(ip, Heap::kFalseValueRootIndex); + __ cmp(input, ip); + final_branch_condition = eq; + + } else if (type_name->Equals(Heap::undefined_symbol())) { + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); + __ cmp(input, ip); + __ b(eq, true_label); + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, false_label); + // Check for undetectable objects => true. + __ ldr(input, FieldMemOperand(input, HeapObject::kMapOffset)); + __ ldrb(ip, FieldMemOperand(input, Map::kBitFieldOffset)); + __ tst(ip, Operand(1 << Map::kIsUndetectable)); + final_branch_condition = ne; + + } else if (type_name->Equals(Heap::function_symbol())) { + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, false_label); + __ CompareObjectType(input, input, core_scratch, JS_FUNCTION_TYPE); + __ b(eq, true_label); + // Regular expressions => 'function' (they are callable). + __ CompareInstanceType(input, core_scratch, JS_REGEXP_TYPE); + final_branch_condition = eq; + + } else if (type_name->Equals(Heap::object_symbol())) { + __ tst(input, Operand(kSmiTagMask)); + __ b(eq, false_label); + __ LoadRoot(ip, Heap::kNullValueRootIndex); + __ cmp(input, ip); + __ b(eq, true_label); + // Regular expressions => 'function', not 'object'. + __ CompareObjectType(input, input, core_scratch, JS_REGEXP_TYPE); + __ b(eq, false_label); + // Check for undetectable objects => false. + __ ldrb(ip, FieldMemOperand(input, Map::kBitFieldOffset)); + __ tst(ip, Operand(1 << Map::kIsUndetectable)); + __ b(ne, false_label); + // Check for JS objects => true. + __ CompareInstanceType(input, core_scratch, FIRST_JS_OBJECT_TYPE); + __ b(lo, false_label); + __ CompareInstanceType(input, core_scratch, LAST_JS_OBJECT_TYPE); + final_branch_condition = ls; + + } else { + final_branch_condition = ne; + __ b(false_label); + // A dead branch instruction will be generated after this point. + } + + return final_branch_condition; +} + + +void LCodeGen::DoLazyBailout(LLazyBailout* instr) { + // No code for lazy bailout instruction. Used to capture environment after a + // call for populating the safepoint data with deoptimization data. +} + + +void LCodeGen::DoDeoptimize(LDeoptimize* instr) { + DeoptimizeIf(no_condition, instr->environment()); +} + + +void LCodeGen::DoDeleteProperty(LDeleteProperty* instr) { + Abort("DoDeleteProperty unimplemented."); +} + + +void LCodeGen::DoStackCheck(LStackCheck* instr) { + // Perform stack overflow check. + Label ok; + __ LoadRoot(ip, Heap::kStackLimitRootIndex); + __ cmp(sp, Operand(ip)); + __ b(hs, &ok); + StackCheckStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + __ bind(&ok); +} + + +void LCodeGen::DoOsrEntry(LOsrEntry* instr) { + Abort("DoOsrEntry unimplemented."); +} + + +#undef __ + +} } // namespace v8::internal diff --git a/src/arm/lithium-codegen-arm.h b/src/arm/lithium-codegen-arm.h new file mode 100644 index 00000000..541a6996 --- /dev/null +++ b/src/arm/lithium-codegen-arm.h @@ -0,0 +1,274 @@ +// Copyright 2010 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. + +#ifndef V8_ARM_LITHIUM_CODEGEN_ARM_H_ +#define V8_ARM_LITHIUM_CODEGEN_ARM_H_ + +#include "arm/lithium-arm.h" + +#include "deoptimizer.h" +#include "safepoint-table.h" +#include "scopes.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LDeferredCode; +class SafepointGenerator; + + +class LCodeGen BASE_EMBEDDED { + public: + LCodeGen(LChunk* chunk, MacroAssembler* assembler, CompilationInfo* info) + : chunk_(chunk), + masm_(assembler), + info_(info), + current_block_(-1), + current_instruction_(-1), + instructions_(chunk->instructions()), + deoptimizations_(4), + deoptimization_literals_(8), + inlined_function_count_(0), + scope_(chunk->graph()->info()->scope()), + status_(UNUSED), + deferred_(8), + osr_pc_offset_(-1) { + PopulateDeoptimizationLiteralsWithInlinedFunctions(); + } + + // Try to generate code for the entire chunk, but it may fail if the + // chunk contains constructs we cannot handle. Returns true if the + // code generation attempt succeeded. + bool GenerateCode(); + + // Finish the code by setting stack height, safepoint, and bailout + // information on it. + void FinishCode(Handle<Code> code); + + // Deferred code support. + void DoDeferredNumberTagD(LNumberTagD* instr); + void DoDeferredNumberTagI(LNumberTagI* instr); + void DoDeferredTaggedToI(LTaggedToI* instr); + void DoDeferredMathAbsTaggedHeapNumber(LUnaryMathOperation* instr); + void DoDeferredStackCheck(LGoto* instr); + + // Parallel move support. + void DoParallelMove(LParallelMove* move); + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) void Do##type(L##type* node); + LITHIUM_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + private: + enum Status { + UNUSED, + GENERATING, + DONE, + ABORTED + }; + + bool is_unused() const { return status_ == UNUSED; } + bool is_generating() const { return status_ == GENERATING; } + bool is_done() const { return status_ == DONE; } + bool is_aborted() const { return status_ == ABORTED; } + + LChunk* chunk() const { return chunk_; } + Scope* scope() const { return scope_; } + HGraph* graph() const { return chunk_->graph(); } + MacroAssembler* masm() const { return masm_; } + + int GetNextEmittedBlock(int block); + LInstruction* GetNextInstruction(); + + void EmitClassOfTest(Label* if_true, + Label* if_false, + Handle<String> class_name, + Register input, + Register temporary, + Register temporary2); + + int StackSlotCount() const { return chunk()->spill_slot_count(); } + int ParameterCount() const { return scope()->num_parameters(); } + + void Abort(const char* format, ...); + void Comment(const char* format, ...); + + void AddDeferredCode(LDeferredCode* code) { deferred_.Add(code); } + + // Code generation passes. Returns true if code generation should + // continue. + bool GeneratePrologue(); + bool GenerateBody(); + bool GenerateDeferredCode(); + bool GenerateSafepointTable(); + + void CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr); + void CallRuntime(Runtime::Function* function, + int num_arguments, + LInstruction* instr); + void CallRuntime(Runtime::FunctionId id, + int num_arguments, + LInstruction* instr) { + Runtime::Function* function = Runtime::FunctionForId(id); + CallRuntime(function, num_arguments, instr); + } + + // Generate a direct call to a known function. Expects the function + // to be in edi. + void CallKnownFunction(Handle<JSFunction> function, + int arity, + LInstruction* instr); + + void LoadPrototype(Register result, Handle<JSObject> prototype); + + void RegisterLazyDeoptimization(LInstruction* instr); + void RegisterEnvironmentForDeoptimization(LEnvironment* environment); + void DeoptimizeIf(Condition cc, LEnvironment* environment); + + void AddToTranslation(Translation* translation, + LOperand* op, + bool is_tagged); + void PopulateDeoptimizationData(Handle<Code> code); + int DefineDeoptimizationLiteral(Handle<Object> literal); + + void PopulateDeoptimizationLiteralsWithInlinedFunctions(); + + Register ToRegister(int index) const; + DoubleRegister ToDoubleRegister(int index) const; + + // LOperand must be a register. + Register ToRegister(LOperand* op) const; + + // LOperand is loaded into scratch, unless already a register. + Register EmitLoadRegister(LOperand* op, Register scratch); + + // LOperand must be a double register. + DoubleRegister ToDoubleRegister(LOperand* op) const; + + // LOperand is loaded into dbl_scratch, unless already a double register. + DoubleRegister EmitLoadDoubleRegister(LOperand* op, + SwVfpRegister flt_scratch, + DoubleRegister dbl_scratch); + + int ToInteger32(LConstantOperand* op) const; + Operand ToOperand(LOperand* op); + MemOperand ToMemOperand(LOperand* op) const; + + // Specific math operations - used from DoUnaryMathOperation. + void DoMathAbs(LUnaryMathOperation* instr); + void DoMathFloor(LUnaryMathOperation* instr); + void DoMathSqrt(LUnaryMathOperation* instr); + + // Support for recording safepoint and position information. + void RecordSafepoint(LPointerMap* pointers, int deoptimization_index); + void RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + int deoptimization_index); + void RecordPosition(int position); + + static Condition TokenToCondition(Token::Value op, bool is_unsigned); + void EmitGoto(int block, LDeferredCode* deferred_stack_check = NULL); + void EmitBranch(int left_block, int right_block, Condition cc); + void EmitCmpI(LOperand* left, LOperand* right); + void EmitNumberUntagD(Register input, + DoubleRegister result, + LEnvironment* env); + + // Emits optimized code for typeof x == "y". Modifies input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitTypeofIs(Label* true_label, Label* false_label, + Register input, Handle<String> type_name); + + // Emits optimized code for %_IsObject(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsObject(Register input, + Register temp1, + Register temp2, + Label* is_not_object, + Label* is_object); + + LChunk* const chunk_; + MacroAssembler* const masm_; + CompilationInfo* const info_; + + int current_block_; + int current_instruction_; + const ZoneList<LInstruction*>* instructions_; + ZoneList<LEnvironment*> deoptimizations_; + ZoneList<Handle<Object> > deoptimization_literals_; + int inlined_function_count_; + Scope* const scope_; + Status status_; + TranslationBuffer translations_; + ZoneList<LDeferredCode*> deferred_; + int osr_pc_offset_; + + // Builder that keeps track of safepoints in the code. The table + // itself is emitted at the end of the generated code. + SafepointTableBuilder safepoints_; + + friend class LDeferredCode; + friend class LEnvironment; + friend class SafepointGenerator; + DISALLOW_COPY_AND_ASSIGN(LCodeGen); +}; + + +class LDeferredCode: public ZoneObject { + public: + explicit LDeferredCode(LCodeGen* codegen) + : codegen_(codegen), external_exit_(NULL) { + codegen->AddDeferredCode(this); + } + + virtual ~LDeferredCode() { } + virtual void Generate() = 0; + + void SetExit(Label *exit) { external_exit_ = exit; } + Label* entry() { return &entry_; } + Label* exit() { return external_exit_ != NULL ? external_exit_ : &exit_; } + + protected: + LCodeGen* codegen() const { return codegen_; } + MacroAssembler* masm() const { return codegen_->masm(); } + + private: + LCodeGen* codegen_; + Label entry_; + Label exit_; + Label* external_exit_; +}; + +} } // namespace v8::internal + +#endif // V8_ARM_LITHIUM_CODEGEN_ARM_H_ diff --git a/src/arm/macro-assembler-arm.cc b/src/arm/macro-assembler-arm.cc index 91a4607a..4a131465 100644 --- a/src/arm/macro-assembler-arm.cc +++ b/src/arm/macro-assembler-arm.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -171,13 +171,6 @@ void MacroAssembler::Ret(Condition cond) { } -void MacroAssembler::StackLimitCheck(Label* on_stack_overflow) { - LoadRoot(ip, Heap::kStackLimitRootIndex); - cmp(sp, Operand(ip)); - b(lo, on_stack_overflow); -} - - void MacroAssembler::Drop(int count, Condition cond) { if (count > 0) { add(sp, sp, Operand(count * kPointerSize), LeaveCC, cond); @@ -185,6 +178,12 @@ void MacroAssembler::Drop(int count, Condition cond) { } +void MacroAssembler::Ret(int drop, Condition cond) { + Drop(drop, cond); + Ret(cond); +} + + void MacroAssembler::Swap(Register reg1, Register reg2, Register scratch, @@ -447,6 +446,34 @@ void MacroAssembler::RecordWrite(Register object, } +// Push and pop all registers that can hold pointers. +void MacroAssembler::PushSafepointRegisters() { + // Safepoints expect a block of contiguous register values starting with r0: + ASSERT(((1 << kNumSafepointSavedRegisters) - 1) == kSafepointSavedRegisters); + // Safepoints expect a block of kNumSafepointRegisters values on the + // stack, so adjust the stack for unsaved registers. + const int num_unsaved = kNumSafepointRegisters - kNumSafepointSavedRegisters; + ASSERT(num_unsaved >= 0); + sub(sp, sp, Operand(num_unsaved * kPointerSize)); + stm(db_w, sp, kSafepointSavedRegisters); +} + + +void MacroAssembler::PopSafepointRegisters() { + const int num_unsaved = kNumSafepointRegisters - kNumSafepointSavedRegisters; + ldm(ia_w, sp, kSafepointSavedRegisters); + add(sp, sp, Operand(num_unsaved * kPointerSize)); +} + + +int MacroAssembler::SafepointRegisterStackIndex(int reg_code) { + // The registers are pushed starting with the highest encoding, + // which means that lowest encodings are closest to the stack pointer. + ASSERT(reg_code >= 0 && reg_code < kNumSafepointRegisters); + return reg_code; +} + + void MacroAssembler::Ldrd(Register dst1, Register dst2, const MemOperand& src, Condition cond) { ASSERT(src.rm().is(no_reg)); @@ -515,12 +542,8 @@ void MacroAssembler::LeaveFrame(StackFrame::Type type) { } -void MacroAssembler::EnterExitFrame() { - // Compute the argv pointer and keep it in a callee-saved register. +void MacroAssembler::EnterExitFrame(bool save_doubles) { // r0 is argc. - add(r6, sp, Operand(r0, LSL, kPointerSizeLog2)); - sub(r6, r6, Operand(kPointerSize)); - // Compute callee's stack pointer before making changes and save it as // ip register so that it is restored as sp register on exit, thereby // popping the args. @@ -528,6 +551,9 @@ void MacroAssembler::EnterExitFrame() { // ip = sp + kPointerSize * #args; add(ip, sp, Operand(r0, LSL, kPointerSizeLog2)); + // Compute the argv pointer and keep it in a callee-saved register. + sub(r6, ip, Operand(kPointerSize)); + // Prepare the stack to be aligned when calling into C. After this point there // are 5 pushes before the call into C, so the stack needs to be aligned after // 5 pushes. @@ -558,6 +584,28 @@ void MacroAssembler::EnterExitFrame() { // Setup argc and the builtin function in callee-saved registers. mov(r4, Operand(r0)); mov(r5, Operand(r1)); + + // Optionally save all double registers. + if (save_doubles) { + // TODO(regis): Use vstrm instruction. + // The stack alignment code above made sp unaligned, so add space for one + // more double register and use aligned addresses. + ASSERT(kDoubleSize == frame_alignment); + // Mark the frame as containing doubles by pushing a non-valid return + // address, i.e. 0. + ASSERT(ExitFrameConstants::kMarkerOffset == -2 * kPointerSize); + mov(ip, Operand(0)); // Marker and alignment word. + push(ip); + int space = DwVfpRegister::kNumRegisters * kDoubleSize + kPointerSize; + sub(sp, sp, Operand(space)); + for (int i = 0; i < DwVfpRegister::kNumRegisters; i++) { + DwVfpRegister reg = DwVfpRegister::from_code(i); + vstr(reg, sp, i * kDoubleSize + kPointerSize); + } + // Note that d0 will be accessible at fp - 2*kPointerSize - + // DwVfpRegister::kNumRegisters * kDoubleSize, since the code slot and the + // alignment word were pushed after the fp. + } } @@ -592,7 +640,18 @@ int MacroAssembler::ActivationFrameAlignment() { } -void MacroAssembler::LeaveExitFrame() { +void MacroAssembler::LeaveExitFrame(bool save_doubles) { + // Optionally restore all double registers. + if (save_doubles) { + // TODO(regis): Use vldrm instruction. + for (int i = 0; i < DwVfpRegister::kNumRegisters; i++) { + DwVfpRegister reg = DwVfpRegister::from_code(i); + // Register d15 is just below the marker. + const int offset = ExitFrameConstants::kMarkerOffset; + vldr(reg, fp, (i - DwVfpRegister::kNumRegisters) * kDoubleSize + offset); + } + } + // Clear top frame. mov(r3, Operand(0, RelocInfo::NONE)); mov(ip, Operand(ExternalReference(Top::k_c_entry_fp_address))); @@ -756,7 +815,47 @@ void MacroAssembler::InvokeFunction(JSFunction* function, // Invoke the cached code. Handle<Code> code(function->code()); ParameterCount expected(function->shared()->formal_parameter_count()); - InvokeCode(code, expected, actual, RelocInfo::CODE_TARGET, flag); + if (V8::UseCrankshaft()) { + // TODO(kasperl): For now, we always call indirectly through the + // code field in the function to allow recompilation to take effect + // without changing any of the call sites. + ldr(r3, FieldMemOperand(r1, JSFunction::kCodeEntryOffset)); + InvokeCode(r3, expected, actual, flag); + } else { + InvokeCode(code, expected, actual, RelocInfo::CODE_TARGET, flag); + } +} + + +void MacroAssembler::IsObjectJSObjectType(Register heap_object, + Register map, + Register scratch, + Label* fail) { + ldr(map, FieldMemOperand(heap_object, HeapObject::kMapOffset)); + IsInstanceJSObjectType(map, scratch, fail); +} + + +void MacroAssembler::IsInstanceJSObjectType(Register map, + Register scratch, + Label* fail) { + ldrb(scratch, FieldMemOperand(map, Map::kInstanceTypeOffset)); + cmp(scratch, Operand(FIRST_JS_OBJECT_TYPE)); + b(lt, fail); + cmp(scratch, Operand(LAST_JS_OBJECT_TYPE)); + b(gt, fail); +} + + +void MacroAssembler::IsObjectJSStringType(Register object, + Register scratch, + Label* fail) { + ASSERT(kNotStringTag != 0); + + ldr(scratch, FieldMemOperand(object, HeapObject::kMapOffset)); + ldrb(scratch, FieldMemOperand(scratch, Map::kInstanceTypeOffset)); + tst(scratch, Operand(kIsNotStringMask)); + b(nz, fail); } @@ -920,6 +1019,7 @@ void MacroAssembler::AllocateInNewSpace(int object_size, } ASSERT(!result.is(scratch1)); + ASSERT(!result.is(scratch2)); ASSERT(!scratch1.is(scratch2)); // Make object size into bytes. @@ -928,38 +1028,55 @@ void MacroAssembler::AllocateInNewSpace(int object_size, } ASSERT_EQ(0, object_size & kObjectAlignmentMask); - // Load address of new object into result and allocation top address into - // scratch1. + // Check relative positions of allocation top and limit addresses. + // The values must be adjacent in memory to allow the use of LDM. + // Also, assert that the registers are numbered such that the values + // are loaded in the correct order. ExternalReference new_space_allocation_top = ExternalReference::new_space_allocation_top_address(); - mov(scratch1, Operand(new_space_allocation_top)); + ExternalReference new_space_allocation_limit = + ExternalReference::new_space_allocation_limit_address(); + intptr_t top = + reinterpret_cast<intptr_t>(new_space_allocation_top.address()); + intptr_t limit = + reinterpret_cast<intptr_t>(new_space_allocation_limit.address()); + ASSERT((limit - top) == kPointerSize); + ASSERT(result.code() < ip.code()); + + // Set up allocation top address and object size registers. + Register topaddr = scratch1; + Register obj_size_reg = scratch2; + mov(topaddr, Operand(new_space_allocation_top)); + mov(obj_size_reg, Operand(object_size)); + + // This code stores a temporary value in ip. This is OK, as the code below + // does not need ip for implicit literal generation. if ((flags & RESULT_CONTAINS_TOP) == 0) { - ldr(result, MemOperand(scratch1)); - } else if (FLAG_debug_code) { - // Assert that result actually contains top on entry. scratch2 is used - // immediately below so this use of scratch2 does not cause difference with - // respect to register content between debug and release mode. - ldr(scratch2, MemOperand(scratch1)); - cmp(result, scratch2); - Check(eq, "Unexpected allocation top"); + // Load allocation top into result and allocation limit into ip. + ldm(ia, topaddr, result.bit() | ip.bit()); + } else { + if (FLAG_debug_code) { + // Assert that result actually contains top on entry. ip is used + // immediately below so this use of ip does not cause difference with + // respect to register content between debug and release mode. + ldr(ip, MemOperand(topaddr)); + cmp(result, ip); + Check(eq, "Unexpected allocation top"); + } + // Load allocation limit into ip. Result already contains allocation top. + ldr(ip, MemOperand(topaddr, limit - top)); } // Calculate new top and bail out if new space is exhausted. Use result // to calculate the new top. - ExternalReference new_space_allocation_limit = - ExternalReference::new_space_allocation_limit_address(); - mov(scratch2, Operand(new_space_allocation_limit)); - ldr(scratch2, MemOperand(scratch2)); - add(result, result, Operand(object_size)); - cmp(result, Operand(scratch2)); + add(scratch2, result, Operand(obj_size_reg)); + cmp(scratch2, Operand(ip)); b(hi, gc_required); - str(result, MemOperand(scratch1)); + str(scratch2, MemOperand(topaddr)); - // Tag and adjust back to start of new object. + // Tag object if requested. if ((flags & TAG_OBJECT) != 0) { - sub(result, result, Operand(object_size - kHeapObjectTag)); - } else { - sub(result, result, Operand(object_size)); + add(result, result, Operand(kHeapObjectTag)); } } @@ -981,53 +1098,69 @@ void MacroAssembler::AllocateInNewSpace(Register object_size, return; } + // Assert that the register arguments are different and that none of + // them are ip. ip is used explicitly in the code generated below. ASSERT(!result.is(scratch1)); + ASSERT(!result.is(scratch2)); ASSERT(!scratch1.is(scratch2)); - - // Load address of new object into result and allocation top address into - // scratch1. + ASSERT(!result.is(ip)); + ASSERT(!scratch1.is(ip)); + ASSERT(!scratch2.is(ip)); + + // Check relative positions of allocation top and limit addresses. + // The values must be adjacent in memory to allow the use of LDM. + // Also, assert that the registers are numbered such that the values + // are loaded in the correct order. ExternalReference new_space_allocation_top = ExternalReference::new_space_allocation_top_address(); - mov(scratch1, Operand(new_space_allocation_top)); + ExternalReference new_space_allocation_limit = + ExternalReference::new_space_allocation_limit_address(); + intptr_t top = + reinterpret_cast<intptr_t>(new_space_allocation_top.address()); + intptr_t limit = + reinterpret_cast<intptr_t>(new_space_allocation_limit.address()); + ASSERT((limit - top) == kPointerSize); + ASSERT(result.code() < ip.code()); + + // Set up allocation top address. + Register topaddr = scratch1; + mov(topaddr, Operand(new_space_allocation_top)); + + // This code stores a temporary value in ip. This is OK, as the code below + // does not need ip for implicit literal generation. if ((flags & RESULT_CONTAINS_TOP) == 0) { - ldr(result, MemOperand(scratch1)); - } else if (FLAG_debug_code) { - // Assert that result actually contains top on entry. scratch2 is used - // immediately below so this use of scratch2 does not cause difference with - // respect to register content between debug and release mode. - ldr(scratch2, MemOperand(scratch1)); - cmp(result, scratch2); - Check(eq, "Unexpected allocation top"); + // Load allocation top into result and allocation limit into ip. + ldm(ia, topaddr, result.bit() | ip.bit()); + } else { + if (FLAG_debug_code) { + // Assert that result actually contains top on entry. ip is used + // immediately below so this use of ip does not cause difference with + // respect to register content between debug and release mode. + ldr(ip, MemOperand(topaddr)); + cmp(result, ip); + Check(eq, "Unexpected allocation top"); + } + // Load allocation limit into ip. Result already contains allocation top. + ldr(ip, MemOperand(topaddr, limit - top)); } // Calculate new top and bail out if new space is exhausted. Use result - // to calculate the new top. Object size is in words so a shift is required to - // get the number of bytes - ExternalReference new_space_allocation_limit = - ExternalReference::new_space_allocation_limit_address(); - mov(scratch2, Operand(new_space_allocation_limit)); - ldr(scratch2, MemOperand(scratch2)); + // to calculate the new top. Object size may be in words so a shift is + // required to get the number of bytes. if ((flags & SIZE_IN_WORDS) != 0) { - add(result, result, Operand(object_size, LSL, kPointerSizeLog2)); + add(scratch2, result, Operand(object_size, LSL, kPointerSizeLog2)); } else { - add(result, result, Operand(object_size)); + add(scratch2, result, Operand(object_size)); } - cmp(result, Operand(scratch2)); + cmp(scratch2, Operand(ip)); b(hi, gc_required); // Update allocation top. result temporarily holds the new top. if (FLAG_debug_code) { - tst(result, Operand(kObjectAlignmentMask)); + tst(scratch2, Operand(kObjectAlignmentMask)); Check(eq, "Unaligned allocation in new space"); } - str(result, MemOperand(scratch1)); - - // Adjust back to start of new object. - if ((flags & SIZE_IN_WORDS) != 0) { - sub(result, result, Operand(object_size, LSL, kPointerSizeLog2)); - } else { - sub(result, result, Operand(object_size)); - } + str(scratch2, MemOperand(topaddr)); // Tag object if requested. if ((flags & TAG_OBJECT) != 0) { @@ -1485,6 +1618,16 @@ void MacroAssembler::CallRuntime(Runtime::FunctionId fid, int num_arguments) { } +void MacroAssembler::CallRuntimeSaveDoubles(Runtime::FunctionId id) { + Runtime::Function* function = Runtime::FunctionForId(id); + mov(r0, Operand(function->nargs)); + mov(r1, Operand(ExternalReference(function))); + CEntryStub stub(1); + stub.SaveDoubles(); + CallStub(&stub); +} + + void MacroAssembler::CallExternalReference(const ExternalReference& ext, int num_arguments) { mov(r0, Operand(num_arguments)); @@ -1747,18 +1890,6 @@ void MacroAssembler::AbortIfSmi(Register object) { } -void MacroAssembler::AbortIfNotString(Register object) { - STATIC_ASSERT(kSmiTag == 0); - tst(object, Operand(kSmiTagMask)); - Assert(ne, "Operand is not a string"); - push(object); - ldr(object, FieldMemOperand(object, HeapObject::kMapOffset)); - CompareInstanceType(object, object, FIRST_NONSTRING_TYPE); - pop(object); - Assert(lo, "Operand is not a string"); -} - - void MacroAssembler::JumpIfNonSmisNotBothSequentialAsciiStrings( Register first, Register second, diff --git a/src/arm/macro-assembler-arm.h b/src/arm/macro-assembler-arm.h index 27d16712..97bbb2fb 100644 --- a/src/arm/macro-assembler-arm.h +++ b/src/arm/macro-assembler-arm.h @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -96,6 +96,7 @@ class MacroAssembler: public Assembler { // from the stack, clobbering only the sp register. void Drop(int count, Condition cond = al); + void Ret(int drop, Condition cond = al); // Swap two registers. If the scratch register is omitted then a slightly // less efficient form using xor instead of mov is emitted. @@ -224,6 +225,12 @@ class MacroAssembler: public Assembler { } } + // Push and pop the registers that can hold pointers, as defined by the + // RegList constant kSafepointSavedRegisters. + void PushSafepointRegisters(); + void PopSafepointRegisters(); + static int SafepointRegisterStackIndex(int reg_code); + // Load two consecutive registers with two consecutive memory locations. void Ldrd(Register dst1, Register dst2, @@ -237,11 +244,6 @@ class MacroAssembler: public Assembler { Condition cond = al); // --------------------------------------------------------------------------- - // Stack limit support - - void StackLimitCheck(Label* on_stack_limit_hit); - - // --------------------------------------------------------------------------- // Activation frames void EnterInternalFrame() { EnterFrame(StackFrame::INTERNAL); } @@ -254,10 +256,10 @@ class MacroAssembler: public Assembler { // Expects the number of arguments in register r0 and // the builtin function to call in register r1. Exits with argc in // r4, argv in r6, and and the builtin function to call in r5. - void EnterExitFrame(); + void EnterExitFrame(bool save_doubles); // Leave the current exit frame. Expects the return value in r0. - void LeaveExitFrame(); + void LeaveExitFrame(bool save_doubles); // Get the actual activation frame alignment for target environment. static int ActivationFrameAlignment(); @@ -297,6 +299,18 @@ class MacroAssembler: public Assembler { const ParameterCount& actual, InvokeFlag flag); + void IsObjectJSObjectType(Register heap_object, + Register map, + Register scratch, + Label* fail); + + void IsInstanceJSObjectType(Register map, + Register scratch, + Label* fail); + + void IsObjectJSStringType(Register object, + Register scratch, + Label* fail); #ifdef ENABLE_DEBUGGER_SUPPORT // --------------------------------------------------------------------------- @@ -575,6 +589,7 @@ class MacroAssembler: public Assembler { // Call a runtime routine. void CallRuntime(Runtime::Function* f, int num_arguments); + void CallRuntimeSaveDoubles(Runtime::FunctionId id); // Convenience function: Same as above, but takes the fid instead. void CallRuntime(Runtime::FunctionId fid, int num_arguments); @@ -665,6 +680,14 @@ class MacroAssembler: public Assembler { // --------------------------------------------------------------------------- // Smi utilities + void SmiTag(Register reg, SBit s = LeaveCC) { + add(reg, reg, Operand(reg), s); + } + + void SmiUntag(Register reg) { + mov(reg, Operand(reg, ASR, kSmiTagSize)); + } + // Jump if either of the registers contain a non-smi. void JumpIfNotBothSmi(Register reg1, Register reg2, Label* on_not_both_smi); // Jump if either of the registers contain a smi. @@ -673,9 +696,6 @@ class MacroAssembler: public Assembler { // Abort execution if argument is a smi. Used in debug code. void AbortIfSmi(Register object); - // Abort execution if argument is a string. Used in debug code. - void AbortIfNotString(Register object); - // --------------------------------------------------------------------------- // String utilities @@ -769,6 +789,17 @@ class CodePatcher { #endif // ENABLE_DEBUGGER_SUPPORT +// Helper class for generating code or data associated with the code +// right after a call instruction. As an example this can be used to +// generate safepoint data after calls for crankshaft. +class PostCallGenerator { + public: + PostCallGenerator() { } + virtual ~PostCallGenerator() { } + virtual void Generate() = 0; +}; + + // ----------------------------------------------------------------------------- // Static helper functions. diff --git a/src/arm/simulator-arm.cc b/src/arm/simulator-arm.cc index ab0cf60e..143b8393 100644 --- a/src/arm/simulator-arm.cc +++ b/src/arm/simulator-arm.cc @@ -74,6 +74,7 @@ class Debugger { Simulator* sim_; int32_t GetRegisterValue(int regnum); + double GetVFPDoubleRegisterValue(int regnum); bool GetValue(const char* desc, int32_t* value); bool GetVFPSingleValue(const char* desc, float* value); bool GetVFPDoubleValue(const char* desc, double* value); @@ -168,6 +169,11 @@ int32_t Debugger::GetRegisterValue(int regnum) { } +double Debugger::GetVFPDoubleRegisterValue(int regnum) { + return sim_->get_double_from_d_register(regnum); +} + + bool Debugger::GetValue(const char* desc, int32_t* value) { int regnum = Registers::Number(desc); if (regnum != kNoRegister) { @@ -309,6 +315,11 @@ void Debugger::Debug() { value = GetRegisterValue(i); PrintF("%3s: 0x%08x %10d\n", Registers::Name(i), value, value); } + for (int i = 0; i < kNumVFPDoubleRegisters; i++) { + dvalue = GetVFPDoubleRegisterValue(i); + PrintF("%3s: %f\n", + VFPRegisters::Name(i, true), dvalue); + } } else { if (GetValue(arg1, &value)) { PrintF("%s: 0x%08x %d \n", arg1, value, value); @@ -837,6 +848,11 @@ void Simulator::set_pc(int32_t value) { } +bool Simulator::has_bad_pc() const { + return ((registers_[pc] == bad_lr) || (registers_[pc] == end_sim_pc)); +} + + // Raw access to the PC register without the special adjustment when reading. int32_t Simulator::get_pc() const { return registers_[pc]; @@ -989,9 +1005,7 @@ int Simulator::ReadW(int32_t addr, Instr* instr) { intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); return *ptr; } - PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", - addr, - reinterpret_cast<intptr_t>(instr)); + PrintF("Unaligned read at 0x%08x, pc=%p\n", addr, instr); UNIMPLEMENTED(); return 0; #endif @@ -1009,9 +1023,7 @@ void Simulator::WriteW(int32_t addr, int value, Instr* instr) { *ptr = value; return; } - PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", - addr, - reinterpret_cast<intptr_t>(instr)); + PrintF("Unaligned write at 0x%08x, pc=%p\n", addr, instr); UNIMPLEMENTED(); #endif } @@ -1026,9 +1038,7 @@ uint16_t Simulator::ReadHU(int32_t addr, Instr* instr) { uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); return *ptr; } - PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", - addr, - reinterpret_cast<intptr_t>(instr)); + PrintF("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr); UNIMPLEMENTED(); return 0; #endif @@ -1062,9 +1072,7 @@ void Simulator::WriteH(int32_t addr, uint16_t value, Instr* instr) { *ptr = value; return; } - PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", - addr, - reinterpret_cast<intptr_t>(instr)); + PrintF("Unaligned unsigned halfword write at 0x%08x, pc=%p\n", addr, instr); UNIMPLEMENTED(); #endif } @@ -1081,9 +1089,7 @@ void Simulator::WriteH(int32_t addr, int16_t value, Instr* instr) { *ptr = value; return; } - PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", - addr, - reinterpret_cast<intptr_t>(instr)); + PrintF("Unaligned halfword write at 0x%08x, pc=%p\n", addr, instr); UNIMPLEMENTED(); #endif } @@ -1520,7 +1526,8 @@ void Simulator::HandleRList(Instr* instr, bool load) { typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, - int32_t arg3); + int32_t arg3, + int32_t arg4); typedef double (*SimulatorRuntimeFPCall)(int32_t arg0, int32_t arg1, int32_t arg2, @@ -1543,6 +1550,8 @@ void Simulator::SoftwareInterrupt(Instr* instr) { int32_t arg1 = get_register(r1); int32_t arg2 = get_register(r2); int32_t arg3 = get_register(r3); + int32_t* stack_pointer = reinterpret_cast<int32_t*>(get_register(sp)); + int32_t arg4 = *stack_pointer; // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_lr = get_register(lr); @@ -1571,19 +1580,20 @@ void Simulator::SoftwareInterrupt(Instr* instr) { reinterpret_cast<SimulatorRuntimeCall>(external); if (::v8::internal::FLAG_trace_sim || !stack_aligned) { PrintF( - "Call to host function at %p with args %08x, %08x, %08x, %08x", + "Call to host function at %p args %08x, %08x, %08x, %08x, %0xc", FUNCTION_ADDR(target), arg0, arg1, arg2, - arg3); + arg3, + arg4); if (!stack_aligned) { PrintF(" with unaligned stack %08x\n", get_register(sp)); } PrintF("\n"); } CHECK(stack_aligned); - int64_t result = target(arg0, arg1, arg2, arg3); + int64_t result = target(arg0, arg1, arg2, arg3, arg4); int32_t lo_res = static_cast<int32_t>(result); int32_t hi_res = static_cast<int32_t>(result >> 32); if (::v8::internal::FLAG_trace_sim) { @@ -1918,9 +1928,12 @@ void Simulator::DecodeType01(Instr* instr) { set_register(lr, old_pc + Instr::kInstrSize); break; } - case BKPT: - v8::internal::OS::DebugBreak(); + case BKPT: { + Debugger dbg(this); + PrintF("Simulator hit BKPT.\n"); + dbg.Debug(); break; + } default: UNIMPLEMENTED(); } diff --git a/src/arm/simulator-arm.h b/src/arm/simulator-arm.h index c37b3f71..7bfe76ac 100644 --- a/src/arm/simulator-arm.h +++ b/src/arm/simulator-arm.h @@ -186,6 +186,10 @@ class Simulator { // ICache checking. static void FlushICache(void* start, size_t size); + // Returns true if pc register contains one of the 'special_values' defined + // below (bad_lr, end_sim_pc). + bool has_bad_pc() const; + private: enum special_values { // Known bad pc value to ensure that the simulator does not execute diff --git a/src/arm/stub-cache-arm.cc b/src/arm/stub-cache-arm.cc index 2117ce6b..c2a9796c 100644 --- a/src/arm/stub-cache-arm.cc +++ b/src/arm/stub-cache-arm.cc @@ -874,6 +874,34 @@ MUST_USE_RESULT static MaybeObject* GenerateCheckPropertyCell( return cell; } +// Calls GenerateCheckPropertyCell for each global object in the prototype chain +// from object to (but not including) holder. +MUST_USE_RESULT static MaybeObject* GenerateCheckPropertyCells( + MacroAssembler* masm, + JSObject* object, + JSObject* holder, + String* name, + Register scratch, + Label* miss) { + JSObject* current = object; + while (current != holder) { + if (current->IsGlobalObject()) { + // Returns a cell or a failure. + MaybeObject* result = GenerateCheckPropertyCell( + masm, + GlobalObject::cast(current), + name, + scratch, + miss); + if (result->IsFailure()) return result; + } + ASSERT(current->IsJSObject()); + current = JSObject::cast(current->GetPrototype()); + } + return NULL; +} + + #undef __ #define __ ACCESS_MASM(masm()) @@ -911,18 +939,19 @@ Register StubCompiler::CheckPrototypes(JSObject* object, // checks are allowed in stubs. ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded()); + ASSERT(current->GetPrototype()->IsJSObject()); JSObject* prototype = JSObject::cast(current->GetPrototype()); if (!current->HasFastProperties() && !current->IsJSGlobalObject() && !current->IsJSGlobalProxy()) { if (!name->IsSymbol()) { - MaybeObject* lookup_result = Heap::LookupSymbol(name); - if (lookup_result->IsFailure()) { - set_failure(Failure::cast(lookup_result)); + MaybeObject* maybe_lookup_result = Heap::LookupSymbol(name); + Object* lookup_result = NULL; // Initialization to please compiler. + if (!maybe_lookup_result->ToObject(&lookup_result)) { + set_failure(Failure::cast(maybe_lookup_result)); return reg; - } else { - name = String::cast(lookup_result->ToObjectUnchecked()); } + name = String::cast(lookup_result); } ASSERT(current->property_dictionary()->FindEntry(name) == StringDictionary::kNotFound); @@ -936,7 +965,7 @@ Register StubCompiler::CheckPrototypes(JSObject* object, __ ldr(scratch1, FieldMemOperand(reg, HeapObject::kMapOffset)); reg = holder_reg; // from now the object is in holder_reg __ ldr(reg, FieldMemOperand(scratch1, Map::kPrototypeOffset)); - } else { + } else if (Heap::InNewSpace(prototype)) { // Get the map of the current object. __ ldr(scratch1, FieldMemOperand(reg, HeapObject::kMapOffset)); __ cmp(scratch1, Operand(Handle<Map>(current->map()))); @@ -956,14 +985,24 @@ Register StubCompiler::CheckPrototypes(JSObject* object, } reg = holder_reg; // from now the object is in holder_reg - if (Heap::InNewSpace(prototype)) { - // The prototype is in new space; we cannot store a reference - // to it in the code. Load it from the map. - __ ldr(reg, FieldMemOperand(scratch1, Map::kPrototypeOffset)); - } else { - // The prototype is in old space; load it directly. - __ mov(reg, Operand(Handle<JSObject>(prototype))); + // The prototype is in new space; we cannot store a reference + // to it in the code. Load it from the map. + __ ldr(reg, FieldMemOperand(scratch1, Map::kPrototypeOffset)); + } else { + // Check the map of the current object. + __ ldr(scratch1, FieldMemOperand(reg, HeapObject::kMapOffset)); + __ cmp(scratch1, Operand(Handle<Map>(current->map()))); + // Branch on the result of the map check. + __ b(ne, miss); + // Check access rights to the global object. This has to happen + // after the map check so that we know that the object is + // actually a global object. + if (current->IsJSGlobalProxy()) { + __ CheckAccessGlobalProxy(reg, scratch1, miss); } + // The prototype is in old space; load it directly. + reg = holder_reg; // from now the object is in holder_reg + __ mov(reg, Operand(Handle<JSObject>(prototype))); } if (save_at_depth == depth) { @@ -982,32 +1021,22 @@ Register StubCompiler::CheckPrototypes(JSObject* object, // Log the check depth. LOG(IntEvent("check-maps-depth", depth + 1)); - // Perform security check for access to the global object and return - // the holder register. - ASSERT(current == holder); - ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded()); - if (current->IsJSGlobalProxy()) { + // Perform security check for access to the global object. + ASSERT(holder->IsJSGlobalProxy() || !holder->IsAccessCheckNeeded()); + if (holder->IsJSGlobalProxy()) { __ CheckAccessGlobalProxy(reg, scratch1, miss); - } + }; // If we've skipped any global objects, it's not enough to verify // that their maps haven't changed. We also need to check that the // property cell for the property is still empty. - current = object; - while (current != holder) { - if (current->IsGlobalObject()) { - MaybeObject* cell = GenerateCheckPropertyCell(masm(), - GlobalObject::cast(current), - name, - scratch1, - miss); - if (cell->IsFailure()) { - set_failure(Failure::cast(cell)); - return reg; - } - } - current = JSObject::cast(current->GetPrototype()); - } + MaybeObject* result = GenerateCheckPropertyCells(masm(), + object, + holder, + name, + scratch1, + miss); + if (result->IsFailure()) set_failure(Failure::cast(result)); // Return the register containing the holder. return reg; @@ -1652,7 +1681,7 @@ MaybeObject* CallStubCompiler::CompileStringCharCodeAtCall( __ Drop(argc + 1); __ Ret(); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_code_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1729,7 +1758,7 @@ MaybeObject* CallStubCompiler::CompileStringCharAtCall( __ Drop(argc + 1); __ Ret(); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1804,7 +1833,7 @@ MaybeObject* CallStubCompiler::CompileStringFromCharCodeCall( __ Drop(argc + 1); __ Ret(); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_from_code_generator.GenerateSlow(masm(), call_helper); // Tail call the full function. We do not have to patch the receiver @@ -1923,7 +1952,7 @@ MaybeObject* CallStubCompiler::CompileMathFloorCall(Object* object, __ cmp(r7, Operand(HeapNumber::kMantissaBits)); // If greater or equal, the argument is already round and in r0. __ b(&restore_fpscr_and_return, ge); - __ b(&wont_fit_smi); + __ b(&slow); __ bind(&no_vfp_exception); // Move the result back to general purpose register r0. @@ -1951,10 +1980,10 @@ MaybeObject* CallStubCompiler::CompileMathFloorCall(Object* object, __ Ret(); __ bind(&wont_fit_smi); + __ bind(&slow); // Restore FPCSR and fall to slow case. __ vmsr(r3); - __ bind(&slow); // Tail call the full function. We do not have to patch the receiver // because the function makes no use of it. __ InvokeFunction(function, arguments(), JUMP_FUNCTION); @@ -2083,8 +2112,8 @@ MaybeObject* CallStubCompiler::CompileCallConstant(Object* object, // -- lr : return address // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, NULL, function, name); Object* result; @@ -2294,8 +2323,8 @@ MaybeObject* CallStubCompiler::CompileCallGlobal(JSObject* object, // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, cell, function, name); Object* result; @@ -2330,8 +2359,16 @@ MaybeObject* CallStubCompiler::CompileCallGlobal(JSObject* object, ASSERT(function->is_compiled()); Handle<Code> code(function->code()); ParameterCount expected(function->shared()->formal_parameter_count()); - __ InvokeCode(code, expected, arguments(), - RelocInfo::CODE_TARGET, JUMP_FUNCTION); + if (V8::UseCrankshaft()) { + // TODO(kasperl): For now, we always call indirectly through the + // code field in the function to allow recompilation to take effect + // without changing any of the call sites. + __ ldr(r3, FieldMemOperand(r1, JSFunction::kCodeEntryOffset)); + __ InvokeCode(r3, expected, arguments(), JUMP_FUNCTION); + } else { + __ InvokeCode(code, expected, arguments(), + RelocInfo::CODE_TARGET, JUMP_FUNCTION); + } // Handle call cache miss. __ bind(&miss); @@ -2864,13 +2901,62 @@ MaybeObject* KeyedLoadStubCompiler::CompileLoadFunctionPrototype(String* name) { } +MaybeObject* KeyedLoadStubCompiler::CompileLoadSpecialized(JSObject* receiver) { + // ----------- S t a t e ------------- + // -- lr : return address + // -- r0 : key + // -- r1 : receiver + // ----------------------------------- + Label miss; + + // Check that the receiver isn't a smi. + __ tst(r1, Operand(kSmiTagMask)); + __ b(eq, &miss); + + // Check that the map matches. + __ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset)); + __ cmp(r2, Operand(Handle<Map>(receiver->map()))); + __ b(ne, &miss); + + // Check that the key is a smi. + __ tst(r0, Operand(kSmiTagMask)); + __ b(ne, &miss); + + // Get the elements array. + __ ldr(r2, FieldMemOperand(r1, JSObject::kElementsOffset)); + __ AssertFastElements(r2); + + // Check that the key is within bounds. + __ ldr(r3, FieldMemOperand(r2, FixedArray::kLengthOffset)); + __ cmp(r0, Operand(r3)); + __ b(hs, &miss); + + // Load the result and make sure it's not the hole. + __ add(r3, r2, Operand(FixedArray::kHeaderSize - kHeapObjectTag)); + ASSERT(kSmiTag == 0 && kSmiTagSize < kPointerSizeLog2); + __ ldr(r4, + MemOperand(r3, r0, LSL, kPointerSizeLog2 - kSmiTagSize)); + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex); + __ cmp(r4, ip); + __ b(eq, &miss); + __ mov(r0, r4); + __ Ret(); + + __ bind(&miss); + GenerateLoadMiss(masm(), Code::KEYED_LOAD_IC); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + MaybeObject* KeyedStoreStubCompiler::CompileStoreField(JSObject* object, int index, Map* transition, String* name) { // ----------- S t a t e ------------- // -- r0 : value - // -- r1 : key + // -- r1 : name // -- r2 : receiver // -- lr : return address // ----------------------------------- @@ -2902,6 +2988,76 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreField(JSObject* object, } +MaybeObject* KeyedStoreStubCompiler::CompileStoreSpecialized( + JSObject* receiver) { + // ----------- S t a t e ------------- + // -- r0 : value + // -- r1 : key + // -- r2 : receiver + // -- lr : return address + // -- r3 : scratch + // -- r4 : scratch (elements) + // ----------------------------------- + Label miss; + + Register value_reg = r0; + Register key_reg = r1; + Register receiver_reg = r2; + Register scratch = r3; + Register elements_reg = r4; + + // Check that the receiver isn't a smi. + __ tst(receiver_reg, Operand(kSmiTagMask)); + __ b(eq, &miss); + + // Check that the map matches. + __ ldr(scratch, FieldMemOperand(receiver_reg, HeapObject::kMapOffset)); + __ cmp(scratch, Operand(Handle<Map>(receiver->map()))); + __ b(ne, &miss); + + // Check that the key is a smi. + __ tst(key_reg, Operand(kSmiTagMask)); + __ b(ne, &miss); + + // Get the elements array and make sure it is a fast element array, not 'cow'. + __ ldr(elements_reg, + FieldMemOperand(receiver_reg, JSObject::kElementsOffset)); + __ ldr(scratch, FieldMemOperand(elements_reg, HeapObject::kMapOffset)); + __ cmp(scratch, Operand(Handle<Map>(Factory::fixed_array_map()))); + __ b(ne, &miss); + + // Check that the key is within bounds. + if (receiver->IsJSArray()) { + __ ldr(scratch, FieldMemOperand(receiver_reg, JSArray::kLengthOffset)); + } else { + __ ldr(scratch, FieldMemOperand(elements_reg, FixedArray::kLengthOffset)); + } + // Compare smis. + __ cmp(key_reg, scratch); + __ b(hs, &miss); + + __ add(scratch, + elements_reg, Operand(FixedArray::kHeaderSize - kHeapObjectTag)); + ASSERT(kSmiTag == 0 && kSmiTagSize < kPointerSizeLog2); + __ str(value_reg, + MemOperand(scratch, key_reg, LSL, kPointerSizeLog2 - kSmiTagSize)); + __ RecordWrite(scratch, + Operand(key_reg, LSL, kPointerSizeLog2 - kSmiTagSize), + receiver_reg , elements_reg); + + // value_reg (r0) is preserved. + // Done. + __ Ret(); + + __ bind(&miss); + Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Miss)); + __ Jump(ic, RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + MaybeObject* ConstructStubCompiler::CompileConstructStub(JSFunction* function) { // ----------- S t a t e ------------- // -- r0 : argc diff --git a/src/arm/virtual-frame-arm.cc b/src/arm/virtual-frame-arm.cc index 3266a16e..45f48767 100644 --- a/src/arm/virtual-frame-arm.cc +++ b/src/arm/virtual-frame-arm.cc @@ -68,8 +68,6 @@ void VirtualFrame::PopToR0() { void VirtualFrame::MergeTo(const VirtualFrame* expected, Condition cond) { if (Equals(expected)) return; - ASSERT((expected->tos_known_smi_map_ & tos_known_smi_map_) == - expected->tos_known_smi_map_); ASSERT(expected->IsCompatibleWith(this)); MergeTOSTo(expected->top_of_stack_state_, cond); ASSERT(register_allocation_map_ == expected->register_allocation_map_); @@ -78,7 +76,7 @@ void VirtualFrame::MergeTo(const VirtualFrame* expected, Condition cond) { void VirtualFrame::MergeTo(VirtualFrame* expected, Condition cond) { if (Equals(expected)) return; - tos_known_smi_map_ &= expected->tos_known_smi_map_; + expected->tos_known_smi_map_ &= tos_known_smi_map_; MergeTOSTo(expected->top_of_stack_state_, cond); ASSERT(register_allocation_map_ == expected->register_allocation_map_); } diff --git a/src/array.js b/src/array.js index 5ecf5e30..0f1e969f 100644 --- a/src/array.js +++ b/src/array.js @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -148,6 +148,9 @@ function Join(array, length, separator, convert) { } } } + elements.length = elements_length; + var result = %_FastAsciiArrayJoin(elements, ""); + if (!IS_UNDEFINED(result)) return result; return %StringBuilderConcat(elements, elements_length, ''); } finally { // Make sure to pop the visited array no matter what happens. @@ -156,9 +159,11 @@ function Join(array, length, separator, convert) { } -function ConvertToString(e) { - if (e == null) return ''; - else return ToString(e); +function ConvertToString(x) { + if (IS_STRING(x)) return x; + if (IS_NUMBER(x)) return %_NumberToString(x); + if (IS_BOOLEAN(x)) return x ? 'true' : 'false'; + return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x)); } @@ -362,14 +367,13 @@ function ArrayJoin(separator) { if (IS_UNDEFINED(separator)) { separator = ','; } else if (!IS_STRING(separator)) { - separator = ToString(separator); + separator = NonStringToString(separator); } var result = %_FastAsciiArrayJoin(this, separator); - if (typeof result != "undefined") return result; + if (!IS_UNDEFINED(result)) return result; - var length = TO_UINT32(this.length); - return Join(this, length, separator, ConvertToString); + return Join(this, TO_UINT32(this.length), separator, ConvertToString); } @@ -673,39 +677,76 @@ function ArraySort(comparefn) { function QuickSort(a, from, to) { // Insertion sort is faster for short arrays. - if (to - from <= 22) { + if (to - from <= 10) { InsertionSort(a, from, to); return; } - var pivot_index = $floor($random() * (to - from)) + from; - var pivot = a[pivot_index]; - // Issue 95: Keep the pivot element out of the comparisons to avoid - // infinite recursion if comparefn(pivot, pivot) != 0. - %_SwapElements(a, from, pivot_index); - var low_end = from; // Upper bound of the elements lower than pivot. - var high_start = to; // Lower bound of the elements greater than pivot. + // Find a pivot as the median of first, last and middle element. + var v0 = a[from]; + var v1 = a[to - 1]; + var middle_index = from + ((to - from) >> 1); + var v2 = a[middle_index]; + var c01 = %_CallFunction(global_receiver, v0, v1, comparefn); + if (c01 > 0) { + // v1 < v0, so swap them. + var tmp = v0; + v0 = v1; + v1 = tmp; + } // v0 <= v1. + var c02 = %_CallFunction(global_receiver, v0, v2, comparefn); + if (c02 >= 0) { + // v2 <= v0 <= v1. + var tmp = v0; + v0 = v2; + v2 = v1; + v1 = tmp; + } else { + // v0 <= v1 && v0 < v2 + var c12 = %_CallFunction(global_receiver, v1, v2, comparefn); + if (c12 > 0) { + // v0 <= v2 < v1 + var tmp = v1; + v1 = v2; + v2 = tmp; + } + } + // v0 <= v1 <= v2 + a[from] = v0; + a[to - 1] = v2; + var pivot = v1; + var low_end = from + 1; // Upper bound of elements lower than pivot. + var high_start = to - 1; // Lower bound of elements greater than pivot. + a[middle_index] = a[low_end]; + a[low_end] = pivot; + // From low_end to i are elements equal to pivot. // From i to high_start are elements that haven't been compared yet. - for (var i = from + 1; i < high_start; ) { + partition: for (var i = low_end + 1; i < high_start; i++) { var element = a[i]; var order = %_CallFunction(global_receiver, element, pivot, comparefn); if (order < 0) { %_SwapElements(a, i, low_end); - i++; low_end++; } else if (order > 0) { - high_start--; + do { + high_start--; + if (high_start == i) break partition; + var top_elem = a[high_start]; + order = %_CallFunction(global_receiver, top_elem, pivot, comparefn); + } while (order > 0); %_SwapElements(a, i, high_start); - } else { // order == 0 - i++; + if (order < 0) { + %_SwapElements(a, i, low_end); + low_end++; + } } } QuickSort(a, from, low_end); QuickSort(a, high_start, to); } - // Copies elements in the range 0..length from obj's prototype chain - // to obj itself, if obj has holes. Returns one more than the maximal index + // Copy elements in the range 0..length from obj's prototype chain + // to obj itself, if obj has holes. Return one more than the maximal index // of a prototype property. function CopyFromPrototype(obj, length) { var max = 0; diff --git a/src/assembler.cc b/src/assembler.cc index 7493673e..eeb84128 100644 --- a/src/assembler.cc +++ b/src/assembler.cc @@ -35,10 +35,12 @@ #include "v8.h" #include "arguments.h" +#include "deoptimizer.h" #include "execution.h" #include "ic-inl.h" #include "factory.h" #include "runtime.h" +#include "runtime-profiler.h" #include "serialize.h" #include "stub-cache.h" #include "regexp-stack.h" @@ -62,6 +64,11 @@ namespace v8 { namespace internal { +const double DoubleConstant::min_int = kMinInt; +const double DoubleConstant::one_half = 0.5; +const double DoubleConstant::negative_infinity = -V8_INFINITY; + + // ----------------------------------------------------------------------------- // Implementation of Label @@ -210,7 +217,7 @@ void RelocInfoWriter::Write(const RelocInfo* rinfo) { #endif Counters::reloc_info_count.Increment(); ASSERT(rinfo->pc() - last_pc_ >= 0); - ASSERT(RelocInfo::NUMBER_OF_MODES < kMaxRelocModes); + ASSERT(RelocInfo::NUMBER_OF_MODES <= kMaxRelocModes); // Use unsigned delta-encoding for pc. uint32_t pc_delta = static_cast<uint32_t>(rinfo->pc() - last_pc_); RelocInfo::Mode rmode = rinfo->rmode(); @@ -350,12 +357,8 @@ void RelocIterator::next() { Advance(); // Check if we want source positions. if (mode_mask_ & RelocInfo::kPositionMask) { - // Check if we want this type of source position. - if (SetMode(DebugInfoModeFromTag(GetPositionTypeTag()))) { - // Finally read the data before returning. - ReadTaggedData(); - return; - } + ReadTaggedData(); + if (SetMode(DebugInfoModeFromTag(GetPositionTypeTag()))) return; } } else { ASSERT(tag == kDefaultTag); @@ -390,7 +393,7 @@ void RelocIterator::next() { RelocIterator::RelocIterator(Code* code, int mode_mask) { rinfo_.pc_ = code->instruction_start(); rinfo_.data_ = 0; - // relocation info is read backwards + // Relocation info is read backwards. pos_ = code->relocation_start() + code->relocation_size(); end_ = code->relocation_start(); done_ = false; @@ -403,7 +406,7 @@ RelocIterator::RelocIterator(Code* code, int mode_mask) { RelocIterator::RelocIterator(const CodeDesc& desc, int mode_mask) { rinfo_.pc_ = desc.buffer; rinfo_.data_ = 0; - // relocation info is read backwards + // Relocation info is read backwards. pos_ = desc.buffer + desc.buffer_size; end_ = pos_ - desc.reloc_size; done_ = false; @@ -435,6 +438,8 @@ const char* RelocInfo::RelocModeName(RelocInfo::Mode rmode) { return "debug break"; case RelocInfo::CODE_TARGET: return "code target"; + case RelocInfo::GLOBAL_PROPERTY_CELL: + return "global property cell"; case RelocInfo::RUNTIME_ENTRY: return "runtime entry"; case RelocInfo::JS_RETURN: @@ -462,27 +467,35 @@ const char* RelocInfo::RelocModeName(RelocInfo::Mode rmode) { } -void RelocInfo::Print() { - PrintF("%p %s", pc_, RelocModeName(rmode_)); +void RelocInfo::Print(FILE* out) { + PrintF(out, "%p %s", pc_, RelocModeName(rmode_)); if (IsComment(rmode_)) { - PrintF(" (%s)", reinterpret_cast<char*>(data_)); + PrintF(out, " (%s)", reinterpret_cast<char*>(data_)); } else if (rmode_ == EMBEDDED_OBJECT) { - PrintF(" ("); - target_object()->ShortPrint(); - PrintF(")"); + PrintF(out, " ("); + target_object()->ShortPrint(out); + PrintF(out, ")"); } else if (rmode_ == EXTERNAL_REFERENCE) { ExternalReferenceEncoder ref_encoder; - PrintF(" (%s) (%p)", + PrintF(out, " (%s) (%p)", ref_encoder.NameOfAddress(*target_reference_address()), *target_reference_address()); } else if (IsCodeTarget(rmode_)) { Code* code = Code::GetCodeFromTargetAddress(target_address()); - PrintF(" (%s) (%p)", Code::Kind2String(code->kind()), target_address()); + PrintF(out, " (%s) (%p)", Code::Kind2String(code->kind()), + target_address()); } else if (IsPosition(rmode_)) { - PrintF(" (%" V8_PTR_PREFIX "d)", data()); + PrintF(out, " (%" V8_PTR_PREFIX "d)", data()); + } else if (rmode_ == RelocInfo::RUNTIME_ENTRY) { + // Depotimization bailouts are stored as runtime entries. + int id = Deoptimizer::GetDeoptimizationId( + target_address(), Deoptimizer::EAGER); + if (id != Deoptimizer::kNotDeoptimizationEntry) { + PrintF(out, " (deoptimization bailout %d)", id); + } } - PrintF("\n"); + PrintF(out, "\n"); } #endif // ENABLE_DISASSEMBLER @@ -493,6 +506,9 @@ void RelocInfo::Verify() { case EMBEDDED_OBJECT: Object::VerifyPointer(target_object()); break; + case GLOBAL_PROPERTY_CELL: + Object::VerifyPointer(target_cell()); + break; case DEBUG_BREAK: #ifndef ENABLE_DEBUGGER_SUPPORT UNREACHABLE(); @@ -599,6 +615,23 @@ ExternalReference ExternalReference::transcendental_cache_array_address() { } +ExternalReference ExternalReference::new_deoptimizer_function() { + return ExternalReference( + Redirect(FUNCTION_ADDR(Deoptimizer::New))); +} + + +ExternalReference ExternalReference::compute_output_frames_function() { + return ExternalReference( + Redirect(FUNCTION_ADDR(Deoptimizer::ComputeOutputFrames))); +} + + +ExternalReference ExternalReference::global_contexts_list() { + return ExternalReference(Heap::global_contexts_list_address()); +} + + ExternalReference ExternalReference::keyed_lookup_cache_keys() { return ExternalReference(KeyedLookupCache::keys_address()); } @@ -679,6 +712,24 @@ ExternalReference ExternalReference::scheduled_exception_address() { } +ExternalReference ExternalReference::address_of_min_int() { + return ExternalReference(reinterpret_cast<void*>( + const_cast<double*>(&DoubleConstant::min_int))); +} + + +ExternalReference ExternalReference::address_of_one_half() { + return ExternalReference(reinterpret_cast<void*>( + const_cast<double*>(&DoubleConstant::one_half))); +} + + +ExternalReference ExternalReference::address_of_negative_infinity() { + return ExternalReference(reinterpret_cast<void*>( + const_cast<double*>(&DoubleConstant::negative_infinity))); +} + + #ifndef V8_INTERPRETED_REGEXP ExternalReference ExternalReference::re_check_stack_guard_state() { @@ -750,6 +801,51 @@ static double mod_two_doubles(double x, double y) { } +// Helper function to compute x^y, where y is known to be an +// integer. Uses binary decomposition to limit the number of +// multiplications; see the discussion in "Hacker's Delight" by Henry +// S. Warren, Jr., figure 11-6, page 213. +double power_double_int(double x, int y) { + double m = (y < 0) ? 1 / x : x; + unsigned n = (y < 0) ? -y : y; + double p = 1; + while (n != 0) { + if ((n & 1) != 0) p *= m; + m *= m; + if ((n & 2) != 0) p *= m; + m *= m; + n >>= 2; + } + return p; +} + + +double power_double_double(double x, double y) { + int y_int = static_cast<int>(y); + if (y == y_int) { + return power_double_int(x, y_int); // Returns 1.0 for exponent 0. + } + if (!isinf(x)) { + if (y == 0.5) return sqrt(x); + if (y == -0.5) return 1.0 / sqrt(x); + } + if (isnan(y) || ((x == 1 || x == -1) && isinf(y))) { + return OS::nan_value(); + } + return pow(x, y); +} + + +ExternalReference ExternalReference::power_double_double_function() { + return ExternalReference(Redirect(FUNCTION_ADDR(power_double_double))); +} + + +ExternalReference ExternalReference::power_double_int_function() { + return ExternalReference(Redirect(FUNCTION_ADDR(power_double_int))); +} + + static int native_compare_doubles(double y, double x) { if (x == y) return EQUAL; return x < y ? LESS : GREATER; @@ -805,19 +901,17 @@ ExternalReference ExternalReference::debug_step_in_fp_address() { #endif -void PositionsRecorder::RecordPosition(int pos, - PositionRecordingType recording_type) { +void PositionsRecorder::RecordPosition(int pos) { ASSERT(pos != RelocInfo::kNoPosition); ASSERT(pos >= 0); - current_position_ = pos; - current_position_recording_type_ = recording_type; + state_.current_position = pos; } void PositionsRecorder::RecordStatementPosition(int pos) { ASSERT(pos != RelocInfo::kNoPosition); ASSERT(pos >= 0); - current_statement_position_ = pos; + state_.current_statement_position = pos; } @@ -826,31 +920,26 @@ bool PositionsRecorder::WriteRecordedPositions() { // Write the statement position if it is different from what was written last // time. - if (current_statement_position_ != written_statement_position_) { + if (state_.current_statement_position != state_.written_statement_position) { EnsureSpace ensure_space(assembler_); assembler_->RecordRelocInfo(RelocInfo::STATEMENT_POSITION, - current_statement_position_); - written_statement_position_ = current_statement_position_; + state_.current_statement_position); + state_.written_statement_position = state_.current_statement_position; written = true; } // Write the position if it is different from what was written last time and - // also different from the written statement position or was forced. - if (current_position_ != written_position_ && - (current_position_ != current_statement_position_ || !written) && - (current_position_ != written_statement_position_ - || current_position_recording_type_ == FORCED_POSITION)) { + // also different from the written statement position. + if (state_.current_position != state_.written_position && + state_.current_position != state_.written_statement_position) { EnsureSpace ensure_space(assembler_); - assembler_->RecordRelocInfo(RelocInfo::POSITION, current_position_); - written_position_ = current_position_; + assembler_->RecordRelocInfo(RelocInfo::POSITION, state_.current_position); + state_.written_position = state_.current_position; written = true; } - current_position_recording_type_ = NORMAL_POSITION; - // Return whether something was written. return written; } - } } // namespace v8::internal diff --git a/src/assembler.h b/src/assembler.h index 09159fed..b68ad389 100644 --- a/src/assembler.h +++ b/src/assembler.h @@ -38,13 +38,23 @@ #include "runtime.h" #include "top.h" #include "token.h" -#include "objects.h" namespace v8 { namespace internal { // ----------------------------------------------------------------------------- +// Common double constants. + +class DoubleConstant: public AllStatic { + public: + static const double min_int; + static const double one_half; + static const double negative_infinity; +}; + + +// ----------------------------------------------------------------------------- // Labels represent pc locations; they are typically jump or call targets. // After declaration, a label can be freely used to denote known or (yet) // unknown pc location. Assembler::bind() is used to bind a label to the @@ -174,6 +184,8 @@ class RelocInfo BASE_EMBEDDED { CODE_TARGET, // Code target which is not any of the above. EMBEDDED_OBJECT, + GLOBAL_PROPERTY_CELL, + // Everything after runtime_entry (inclusive) is not GC'ed. RUNTIME_ENTRY, JS_RETURN, // Marks start of the ExitJSFrame code. @@ -254,6 +266,10 @@ class RelocInfo BASE_EMBEDDED { INLINE(Handle<Object> target_object_handle(Assembler* origin)); INLINE(Object** target_object_address()); INLINE(void set_target_object(Object* target)); + INLINE(JSGlobalPropertyCell* target_cell()); + INLINE(Handle<JSGlobalPropertyCell> target_cell_handle()); + INLINE(void set_target_cell(JSGlobalPropertyCell* cell)); + // Read the address of the word containing the target_address in an // instruction stream. What this means exactly is architecture-independent. @@ -306,7 +322,7 @@ class RelocInfo BASE_EMBEDDED { #ifdef ENABLE_DISASSEMBLER // Printing static const char* RelocModeName(Mode rmode); - void Print(); + void Print(FILE* out); #endif // ENABLE_DISASSEMBLER #ifdef DEBUG // Debugging @@ -419,7 +435,7 @@ class RelocIterator: public Malloced { // If the given mode is wanted, set it in rinfo_ and return true. // Else return false. Used for efficiently skipping unwanted modes. bool SetMode(RelocInfo::Mode mode) { - return (mode_mask_ & 1 << mode) ? (rinfo_.rmode_ = mode, true) : false; + return (mode_mask_ & (1 << mode)) ? (rinfo_.rmode_ = mode, true) : false; } byte* pos_; @@ -484,6 +500,11 @@ class ExternalReference BASE_EMBEDDED { static ExternalReference transcendental_cache_array_address(); static ExternalReference delete_handle_scope_extensions(); + // Deoptimization support. + static ExternalReference new_deoptimizer_function(); + static ExternalReference compute_output_frames_function(); + static ExternalReference global_contexts_list(); + // Static data in the keyed lookup cache. static ExternalReference keyed_lookup_cache_keys(); static ExternalReference keyed_lookup_cache_field_offsets(); @@ -519,6 +540,8 @@ class ExternalReference BASE_EMBEDDED { static ExternalReference double_fp_operation(Token::Value operation); static ExternalReference compare_doubles(); + static ExternalReference power_double_double_function(); + static ExternalReference power_double_int_function(); static ExternalReference handle_scope_next_address(); static ExternalReference handle_scope_limit_address(); @@ -526,6 +549,11 @@ class ExternalReference BASE_EMBEDDED { static ExternalReference scheduled_exception_address(); + // Static variables containing common double constants. + static ExternalReference address_of_min_int(); + static ExternalReference address_of_one_half(); + static ExternalReference address_of_negative_infinity(); + Address address() const {return reinterpret_cast<Address>(address_);} #ifdef ENABLE_DEBUGGER_SUPPORT @@ -587,23 +615,27 @@ class ExternalReference BASE_EMBEDDED { // ----------------------------------------------------------------------------- // Position recording support -enum PositionRecordingType { FORCED_POSITION, NORMAL_POSITION }; +struct PositionState { + PositionState() : current_position(RelocInfo::kNoPosition), + written_position(RelocInfo::kNoPosition), + current_statement_position(RelocInfo::kNoPosition), + written_statement_position(RelocInfo::kNoPosition) {} + + int current_position; + int written_position; + + int current_statement_position; + int written_statement_position; +}; + class PositionsRecorder BASE_EMBEDDED { public: explicit PositionsRecorder(Assembler* assembler) - : assembler_(assembler), - current_position_(RelocInfo::kNoPosition), - current_position_recording_type_(NORMAL_POSITION), - written_position_(RelocInfo::kNoPosition), - current_statement_position_(RelocInfo::kNoPosition), - written_statement_position_(RelocInfo::kNoPosition) { } - - // Set current position to pos. If recording_type is FORCED_POSITION then - // WriteRecordedPositions will write this position even if it is equal to - // statement position previously written for another pc. - void RecordPosition(int pos, - PositionRecordingType recording_type = NORMAL_POSITION); + : assembler_(assembler) {} + + // Set current position to pos. + void RecordPosition(int pos); // Set current statement position to pos. void RecordStatementPosition(int pos); @@ -611,37 +643,37 @@ class PositionsRecorder BASE_EMBEDDED { // Write recorded positions to relocation information. bool WriteRecordedPositions(); - int current_position() const { return current_position_; } + int current_position() const { return state_.current_position; } - int current_statement_position() const { return current_statement_position_; } + int current_statement_position() const { + return state_.current_statement_position; + } private: Assembler* assembler_; + PositionState state_; - int current_position_; - PositionRecordingType current_position_recording_type_; - int written_position_; + friend class PreservePositionScope; - int current_statement_position_; - int written_statement_position_; + DISALLOW_COPY_AND_ASSIGN(PositionsRecorder); }; -class PreserveStatementPositionScope BASE_EMBEDDED { +class PreservePositionScope BASE_EMBEDDED { public: - explicit PreserveStatementPositionScope(PositionsRecorder* positions_recorder) + explicit PreservePositionScope(PositionsRecorder* positions_recorder) : positions_recorder_(positions_recorder), - statement_position_(positions_recorder->current_statement_position()) {} + saved_state_(positions_recorder->state_) {} - ~PreserveStatementPositionScope() { - if (statement_position_ != RelocInfo::kNoPosition) { - positions_recorder_->RecordStatementPosition(statement_position_); - } + ~PreservePositionScope() { + positions_recorder_->state_ = saved_state_; } private: PositionsRecorder* positions_recorder_; - int statement_position_; + const PositionState saved_state_; + + DISALLOW_COPY_AND_ASSIGN(PreservePositionScope); }; @@ -682,6 +714,10 @@ static inline int NumberOfBitsSet(uint32_t x) { return num_bits_set; } +// Computes pow(x, y) with the special cases in the spec for Math.pow. +double power_double_int(double x, int y); +double power_double_double(double x, double y); + } } // namespace v8::internal #endif // V8_ASSEMBLER_H_ diff --git a/src/ast-inl.h b/src/ast-inl.h index f0a25c17..eb81c3a8 100644 --- a/src/ast-inl.h +++ b/src/ast-inl.h @@ -25,18 +25,17 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef V8_AST_INL_H_ +#define V8_AST_INL_H_ + #include "v8.h" #include "ast.h" +#include "jump-target-inl.h" namespace v8 { namespace internal { -BreakableStatement::BreakableStatement(ZoneStringList* labels, Type type) - : labels_(labels), type_(type) { - ASSERT(labels == NULL || labels->length() > 0); -} - SwitchStatement::SwitchStatement(ZoneStringList* labels) : BreakableStatement(labels, TARGET_FOR_ANONYMOUS), @@ -44,17 +43,44 @@ SwitchStatement::SwitchStatement(ZoneStringList* labels) } +Block::Block(ZoneStringList* labels, int capacity, bool is_initializer_block) + : BreakableStatement(labels, TARGET_FOR_NAMED_ONLY), + statements_(capacity), + is_initializer_block_(is_initializer_block) { +} + + +BreakableStatement::BreakableStatement(ZoneStringList* labels, Type type) + : labels_(labels), + type_(type), + entry_id_(GetNextId()), + exit_id_(GetNextId()) { + ASSERT(labels == NULL || labels->length() > 0); +} + + IterationStatement::IterationStatement(ZoneStringList* labels) : BreakableStatement(labels, TARGET_FOR_ANONYMOUS), body_(NULL), - continue_target_(JumpTarget::BIDIRECTIONAL) { + continue_target_(JumpTarget::BIDIRECTIONAL), + osr_entry_id_(GetNextId()) { } -Block::Block(ZoneStringList* labels, int capacity, bool is_initializer_block) - : BreakableStatement(labels, TARGET_FOR_NAMED_ONLY), - statements_(capacity), - is_initializer_block_(is_initializer_block) { +DoWhileStatement::DoWhileStatement(ZoneStringList* labels) + : IterationStatement(labels), + cond_(NULL), + condition_position_(-1), + continue_id_(GetNextId()), + back_edge_id_(GetNextId()) { +} + + +WhileStatement::WhileStatement(ZoneStringList* labels) + : IterationStatement(labels), + cond_(NULL), + may_have_function_literal_(true), + body_id_(GetNextId()) { } @@ -64,17 +90,18 @@ ForStatement::ForStatement(ZoneStringList* labels) cond_(NULL), next_(NULL), may_have_function_literal_(true), - loop_variable_(NULL) { + loop_variable_(NULL), + continue_id_(GetNextId()), + body_id_(GetNextId()) { } ForInStatement::ForInStatement(ZoneStringList* labels) - : IterationStatement(labels), each_(NULL), enumerable_(NULL) { + : IterationStatement(labels), each_(NULL), enumerable_(NULL), + assignment_id_(GetNextId()) { } -DoWhileStatement::DoWhileStatement(ZoneStringList* labels) - : IterationStatement(labels), cond_(NULL), condition_position_(-1) { -} - } } // namespace v8::internal + +#endif // V8_AST_INL_H_ @@ -28,16 +28,17 @@ #include "v8.h" #include "ast.h" +#include "jump-target-inl.h" #include "parser.h" #include "scopes.h" #include "string-stream.h" -#include "ast-inl.h" -#include "jump-target-inl.h" +#include "stub-cache.h" namespace v8 { namespace internal { - +unsigned AstNode::current_id_ = 0; +unsigned AstNode::count_ = 0; VariableProxySentinel VariableProxySentinel::this_proxy_(true); VariableProxySentinel VariableProxySentinel::identifier_proxy_(false); ValidLeftHandSideSentinel ValidLeftHandSideSentinel::instance_; @@ -48,6 +49,8 @@ Call Call::sentinel_(NULL, NULL, 0); // ---------------------------------------------------------------------------- // All the Accept member functions for each syntax tree node type. +void Slot::Accept(AstVisitor* v) { v->VisitSlot(this); } + #define DECL_ACCEPT(type) \ void type::Accept(AstVisitor* v) { v->Visit##type(this); } AST_NODE_LIST(DECL_ACCEPT) @@ -115,6 +118,30 @@ void VariableProxy::BindTo(Variable* var) { } +Assignment::Assignment(Token::Value op, + Expression* target, + Expression* value, + int pos) + : op_(op), + target_(target), + value_(value), + pos_(pos), + binary_operation_(NULL), + compound_load_id_(kNoNumber), + assignment_id_(GetNextId()), + block_start_(false), + block_end_(false), + is_monomorphic_(false), + receiver_types_(NULL) { + ASSERT(Token::IsAssignmentOp(op)); + if (is_compound()) { + binary_operation_ = + new BinaryOperation(binary_op(), target, value, pos + 1); + compound_load_id_ = GetNextId(); + } +} + + Token::Value Assignment::binary_op() const { switch (op_) { case Token::ASSIGN_BIT_OR: return Token::BIT_OR; @@ -139,6 +166,12 @@ bool FunctionLiteral::AllowsLazyCompilation() { } +bool FunctionLiteral::AllowOptimize() { + // We can't deal with heap-allocated locals. + return scope()->num_heap_slots() == 0; +} + + ObjectLiteral::Property::Property(Literal* key, Expression* value) { emit_store_ = true; key_ = key; @@ -373,6 +406,267 @@ BinaryOperation::BinaryOperation(Assignment* assignment) { // ---------------------------------------------------------------------------- +// Inlining support + +bool Block::IsInlineable() const { + const int count = statements_.length(); + for (int i = 0; i < count; ++i) { + if (!statements_[i]->IsInlineable()) return false; + } + return true; +} + + +bool ExpressionStatement::IsInlineable() const { + return expression()->IsInlineable(); +} + + +bool IfStatement::IsInlineable() const { + return condition()->IsInlineable() && then_statement()->IsInlineable() && + else_statement()->IsInlineable(); +} + + +bool ReturnStatement::IsInlineable() const { + return expression()->IsInlineable(); +} + + +bool Conditional::IsInlineable() const { + return condition()->IsInlineable() && then_expression()->IsInlineable() && + else_expression()->IsInlineable(); +} + + +bool VariableProxy::IsInlineable() const { + return var()->is_global() || var()->IsStackAllocated(); +} + + +bool Assignment::IsInlineable() const { + return target()->IsInlineable() && value()->IsInlineable(); +} + + +bool Property::IsInlineable() const { + return obj()->IsInlineable() && key()->IsInlineable(); +} + + +bool Call::IsInlineable() const { + if (!expression()->IsInlineable()) return false; + const int count = arguments()->length(); + for (int i = 0; i < count; ++i) { + if (!arguments()->at(i)->IsInlineable()) return false; + } + return true; +} + + +bool CallNew::IsInlineable() const { + if (!expression()->IsInlineable()) return false; + const int count = arguments()->length(); + for (int i = 0; i < count; ++i) { + if (!arguments()->at(i)->IsInlineable()) return false; + } + return true; +} + + +bool CallRuntime::IsInlineable() const { + const int count = arguments()->length(); + for (int i = 0; i < count; ++i) { + if (!arguments()->at(i)->IsInlineable()) return false; + } + return true; +} + + +bool UnaryOperation::IsInlineable() const { + return expression()->IsInlineable(); +} + + +bool BinaryOperation::IsInlineable() const { + return left()->IsInlineable() && right()->IsInlineable(); +} + + +bool CompareOperation::IsInlineable() const { + return left()->IsInlineable() && right()->IsInlineable(); +} + + +bool CompareToNull::IsInlineable() const { + return expression()->IsInlineable(); +} + + +bool CountOperation::IsInlineable() const { + return expression()->IsInlineable(); +} + + +// ---------------------------------------------------------------------------- +// Recording of type feedback + +void Property::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + // Record type feedback from the oracle in the AST. + is_monomorphic_ = oracle->LoadIsMonomorphic(this); + if (key()->IsPropertyName()) { + if (oracle->LoadIsBuiltin(this, Builtins::LoadIC_ArrayLength)) { + is_array_length_ = true; + } else { + Literal* lit_key = key()->AsLiteral(); + ASSERT(lit_key != NULL && lit_key->handle()->IsString()); + Handle<String> name = Handle<String>::cast(lit_key->handle()); + ZoneMapList* types = oracle->LoadReceiverTypes(this, name); + receiver_types_ = types; + } + } else if (is_monomorphic_) { + monomorphic_receiver_type_ = oracle->LoadMonomorphicReceiverType(this); + } +} + + +void Assignment::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + Property* prop = target()->AsProperty(); + ASSERT(prop != NULL); + is_monomorphic_ = oracle->StoreIsMonomorphic(this); + if (prop->key()->IsPropertyName()) { + Literal* lit_key = prop->key()->AsLiteral(); + ASSERT(lit_key != NULL && lit_key->handle()->IsString()); + Handle<String> name = Handle<String>::cast(lit_key->handle()); + ZoneMapList* types = oracle->StoreReceiverTypes(this, name); + receiver_types_ = types; + } else if (is_monomorphic_) { + // Record receiver type for monomorphic keyed loads. + monomorphic_receiver_type_ = oracle->StoreMonomorphicReceiverType(this); + } +} + + +void CaseClause::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + TypeInfo info = oracle->SwitchType(this); + if (info.IsSmi()) { + compare_type_ = SMI_ONLY; + } else if (info.IsNonPrimitive()) { + compare_type_ = OBJECT_ONLY; + } else { + ASSERT(compare_type_ == NONE); + } +} + + +static bool CallWithoutIC(Handle<JSFunction> target, int arity) { + SharedFunctionInfo* info = target->shared(); + if (target->NeedsArgumentsAdaption()) { + // If the number of formal parameters of the target function + // does not match the number of arguments we're passing, we + // don't want to deal with it. + return info->formal_parameter_count() == arity; + } else { + // If the target doesn't need arguments adaption, we can call + // it directly, but we avoid to do so if it has a custom call + // generator, because that is likely to generate better code. + return !info->HasBuiltinFunctionId() || + !CallStubCompiler::HasCustomCallGenerator(info->builtin_function_id()); + } +} + + +bool Call::ComputeTarget(Handle<Map> type, Handle<String> name) { + holder_ = Handle<JSObject>::null(); + while (true) { + LookupResult lookup; + type->LookupInDescriptors(NULL, *name, &lookup); + // If the function wasn't found directly in the map, we start + // looking upwards through the prototype chain. + if (!lookup.IsFound() && type->prototype()->IsJSObject()) { + holder_ = Handle<JSObject>(JSObject::cast(type->prototype())); + type = Handle<Map>(holder()->map()); + } else if (lookup.IsProperty() && lookup.type() == CONSTANT_FUNCTION) { + target_ = Handle<JSFunction>(lookup.GetConstantFunctionFromMap(*type)); + return CallWithoutIC(target_, arguments()->length()); + } else { + return false; + } + } +} + + +bool Call::ComputeGlobalTarget(Handle<GlobalObject> global, + Handle<String> name) { + target_ = Handle<JSFunction>::null(); + cell_ = Handle<JSGlobalPropertyCell>::null(); + LookupResult lookup; + global->Lookup(*name, &lookup); + if (lookup.IsProperty() && lookup.type() == NORMAL) { + cell_ = Handle<JSGlobalPropertyCell>(global->GetPropertyCell(&lookup)); + if (cell_->value()->IsJSFunction()) { + Handle<JSFunction> candidate(JSFunction::cast(cell_->value())); + // If the function is in new space we assume it's more likely to + // change and thus prefer the general IC code. + if (!Heap::InNewSpace(*candidate) + && CallWithoutIC(candidate, arguments()->length())) { + target_ = candidate; + return true; + } + } + } + return false; +} + + +void Call::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + Property* property = expression()->AsProperty(); + ASSERT(property != NULL); + // Specialize for the receiver types seen at runtime. + Literal* key = property->key()->AsLiteral(); + ASSERT(key != NULL && key->handle()->IsString()); + Handle<String> name = Handle<String>::cast(key->handle()); + receiver_types_ = oracle->CallReceiverTypes(this, name); +#ifdef DEBUG + if (FLAG_enable_slow_asserts) { + if (receiver_types_ != NULL) { + int length = receiver_types_->length(); + for (int i = 0; i < length; i++) { + Handle<Map> map = receiver_types_->at(i); + ASSERT(!map.is_null() && *map != NULL); + } + } + } +#endif + if (receiver_types_ != NULL && receiver_types_->length() > 0) { + Handle<Map> type = receiver_types_->at(0); + is_monomorphic_ = oracle->CallIsMonomorphic(this); + if (is_monomorphic_) is_monomorphic_ = ComputeTarget(type, name); + } +} + + +void BinaryOperation::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + TypeInfo left = oracle->BinaryType(this, TypeFeedbackOracle::LEFT); + TypeInfo right = oracle->BinaryType(this, TypeFeedbackOracle::RIGHT); + is_smi_only_ = left.IsSmi() && right.IsSmi(); +} + + +void CompareOperation::RecordTypeFeedback(TypeFeedbackOracle* oracle) { + TypeInfo left = oracle->CompareType(this, TypeFeedbackOracle::LEFT); + TypeInfo right = oracle->CompareType(this, TypeFeedbackOracle::RIGHT); + if (left.IsSmi() && right.IsSmi()) { + compare_type_ = SMI_ONLY; + } else if (left.IsNonPrimitive() && right.IsNonPrimitive()) { + compare_type_ = OBJECT_ONLY; + } else { + ASSERT(compare_type_ == NONE); + } +} + + +// ---------------------------------------------------------------------------- // Implementation of AstVisitor bool AstVisitor::CheckStackOverflow() { @@ -742,15 +1036,12 @@ RegExpAlternative::RegExpAlternative(ZoneList<RegExpTree*>* nodes) } -WhileStatement::WhileStatement(ZoneStringList* labels) - : IterationStatement(labels), - cond_(NULL), - may_have_function_literal_(true) { -} - - -CaseClause::CaseClause(Expression* label, ZoneList<Statement*>* statements) - : label_(label), statements_(statements) { -} +CaseClause::CaseClause(Expression* label, + ZoneList<Statement*>* statements, + int pos) + : label_(label), + statements_(statements), + position_(pos), + compare_type_(NONE) {} } } // namespace v8::internal @@ -75,7 +75,6 @@ namespace internal { V(FunctionLiteral) \ V(SharedFunctionInfoLiteral) \ V(Conditional) \ - V(Slot) \ V(VariableProxy) \ V(Literal) \ V(RegExpLiteral) \ @@ -102,10 +101,11 @@ namespace internal { EXPRESSION_NODE_LIST(V) // Forward declarations -class TargetCollector; -class MaterializedLiteral; -class DefinitionInfo; class BitVector; +class DefinitionInfo; +class MaterializedLiteral; +class TargetCollector; +class TypeFeedbackOracle; #define DEF_FORWARD_DECLARATION(type) class type; AST_NODE_LIST(DEF_FORWARD_DECLARATION) @@ -133,6 +133,10 @@ class AstNode: public ZoneObject { }; #undef DECLARE_TYPE_ENUM + static const int kNoNumber = -1; + + AstNode() : id_(GetNextId()) { count_++; } + virtual ~AstNode() { } virtual void Accept(AstVisitor* v) = 0; @@ -150,6 +154,27 @@ class AstNode: public ZoneObject { virtual BreakableStatement* AsBreakableStatement() { return NULL; } virtual IterationStatement* AsIterationStatement() { return NULL; } virtual MaterializedLiteral* AsMaterializedLiteral() { return NULL; } + virtual Slot* AsSlot() { return NULL; } + + // True if the node is simple enough for us to inline calls containing it. + virtual bool IsInlineable() const { return false; } + + static int Count() { return count_; } + static void ResetIds() { current_id_ = 0; } + unsigned id() const { return id_; } + + protected: + static unsigned GetNextId() { return current_id_++; } + static unsigned ReserveIdRange(int n) { + unsigned tmp = current_id_; + current_id_ += n; + return tmp; + } + + private: + static unsigned current_id_; + static unsigned count_; + unsigned id_; }; @@ -174,6 +199,18 @@ class Statement: public AstNode { class Expression: public AstNode { public: + enum Context { + // Not assigned a context yet, or else will not be visited during + // code generation. + kUninitialized, + // Evaluated for its side effects. + kEffect, + // Evaluated for its value (and side effects). + kValue, + // Evaluated for control flow (and side effects). + kTest + }; + Expression() : bitfields_(0) {} virtual Expression* AsExpression() { return this; } @@ -181,6 +218,10 @@ class Expression: public AstNode { virtual bool IsTrivial() { return false; } virtual bool IsValidLeftHandSide() { return false; } + // Helpers for ToBoolean conversion. + virtual bool ToBooleanIsTrue() { return false; } + virtual bool ToBooleanIsFalse() { return false; } + // Symbols that cannot be parsed as array indices are considered property // names. We do not treat symbols that can be array indexes as property // names because [] for string objects is handled only by keyed ICs. @@ -198,6 +239,24 @@ class Expression: public AstNode { // True iff the expression is a literal represented as a smi. virtual bool IsSmiLiteral() { return false; } + // Type feedback information for assignments and properties. + virtual bool IsMonomorphic() { + UNREACHABLE(); + return false; + } + virtual bool IsArrayLength() { + UNREACHABLE(); + return false; + } + virtual ZoneMapList* GetReceiverTypes() { + UNREACHABLE(); + return NULL; + } + virtual Handle<Map> GetMonomorphicReceiverType() { + UNREACHABLE(); + return Handle<Map>(); + } + // Static type information for this expression. StaticType* type() { return &type_; } @@ -301,6 +360,10 @@ class BreakableStatement: public Statement { // Testers. bool is_target_for_anonymous() const { return type_ == TARGET_FOR_ANONYMOUS; } + // Bailout support. + int EntryId() const { return entry_id_; } + int ExitId() const { return exit_id_; } + protected: inline BreakableStatement(ZoneStringList* labels, Type type); @@ -308,6 +371,8 @@ class BreakableStatement: public Statement { ZoneStringList* labels_; Type type_; BreakTarget break_target_; + int entry_id_; + int exit_id_; }; @@ -327,6 +392,8 @@ class Block: public BreakableStatement { return statements_[0]->StatementAsCountOperation(); } + virtual bool IsInlineable() const; + void AddStatement(Statement* statement) { statements_.Add(statement); } ZoneList<Statement*>* statements() { return &statements_; } @@ -368,7 +435,10 @@ class IterationStatement: public BreakableStatement { virtual IterationStatement* AsIterationStatement() { return this; } Statement* body() const { return body_; } - void set_body(Statement* stmt) { body_ = stmt; } + + // Bailout support. + int OsrEntryId() const { return osr_entry_id_; } + virtual int ContinueId() const = 0; // Code generation BreakTarget* continue_target() { return &continue_target_; } @@ -383,6 +453,7 @@ class IterationStatement: public BreakableStatement { private: Statement* body_; BreakTarget continue_target_; + int osr_entry_id_; }; @@ -404,15 +475,21 @@ class DoWhileStatement: public IterationStatement { int condition_position() { return condition_position_; } void set_condition_position(int pos) { condition_position_ = pos; } + // Bailout support. + virtual int ContinueId() const { return continue_id_; } + int BackEdgeId() const { return back_edge_id_; } + private: Expression* cond_; int condition_position_; + int continue_id_; + int back_edge_id_; }; class WhileStatement: public IterationStatement { public: - explicit WhileStatement(ZoneStringList* labels); + explicit inline WhileStatement(ZoneStringList* labels); DECLARE_NODE_TYPE(WhileStatement) @@ -429,10 +506,15 @@ class WhileStatement: public IterationStatement { may_have_function_literal_ = value; } + // Bailout support. + virtual int ContinueId() const { return EntryId(); } + int BodyId() const { return body_id_; } + private: Expression* cond_; // True if there is a function literal subexpression in the condition. bool may_have_function_literal_; + int body_id_; }; @@ -453,11 +535,8 @@ class ForStatement: public IterationStatement { } Statement* init() const { return init_; } - void set_init(Statement* stmt) { init_ = stmt; } Expression* cond() const { return cond_; } - void set_cond(Expression* expr) { cond_ = expr; } Statement* next() const { return next_; } - void set_next(Statement* stmt) { next_ = stmt; } bool may_have_function_literal() const { return may_have_function_literal_; @@ -466,6 +545,10 @@ class ForStatement: public IterationStatement { may_have_function_literal_ = value; } + // Bailout support. + virtual int ContinueId() const { return continue_id_; } + int BodyId() const { return body_id_; } + bool is_fast_smi_loop() { return loop_variable_ != NULL; } Variable* loop_variable() { return loop_variable_; } void set_loop_variable(Variable* var) { loop_variable_ = var; } @@ -477,6 +560,8 @@ class ForStatement: public IterationStatement { // True if there is a function literal subexpression in the condition. bool may_have_function_literal_; Variable* loop_variable_; + int continue_id_; + int body_id_; }; @@ -495,9 +580,14 @@ class ForInStatement: public IterationStatement { Expression* each() const { return each_; } Expression* enumerable() const { return enumerable_; } + // Bailout support. + int AssignmentId() const { return assignment_id_; } + virtual int ContinueId() const { return EntryId(); } + private: Expression* each_; Expression* enumerable_; + int assignment_id_; }; @@ -508,11 +598,13 @@ class ExpressionStatement: public Statement { DECLARE_NODE_TYPE(ExpressionStatement) + virtual bool IsInlineable() const; + virtual Assignment* StatementAsSimpleAssignment(); virtual CountOperation* StatementAsCountOperation(); void set_expression(Expression* e) { expression_ = e; } - Expression* expression() { return expression_; } + Expression* expression() const { return expression_; } private: Expression* expression_; @@ -554,7 +646,8 @@ class ReturnStatement: public Statement { DECLARE_NODE_TYPE(ReturnStatement) - Expression* expression() { return expression_; } + Expression* expression() const { return expression_; } + virtual bool IsInlineable() const; private: Expression* expression_; @@ -588,7 +681,7 @@ class WithExitStatement: public Statement { class CaseClause: public ZoneObject { public: - CaseClause(Expression* label, ZoneList<Statement*>* statements); + CaseClause(Expression* label, ZoneList<Statement*>* statements, int pos); bool is_default() const { return label_ == NULL; } Expression* label() const { @@ -598,10 +691,21 @@ class CaseClause: public ZoneObject { JumpTarget* body_target() { return &body_target_; } ZoneList<Statement*>* statements() const { return statements_; } + int position() { return position_; } + void set_position(int pos) { position_ = pos; } + + // Type feedback information. + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + bool IsSmiCompare() { return compare_type_ == SMI_ONLY; } + bool IsObjectCompare() { return compare_type_ == OBJECT_ONLY; } + private: Expression* label_; JumpTarget body_target_; ZoneList<Statement*>* statements_; + int position_; + enum CompareTypeFeedback { NONE, SMI_ONLY, OBJECT_ONLY }; + CompareTypeFeedback compare_type_; }; @@ -637,23 +741,31 @@ class IfStatement: public Statement { Statement* else_statement) : condition_(condition), then_statement_(then_statement), - else_statement_(else_statement) { } + else_statement_(else_statement), + then_id_(GetNextId()), + else_id_(GetNextId()) { + } DECLARE_NODE_TYPE(IfStatement) + virtual bool IsInlineable() const; + bool HasThenStatement() const { return !then_statement()->IsEmpty(); } bool HasElseStatement() const { return !else_statement()->IsEmpty(); } Expression* condition() const { return condition_; } Statement* then_statement() const { return then_statement_; } - void set_then_statement(Statement* stmt) { then_statement_ = stmt; } Statement* else_statement() const { return else_statement_; } - void set_else_statement(Statement* stmt) { else_statement_ = stmt; } + + int ThenId() const { return then_id_; } + int ElseId() const { return else_id_; } private: Expression* condition_; Statement* then_statement_; Statement* else_statement_; + int then_id_; + int else_id_; }; @@ -744,6 +856,8 @@ class DebuggerStatement: public Statement { class EmptyStatement: public Statement { public: DECLARE_NODE_TYPE(EmptyStatement) + + virtual bool IsInlineable() const { return true; } }; @@ -754,6 +868,7 @@ class Literal: public Expression { DECLARE_NODE_TYPE(Literal) virtual bool IsTrivial() { return true; } + virtual bool IsInlineable() const { return true; } virtual bool IsSmiLiteral() { return handle_->IsSmi(); } // Check if this literal is identical to the other literal. @@ -769,6 +884,14 @@ class Literal: public Expression { return false; } + Handle<String> AsPropertyName() { + ASSERT(IsPropertyName()); + return Handle<String>::cast(handle_); + } + + virtual bool ToBooleanIsTrue() { return handle_->ToBoolean()->IsTrue(); } + virtual bool ToBooleanIsFalse() { return handle_->ToBoolean()->IsFalse(); } + // Identity testers. bool IsNull() const { return handle_.is_identical_to(Factory::null_value()); } bool IsTrue() const { return handle_.is_identical_to(Factory::true_value()); } @@ -906,16 +1029,21 @@ class ArrayLiteral: public MaterializedLiteral { int depth) : MaterializedLiteral(literal_index, is_simple, depth), constant_elements_(constant_elements), - values_(values) {} + values_(values), + first_element_id_(ReserveIdRange(values->length())) {} DECLARE_NODE_TYPE(ArrayLiteral) Handle<FixedArray> constant_elements() const { return constant_elements_; } ZoneList<Expression*>* values() const { return values_; } + // Return an AST id for an element that is used in simulate instructions. + int GetIdForElement(int i) { return first_element_id_ + i; } + private: Handle<FixedArray> constant_elements_; ZoneList<Expression*>* values_; + int first_element_id_; }; @@ -967,6 +1095,8 @@ class VariableProxy: public Expression { return is_this_ || is_trivial_; } + virtual bool IsInlineable() const; + bool IsVariable(Handle<String> n) { return !is_this() && name().is_identical_to(n); } @@ -1044,7 +1174,9 @@ class Slot: public Expression { ASSERT(var != NULL); } - DECLARE_NODE_TYPE(Slot) + virtual void Accept(AstVisitor* v); + + virtual Slot* AsSlot() { return this; } bool IsStackAllocated() { return type_ == PARAMETER || type_ == LOCAL; } @@ -1069,17 +1201,41 @@ class Property: public Expression { // of the resolved Reference. enum Type { NORMAL, SYNTHETIC }; Property(Expression* obj, Expression* key, int pos, Type type = NORMAL) - : obj_(obj), key_(key), pos_(pos), type_(type) { } + : obj_(obj), + key_(key), + pos_(pos), + type_(type), + is_monomorphic_(false), + receiver_types_(NULL), + is_array_length_(false), + is_arguments_access_(false) { } DECLARE_NODE_TYPE(Property) virtual bool IsValidLeftHandSide() { return true; } + virtual bool IsInlineable() const; Expression* obj() const { return obj_; } Expression* key() const { return key_; } int position() const { return pos_; } bool is_synthetic() const { return type_ == SYNTHETIC; } + // Marks that this is actually an argument rewritten to a keyed property + // accessing the argument through the arguments shadow object. + void set_is_arguments_access(bool is_arguments_access) { + is_arguments_access_ = is_arguments_access; + } + bool is_arguments_access() const { return is_arguments_access_; } + + // Type feedback information. + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + virtual bool IsMonomorphic() { return is_monomorphic_; } + virtual ZoneMapList* GetReceiverTypes() { return receiver_types_; } + virtual bool IsArrayLength() { return is_array_length_; } + virtual Handle<Map> GetMonomorphicReceiverType() { + return monomorphic_receiver_type_; + } + // Returns a property singleton property access on 'this'. Used // during preparsing. static Property* this_property() { return &this_property_; } @@ -1090,6 +1246,12 @@ class Property: public Expression { int pos_; Type type_; + bool is_monomorphic_; + ZoneMapList* receiver_types_; + bool is_array_length_; + bool is_arguments_access_; + Handle<Map> monomorphic_receiver_type_; + // Dummy property used during preparsing. static Property this_property_; }; @@ -1098,21 +1260,55 @@ class Property: public Expression { class Call: public Expression { public: Call(Expression* expression, ZoneList<Expression*>* arguments, int pos) - : expression_(expression), arguments_(arguments), pos_(pos) { } + : expression_(expression), + arguments_(arguments), + pos_(pos), + is_monomorphic_(false), + receiver_types_(NULL), + return_id_(GetNextId()) { + } DECLARE_NODE_TYPE(Call) + virtual bool IsInlineable() const; + Expression* expression() const { return expression_; } ZoneList<Expression*>* arguments() const { return arguments_; } int position() { return pos_; } + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + virtual ZoneMapList* GetReceiverTypes() { return receiver_types_; } + virtual bool IsMonomorphic() { return is_monomorphic_; } + Handle<JSFunction> target() { return target_; } + Handle<JSObject> holder() { return holder_; } + Handle<JSGlobalPropertyCell> cell() { return cell_; } + + bool ComputeTarget(Handle<Map> type, Handle<String> name); + bool ComputeGlobalTarget(Handle<GlobalObject> global, Handle<String> name); + + // Bailout support. + int ReturnId() const { return return_id_; } + static Call* sentinel() { return &sentinel_; } +#ifdef DEBUG + // Used to assert that the FullCodeGenerator records the return site. + bool return_is_recorded_; +#endif + private: Expression* expression_; ZoneList<Expression*>* arguments_; int pos_; + bool is_monomorphic_; + ZoneMapList* receiver_types_; + Handle<JSFunction> target_; + Handle<JSObject> holder_; + Handle<JSGlobalPropertyCell> cell_; + + int return_id_; + static Call sentinel_; }; @@ -1124,6 +1320,8 @@ class CallNew: public Expression { DECLARE_NODE_TYPE(CallNew) + virtual bool IsInlineable() const; + Expression* expression() const { return expression_; } ZoneList<Expression*>* arguments() const { return arguments_; } int position() { return pos_; } @@ -1148,6 +1346,8 @@ class CallRuntime: public Expression { DECLARE_NODE_TYPE(CallRuntime) + virtual bool IsInlineable() const; + Handle<String> name() const { return name_; } Runtime::Function* function() const { return function_; } ZoneList<Expression*>* arguments() const { return arguments_; } @@ -1169,6 +1369,8 @@ class UnaryOperation: public Expression { DECLARE_NODE_TYPE(UnaryOperation) + virtual bool IsInlineable() const; + virtual bool ResultOverwriteAllowed(); Token::Value op() const { return op_; } @@ -1186,8 +1388,11 @@ class BinaryOperation: public Expression { Expression* left, Expression* right, int pos) - : op_(op), left_(left), right_(right), pos_(pos) { + : op_(op), left_(left), right_(right), pos_(pos), is_smi_only_(false) { ASSERT(Token::IsBinaryOp(op)); + right_id_ = (op == Token::AND || op == Token::OR) + ? GetNextId() + : AstNode::kNoNumber; } // Create the binary operation corresponding to a compound assignment. @@ -1195,6 +1400,8 @@ class BinaryOperation: public Expression { DECLARE_NODE_TYPE(BinaryOperation) + virtual bool IsInlineable() const; + virtual bool ResultOverwriteAllowed(); Token::Value op() const { return op_; } @@ -1202,11 +1409,22 @@ class BinaryOperation: public Expression { Expression* right() const { return right_; } int position() const { return pos_; } + // Type feedback information. + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + bool IsSmiOnly() const { return is_smi_only_; } + + // Bailout support. + int RightId() const { return right_id_; } + private: Token::Value op_; Expression* left_; Expression* right_; int pos_; + bool is_smi_only_; + // The short-circuit logical operations have an AST ID for their + // right-hand subexpression. + int right_id_; }; @@ -1233,7 +1451,9 @@ class IncrementOperation: public Expression { class CountOperation: public Expression { public: CountOperation(bool is_prefix, IncrementOperation* increment, int pos) - : is_prefix_(is_prefix), increment_(increment), pos_(pos) { } + : is_prefix_(is_prefix), increment_(increment), pos_(pos), + assignment_id_(GetNextId()) { + } DECLARE_NODE_TYPE(CountOperation) @@ -1251,10 +1471,16 @@ class CountOperation: public Expression { virtual void MarkAsStatement() { is_prefix_ = true; } + virtual bool IsInlineable() const; + + // Bailout support. + int AssignmentId() const { return assignment_id_; } + private: bool is_prefix_; IncrementOperation* increment_; int pos_; + int assignment_id_; }; @@ -1264,7 +1490,7 @@ class CompareOperation: public Expression { Expression* left, Expression* right, int pos) - : op_(op), left_(left), right_(right), pos_(pos) { + : op_(op), left_(left), right_(right), pos_(pos), compare_type_(NONE) { ASSERT(Token::IsCompareOp(op)); } @@ -1275,11 +1501,21 @@ class CompareOperation: public Expression { Expression* right() const { return right_; } int position() const { return pos_; } + virtual bool IsInlineable() const; + + // Type feedback information. + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + bool IsSmiCompare() { return compare_type_ == SMI_ONLY; } + bool IsObjectCompare() { return compare_type_ == OBJECT_ONLY; } + private: Token::Value op_; Expression* left_; Expression* right_; int pos_; + + enum CompareTypeFeedback { NONE, SMI_ONLY, OBJECT_ONLY }; + CompareTypeFeedback compare_type_; }; @@ -1290,6 +1526,8 @@ class CompareToNull: public Expression { DECLARE_NODE_TYPE(CompareToNull) + virtual bool IsInlineable() const; + bool is_strict() const { return is_strict_; } Token::Value op() const { return is_strict_ ? Token::EQ_STRICT : Token::EQ; } Expression* expression() const { return expression_; } @@ -1311,16 +1549,24 @@ class Conditional: public Expression { then_expression_(then_expression), else_expression_(else_expression), then_expression_position_(then_expression_position), - else_expression_position_(else_expression_position) { } + else_expression_position_(else_expression_position), + then_id_(GetNextId()), + else_id_(GetNextId()) { + } DECLARE_NODE_TYPE(Conditional) + virtual bool IsInlineable() const; + Expression* condition() const { return condition_; } Expression* then_expression() const { return then_expression_; } Expression* else_expression() const { return else_expression_; } - int then_expression_position() { return then_expression_position_; } - int else_expression_position() { return else_expression_position_; } + int then_expression_position() const { return then_expression_position_; } + int else_expression_position() const { return else_expression_position_; } + + int ThenId() const { return then_id_; } + int ElseId() const { return else_id_; } private: Expression* condition_; @@ -1328,19 +1574,19 @@ class Conditional: public Expression { Expression* else_expression_; int then_expression_position_; int else_expression_position_; + int then_id_; + int else_id_; }; class Assignment: public Expression { public: - Assignment(Token::Value op, Expression* target, Expression* value, int pos) - : op_(op), target_(target), value_(value), pos_(pos), - block_start_(false), block_end_(false) { - ASSERT(Token::IsAssignmentOp(op)); - } + Assignment(Token::Value op, Expression* target, Expression* value, int pos); DECLARE_NODE_TYPE(Assignment) + virtual bool IsInlineable() const; + Assignment* AsSimpleAssignment() { return !is_compound() ? this : NULL; } Token::Value binary_op() const; @@ -1349,6 +1595,8 @@ class Assignment: public Expression { Expression* target() const { return target_; } Expression* value() const { return value_; } int position() { return pos_; } + BinaryOperation* binary_operation() const { return binary_operation_; } + // This check relies on the definition order of token in token.h. bool is_compound() const { return op() > Token::ASSIGN; } @@ -1361,13 +1609,33 @@ class Assignment: public Expression { void mark_block_start() { block_start_ = true; } void mark_block_end() { block_end_ = true; } + // Type feedback information. + void RecordTypeFeedback(TypeFeedbackOracle* oracle); + virtual bool IsMonomorphic() { return is_monomorphic_; } + virtual ZoneMapList* GetReceiverTypes() { return receiver_types_; } + virtual Handle<Map> GetMonomorphicReceiverType() { + return monomorphic_receiver_type_; + } + + // Bailout support. + int CompoundLoadId() const { return compound_load_id_; } + int AssignmentId() const { return assignment_id_; } + private: Token::Value op_; Expression* target_; Expression* value_; int pos_; + BinaryOperation* binary_operation_; + int compound_load_id_; + int assignment_id_; + bool block_start_; bool block_end_; + + bool is_monomorphic_; + ZoneMapList* receiver_types_; + Handle<Map> monomorphic_receiver_type_; }; @@ -1417,11 +1685,7 @@ class FunctionLiteral: public Expression { function_token_position_(RelocInfo::kNoPosition), inferred_name_(Heap::empty_string()), try_full_codegen_(false), - pretenure_(false) { -#ifdef DEBUG - already_compiled_ = false; -#endif - } + pretenure_(false) { } DECLARE_NODE_TYPE(FunctionLiteral) @@ -1446,6 +1710,7 @@ class FunctionLiteral: public Expression { int num_parameters() { return num_parameters_; } bool AllowsLazyCompilation(); + bool AllowOptimize(); Handle<String> debug_name() const { if (name_->length() > 0) return name_; @@ -1463,13 +1728,6 @@ class FunctionLiteral: public Expression { bool pretenure() { return pretenure_; } void set_pretenure(bool value) { pretenure_ = value; } -#ifdef DEBUG - void mark_as_compiled() { - ASSERT(!already_compiled_); - already_compiled_ = true; - } -#endif - private: Handle<String> name_; Scope* scope_; @@ -1487,9 +1745,6 @@ class FunctionLiteral: public Expression { Handle<String> inferred_name_; bool try_full_codegen_; bool pretenure_; -#ifdef DEBUG - bool already_compiled_; -#endif }; @@ -1894,8 +2149,12 @@ class AstVisitor BASE_EMBEDDED { // node, calling SetStackOverflow will make sure that the visitor // bails out without visiting more nodes. void SetStackOverflow() { stack_overflow_ = true; } + void ClearStackOverflow() { stack_overflow_ = false; } + + // Nodes not appearing in the AST, including slots. + virtual void VisitSlot(Slot* node) { UNREACHABLE(); } - // Individual nodes + // Individual AST nodes. #define DEF_VISIT(type) \ virtual void Visit##type(type* node) = 0; AST_NODE_LIST(DEF_VISIT) diff --git a/src/atomicops.h b/src/atomicops.h new file mode 100644 index 00000000..72a0d0fb --- /dev/null +++ b/src/atomicops.h @@ -0,0 +1,165 @@ +// Copyright 2010 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. + +// The routines exported by this module are subtle. If you use them, even if +// you get the code right, it will depend on careful reasoning about atomicity +// and memory ordering; it will be less readable, and harder to maintain. If +// you plan to use these routines, you should have a good reason, such as solid +// evidence that performance would otherwise suffer, or there being no +// alternative. You should assume only properties explicitly guaranteed by the +// specifications in this file. You are almost certainly _not_ writing code +// just for the x86; if you assume x86 semantics, x86 hardware bugs and +// implementations on other archtectures will cause your code to break. If you +// do not know what you are doing, avoid these routines, and use a Mutex. +// +// It is incorrect to make direct assignments to/from an atomic variable. +// You should use one of the Load or Store routines. The NoBarrier +// versions are provided when no barriers are needed: +// NoBarrier_Store() +// NoBarrier_Load() +// Although there are currently no compiler enforcement, you are encouraged +// to use these. +// + +#ifndef V8_ATOMICOPS_H_ +#define V8_ATOMICOPS_H_ + +#include "../include/v8.h" +#include "globals.h" + +namespace v8 { +namespace internal { + +typedef int32_t Atomic32; +#ifdef V8_HOST_ARCH_64_BIT +// We need to be able to go between Atomic64 and AtomicWord implicitly. This +// means Atomic64 and AtomicWord should be the same type on 64-bit. +#if defined(__APPLE__) +// MacOS is an exception to the implicit conversion rule above, +// because it uses long for intptr_t. +typedef int64_t Atomic64; +#else +typedef intptr_t Atomic64; +#endif +#endif + +// Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or +// Atomic64 routines below, depending on your architecture. +typedef intptr_t AtomicWord; + +// Atomically execute: +// result = *ptr; +// if (*ptr == old_value) +// *ptr = new_value; +// return result; +// +// I.e., replace "*ptr" with "new_value" if "*ptr" used to be "old_value". +// Always return the old value of "*ptr" +// +// This routine implies no memory barriers. +Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +// Atomically store new_value into *ptr, returning the previous value held in +// *ptr. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, Atomic32 new_value); + +// Atomically increment *ptr by "increment". Returns the new value of +// *ptr with the increment applied. This routine implies no memory barriers. +Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, Atomic32 increment); + +Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment); + +// These following lower-level operations are typically useful only to people +// implementing higher-level synchronization operations like spinlocks, +// mutexes, and condition-variables. They combine CompareAndSwap(), a load, or +// a store with appropriate memory-ordering instructions. "Acquire" operations +// ensure that no later memory access can be reordered ahead of the operation. +// "Release" operations ensure that no previous memory access can be reordered +// after the operation. "Barrier" operations have both "Acquire" and "Release" +// semantics. A MemoryBarrier() has "Barrier" semantics, but does no memory +// access. +Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); +Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value); + +void MemoryBarrier(); +void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value); +void Acquire_Store(volatile Atomic32* ptr, Atomic32 value); +void Release_Store(volatile Atomic32* ptr, Atomic32 value); + +Atomic32 NoBarrier_Load(volatile const Atomic32* ptr); +Atomic32 Acquire_Load(volatile const Atomic32* ptr); +Atomic32 Release_Load(volatile const Atomic32* ptr); + +// 64-bit atomic operations (only available on 64-bit processors). +#ifdef V8_HOST_ARCH_64_BIT +Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, Atomic64 new_value); +Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); +Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, Atomic64 increment); + +Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value); +void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value); +void Acquire_Store(volatile Atomic64* ptr, Atomic64 value); +void Release_Store(volatile Atomic64* ptr, Atomic64 value); +Atomic64 NoBarrier_Load(volatile const Atomic64* ptr); +Atomic64 Acquire_Load(volatile const Atomic64* ptr); +Atomic64 Release_Load(volatile const Atomic64* ptr); +#endif // V8_HOST_ARCH_64_BIT + +} } // namespace v8::internal + +// Include our platform specific implementation. +#if defined(_MSC_VER) && \ + (defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64)) +#include "atomicops_internals_x86_msvc.h" +#elif defined(__APPLE__) && \ + (defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64)) +#include "atomicops_internals_x86_macosx.h" +#elif defined(__GNUC__) && \ + (defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_X64)) +#include "atomicops_internals_x86_gcc.h" +#elif defined(__GNUC__) && defined(V8_HOST_ARCH_ARM) +#include "atomicops_internals_arm_gcc.h" +#else +#error "Atomic operations are not supported on your platform" +#endif + +#endif // V8_ATOMICOPS_H_ diff --git a/src/atomicops_internals_arm_gcc.h b/src/atomicops_internals_arm_gcc.h new file mode 100644 index 00000000..6c30256d --- /dev/null +++ b/src/atomicops_internals_arm_gcc.h @@ -0,0 +1,145 @@ +// Copyright 2010 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. + +// This file is an internal atomic implementation, use atomicops.h instead. +// +// LinuxKernelCmpxchg and Barrier_AtomicIncrement are from Google Gears. + +#ifndef V8_ATOMICOPS_INTERNALS_ARM_GCC_H_ +#define V8_ATOMICOPS_INTERNALS_ARM_GCC_H_ + +namespace v8 { +namespace internal { + +// 0xffff0fc0 is the hard coded address of a function provided by +// the kernel which implements an atomic compare-exchange. On older +// ARM architecture revisions (pre-v6) this may be implemented using +// a syscall. This address is stable, and in active use (hard coded) +// by at least glibc-2.7 and the Android C library. +typedef Atomic32 (*LinuxKernelCmpxchgFunc)(Atomic32 old_value, + Atomic32 new_value, + volatile Atomic32* ptr); +LinuxKernelCmpxchgFunc pLinuxKernelCmpxchg __attribute__((weak)) = + (LinuxKernelCmpxchgFunc) 0xffff0fc0; + +typedef void (*LinuxKernelMemoryBarrierFunc)(void); +LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = + (LinuxKernelMemoryBarrierFunc) 0xffff0fa0; + + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value = *ptr; + do { + if (!pLinuxKernelCmpxchg(old_value, new_value, + const_cast<Atomic32*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (pLinuxKernelCmpxchg(old_value, new_value, + const_cast<Atomic32*>(ptr))); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + for (;;) { + // Atomic exchange the old value with an incremented one. + Atomic32 old_value = *ptr; + Atomic32 new_value = old_value + increment; + if (pLinuxKernelCmpxchg(old_value, new_value, + const_cast<Atomic32*>(ptr)) == 0) { + // The exchange took place as expected. + return new_value; + } + // Otherwise, *ptr changed mid-loop and we need to retry. + } +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void MemoryBarrier() { + pLinuxKernelMemoryBarrier(); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +} } // namespace v8::internal + +#endif // V8_ATOMICOPS_INTERNALS_ARM_GCC_H_ diff --git a/src/atomicops_internals_x86_gcc.cc b/src/atomicops_internals_x86_gcc.cc new file mode 100644 index 00000000..a5725647 --- /dev/null +++ b/src/atomicops_internals_x86_gcc.cc @@ -0,0 +1,126 @@ +// Copyright 2010 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. + +// This module gets enough CPU information to optimize the +// atomicops module on x86. + +#include <string.h> + +#include "atomicops.h" + +// This file only makes sense with atomicops_internals_x86_gcc.h -- it +// depends on structs that are defined in that file. If atomicops.h +// doesn't sub-include that file, then we aren't needed, and shouldn't +// try to do anything. +#ifdef V8_ATOMICOPS_INTERNALS_X86_GCC_H_ + +// Inline cpuid instruction. In PIC compilations, %ebx contains the address +// of the global offset table. To avoid breaking such executables, this code +// must preserve that register's value across cpuid instructions. +#if defined(__i386__) +#define cpuid(a, b, c, d, inp) \ + asm("mov %%ebx, %%edi\n" \ + "cpuid\n" \ + "xchg %%edi, %%ebx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#elif defined(__x86_64__) +#define cpuid(a, b, c, d, inp) \ + asm("mov %%rbx, %%rdi\n" \ + "cpuid\n" \ + "xchg %%rdi, %%rbx\n" \ + : "=a" (a), "=D" (b), "=c" (c), "=d" (d) : "a" (inp)) +#endif + +#if defined(cpuid) // initialize the struct only on x86 + +// Set the flags so that code will run correctly and conservatively, so even +// if we haven't been initialized yet, we're probably single threaded, and our +// default values should hopefully be pretty safe. +struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures = { + false, // bug can't exist before process spawns multiple threads + false, // no SSE2 +}; + +// Initialize the AtomicOps_Internalx86CPUFeatures struct. +static void AtomicOps_Internalx86CPUFeaturesInit() { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + + // Get vendor string (issue CPUID with eax = 0) + cpuid(eax, ebx, ecx, edx, 0); + char vendor[13]; + memcpy(vendor, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = 0; + + // get feature flags in ecx/edx, and family/model in eax + cpuid(eax, ebx, ecx, edx, 1); + + int family = (eax >> 8) & 0xf; // family and model fields + int model = (eax >> 4) & 0xf; + if (family == 0xf) { // use extended family and model fields + family += (eax >> 20) & 0xff; + model += ((eax >> 16) & 0xf) << 4; + } + + // Opteron Rev E has a bug in which on very rare occasions a locked + // instruction doesn't act as a read-acquire barrier if followed by a + // non-locked read-modify-write instruction. Rev F has this bug in + // pre-release versions, but not in versions released to customers, + // so we test only for Rev E, which is family 15, model 32..63 inclusive. + if (strcmp(vendor, "AuthenticAMD") == 0 && // AMD + family == 15 && + 32 <= model && model <= 63) { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = true; + } else { + AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug = false; + } + + // edx bit 26 is SSE2 which we use to tell use whether we can use mfence + AtomicOps_Internalx86CPUFeatures.has_sse2 = ((edx >> 26) & 1); +} + +namespace { + +class AtomicOpsx86Initializer { + public: + AtomicOpsx86Initializer() { + AtomicOps_Internalx86CPUFeaturesInit(); + } +}; + +// A global to get use initialized on startup via static initialization :/ +AtomicOpsx86Initializer g_initer; + +} // namespace + +#endif // if x86 + +#endif // ifdef V8_ATOMICOPS_INTERNALS_X86_GCC_H_ diff --git a/src/atomicops_internals_x86_gcc.h b/src/atomicops_internals_x86_gcc.h new file mode 100644 index 00000000..3f17fa0d --- /dev/null +++ b/src/atomicops_internals_x86_gcc.h @@ -0,0 +1,287 @@ +// Copyright 2010 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. + +// This file is an internal atomic implementation, use atomicops.h instead. + +#ifndef V8_ATOMICOPS_INTERNALS_X86_GCC_H_ +#define V8_ATOMICOPS_INTERNALS_X86_GCC_H_ + +// This struct is not part of the public API of this module; clients may not +// use it. +// Features of this x86. Values may not be correct before main() is run, +// but are set conservatively. +struct AtomicOps_x86CPUFeatureStruct { + bool has_amd_lock_mb_bug; // Processor has AMD memory-barrier bug; do lfence + // after acquire compare-and-swap. + bool has_sse2; // Processor has SSE2. +}; +extern struct AtomicOps_x86CPUFeatureStruct AtomicOps_Internalx86CPUFeatures; + +#define ATOMICOPS_COMPILER_BARRIER() __asm__ __volatile__("" : : : "memory") + +namespace v8 { +namespace internal { + +// 32-bit low-level operations on any platform. + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev; + __asm__ __volatile__("lock; cmpxchgl %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + __asm__ __volatile__("xchgl %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + return temp + increment; +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + Atomic32 temp = increment; + __asm__ __volatile__("lock; xaddl %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now holds the old value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +#if defined(__x86_64__) + +// 64-bit implementations of memory barrier can be simpler, because it +// "mfence" is guaranteed to exist. +inline void MemoryBarrier() { + __asm__ __volatile__("mfence" : : : "memory"); +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +#else + +inline void MemoryBarrier() { + if (AtomicOps_Internalx86CPUFeatures.has_sse2) { + __asm__ __volatile__("mfence" : : : "memory"); + } else { // mfence is faster but not present on PIII + Atomic32 x = 0; + NoBarrier_AtomicExchange(&x, 0); // acts as a barrier on PIII + } +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + if (AtomicOps_Internalx86CPUFeatures.has_sse2) { + *ptr = value; + __asm__ __volatile__("mfence" : : : "memory"); + } else { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier on PIII + } +} +#endif + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + ATOMICOPS_COMPILER_BARRIER(); + *ptr = value; // An x86 store acts as a release barrier. + // See comments in Atomic64 version of Release_Store(), below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; // An x86 load acts as a acquire barrier. + // See comments in Atomic64 version of Release_Store(), below. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(__x86_64__) + +// 64-bit low-level operations on 64-bit platform. + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev; + __asm__ __volatile__("lock; cmpxchgq %1,%2" + : "=a" (prev) + : "q" (new_value), "m" (*ptr), "0" (old_value) + : "memory"); + return prev; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + __asm__ __volatile__("xchgq %1,%0" // The lock prefix is implicit for xchg. + : "=r" (new_value) + : "m" (*ptr), "0" (new_value) + : "memory"); + return new_value; // Now it's the previous value. +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + return temp + increment; +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + Atomic64 temp = increment; + __asm__ __volatile__("lock; xaddq %0,%1" + : "+r" (temp), "+m" (*ptr) + : : "memory"); + // temp now contains the previous value of *ptr + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return temp + increment; +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + ATOMICOPS_COMPILER_BARRIER(); + + *ptr = value; // An x86 store acts as a release barrier + // for current AMD/Intel chips as of Jan 2008. + // See also Acquire_Load(), below. + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm + // + // x86 stores/loads fail to act as barriers for a few instructions (clflush + // maskmovdqu maskmovq movntdq movnti movntpd movntps movntq) but these are + // not generated by the compiler, and are rare. Users of these instructions + // need to know about cache behaviour in any case since all of these involve + // either flushing cache lines or non-temporal cache hints. +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; // An x86 load acts as a acquire barrier, + // for current AMD/Intel chips as of Jan 2008. + // See also Release_Store(), above. + ATOMICOPS_COMPILER_BARRIER(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 x = NoBarrier_CompareAndSwap(ptr, old_value, new_value); + if (AtomicOps_Internalx86CPUFeatures.has_amd_lock_mb_bug) { + __asm__ __volatile__("lfence" : : : "memory"); + } + return x; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +#endif // defined(__x86_64__) + +} } // namespace v8::internal + +#undef ATOMICOPS_COMPILER_BARRIER + +#endif // V8_ATOMICOPS_INTERNALS_X86_GCC_H_ diff --git a/src/atomicops_internals_x86_macosx.h b/src/atomicops_internals_x86_macosx.h new file mode 100644 index 00000000..2bac006b --- /dev/null +++ b/src/atomicops_internals_x86_macosx.h @@ -0,0 +1,301 @@ +// Copyright 2010 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. + +// This file is an internal atomic implementation, use atomicops.h instead. + +#ifndef V8_ATOMICOPS_INTERNALS_X86_MACOSX_H_ +#define V8_ATOMICOPS_INTERNALS_X86_MACOSX_H_ + +#include <libkern/OSAtomic.h> + +namespace v8 { +namespace internal { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32(old_value, new_value, + const_cast<Atomic32*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32 *ptr, + Atomic32 new_value) { + Atomic32 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap32(old_value, new_value, + const_cast<Atomic32*>(ptr))); + return old_value; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32(increment, const_cast<Atomic32*>(ptr)); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32 *ptr, + Atomic32 increment) { + return OSAtomicAdd32Barrier(increment, const_cast<Atomic32*>(ptr)); +} + +inline void MemoryBarrier() { + OSMemoryBarrier(); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + Atomic32 prev_value; + do { + if (OSAtomicCompareAndSwap32Barrier(old_value, new_value, + const_cast<Atomic32*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32 *ptr, + Atomic32 old_value, + Atomic32 new_value) { + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32 *ptr, Atomic32 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic32 *ptr, Atomic32 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32 *ptr) { + Atomic32 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32 *ptr) { + MemoryBarrier(); + return *ptr; +} + +#ifdef __LP64__ + +// 64-bit implementation on 64-bit platform + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64(old_value, new_value, + const_cast<Atomic64*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64 *ptr, + Atomic64 new_value) { + Atomic64 old_value; + do { + old_value = *ptr; + } while (!OSAtomicCompareAndSwap64(old_value, new_value, + const_cast<Atomic64*>(ptr))); + return old_value; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64(increment, const_cast<Atomic64*>(ptr)); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64 *ptr, + Atomic64 increment) { + return OSAtomicAdd64Barrier(increment, const_cast<Atomic64*>(ptr)); +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + Atomic64 prev_value; + do { + if (OSAtomicCompareAndSwap64Barrier(old_value, new_value, + const_cast<Atomic64*>(ptr))) { + return old_value; + } + prev_value = *ptr; + } while (prev_value == old_value); + return prev_value; +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64 *ptr, + Atomic64 old_value, + Atomic64 new_value) { + // The lib kern interface does not distinguish between + // Acquire and Release memory barriers; they are equivalent. + return Acquire_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64 *ptr, Atomic64 value) { + *ptr = value; + MemoryBarrier(); +} + +inline void Release_Store(volatile Atomic64 *ptr, Atomic64 value) { + MemoryBarrier(); + *ptr = value; +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64 *ptr) { + Atomic64 value = *ptr; + MemoryBarrier(); + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64 *ptr) { + MemoryBarrier(); + return *ptr; +} + +#endif // defined(__LP64__) + +// MacOS uses long for intptr_t, AtomicWord and Atomic32 are always different +// on the Mac, even when they are the same size. We need to explicitly cast +// from AtomicWord to Atomic32/64 to implement the AtomicWord interface. +#ifdef __LP64__ +#define AtomicWordCastType Atomic64 +#else +#define AtomicWordCastType Atomic32 +#endif + +inline AtomicWord NoBarrier_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return NoBarrier_CompareAndSwap( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), + old_value, new_value); +} + +inline AtomicWord NoBarrier_AtomicExchange(volatile AtomicWord* ptr, + AtomicWord new_value) { + return NoBarrier_AtomicExchange( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), new_value); +} + +inline AtomicWord NoBarrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return NoBarrier_AtomicIncrement( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), increment); +} + +inline AtomicWord Barrier_AtomicIncrement(volatile AtomicWord* ptr, + AtomicWord increment) { + return Barrier_AtomicIncrement( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), increment); +} + +inline AtomicWord Acquire_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return v8::internal::Acquire_CompareAndSwap( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), + old_value, new_value); +} + +inline AtomicWord Release_CompareAndSwap(volatile AtomicWord* ptr, + AtomicWord old_value, + AtomicWord new_value) { + return v8::internal::Release_CompareAndSwap( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), + old_value, new_value); +} + +inline void NoBarrier_Store(volatile AtomicWord *ptr, AtomicWord value) { + NoBarrier_Store( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), value); +} + +inline void Acquire_Store(volatile AtomicWord* ptr, AtomicWord value) { + return v8::internal::Acquire_Store( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), value); +} + +inline void Release_Store(volatile AtomicWord* ptr, AtomicWord value) { + return v8::internal::Release_Store( + reinterpret_cast<volatile AtomicWordCastType*>(ptr), value); +} + +inline AtomicWord NoBarrier_Load(volatile const AtomicWord *ptr) { + return NoBarrier_Load( + reinterpret_cast<volatile const AtomicWordCastType*>(ptr)); +} + +inline AtomicWord Acquire_Load(volatile const AtomicWord* ptr) { + return v8::internal::Acquire_Load( + reinterpret_cast<volatile const AtomicWordCastType*>(ptr)); +} + +inline AtomicWord Release_Load(volatile const AtomicWord* ptr) { + return v8::internal::Release_Load( + reinterpret_cast<volatile const AtomicWordCastType*>(ptr)); +} + +#undef AtomicWordCastType + +} } // namespace v8::internal + +#endif // V8_ATOMICOPS_INTERNALS_X86_MACOSX_H_ diff --git a/src/atomicops_internals_x86_msvc.h b/src/atomicops_internals_x86_msvc.h new file mode 100644 index 00000000..fcf6a651 --- /dev/null +++ b/src/atomicops_internals_x86_msvc.h @@ -0,0 +1,203 @@ +// Copyright 2010 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. + +// This file is an internal atomic implementation, use atomicops.h instead. + +#ifndef V8_ATOMICOPS_INTERNALS_X86_MSVC_H_ +#define V8_ATOMICOPS_INTERNALS_X86_MSVC_H_ + +#include "checks.h" +#include "win32-headers.h" + +namespace v8 { +namespace internal { + +inline Atomic32 NoBarrier_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + LONG result = InterlockedCompareExchange( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(new_value), + static_cast<LONG>(old_value)); + return static_cast<Atomic32>(result); +} + +inline Atomic32 NoBarrier_AtomicExchange(volatile Atomic32* ptr, + Atomic32 new_value) { + LONG result = InterlockedExchange( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(new_value)); + return static_cast<Atomic32>(result); +} + +inline Atomic32 Barrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return InterlockedExchangeAdd( + reinterpret_cast<volatile LONG*>(ptr), + static_cast<LONG>(increment)) + increment; +} + +inline Atomic32 NoBarrier_AtomicIncrement(volatile Atomic32* ptr, + Atomic32 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +#if !(defined(_MSC_VER) && _MSC_VER >= 1400) +#error "We require at least vs2005 for MemoryBarrier" +#endif +inline void MemoryBarrier() { + // We use MemoryBarrier from WinNT.h + ::MemoryBarrier(); +} + +inline Atomic32 Acquire_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic32 Release_CompareAndSwap(volatile Atomic32* ptr, + Atomic32 old_value, + Atomic32 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline void NoBarrier_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic32* ptr, Atomic32 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic32* ptr, Atomic32 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + // See comments in Atomic64 version of Release_Store() below. +} + +inline Atomic32 NoBarrier_Load(volatile const Atomic32* ptr) { + return *ptr; +} + +inline Atomic32 Acquire_Load(volatile const Atomic32* ptr) { + Atomic32 value = *ptr; + return value; +} + +inline Atomic32 Release_Load(volatile const Atomic32* ptr) { + MemoryBarrier(); + return *ptr; +} + +#if defined(_WIN64) + +// 64-bit low-level operations on 64-bit platform. + +STATIC_ASSERT(sizeof(Atomic64) == sizeof(PVOID)); + +inline Atomic64 NoBarrier_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + PVOID result = InterlockedCompareExchangePointer( + reinterpret_cast<volatile PVOID*>(ptr), + reinterpret_cast<PVOID>(new_value), reinterpret_cast<PVOID>(old_value)); + return reinterpret_cast<Atomic64>(result); +} + +inline Atomic64 NoBarrier_AtomicExchange(volatile Atomic64* ptr, + Atomic64 new_value) { + PVOID result = InterlockedExchangePointer( + reinterpret_cast<volatile PVOID*>(ptr), + reinterpret_cast<PVOID>(new_value)); + return reinterpret_cast<Atomic64>(result); +} + +inline Atomic64 Barrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return InterlockedExchangeAdd64( + reinterpret_cast<volatile LONGLONG*>(ptr), + static_cast<LONGLONG>(increment)) + increment; +} + +inline Atomic64 NoBarrier_AtomicIncrement(volatile Atomic64* ptr, + Atomic64 increment) { + return Barrier_AtomicIncrement(ptr, increment); +} + +inline void NoBarrier_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; +} + +inline void Acquire_Store(volatile Atomic64* ptr, Atomic64 value) { + NoBarrier_AtomicExchange(ptr, value); + // acts as a barrier in this implementation +} + +inline void Release_Store(volatile Atomic64* ptr, Atomic64 value) { + *ptr = value; // works w/o barrier for current Intel chips as of June 2005 + + // When new chips come out, check: + // IA-32 Intel Architecture Software Developer's Manual, Volume 3: + // System Programming Guide, Chatper 7: Multiple-processor management, + // Section 7.2, Memory Ordering. + // Last seen at: + // http://developer.intel.com/design/pentium4/manuals/index_new.htm +} + +inline Atomic64 NoBarrier_Load(volatile const Atomic64* ptr) { + return *ptr; +} + +inline Atomic64 Acquire_Load(volatile const Atomic64* ptr) { + Atomic64 value = *ptr; + return value; +} + +inline Atomic64 Release_Load(volatile const Atomic64* ptr) { + MemoryBarrier(); + return *ptr; +} + +inline Atomic64 Acquire_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + +inline Atomic64 Release_CompareAndSwap(volatile Atomic64* ptr, + Atomic64 old_value, + Atomic64 new_value) { + return NoBarrier_CompareAndSwap(ptr, old_value, new_value); +} + + +#endif // defined(_WIN64) + +} } // namespace v8::internal + +#endif // V8_ATOMICOPS_INTERNALS_X86_MSVC_H_ diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index f60a975d..cae1a9a2 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -38,7 +38,6 @@ #include "natives.h" #include "objects-visiting.h" #include "snapshot.h" -#include "stub-cache.h" #include "extensions/externalize-string-extension.h" #include "extensions/gc-extension.h" @@ -234,7 +233,7 @@ class Genesis BASE_EMBEDDED { // Used for creating a context from scratch. void InstallNativeFunctions(); bool InstallNatives(); - void InstallCustomCallGenerators(); + void InstallBuiltinFunctionIds(); void InstallJSFunctionResultCaches(); void InitializeNormalizedMapCaches(); // Used both for deserialized and from-scratch contexts to add the extensions @@ -500,6 +499,24 @@ Handle<JSFunction> Genesis::CreateEmptyFunction() { } +static void AddToWeakGlobalContextList(Context* context) { + ASSERT(context->IsGlobalContext()); +#ifdef DEBUG + { // NOLINT + ASSERT(context->get(Context::NEXT_CONTEXT_LINK)->IsUndefined()); + // Check that context is not in the list yet. + for (Object* current = Heap::global_contexts_list(); + !current->IsUndefined(); + current = Context::cast(current)->get(Context::NEXT_CONTEXT_LINK)) { + ASSERT(current != context); + } + } +#endif + context->set(Context::NEXT_CONTEXT_LINK, Heap::global_contexts_list()); + Heap::set_global_contexts_list(context); +} + + void Genesis::CreateRoots() { // Allocate the global context FixedArray first and then patch the // closure and extension object later (we need the empty function @@ -508,6 +525,7 @@ void Genesis::CreateRoots() { global_context_ = Handle<Context>::cast( GlobalHandles::Create(*Factory::NewGlobalContext())); + AddToWeakGlobalContextList(*global_context_); Top::set_context(*global_context()); // Allocate the message listeners object. @@ -1251,7 +1269,7 @@ bool Genesis::InstallNatives() { global_context()->set_string_function_prototype_map( HeapObject::cast(string_function->initial_map()->prototype())->map()); - InstallCustomCallGenerators(); + InstallBuiltinFunctionIds(); // Install Function.prototype.call and apply. { Handle<String> key = Factory::function_class_symbol(); @@ -1350,7 +1368,7 @@ bool Genesis::InstallNatives() { } -static Handle<JSObject> ResolveCustomCallGeneratorHolder( +static Handle<JSObject> ResolveBuiltinIdHolder( Handle<Context> global_context, const char* holder_expr) { Handle<GlobalObject> global(global_context->global()); @@ -1368,9 +1386,9 @@ static Handle<JSObject> ResolveCustomCallGeneratorHolder( } -static void InstallCustomCallGenerator(Handle<JSObject> holder, - const char* function_name, - int id) { +static void InstallBuiltinFunctionId(Handle<JSObject> holder, + const char* function_name, + BuiltinFunctionId id) { Handle<String> name = Factory::LookupAsciiSymbol(function_name); Object* function_object = holder->GetProperty(*name)->ToObjectUnchecked(); Handle<JSFunction> function(JSFunction::cast(function_object)); @@ -1378,17 +1396,17 @@ static void InstallCustomCallGenerator(Handle<JSObject> holder, } -void Genesis::InstallCustomCallGenerators() { +void Genesis::InstallBuiltinFunctionIds() { HandleScope scope; -#define INSTALL_CALL_GENERATOR(holder_expr, fun_name, name) \ - { \ - Handle<JSObject> holder = ResolveCustomCallGeneratorHolder( \ - global_context(), #holder_expr); \ - const int id = CallStubCompiler::k##name##CallGenerator; \ - InstallCustomCallGenerator(holder, #fun_name, id); \ +#define INSTALL_BUILTIN_ID(holder_expr, fun_name, name) \ + { \ + Handle<JSObject> holder = ResolveBuiltinIdHolder( \ + global_context(), #holder_expr); \ + BuiltinFunctionId id = k##name; \ + InstallBuiltinFunctionId(holder, #fun_name, id); \ } - CUSTOM_CALL_IC_GENERATORS(INSTALL_CALL_GENERATOR) -#undef INSTALL_CALL_GENERATOR + FUNCTIONS_WITH_ID_LIST(INSTALL_BUILTIN_ID) +#undef INSTALL_BUILTIN_ID } @@ -1596,7 +1614,7 @@ bool Genesis::InstallJSBuiltins(Handle<JSBuiltinsObject> builtins) { = Handle<SharedFunctionInfo>(function->shared()); if (!EnsureCompiled(shared, CLEAR_EXCEPTION)) return false; // Set the code object on the function object. - function->set_code(function->shared()->code()); + function->ReplaceCode(function->shared()->code()); builtins->set_javascript_builtin_code(id, shared->code()); } return true; @@ -1784,6 +1802,7 @@ Genesis::Genesis(Handle<Object> global_object, if (!new_context.is_null()) { global_context_ = Handle<Context>::cast(GlobalHandles::Create(*new_context)); + AddToWeakGlobalContextList(*global_context_); Top::set_context(*global_context_); i::Counters::contexts_created_by_snapshot.Increment(); result_ = global_context_; @@ -1819,11 +1838,6 @@ Genesis::Genesis(Handle<Object> global_object, i::Counters::contexts_created_from_scratch.Increment(); } - // Add this context to the weak list of global contexts. - (*global_context_)->set(Context::NEXT_CONTEXT_LINK, - Heap::global_contexts_list()); - Heap::set_global_contexts_list(*global_context_); - result_ = global_context_; } diff --git a/src/builtins.cc b/src/builtins.cc index e88ef6f0..21381f15 100644 --- a/src/builtins.cc +++ b/src/builtins.cc @@ -32,6 +32,7 @@ #include "bootstrapper.h" #include "builtins.h" #include "ic-inl.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -514,10 +515,10 @@ BUILTIN(ArrayShift) { Object* elms_obj; { MaybeObject* maybe_elms_obj = EnsureJSArrayWithWritableFastElements(receiver); + if (maybe_elms_obj == NULL) return CallJsBuiltin("ArrayShift", args); if (!maybe_elms_obj->ToObject(&elms_obj)) return maybe_elms_obj; } - if (elms_obj == NULL || - !IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { + if (!IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { return CallJsBuiltin("ArrayShift", args); } FixedArray* elms = FixedArray::cast(elms_obj); @@ -556,10 +557,10 @@ BUILTIN(ArrayUnshift) { Object* elms_obj; { MaybeObject* maybe_elms_obj = EnsureJSArrayWithWritableFastElements(receiver); + if (maybe_elms_obj == NULL) return CallJsBuiltin("ArrayUnshift", args); if (!maybe_elms_obj->ToObject(&elms_obj)) return maybe_elms_obj; } - if (elms_obj == NULL || - !IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { + if (!IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { return CallJsBuiltin("ArrayUnshift", args); } FixedArray* elms = FixedArray::cast(elms_obj); @@ -610,21 +611,46 @@ BUILTIN(ArrayUnshift) { BUILTIN(ArraySlice) { Object* receiver = *args.receiver(); - Object* elms_obj; + FixedArray* elms; + int len = -1; { MaybeObject* maybe_elms_obj = EnsureJSArrayWithWritableFastElements(receiver); - if (!maybe_elms_obj->ToObject(&elms_obj)) return maybe_elms_obj; - } - if (elms_obj == NULL || - !IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { - return CallJsBuiltin("ArraySlice", args); - } - FixedArray* elms = FixedArray::cast(elms_obj); - JSArray* array = JSArray::cast(receiver); - ASSERT(array->HasFastElements()); - - int len = Smi::cast(array->length())->value(); + Object* elms_obj; + if (maybe_elms_obj != NULL && maybe_elms_obj->ToObject(&elms_obj)) { + if (!IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { + return CallJsBuiltin("ArraySlice", args); + } + elms = FixedArray::cast(elms_obj); + JSArray* array = JSArray::cast(receiver); + ASSERT(array->HasFastElements()); + len = Smi::cast(array->length())->value(); + } else { + // Array.slice(arguments, ...) is quite a common idiom (notably more + // than 50% of invocations in Web apps). Treat it in C++ as well. + Map* arguments_map = + Top::context()->global_context()->arguments_boilerplate()->map(); + + bool is_arguments_object_with_fast_elements = + receiver->IsJSObject() + && JSObject::cast(receiver)->map() == arguments_map + && JSObject::cast(receiver)->HasFastElements(); + if (!is_arguments_object_with_fast_elements) { + return CallJsBuiltin("ArraySlice", args); + } + elms = FixedArray::cast(JSObject::cast(receiver)->elements()); + len = elms->length(); +#ifdef DEBUG + // Arguments object by construction should have no holes, check it. + if (FLAG_enable_slow_asserts) { + for (int i = 0; i < len; i++) { + ASSERT(elms->get(i) != Heap::the_hole_value()); + } + } +#endif + } + } + ASSERT(len >= 0); int n_arguments = args.length() - 1; // Note carefully choosen defaults---if argument is missing, @@ -692,10 +718,10 @@ BUILTIN(ArraySplice) { Object* elms_obj; { MaybeObject* maybe_elms_obj = EnsureJSArrayWithWritableFastElements(receiver); + if (maybe_elms_obj == NULL) return CallJsBuiltin("ArraySplice", args); if (!maybe_elms_obj->ToObject(&elms_obj)) return maybe_elms_obj; } - if (elms_obj == NULL || - !IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { + if (!IsJSArrayFastElementMovingAllowed(JSArray::cast(receiver))) { return CallJsBuiltin("ArraySplice", args); } FixedArray* elms = FixedArray::cast(elms_obj); @@ -1031,9 +1057,7 @@ MUST_USE_RESULT static MaybeObject* HandleApiCallHelper( { // Leaving JavaScript. VMState state(EXTERNAL); -#ifdef ENABLE_LOGGING_AND_PROFILING - state.set_external_callback(v8::ToCData<Address>(callback_obj)); -#endif + ExternalCallbackScope call_scope(v8::ToCData<Address>(callback_obj)); value = callback(new_args); } if (value.IsEmpty()) { @@ -1103,9 +1127,7 @@ BUILTIN(FastHandleApiCall) { { // Leaving JavaScript. VMState state(EXTERNAL); -#ifdef ENABLE_LOGGING_AND_PROFILING - state.set_external_callback(v8::ToCData<Address>(callback_obj)); -#endif + ExternalCallbackScope call_scope(v8::ToCData<Address>(callback_obj)); v8::InvocationCallback callback = v8::ToCData<v8::InvocationCallback>(callback_obj); @@ -1169,9 +1191,7 @@ MUST_USE_RESULT static MaybeObject* HandleApiCallAsFunctionOrConstructor( { // Leaving JavaScript. VMState state(EXTERNAL); -#ifdef ENABLE_LOGGING_AND_PROFILING - state.set_external_callback(v8::ToCData<Address>(callback_obj)); -#endif + ExternalCallbackScope call_scope(v8::ToCData<Address>(callback_obj)); value = callback(new_args); } if (value.IsEmpty()) { @@ -1332,6 +1352,11 @@ static void Generate_StoreIC_ArrayLength(MacroAssembler* masm) { } +static void Generate_StoreIC_GlobalProxy(MacroAssembler* masm) { + StoreIC::GenerateGlobalProxy(masm); +} + + static void Generate_KeyedStoreIC_Generic(MacroAssembler* masm) { KeyedStoreIC::GenerateGeneric(masm); } @@ -1581,4 +1606,5 @@ const char* Builtins::Lookup(byte* pc) { return NULL; } + } } // namespace v8::internal diff --git a/src/builtins.h b/src/builtins.h index b5e8c4e8..d2b4be2f 100644 --- a/src/builtins.h +++ b/src/builtins.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -71,6 +71,10 @@ enum BuiltinExtraArguments { V(JSEntryTrampoline, BUILTIN, UNINITIALIZED) \ V(JSConstructEntryTrampoline, BUILTIN, UNINITIALIZED) \ V(LazyCompile, BUILTIN, UNINITIALIZED) \ + V(LazyRecompile, BUILTIN, UNINITIALIZED) \ + V(NotifyDeoptimized, BUILTIN, UNINITIALIZED) \ + V(NotifyLazyDeoptimized, BUILTIN, UNINITIALIZED) \ + V(NotifyOSR, BUILTIN, UNINITIALIZED) \ \ V(LoadIC_Miss, BUILTIN, UNINITIALIZED) \ V(KeyedLoadIC_Miss, BUILTIN, UNINITIALIZED) \ @@ -102,6 +106,7 @@ enum BuiltinExtraArguments { V(StoreIC_ArrayLength, STORE_IC, MONOMORPHIC) \ V(StoreIC_Normal, STORE_IC, MONOMORPHIC) \ V(StoreIC_Megamorphic, STORE_IC, MEGAMORPHIC) \ + V(StoreIC_GlobalProxy, STORE_IC, MEGAMORPHIC) \ \ V(KeyedStoreIC_Initialize, KEYED_STORE_IC, UNINITIALIZED) \ V(KeyedStoreIC_Generic, KEYED_STORE_IC, MEGAMORPHIC) \ @@ -120,7 +125,9 @@ enum BuiltinExtraArguments { V(ArrayCode, BUILTIN, UNINITIALIZED) \ V(ArrayConstructCode, BUILTIN, UNINITIALIZED) \ \ - V(StringConstructCode, BUILTIN, UNINITIALIZED) + V(StringConstructCode, BUILTIN, UNINITIALIZED) \ + \ + V(OnStackReplacement, BUILTIN, UNINITIALIZED) #ifdef ENABLE_DEBUGGER_SUPPORT @@ -256,6 +263,10 @@ class Builtins : public AllStatic { static void Generate_JSEntryTrampoline(MacroAssembler* masm); static void Generate_JSConstructEntryTrampoline(MacroAssembler* masm); static void Generate_LazyCompile(MacroAssembler* masm); + static void Generate_LazyRecompile(MacroAssembler* masm); + static void Generate_NotifyDeoptimized(MacroAssembler* masm); + static void Generate_NotifyLazyDeoptimized(MacroAssembler* masm); + static void Generate_NotifyOSR(MacroAssembler* masm); static void Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm); static void Generate_FunctionCall(MacroAssembler* masm); @@ -265,6 +276,8 @@ class Builtins : public AllStatic { static void Generate_ArrayConstructCode(MacroAssembler* masm); static void Generate_StringConstructCode(MacroAssembler* masm); + + static void Generate_OnStackReplacement(MacroAssembler* masm); }; } } // namespace v8::internal diff --git a/src/checks.h b/src/checks.h index d49f97f1..2bb94bb0 100644 --- a/src/checks.h +++ b/src/checks.h @@ -30,6 +30,7 @@ #include <string.h> +#include "../include/v8stdint.h" extern "C" void V8_Fatal(const char* file, int line, const char* format, ...); // The FATAL, UNREACHABLE and UNIMPLEMENTED macros are useful during @@ -231,6 +232,8 @@ static inline void CheckNonEqualsHelper(const char* file, #define CHECK_GT(a, b) CHECK((a) > (b)) #define CHECK_GE(a, b) CHECK((a) >= (b)) +#define CHECK_LT(a, b) CHECK((a) < (b)) +#define CHECK_LE(a, b) CHECK((a) <= (b)) // This is inspired by the static assertion facility in boost. This @@ -281,7 +284,7 @@ bool EnableSlowAsserts(); // safely enabled in release mode. Moreover, the ((void) 0) expression // obeys different syntax rules than typedef's, e.g. it can't appear // inside class declaration, this leads to inconsistency between debug -// and release compilation modes behaviour. +// and release compilation modes behavior. #define STATIC_ASSERT(test) STATIC_CHECK(test) #define ASSERT_NOT_NULL(p) ASSERT_NE(NULL, p) diff --git a/src/code-stubs.cc b/src/code-stubs.cc index 8b9198fb..1b0d8b0b 100644 --- a/src/code-stubs.cc +++ b/src/code-stubs.cc @@ -103,6 +103,7 @@ Handle<Code> CodeStub::GetCode() { GetICState()); Handle<Code> new_object = Factory::NewCode(desc, flags, masm.CodeObject()); RecordCodeGeneration(*new_object, &masm); + FinishCode(*new_object); // Update the dictionary and the root in Heap. Handle<NumberDictionary> dict = @@ -142,6 +143,7 @@ MaybeObject* CodeStub::TryGetCode() { } code = Code::cast(new_object); RecordCodeGeneration(code, &masm); + FinishCode(code); // Try to update the code cache but do not fail if unable. MaybeObject* maybe_new_object = @@ -170,4 +172,29 @@ const char* CodeStub::MajorName(CodeStub::Major major_key, } +int ICCompareStub::MinorKey() { + return OpField::encode(op_ - Token::EQ) | StateField::encode(state_); +} + + +void ICCompareStub::Generate(MacroAssembler* masm) { + switch (state_) { + case CompareIC::UNINITIALIZED: + GenerateMiss(masm); + break; + case CompareIC::SMIS: + GenerateSmis(masm); + break; + case CompareIC::HEAP_NUMBERS: + GenerateHeapNumbers(masm); + break; + case CompareIC::OBJECTS: + GenerateObjects(masm); + break; + default: + UNREACHABLE(); + } +} + + } } // namespace v8::internal diff --git a/src/code-stubs.h b/src/code-stubs.h index b156647d..b7804b77 100644 --- a/src/code-stubs.h +++ b/src/code-stubs.h @@ -29,7 +29,6 @@ #define V8_CODE_STUBS_H_ #include "globals.h" -#include "macro-assembler.h" namespace v8 { namespace internal { @@ -39,11 +38,16 @@ namespace internal { #define CODE_STUB_LIST_ALL_PLATFORMS(V) \ V(CallFunction) \ V(GenericBinaryOp) \ + V(TypeRecordingBinaryOp) \ V(StringAdd) \ + V(StringCharAt) \ V(SubString) \ V(StringCompare) \ V(SmiOp) \ V(Compare) \ + V(CompareIC) \ + V(MathPow) \ + V(TranscendentalCache) \ V(RecordWrite) \ V(ConvertToDouble) \ V(WriteInt32ToHeapNumber) \ @@ -52,7 +56,6 @@ namespace internal { V(FastNewClosure) \ V(FastNewContext) \ V(FastCloneShallowArray) \ - V(TranscendentalCache) \ V(GenericUnaryOp) \ V(RevertToNumber) \ V(ToBoolean) \ @@ -60,6 +63,7 @@ namespace internal { V(CounterOp) \ V(ArgumentsAccess) \ V(RegExpExec) \ + V(RegExpConstructResult) \ V(NumberToString) \ V(CEntry) \ V(JSEntry) \ @@ -125,7 +129,7 @@ class CodeStub BASE_EMBEDDED { virtual ~CodeStub() {} protected: - static const int kMajorBits = 5; + static const int kMajorBits = 6; static const int kMinorBits = kBitsPerInt - kSmiTagSize - kMajorBits; private: @@ -143,6 +147,9 @@ class CodeStub BASE_EMBEDDED { // initially generated. void RecordCodeGeneration(Code* code, MacroAssembler* masm); + // Finish the code object after it has been generated. + virtual void FinishCode(Code* code) { } + // Returns information for computing the number key. virtual Major MajorKey() = 0; virtual int MinorKey() = 0; @@ -216,11 +223,11 @@ namespace v8 { namespace internal { -// RuntimeCallHelper implementation used in IC stubs: enters/leaves a +// RuntimeCallHelper implementation used in stubs: enters/leaves a // newly created internal frame before/after the runtime call. -class ICRuntimeCallHelper : public RuntimeCallHelper { +class StubRuntimeCallHelper : public RuntimeCallHelper { public: - ICRuntimeCallHelper() {} + StubRuntimeCallHelper() {} virtual void BeforeCall(MacroAssembler* masm) const; @@ -318,13 +325,24 @@ class FastCloneShallowArrayStub : public CodeStub { class InstanceofStub: public CodeStub { public: - InstanceofStub() { } + enum Flags { + kNoFlags = 0, + kArgsInRegisters = 1 << 0 + }; + + explicit InstanceofStub(Flags flags) : flags_(flags) { } void Generate(MacroAssembler* masm); private: Major MajorKey() { return Instanceof; } - int MinorKey() { return 0; } + int MinorKey() { return args_in_registers() ? 1 : 0; } + + bool args_in_registers() { + return (flags_ & kArgsInRegisters) != 0; + } + + Flags flags_; }; @@ -376,9 +394,61 @@ class GenericUnaryOpStub : public CodeStub { }; -enum NaNInformation { - kBothCouldBeNaN, - kCantBothBeNaN +class MathPowStub: public CodeStub { + public: + MathPowStub() {} + virtual void Generate(MacroAssembler* masm); + + private: + virtual CodeStub::Major MajorKey() { return MathPow; } + virtual int MinorKey() { return 0; } + + const char* GetName() { return "MathPowStub"; } +}; + + +class StringCharAtStub: public CodeStub { + public: + StringCharAtStub() {} + + private: + Major MajorKey() { return StringCharAt; } + int MinorKey() { return 0; } + + void Generate(MacroAssembler* masm); +}; + + +class ICCompareStub: public CodeStub { + public: + ICCompareStub(Token::Value op, CompareIC::State state) + : op_(op), state_(state) { + ASSERT(Token::IsCompareOp(op)); + } + + virtual void Generate(MacroAssembler* masm); + + private: + class OpField: public BitField<int, 0, 3> { }; + class StateField: public BitField<int, 3, 5> { }; + + virtual void FinishCode(Code* code) { code->set_compare_state(state_); } + + virtual CodeStub::Major MajorKey() { return CompareIC; } + virtual int MinorKey(); + + virtual int GetCodeKind() { return Code::COMPARE_IC; } + + void GenerateSmis(MacroAssembler* masm); + void GenerateHeapNumbers(MacroAssembler* masm); + void GenerateObjects(MacroAssembler* masm); + void GenerateMiss(MacroAssembler* masm); + + bool strict() const { return op_ == Token::EQ_STRICT; } + Condition GetCondition() const { return CompareIC::ComputeCondition(op_); } + + Token::Value op_; + CompareIC::State state_; }; @@ -391,6 +461,12 @@ enum CompareFlags { }; +enum NaNInformation { + kBothCouldBeNaN, + kCantBothBeNaN +}; + + class CompareStub: public CodeStub { public: CompareStub(Condition cc, @@ -398,7 +474,7 @@ class CompareStub: public CodeStub { CompareFlags flags, Register lhs, Register rhs) : - cc_(cc), + cc_(cc), strict_(strict), never_nan_nan_((flags & CANT_BOTH_BE_NAN) != 0), include_number_compare_((flags & NO_NUMBER_COMPARE_IN_STUB) == 0), @@ -440,6 +516,7 @@ class CompareStub: public CodeStub { // Register holding the left hand side of the comparison if the stub gives // a choice, no_reg otherwise. + Register lhs_; // Register holding the right hand side of the comparison if the stub gives // a choice, no_reg otherwise. @@ -457,6 +534,11 @@ class CompareStub: public CodeStub { int MinorKey(); + virtual int GetCodeKind() { return Code::COMPARE_IC; } + virtual void FinishCode(Code* code) { + code->set_compare_state(CompareIC::GENERIC); + } + // Branch to the label if the given object isn't a symbol. void BranchIfNonSymbol(MacroAssembler* masm, Label* label, @@ -490,9 +572,11 @@ class CompareStub: public CodeStub { class CEntryStub : public CodeStub { public: - explicit CEntryStub(int result_size) : result_size_(result_size) { } + explicit CEntryStub(int result_size) + : result_size_(result_size), save_doubles_(false) { } void Generate(MacroAssembler* masm); + void SaveDoubles() { save_doubles_ = true; } private: void GenerateCore(MacroAssembler* masm, @@ -508,10 +592,9 @@ class CEntryStub : public CodeStub { // Number of pointers/values returned. const int result_size_; + bool save_doubles_; Major MajorKey() { return CEntry; } - // Minor key must differ if different result_size_ values means different - // code is generated. int MinorKey(); const char* GetName() { return "CEntryStub"; } @@ -597,6 +680,26 @@ class RegExpExecStub: public CodeStub { }; +class RegExpConstructResultStub: public CodeStub { + public: + RegExpConstructResultStub() { } + + private: + Major MajorKey() { return RegExpConstructResult; } + int MinorKey() { return 0; } + + void Generate(MacroAssembler* masm); + + const char* GetName() { return "RegExpConstructResultStub"; } + +#ifdef DEBUG + void Print() { + PrintF("RegExpConstructResultStub\n"); + } +#endif +}; + + class CallFunctionStub: public CodeStub { public: CallFunctionStub(int argc, InLoopFlag in_loop, CallFunctionFlags flags) diff --git a/src/codegen.cc b/src/codegen.cc index fb8c5cd4..da479e8f 100644 --- a/src/codegen.cc +++ b/src/codegen.cc @@ -139,6 +139,16 @@ void CodeGenerator::MakeCodePrologue(CompilationInfo* info) { print_source = FLAG_print_source; print_ast = FLAG_print_ast; print_json_ast = FLAG_print_json_ast; + Vector<const char> filter = CStrVector(FLAG_hydrogen_filter); + if (print_source && !filter.is_empty()) { + print_source = info->function()->name()->IsEqualTo(filter); + } + if (print_ast && !filter.is_empty()) { + print_ast = info->function()->name()->IsEqualTo(filter); + } + if (print_json_ast && !filter.is_empty()) { + print_json_ast = info->function()->name()->IsEqualTo(filter); + } ftype = "user-defined"; } @@ -174,14 +184,24 @@ Handle<Code> CodeGenerator::MakeCodeEpilogue(MacroAssembler* masm, masm->GetCode(&desc); Handle<Code> code = Factory::NewCode(desc, flags, masm->CodeObject()); + if (!code.is_null()) { + Counters::total_compiled_code_size.Increment(code->instruction_size()); + } + return code; +} + + +void CodeGenerator::PrintCode(Handle<Code> code, CompilationInfo* info) { #ifdef ENABLE_DISASSEMBLER bool print_code = Bootstrapper::IsActive() ? FLAG_print_builtin_code - : FLAG_print_code; - if (print_code) { + : (FLAG_print_code || (info->IsOptimizing() && FLAG_print_opt_code)); + Vector<const char> filter = CStrVector(FLAG_hydrogen_filter); + FunctionLiteral* function = info->function(); + bool match = filter.is_empty() || function->debug_name()->IsEqualTo(filter); + if (print_code && match) { // Print the source code if available. Handle<Script> script = info->script(); - FunctionLiteral* function = info->function(); if (!script->IsUndefined() && !script->source()->IsUndefined()) { PrintF("--- Raw source ---\n"); StringInputBuffer stream(String::cast(script->source())); @@ -195,26 +215,35 @@ Handle<Code> CodeGenerator::MakeCodeEpilogue(MacroAssembler* masm, } PrintF("\n\n"); } - PrintF("--- Code ---\n"); - code->Disassemble(*function->name()->ToCString()); + if (info->IsOptimizing()) { + if (FLAG_print_unopt_code) { + PrintF("--- Unoptimized code ---\n"); + info->closure()->shared()->code()->Disassemble( + *function->debug_name()->ToCString()); + } + PrintF("--- Optimized code ---\n"); + } else { + PrintF("--- Code ---\n"); + } + code->Disassemble(*function->debug_name()->ToCString()); } #endif // ENABLE_DISASSEMBLER - - if (!code.is_null()) { - Counters::total_compiled_code_size.Increment(code->instruction_size()); - } - return code; } // Generate the code. Compile the AST and assemble all the pieces into a // Code object. bool CodeGenerator::MakeCode(CompilationInfo* info) { + // When using Crankshaft the classic backend should never be used. + ASSERT(!V8::UseCrankshaft()); Handle<Script> script = info->script(); if (!script->IsUndefined() && !script->source()->IsUndefined()) { int len = String::cast(script->source())->length(); Counters::total_old_codegen_source_size.Increment(len); } + if (FLAG_trace_codegen) { + PrintF("Classic Compiler - "); + } MakeCodePrologue(info); // Generate code. const int kInitialBufferSize = 4 * KB; @@ -230,6 +259,9 @@ bool CodeGenerator::MakeCode(CompilationInfo* info) { InLoopFlag in_loop = info->is_in_loop() ? IN_LOOP : NOT_IN_LOOP; Code::Flags flags = Code::ComputeFlags(Code::FUNCTION, in_loop); Handle<Code> code = MakeCodeEpilogue(cgen.masm(), flags, info); + // There is no stack check table in code generated by the classic backend. + code->SetNoStackCheckTable(); + CodeGenerator::PrintCode(code, info); info->SetCode(code); // May be an empty handle. return !code.is_null(); } @@ -441,10 +473,11 @@ void ArgumentsAccessStub::Generate(MacroAssembler* masm) { int CEntryStub::MinorKey() { ASSERT(result_size_ == 1 || result_size_ == 2); + int result = save_doubles_ ? 1 : 0; #ifdef _WIN64 - return result_size_ == 1 ? 0 : 1; + return result | ((result_size_ == 1) ? 0 : 2); #else - return 0; + return result; #endif } diff --git a/src/codegen.h b/src/codegen.h index 66300d6c..23b36f07 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -68,6 +68,9 @@ // CodeForDoWhileConditionPosition // CodeForSourcePosition +enum InitState { CONST_INIT, NOT_CONST_INIT }; +enum TypeofState { INSIDE_TYPEOF, NOT_INSIDE_TYPEOF }; + #if V8_TARGET_ARCH_IA32 #include "ia32/codegen-ia32.h" #elif V8_TARGET_ARCH_X64 diff --git a/src/compilation-cache.cc b/src/compilation-cache.cc index 6e4e4bff..38438cb9 100644 --- a/src/compilation-cache.cc +++ b/src/compilation-cache.cc @@ -86,6 +86,9 @@ class CompilationSubCache { // Clear this sub-cache evicting all its content. void Clear(); + // Remove given shared function info from sub-cache. + void Remove(Handle<SharedFunctionInfo> function_info); + // Number of generations in this sub-cache. inline int generations() { return generations_; } @@ -249,6 +252,18 @@ void CompilationSubCache::Clear() { } +void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) { + // Probe the script generation tables. Make sure not to leak handles + // into the caller's handle scope. + { HandleScope scope; + for (int generation = 0; generation < generations(); generation++) { + Handle<CompilationCacheTable> table = GetTable(generation); + table->Remove(*function_info); + } + } +} + + // We only re-use a cached function for some script source code if the // script originates from the same place. This is to avoid issues // when reporting errors, etc. @@ -467,6 +482,15 @@ void CompilationCacheRegExp::Put(Handle<String> source, } +void CompilationCache::Remove(Handle<SharedFunctionInfo> function_info) { + if (!IsEnabled()) return; + + eval_global.Remove(function_info); + eval_contextual.Remove(function_info); + script.Remove(function_info); +} + + Handle<SharedFunctionInfo> CompilationCache::LookupScript(Handle<String> source, Handle<Object> name, int line_offset, @@ -545,6 +569,45 @@ void CompilationCache::PutRegExp(Handle<String> source, } +static bool SourceHashCompare(void* key1, void* key2) { + return key1 == key2; +} + + +static HashMap* EagerOptimizingSet() { + static HashMap map(&SourceHashCompare); + return ↦ +} + + +bool CompilationCache::ShouldOptimizeEagerly(Handle<JSFunction> function) { + if (FLAG_opt_eagerly) return true; + uint32_t hash = function->SourceHash(); + void* key = reinterpret_cast<void*>(hash); + return EagerOptimizingSet()->Lookup(key, hash, false) != NULL; +} + + +void CompilationCache::MarkForEagerOptimizing(Handle<JSFunction> function) { + uint32_t hash = function->SourceHash(); + void* key = reinterpret_cast<void*>(hash); + EagerOptimizingSet()->Lookup(key, hash, true); +} + + +void CompilationCache::MarkForLazyOptimizing(Handle<JSFunction> function) { + uint32_t hash = function->SourceHash(); + void* key = reinterpret_cast<void*>(hash); + EagerOptimizingSet()->Remove(key, hash); +} + + +void CompilationCache::ResetEagerOptimizingData() { + HashMap* set = EagerOptimizingSet(); + if (set->occupancy() > 0) set->Clear(); +} + + void CompilationCache::Clear() { for (int i = 0; i < kSubCacheCount; i++) { subcaches[i]->Clear(); diff --git a/src/compilation-cache.h b/src/compilation-cache.h index 22ecff83..37e21be9 100644 --- a/src/compilation-cache.h +++ b/src/compilation-cache.h @@ -76,9 +76,20 @@ class CompilationCache { JSRegExp::Flags flags, Handle<FixedArray> data); + // Support for eager optimization tracking. + static bool ShouldOptimizeEagerly(Handle<JSFunction> function); + static void MarkForEagerOptimizing(Handle<JSFunction> function); + static void MarkForLazyOptimizing(Handle<JSFunction> function); + + // Reset the eager optimization tracking data. + static void ResetEagerOptimizingData(); + // Clear the cache - also used to initialize the cache at startup. static void Clear(); + // Remove given shared function info from all caches. + static void Remove(Handle<SharedFunctionInfo> function_info); + // GC support. static void Iterate(ObjectVisitor* v); static void IterateFunctions(ObjectVisitor* v); diff --git a/src/compiler.cc b/src/compiler.cc index 29bbbc70..e4864e48 100755 --- a/src/compiler.cc +++ b/src/compiler.cc @@ -35,12 +35,16 @@ #include "data-flow.h" #include "debug.h" #include "full-codegen.h" +#include "hydrogen.h" +#include "lithium-allocator.h" #include "liveedit.h" #include "oprofile-agent.h" #include "parser.h" #include "rewriter.h" +#include "runtime-profiler.h" #include "scopeinfo.h" #include "scopes.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -52,7 +56,10 @@ CompilationInfo::CompilationInfo(Handle<Script> script) scope_(NULL), script_(script), extension_(NULL), - pre_parse_data_(NULL) { + pre_parse_data_(NULL), + supports_deoptimization_(false), + osr_ast_id_(AstNode::kNoNumber) { + Initialize(NONOPT); } @@ -63,7 +70,10 @@ CompilationInfo::CompilationInfo(Handle<SharedFunctionInfo> shared_info) shared_info_(shared_info), script_(Handle<Script>(Script::cast(shared_info->script()))), extension_(NULL), - pre_parse_data_(NULL) { + pre_parse_data_(NULL), + supports_deoptimization_(false), + osr_ast_id_(AstNode::kNoNumber) { + Initialize(BASE); } @@ -75,31 +85,213 @@ CompilationInfo::CompilationInfo(Handle<JSFunction> closure) shared_info_(Handle<SharedFunctionInfo>(closure->shared())), script_(Handle<Script>(Script::cast(shared_info_->script()))), extension_(NULL), - pre_parse_data_(NULL) { + pre_parse_data_(NULL), + supports_deoptimization_(false), + osr_ast_id_(AstNode::kNoNumber) { + Initialize(BASE); } -// For normal operation the syntax checker is used to determine whether to -// use the full compiler for top level code or not. However if the flag -// --always-full-compiler is specified or debugging is active the full -// compiler will be used for all code. +// Determine whether to use the full compiler for all code. If the flag +// --always-full-compiler is specified this is the case. For the virtual frame +// based compiler the full compiler is also used if a debugger is connected, as +// the code from the full compiler supports mode precise break points. For the +// crankshaft adaptive compiler debugging the optimized code is not possible at +// all. However crankshaft support recompilation of functions, so in this case +// the full compiler need not be be used if a debugger is attached, but only if +// break points has actually been set. static bool AlwaysFullCompiler() { #ifdef ENABLE_DEBUGGER_SUPPORT - return FLAG_always_full_compiler || Debugger::IsDebuggerActive(); + if (V8::UseCrankshaft()) { + return FLAG_always_full_compiler || Debug::has_break_points(); + } else { + return FLAG_always_full_compiler || Debugger::IsDebuggerActive(); + } #else return FLAG_always_full_compiler; #endif } +static void FinishOptimization(Handle<JSFunction> function, int64_t start) { + int opt_count = function->shared()->opt_count(); + function->shared()->set_opt_count(opt_count + 1); + double ms = static_cast<double>(OS::Ticks() - start) / 1000; + if (FLAG_trace_opt) { + PrintF("[optimizing: "); + function->PrintName(); + PrintF(" / %" V8PRIxPTR, reinterpret_cast<intptr_t>(*function)); + PrintF(" - took %0.3f ms]\n", ms); + } + if (FLAG_trace_opt_stats) { + static double compilation_time = 0.0; + static int compiled_functions = 0; + static int code_size = 0; + + compilation_time += ms; + compiled_functions++; + code_size += function->shared()->SourceSize(); + PrintF("Compiled: %d functions with %d byte source size in %fms.\n", + compiled_functions, + code_size, + compilation_time); + } +} + + +static void AbortAndDisable(CompilationInfo* info) { + // Disable optimization for the shared function info and mark the + // code as non-optimizable. The marker on the shared function info + // is there because we flush non-optimized code thereby loosing the + // non-optimizable information for the code. When the code is + // regenerated and set on the shared function info it is marked as + // non-optimizable if optimization is disabled for the shared + // function info. + Handle<SharedFunctionInfo> shared = info->shared_info(); + shared->set_optimization_disabled(true); + Handle<Code> code = Handle<Code>(shared->code()); + ASSERT(code->kind() == Code::FUNCTION); + code->set_optimizable(false); + info->SetCode(code); + if (FLAG_trace_opt) { + PrintF("[disabled optimization for: "); + info->closure()->PrintName(); + PrintF(" / %" V8PRIxPTR "]\n", + reinterpret_cast<intptr_t>(*info->closure())); + } +} + + +static bool MakeCrankshaftCode(CompilationInfo* info) { + // Test if we can optimize this function when asked to. We can only + // do this after the scopes are computed. + if (!info->AllowOptimize()) info->DisableOptimization(); + + // In case we are not optimizing simply return the code from + // the full code generator. + if (!info->IsOptimizing()) { + return FullCodeGenerator::MakeCode(info); + } + + // We should never arrive here if there is not code object on the + // shared function object. + Handle<Code> code(info->shared_info()->code()); + ASSERT(code->kind() == Code::FUNCTION); + + // Fall back to using the full code generator if it's not possible + // to use the Hydrogen-based optimizing compiler. We already have + // generated code for this from the shared function object. + if (AlwaysFullCompiler() || !FLAG_use_hydrogen) { + info->SetCode(code); + return true; + } + + // Limit the number of times we re-compile a functions with + // the optimizing compiler. + const int kMaxOptCount = FLAG_deopt_every_n_times == 0 ? 10 : 1000; + if (info->shared_info()->opt_count() > kMaxOptCount) { + AbortAndDisable(info); + // True indicates the compilation pipeline is still going, not + // necessarily that we optimized the code. + return true; + } + + // Due to an encoding limit on LUnallocated operands in the Lithium + // language, we cannot optimize functions with too many formal parameters + // or perform on-stack replacement for function with too many + // stack-allocated local variables. + // + // The encoding is as a signed value, with parameters using the negative + // indices and locals the non-negative ones. + const int limit = LUnallocated::kMaxFixedIndices / 2; + Scope* scope = info->scope(); + if (scope->num_parameters() > limit || scope->num_stack_slots() > limit) { + AbortAndDisable(info); + // True indicates the compilation pipeline is still going, not + // necessarily that we optimized the code. + return true; + } + + // Take --hydrogen-filter into account. + Vector<const char> filter = CStrVector(FLAG_hydrogen_filter); + Handle<String> name = info->function()->debug_name(); + bool match = filter.is_empty() || name->IsEqualTo(filter); + if (!match) { + info->SetCode(code); + return true; + } + + // Recompile the unoptimized version of the code if the current version + // doesn't have deoptimization support. Alternatively, we may decide to + // run the full code generator to get a baseline for the compile-time + // performance of the hydrogen-based compiler. + int64_t start = OS::Ticks(); + bool should_recompile = !info->shared_info()->has_deoptimization_support(); + if (should_recompile || FLAG_time_hydrogen) { + HPhase phase(HPhase::kFullCodeGen); + CompilationInfo unoptimized(info->shared_info()); + // Note that we use the same AST that we will use for generating the + // optimized code. + unoptimized.SetFunction(info->function()); + unoptimized.SetScope(info->scope()); + if (should_recompile) unoptimized.EnableDeoptimizationSupport(); + bool succeeded = FullCodeGenerator::MakeCode(&unoptimized); + if (should_recompile) { + if (!succeeded) return false; + Handle<SharedFunctionInfo> shared = info->shared_info(); + shared->EnableDeoptimizationSupport(*unoptimized.code()); + // The existing unoptimized code was replaced with the new one. + Compiler::RecordFunctionCompilation(Logger::LAZY_COMPILE_TAG, + Handle<String>(shared->DebugName()), + shared->start_position(), + &unoptimized); + } + } + + // Check that the unoptimized, shared code is ready for + // optimizations. When using the always_opt flag we disregard the + // optimizable marker in the code object and optimize anyway. This + // is safe as long as the unoptimized code has deoptimization + // support. + ASSERT(FLAG_always_opt || info->shared_info()->code()->optimizable()); + ASSERT(info->shared_info()->has_deoptimization_support()); + + if (FLAG_trace_hydrogen) { + PrintF("-----------------------------------------------------------\n"); + PrintF("Compiling method %s using hydrogen\n", *name->ToCString()); + HTracer::Instance()->TraceCompilation(info->function()); + } + + TypeFeedbackOracle oracle(Handle<Code>(info->shared_info()->code())); + HGraphBuilder builder(&oracle); + HPhase phase(HPhase::kTotal); + HGraph* graph = builder.CreateGraph(info); + if (graph != NULL && FLAG_build_lithium) { + Handle<Code> code = graph->Compile(); + if (!code.is_null()) { + info->SetCode(code); + FinishOptimization(info->closure(), start); + return true; + } + } + + // Compilation with the Hydrogen compiler failed. Keep using the + // shared code but mark it as unoptimizable. + AbortAndDisable(info); + // True indicates the compilation pipeline is still going, not necessarily + // that we optimized the code. + return true; +} + + static bool MakeCode(CompilationInfo* info) { // Precondition: code has been parsed. Postcondition: the code field in // the compilation info is set if compilation succeeded. ASSERT(info->function() != NULL); - if (Rewriter::Rewrite(info) && - Scope::Analyze(info) && - Rewriter::Analyze(info)) { + if (Rewriter::Rewrite(info) && Scope::Analyze(info)) { + if (V8::UseCrankshaft()) return MakeCrankshaftCode(info); + // Generate code and return it. Code generator selection is governed by // which backends are enabled and whether the function is considered // run-once code or not. @@ -109,17 +301,19 @@ static bool MakeCode(CompilationInfo* info) { // // The normal choice of backend can be overridden with the flags // --always-full-compiler. - Handle<SharedFunctionInfo> shared = info->shared_info(); - bool is_run_once = (shared.is_null()) - ? info->scope()->is_global_scope() - : (shared->is_toplevel() || shared->try_full_codegen()); - bool can_use_full = - FLAG_full_compiler && !info->function()->contains_loops(); - if (AlwaysFullCompiler() || (is_run_once && can_use_full)) { - return FullCodeGenerator::MakeCode(info); - } else { - AssignedVariablesAnalyzer ava; - return ava.Analyze(info) && CodeGenerator::MakeCode(info); + if (Rewriter::Analyze(info)) { + Handle<SharedFunctionInfo> shared = info->shared_info(); + bool is_run_once = (shared.is_null()) + ? info->scope()->is_global_scope() + : (shared->is_toplevel() || shared->try_full_codegen()); + bool can_use_full = + FLAG_full_compiler && !info->function()->contains_loops(); + if (AlwaysFullCompiler() || (is_run_once && can_use_full)) { + return FullCodeGenerator::MakeCode(info); + } else { + return AssignedVariablesAnalyzer::Analyze(info) && + CodeGenerator::MakeCode(info); + } } } @@ -280,7 +474,14 @@ Handle<SharedFunctionInfo> Compiler::Compile(Handle<String> source, ScriptDataImpl* pre_data = input_pre_data; if (pre_data == NULL && source_length >= FLAG_min_preparse_length) { - pre_data = ParserApi::PartialPreParse(source, NULL, extension); + if (source->IsExternalTwoByteString()) { + ExternalTwoByteStringUC16CharacterStream stream( + Handle<ExternalTwoByteString>::cast(source), 0, source->length()); + pre_data = ParserApi::PartialPreParse(&stream, extension); + } else { + GenericStringUC16CharacterStream stream(source, 0, source->length()); + pre_data = ParserApi::PartialPreParse(&stream, extension); + } } // Create a script object describing the script to be compiled. @@ -374,40 +575,60 @@ bool Compiler::CompileLazy(CompilationInfo* info) { Top::StackOverflow(); } else { ASSERT(!info->code().is_null()); + Handle<Code> code = info->code(); + Handle<JSFunction> function = info->closure(); RecordFunctionCompilation(Logger::LAZY_COMPILE_TAG, Handle<String>(shared->DebugName()), shared->start_position(), info); - // Update the shared function info with the compiled code and the - // scope info. Please note, that the order of the sharedfunction - // initialization is important since SerializedScopeInfo::Create might - // trigger a GC, causing the ASSERT below to be invalid if the code - // was flushed. By setting the code object last we avoid this. - Handle<SerializedScopeInfo> scope_info = - SerializedScopeInfo::Create(info->scope()); - shared->set_scope_info(*scope_info); - shared->set_code(*info->code()); - if (!info->closure().is_null()) { - info->closure()->set_code(*info->code()); + if (info->IsOptimizing()) { + function->ReplaceCode(*code); + } else { + // Update the shared function info with the compiled code and the + // scope info. Please note, that the order of the shared function + // info initialization is important since set_scope_info might + // trigger a GC, causing the ASSERT below to be invalid if the code + // was flushed. By settting the code object last we avoid this. + Handle<SerializedScopeInfo> scope_info = + SerializedScopeInfo::Create(info->scope()); + shared->set_scope_info(*scope_info); + shared->set_code(*code); + if (!function.is_null()) { + function->ReplaceCode(*code); + ASSERT(!function->IsOptimized()); + } + + // Set the expected number of properties for instances. + FunctionLiteral* lit = info->function(); + int expected = lit->expected_property_count(); + SetExpectedNofPropertiesFromEstimate(shared, expected); + + // Set the optimization hints after performing lazy compilation, as + // these are not set when the function is set up as a lazily + // compiled function. + shared->SetThisPropertyAssignmentsInfo( + lit->has_only_simple_this_property_assignments(), + *lit->this_property_assignments()); + + // Check the function has compiled code. + ASSERT(shared->is_compiled()); + shared->set_code_age(0); + + if (V8::UseCrankshaft() && info->AllowOptimize()) { + // If we're asked to always optimize, we compile the optimized + // version of the function right away - unless the debugger is + // active as it makes no sense to compile optimized code then. + if (FLAG_always_opt && !Debug::has_break_points()) { + CompilationInfo optimized(function); + optimized.SetOptimizing(AstNode::kNoNumber); + return CompileLazy(&optimized); + } else if (CompilationCache::ShouldOptimizeEagerly(function)) { + RuntimeProfiler::OptimizeSoon(*function); + } + } } - // Set the expected number of properties for instances. - FunctionLiteral* lit = info->function(); - SetExpectedNofPropertiesFromEstimate(shared, - lit->expected_property_count()); - - // Set the optimization hints after performing lazy compilation, as - // these are not set when the function is set up as a lazily compiled - // function. - shared->SetThisPropertyAssignmentsInfo( - lit->has_only_simple_this_property_assignments(), - *lit->this_property_assignments()); - - // Check the function has compiled code. - ASSERT(shared->is_compiled()); - shared->set_code_age(0); - ASSERT(!info->code().is_null()); return true; } } @@ -419,12 +640,6 @@ bool Compiler::CompileLazy(CompilationInfo* info) { Handle<SharedFunctionInfo> Compiler::BuildFunctionInfo(FunctionLiteral* literal, Handle<Script> script) { -#ifdef DEBUG - // We should not try to compile the same function literal more than - // once. - literal->mark_as_compiled(); -#endif - // Precondition: code has been parsed and scopes have been analyzed. CompilationInfo info(script); info.SetFunction(literal); @@ -446,28 +661,31 @@ Handle<SharedFunctionInfo> Compiler::BuildFunctionInfo(FunctionLiteral* literal, Handle<Code> code(Builtins::builtin(Builtins::LazyCompile)); info.SetCode(code); } else { - // Generate code and return it. The way that the compilation mode - // is controlled by the command-line flags is described in - // the static helper function MakeCode. - // - // The bodies of function literals have not yet been visited by - // the AST analyzer. - if (!Rewriter::Analyze(&info)) return Handle<SharedFunctionInfo>::null(); - - bool is_run_once = literal->try_full_codegen(); - bool use_full = FLAG_full_compiler && !literal->contains_loops(); - if (AlwaysFullCompiler() || (use_full && is_run_once)) { - if (!FullCodeGenerator::MakeCode(&info)) { + if (V8::UseCrankshaft()) { + if (!MakeCrankshaftCode(&info)) { return Handle<SharedFunctionInfo>::null(); } } else { - // We fall back to the classic V8 code generator. - AssignedVariablesAnalyzer ava; - if (!ava.Analyze(&info)) return Handle<SharedFunctionInfo>::null(); - if (!CodeGenerator::MakeCode(&info)) { - return Handle<SharedFunctionInfo>::null(); + // The bodies of function literals have not yet been visited by the + // AST optimizer/analyzer. + if (!Rewriter::Analyze(&info)) return Handle<SharedFunctionInfo>::null(); + + bool is_run_once = literal->try_full_codegen(); + bool can_use_full = FLAG_full_compiler && !literal->contains_loops(); + + if (AlwaysFullCompiler() || (is_run_once && can_use_full)) { + if (!FullCodeGenerator::MakeCode(&info)) { + return Handle<SharedFunctionInfo>::null(); + } + } else { + // We fall back to the classic V8 code generator. + if (!AssignedVariablesAnalyzer::Analyze(&info) || + !CodeGenerator::MakeCode(&info)) { + return Handle<SharedFunctionInfo>::null(); + } } } + ASSERT(!info.code().is_null()); // Function compilation complete. RecordFunctionCompilation(Logger::FUNCTION_TAG, @@ -484,6 +702,7 @@ Handle<SharedFunctionInfo> Compiler::BuildFunctionInfo(FunctionLiteral* literal, info.code(), scope_info); SetFunctionInfo(result, literal, false, script); + result->set_allows_lazy_compilation(allow_lazy); // Set the expected number of properties for instances and return // the resulting function. diff --git a/src/compiler.h b/src/compiler.h index 20868e54..1176c694 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -59,6 +59,7 @@ class CompilationInfo BASE_EMBEDDED { v8::Extension* extension() const { return extension_; } ScriptDataImpl* pre_parse_data() const { return pre_parse_data_; } Handle<Context> calling_context() const { return calling_context_; } + int osr_ast_id() const { return osr_ast_id_; } void MarkAsEval() { ASSERT(!is_lazy()); @@ -93,8 +94,66 @@ class CompilationInfo BASE_EMBEDDED { ASSERT(is_eval()); calling_context_ = context; } + void SetOsrAstId(int osr_ast_id) { + ASSERT(IsOptimizing()); + osr_ast_id_ = osr_ast_id; + } + + bool has_global_object() const { + return !closure().is_null() && (closure()->context()->global() != NULL); + } + + GlobalObject* global_object() const { + return has_global_object() ? closure()->context()->global() : NULL; + } + + // Accessors for the different compilation modes. + bool IsOptimizing() const { return mode_ == OPTIMIZE; } + bool IsOptimizable() const { return mode_ == BASE; } + void SetOptimizing(int osr_ast_id) { + SetMode(OPTIMIZE); + osr_ast_id_ = osr_ast_id; + } + void DisableOptimization() { SetMode(NONOPT); } + + // Deoptimization support. + bool HasDeoptimizationSupport() const { return supports_deoptimization_; } + void EnableDeoptimizationSupport() { + ASSERT(IsOptimizable()); + supports_deoptimization_ = true; + } + + // Determine whether or not we can adaptively optimize. + bool AllowOptimize() { + return V8::UseCrankshaft() && + !closure_.is_null() && + function_->AllowOptimize(); + } private: + // Compilation mode. + // BASE is generated by the full codegen, optionally prepared for bailouts. + // OPTIMIZE is optimized code generated by the Hydrogen-based backend. + // NONOPT is generated by the full codegen or the classic backend + // and is not prepared for recompilation/bailouts. These functions + // are never recompiled. + enum Mode { + BASE, + OPTIMIZE, + NONOPT + }; + + CompilationInfo() : function_(NULL) {} + + void Initialize(Mode mode) { + mode_ = V8::UseCrankshaft() ? mode : NONOPT; + } + + void SetMode(Mode mode) { + ASSERT(V8::UseCrankshaft()); + mode_ = mode; + } + // Flags using template class BitField<type, start, length>. All are // false by default. // @@ -130,6 +189,11 @@ class CompilationInfo BASE_EMBEDDED { // handle otherwise. Handle<Context> calling_context_; + // Compilation mode flag and whether deoptimization is allowed. + Mode mode_; + bool supports_deoptimization_; + int osr_ast_id_; + DISALLOW_COPY_AND_ASSIGN(CompilationInfo); }; @@ -185,7 +249,6 @@ class Compiler : public AllStatic { static bool MakeCodeForLiveEdit(CompilationInfo* info); #endif - private: static void RecordFunctionCompilation(Logger::LogEventsAndTags tag, Handle<String> name, int start_position, diff --git a/src/contexts.cc b/src/contexts.cc index 1ce5007d..3ad72a16 100644 --- a/src/contexts.cc +++ b/src/contexts.cc @@ -239,6 +239,69 @@ bool Context::GlobalIfNotShadowedByEval(Handle<String> name) { } +void Context::AddOptimizedFunction(JSFunction* function) { + ASSERT(IsGlobalContext()); +#ifdef DEBUG + Object* element = get(OPTIMIZED_FUNCTIONS_LIST); + while (!element->IsUndefined()) { + CHECK(element != function); + element = JSFunction::cast(element)->next_function_link(); + } + + CHECK(function->next_function_link()->IsUndefined()); + + // Check that the context belongs to the weak global contexts list. + bool found = false; + Object* context = Heap::global_contexts_list(); + while (!context->IsUndefined()) { + if (context == this) { + found = true; + break; + } + context = Context::cast(context)->get(Context::NEXT_CONTEXT_LINK); + } + CHECK(found); +#endif + function->set_next_function_link(get(OPTIMIZED_FUNCTIONS_LIST)); + set(OPTIMIZED_FUNCTIONS_LIST, function); +} + + +void Context::RemoveOptimizedFunction(JSFunction* function) { + ASSERT(IsGlobalContext()); + Object* element = get(OPTIMIZED_FUNCTIONS_LIST); + JSFunction* prev = NULL; + while (!element->IsUndefined()) { + JSFunction* element_function = JSFunction::cast(element); + ASSERT(element_function->next_function_link()->IsUndefined() || + element_function->next_function_link()->IsJSFunction()); + if (element_function == function) { + if (prev == NULL) { + set(OPTIMIZED_FUNCTIONS_LIST, element_function->next_function_link()); + } else { + prev->set_next_function_link(element_function->next_function_link()); + } + element_function->set_next_function_link(Heap::undefined_value()); + return; + } + prev = element_function; + element = element_function->next_function_link(); + } + UNREACHABLE(); +} + + +Object* Context::OptimizedFunctionsListHead() { + ASSERT(IsGlobalContext()); + return get(OPTIMIZED_FUNCTIONS_LIST); +} + + +void Context::ClearOptimizedFunctions() { + set(OPTIMIZED_FUNCTIONS_LIST, Heap::undefined_value()); +} + + #ifdef DEBUG bool Context::IsBootstrappingOrContext(Object* object) { // During bootstrapping we allow all objects to pass as diff --git a/src/contexts.h b/src/contexts.h index 9722a938..d0d54d1b 100644 --- a/src/contexts.h +++ b/src/contexts.h @@ -228,12 +228,13 @@ class Context: public FixedArray { // Properties from here are treated as weak references by the full GC. // Scavenge treats them as strong references. - NEXT_CONTEXT_LINK, + OPTIMIZED_FUNCTIONS_LIST, // Weak. + NEXT_CONTEXT_LINK, // Weak. // Total number of slots. GLOBAL_CONTEXT_SLOTS, - FIRST_WEAK_SLOT = NEXT_CONTEXT_LINK + FIRST_WEAK_SLOT = OPTIMIZED_FUNCTIONS_LIST }; // Direct slot access. @@ -291,6 +292,12 @@ class Context: public FixedArray { return IsCatchContext() && extension() == object; } + // A global context hold a list of all functions which have been optimized. + void AddOptimizedFunction(JSFunction* function); + void RemoveOptimizedFunction(JSFunction* function); + Object* OptimizedFunctionsListHead(); + void ClearOptimizedFunctions(); + #define GLOBAL_CONTEXT_FIELD_ACCESSORS(index, type, name) \ void set_##name(type* value) { \ ASSERT(IsGlobalContext()); \ diff --git a/src/counters.h b/src/counters.h index aed46cfb..048fdaab 100644 --- a/src/counters.h +++ b/src/counters.h @@ -28,6 +28,9 @@ #ifndef V8_COUNTERS_H_ #define V8_COUNTERS_H_ +#include "../include/v8.h" +#include "allocation.h" + namespace v8 { namespace internal { diff --git a/src/cpu-profiler.cc b/src/cpu-profiler.cc index da19a450..f13c0eef 100644 --- a/src/cpu-profiler.cc +++ b/src/cpu-profiler.cc @@ -34,6 +34,7 @@ #include "frames-inl.h" #include "hashmap.h" #include "log-inl.h" +#include "vm-state-inl.h" #include "../include/v8-profiler.h" @@ -223,7 +224,7 @@ void ProfilerEventsProcessor::RegExpCodeCreateEvent( void ProfilerEventsProcessor::AddCurrentStack() { TickSampleEventRecord record; TickSample* sample = &record.sample; - sample->state = VMState::current_state(); + sample->state = Top::current_vm_state(); sample->pc = reinterpret_cast<Address>(sample); // Not NULL. sample->frames_count = 0; for (StackTraceFrameIterator it; @@ -314,6 +315,7 @@ void ProfilerEventsProcessor::Run() { CpuProfiler* CpuProfiler::singleton_ = NULL; +Atomic32 CpuProfiler::is_profiling_ = false; void CpuProfiler::StartProfiling(const char* title) { ASSERT(singleton_ != NULL); @@ -435,7 +437,7 @@ void CpuProfiler::FunctionCreateEvent(JSFunction* function) { } singleton_->processor_->FunctionCreateEvent( function->address(), - function->code()->address(), + function->shared()->code()->address(), security_token_id); } @@ -525,6 +527,7 @@ void CpuProfiler::StartProcessorIfNotStarted() { Logger::logging_nesting_ = 0; generator_ = new ProfileGenerator(profiles_); processor_ = new ProfilerEventsProcessor(generator_); + NoBarrier_Store(&is_profiling_, true); processor_->Start(); // Enumerate stuff we already have in the heap. if (Heap::HasBeenSetup()) { @@ -539,7 +542,9 @@ void CpuProfiler::StartProcessorIfNotStarted() { Logger::LogAccessorCallbacks(); } // Enable stack sampling. - reinterpret_cast<Sampler*>(Logger::ticker_)->Start(); + Sampler* sampler = reinterpret_cast<Sampler*>(Logger::ticker_); + if (!sampler->IsActive()) sampler->Start(); + sampler->IncreaseProfilingDepth(); } } @@ -570,12 +575,15 @@ CpuProfile* CpuProfiler::StopCollectingProfile(Object* security_token, void CpuProfiler::StopProcessorIfLastProfile(const char* title) { if (profiles_->IsLastProfile(title)) { - reinterpret_cast<Sampler*>(Logger::ticker_)->Stop(); + Sampler* sampler = reinterpret_cast<Sampler*>(Logger::ticker_); + sampler->DecreaseProfilingDepth(); + sampler->Stop(); processor_->Stop(); processor_->Join(); delete processor_; delete generator_; processor_ = NULL; + NoBarrier_Store(&is_profiling_, false); generator_ = NULL; Logger::logging_nesting_ = saved_logging_nesting_; } diff --git a/src/cpu-profiler.h b/src/cpu-profiler.h index d3158d7a..10165f67 100644 --- a/src/cpu-profiler.h +++ b/src/cpu-profiler.h @@ -30,6 +30,7 @@ #ifdef ENABLE_LOGGING_AND_PROFILING +#include "atomicops.h" #include "circular-queue.h" #include "unbound-queue.h" @@ -269,7 +270,7 @@ class CpuProfiler { static void SetterCallbackEvent(String* name, Address entry_point); static INLINE(bool is_profiling()) { - return singleton_ != NULL && singleton_->processor_ != NULL; + return NoBarrier_Load(&is_profiling_); } private: @@ -290,6 +291,7 @@ class CpuProfiler { int saved_logging_nesting_; static CpuProfiler* singleton_; + static Atomic32 is_profiling_; #else static INLINE(bool is_profiling()) { return false; } diff --git a/src/d8.gyp b/src/d8.gyp new file mode 100644 index 00000000..3283e38a --- /dev/null +++ b/src/d8.gyp @@ -0,0 +1,85 @@ +# Copyright 2010 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. + +{ + 'targets': [ + { + 'target_name': 'd8', + 'type': 'executable', + 'dependencies': [ + 'd8_js2c#host', + '../tools/gyp/v8.gyp:v8', + ], + 'include_dirs+': [ + '../src', + ], + 'defines': [ + 'ENABLE_DEBUGGER_SUPPORT', + ], + 'sources': [ + 'd8.cc', + 'd8-debug.cc', + '<(SHARED_INTERMEDIATE_DIR)/d8-js.cc', + ], + 'conditions': [ + [ 'OS=="linux" or OS=="mac" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', { + 'sources': [ 'd8-posix.cc', ] + }], + ], + }, + { + 'target_name': 'd8_js2c', + 'type': 'none', + 'toolsets': ['host'], + 'variables': { + 'js_files': [ + 'd8.js', + ], + }, + 'actions': [ + { + 'action_name': 'd8_js2c', + 'inputs': [ + '../tools/js2c.py', + '<@(js_files)', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/d8-js.cc', + '<(SHARED_INTERMEDIATE_DIR)/d8-js-empty.cc', + ], + 'action': [ + 'python', + '../tools/js2c.py', + '<@(_outputs)', + 'D8', + '<@(js_files)' + ], + }, + ], + } + ], +} @@ -138,6 +138,10 @@ class Shell: public i::AllStatic { static Handle<Value> DebugCommandToJSONRequest(Handle<String> command); #endif +#ifdef WIN32 +#undef Yield +#endif + static Handle<Value> Print(const Arguments& args); static Handle<Value> Write(const Arguments& args); static Handle<Value> Yield(const Arguments& args); diff --git a/src/data-flow.cc b/src/data-flow.cc index be824460..9c02ff48 100644 --- a/src/data-flow.cc +++ b/src/data-flow.cc @@ -33,7 +33,6 @@ namespace v8 { namespace internal { - #ifdef DEBUG void BitVector::Print() { bool first = true; @@ -50,13 +49,39 @@ void BitVector::Print() { #endif +void BitVector::Iterator::Advance() { + current_++; + uint32_t val = current_value_; + while (val == 0) { + current_index_++; + if (Done()) return; + val = target_->data_[current_index_]; + current_ = current_index_ << 5; + } + val = SkipZeroBytes(val); + val = SkipZeroBits(val); + current_value_ = val >> 1; +} + + bool AssignedVariablesAnalyzer::Analyze(CompilationInfo* info) { - info_ = info; Scope* scope = info->scope(); - int variables = scope->num_parameters() + scope->num_stack_slots(); - if (variables == 0) return true; - av_.ExpandTo(variables); - VisitStatements(info->function()->body()); + int size = scope->num_parameters() + scope->num_stack_slots(); + if (size == 0) return true; + AssignedVariablesAnalyzer analyzer(info, size); + return analyzer.Analyze(); +} + + +AssignedVariablesAnalyzer::AssignedVariablesAnalyzer(CompilationInfo* info, + int size) + : info_(info), av_(size) { +} + + +bool AssignedVariablesAnalyzer::Analyze() { + ASSERT(av_.length() > 0); + VisitStatements(info_->function()->body()); return !HasStackOverflow(); } @@ -318,11 +343,6 @@ void AssignedVariablesAnalyzer::VisitConditional(Conditional* expr) { } -void AssignedVariablesAnalyzer::VisitSlot(Slot* expr) { - UNREACHABLE(); -} - - void AssignedVariablesAnalyzer::VisitVariableProxy(VariableProxy* expr) { // Nothing to do. ASSERT(av_.IsEmpty()); diff --git a/src/data-flow.h b/src/data-flow.h index efce1ea7..6e2230c6 100644 --- a/src/data-flow.h +++ b/src/data-flow.h @@ -42,10 +42,57 @@ class Node; class BitVector: public ZoneObject { public: - BitVector() : length_(0), data_length_(0), data_(NULL) { } + // Iterator for the elements of this BitVector. + class Iterator BASE_EMBEDDED { + public: + explicit Iterator(BitVector* target) + : target_(target), + current_index_(0), + current_value_(target->data_[0]), + current_(-1) { + ASSERT(target->data_length_ > 0); + Advance(); + } + ~Iterator() { } + + bool Done() const { return current_index_ >= target_->data_length_; } + void Advance(); + + int Current() const { + ASSERT(!Done()); + return current_; + } + + private: + uint32_t SkipZeroBytes(uint32_t val) { + while ((val & 0xFF) == 0) { + val >>= 8; + current_ += 8; + } + return val; + } + uint32_t SkipZeroBits(uint32_t val) { + while ((val & 0x1) == 0) { + val >>= 1; + current_++; + } + return val; + } - explicit BitVector(int length) { - ExpandTo(length); + BitVector* target_; + int current_index_; + uint32_t current_value_; + int current_; + + friend class BitVector; + }; + + explicit BitVector(int length) + : length_(length), + data_length_(SizeFor(length)), + data_(Zone::NewArray<uint32_t>(data_length_)) { + ASSERT(length > 0); + Clear(); } BitVector(const BitVector& other) @@ -55,12 +102,8 @@ class BitVector: public ZoneObject { CopyFrom(other); } - void ExpandTo(int length) { - ASSERT(length > 0); - length_ = length; - data_length_ = SizeFor(length); - data_ = Zone::NewArray<uint32_t>(data_length_); - Clear(); + static int SizeFor(int length) { + return 1 + ((length - 1) / 32); } BitVector& operator=(const BitVector& rhs) { @@ -75,7 +118,7 @@ class BitVector: public ZoneObject { } } - bool Contains(int i) { + bool Contains(int i) const { ASSERT(i >= 0 && i < length()); uint32_t block = data_[i / 32]; return (block & (1U << (i % 32))) != 0; @@ -98,6 +141,17 @@ class BitVector: public ZoneObject { } } + bool UnionIsChanged(const BitVector& other) { + ASSERT(other.length() == length()); + bool changed = false; + for (int i = 0; i < data_length_; i++) { + uint32_t old_data = data_[i]; + data_[i] |= other.data_[i]; + if (data_[i] != old_data) changed = true; + } + return changed; + } + void Intersect(const BitVector& other) { ASSERT(other.length() == length()); for (int i = 0; i < data_length_; i++) { @@ -139,16 +193,102 @@ class BitVector: public ZoneObject { #endif private: - static int SizeFor(int length) { - return 1 + ((length - 1) / 32); - } - int length_; int data_length_; uint32_t* data_; }; +// An implementation of a sparse set whose elements are drawn from integers +// in the range [0..universe_size[. It supports constant-time Contains, +// destructive Add, and destructuve Remove operations and linear-time (in +// the number of elements) destructive Union. +class SparseSet: public ZoneObject { + public: + // Iterator for sparse set elements. Elements should not be added or + // removed during iteration. + class Iterator BASE_EMBEDDED { + public: + explicit Iterator(SparseSet* target) : target_(target), current_(0) { + ASSERT(++target->iterator_count_ > 0); + } + ~Iterator() { + ASSERT(target_->iterator_count_-- > 0); + } + bool Done() const { return current_ >= target_->dense_.length(); } + void Advance() { + ASSERT(!Done()); + ++current_; + } + int Current() { + ASSERT(!Done()); + return target_->dense_[current_]; + } + + private: + SparseSet* target_; + int current_; + + friend class SparseSet; + }; + + explicit SparseSet(int universe_size) + : dense_(4), + sparse_(Zone::NewArray<int>(universe_size)) { +#ifdef DEBUG + size_ = universe_size; + iterator_count_ = 0; +#endif + } + + bool Contains(int n) const { + ASSERT(0 <= n && n < size_); + int dense_index = sparse_[n]; + return (0 <= dense_index) && + (dense_index < dense_.length()) && + (dense_[dense_index] == n); + } + + void Add(int n) { + ASSERT(0 <= n && n < size_); + ASSERT(iterator_count_ == 0); + if (!Contains(n)) { + sparse_[n] = dense_.length(); + dense_.Add(n); + } + } + + void Remove(int n) { + ASSERT(0 <= n && n < size_); + ASSERT(iterator_count_ == 0); + if (Contains(n)) { + int dense_index = sparse_[n]; + int last = dense_.RemoveLast(); + if (dense_index < dense_.length()) { + dense_[dense_index] = last; + sparse_[last] = dense_index; + } + } + } + + void Union(const SparseSet& other) { + for (int i = 0; i < other.dense_.length(); ++i) { + Add(other.dense_[i]); + } + } + + private: + // The set is implemented as a pair of a growable dense list and an + // uninitialized sparse array. + ZoneList<int> dense_; + int* sparse_; +#ifdef DEBUG + int size_; + int iterator_count_; +#endif +}; + + // Simple fixed-capacity list-based worklist (managed as a queue) of // pointers to T. template<typename T> @@ -198,10 +338,12 @@ class WorkList BASE_EMBEDDED { // is guaranteed to be a smi. class AssignedVariablesAnalyzer : public AstVisitor { public: - explicit AssignedVariablesAnalyzer() : info_(NULL) { } - bool Analyze(CompilationInfo* info); + static bool Analyze(CompilationInfo* info); private: + AssignedVariablesAnalyzer(CompilationInfo* info, int bits); + bool Analyze(); + Variable* FindSmiLoopVariable(ForStatement* stmt); int BitIndex(Variable* var); diff --git a/src/date.js b/src/date.js index 96014707..bc70327c 100644 --- a/src/date.js +++ b/src/date.js @@ -81,12 +81,12 @@ function TimeFromYear(year) { function InLeapYear(time) { - return DaysInYear(YEAR_FROM_TIME(time)) == 366 ? 1 : 0; + return DaysInYear(YearFromTime(time)) == 366 ? 1 : 0; } function DayWithinYear(time) { - return DAY(time) - DayFromYear(YEAR_FROM_TIME(time)); + return DAY(time) - DayFromYear(YearFromTime(time)); } @@ -114,9 +114,9 @@ function EquivalentTime(t) { // the actual year if it is in the range 1970..2037 if (t >= 0 && t <= 2.1e12) return t; - var day = MakeDay(EquivalentYear(YEAR_FROM_TIME(t)), - MONTH_FROM_TIME(t), - DATE_FROM_TIME(t)); + var day = MakeDay(EquivalentYear(YearFromTime(t)), + MonthFromTime(t), + DateFromTime(t)); return MakeDate(day, TimeWithinDay(t)); } @@ -253,9 +253,6 @@ var ltcache = { function LocalTimeNoCheck(time) { var ltc = ltcache; if (%_ObjectEquals(time, ltc.key)) return ltc.val; - if (time < -MAX_TIME_MS || time > MAX_TIME_MS) { - return $NaN; - } // Inline the DST offset cache checks for speed. // The cache is hit, or DaylightSavingsOffset is called, @@ -371,16 +368,21 @@ function MakeDay(year, month, date) { // ECMA 262 - 15.9.1.13 function MakeDate(day, time) { - if (!$isFinite(day)) return $NaN; - if (!$isFinite(time)) return $NaN; - return day * msPerDay + time; + var time = day * msPerDay + time; + // Some of our runtime funtions for computing UTC(time) rely on + // times not being significantly larger than MAX_TIME_MS. If there + // is no way that the time can be within range even after UTC + // conversion we return NaN immediately instead of relying on + // TimeClip to do it. + if ($abs(time) > MAX_TIME_BEFORE_UTC) return $NaN; + return time; } // ECMA 262 - 15.9.1.14 function TimeClip(time) { if (!$isFinite(time)) return $NaN; - if ($abs(time) > 8.64E15) return $NaN; + if ($abs(time) > MAX_TIME_MS) return $NaN; return TO_INTEGER(time); } @@ -424,7 +426,7 @@ var Date_cache = { value = DateParse(year); if (!NUMBER_IS_NAN(value)) { cache.time = value; - cache.year = YEAR_FROM_TIME(LocalTimeNoCheck(value)); + cache.year = YearFromTime(LocalTimeNoCheck(value)); cache.string = year; } } @@ -642,7 +644,7 @@ function DateGetFullYear() { if (NUMBER_IS_NAN(t)) return t; var cache = Date_cache; if (cache.time === t) return cache.year; - return YEAR_FROM_TIME(LocalTimeNoCheck(t)); + return YearFromTime(LocalTimeNoCheck(t)); } @@ -650,7 +652,7 @@ function DateGetFullYear() { function DateGetUTCFullYear() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return t; - return YEAR_FROM_TIME(t); + return YearFromTime(t); } @@ -658,7 +660,7 @@ function DateGetUTCFullYear() { function DateGetMonth() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return t; - return MONTH_FROM_TIME(LocalTimeNoCheck(t)); + return MonthFromTime(LocalTimeNoCheck(t)); } @@ -666,7 +668,7 @@ function DateGetMonth() { function DateGetUTCMonth() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return t; - return MONTH_FROM_TIME(t); + return MonthFromTime(t); } @@ -674,7 +676,7 @@ function DateGetUTCMonth() { function DateGetDate() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return t; - return DATE_FROM_TIME(LocalTimeNoCheck(t)); + return DateFromTime(LocalTimeNoCheck(t)); } @@ -869,7 +871,7 @@ function DateSetUTCHours(hour, min, sec, ms) { function DateSetDate(date) { var t = LocalTime(DATE_VALUE(this)); date = ToNumber(date); - var day = MakeDay(YEAR_FROM_TIME(t), MONTH_FROM_TIME(t), date); + var day = MakeDay(YearFromTime(t), MonthFromTime(t), date); return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t))))); } @@ -878,7 +880,7 @@ function DateSetDate(date) { function DateSetUTCDate(date) { var t = DATE_VALUE(this); date = ToNumber(date); - var day = MakeDay(YEAR_FROM_TIME(t), MONTH_FROM_TIME(t), date); + var day = MakeDay(YearFromTime(t), MonthFromTime(t), date); return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t)))); } @@ -888,7 +890,7 @@ function DateSetMonth(month, date) { var t = LocalTime(DATE_VALUE(this)); month = ToNumber(month); date = %_ArgumentsLength() < 2 ? NAN_OR_DATE_FROM_TIME(t) : ToNumber(date); - var day = MakeDay(YEAR_FROM_TIME(t), month, date); + var day = MakeDay(YearFromTime(t), month, date); return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t))))); } @@ -898,7 +900,7 @@ function DateSetUTCMonth(month, date) { var t = DATE_VALUE(this); month = ToNumber(month); date = %_ArgumentsLength() < 2 ? NAN_OR_DATE_FROM_TIME(t) : ToNumber(date); - var day = MakeDay(YEAR_FROM_TIME(t), month, date); + var day = MakeDay(YearFromTime(t), month, date); return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t)))); } @@ -909,8 +911,8 @@ function DateSetFullYear(year, month, date) { t = NUMBER_IS_NAN(t) ? 0 : LocalTimeNoCheck(t); year = ToNumber(year); var argc = %_ArgumentsLength(); - month = argc < 2 ? MONTH_FROM_TIME(t) : ToNumber(month); - date = argc < 3 ? DATE_FROM_TIME(t) : ToNumber(date); + month = argc < 2 ? MonthFromTime(t) : ToNumber(month); + date = argc < 3 ? DateFromTime(t) : ToNumber(date); var day = MakeDay(year, month, date); return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t))))); } @@ -922,8 +924,8 @@ function DateSetUTCFullYear(year, month, date) { if (NUMBER_IS_NAN(t)) t = 0; var argc = %_ArgumentsLength(); year = ToNumber(year); - month = argc < 2 ? MONTH_FROM_TIME(t) : ToNumber(month); - date = argc < 3 ? DATE_FROM_TIME(t) : ToNumber(date); + month = argc < 2 ? MonthFromTime(t) : ToNumber(month); + date = argc < 3 ? DateFromTime(t) : ToNumber(date); var day = MakeDay(year, month, date); return %_SetValueOf(this, TimeClip(MakeDate(day, TimeWithinDay(t)))); } @@ -935,9 +937,9 @@ function DateToUTCString() { if (NUMBER_IS_NAN(t)) return kInvalidDate; // Return UTC string of the form: Sat, 31 Jan 1970 23:00:00 GMT return WeekDays[WeekDay(t)] + ', ' - + TwoDigitString(DATE_FROM_TIME(t)) + ' ' - + Months[MONTH_FROM_TIME(t)] + ' ' - + YEAR_FROM_TIME(t) + ' ' + + TwoDigitString(DateFromTime(t)) + ' ' + + Months[MonthFromTime(t)] + ' ' + + YearFromTime(t) + ' ' + TimeString(t) + ' GMT'; } @@ -946,7 +948,7 @@ function DateToUTCString() { function DateGetYear() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return $NaN; - return YEAR_FROM_TIME(LocalTimeNoCheck(t)) - 1900; + return YearFromTime(LocalTimeNoCheck(t)) - 1900; } @@ -958,7 +960,7 @@ function DateSetYear(year) { if (NUMBER_IS_NAN(year)) return %_SetValueOf(this, $NaN); year = (0 <= TO_INTEGER(year) && TO_INTEGER(year) <= 99) ? 1900 + TO_INTEGER(year) : year; - var day = MakeDay(year, MONTH_FROM_TIME(t), DATE_FROM_TIME(t)); + var day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); return %_SetValueOf(this, TimeClip(UTC(MakeDate(day, TimeWithinDay(t))))); } @@ -984,16 +986,57 @@ function PadInt(n, digits) { function DateToISOString() { var t = DATE_VALUE(this); if (NUMBER_IS_NAN(t)) return kInvalidDate; - return this.getUTCFullYear() + '-' + PadInt(this.getUTCMonth() + 1, 2) + - '-' + PadInt(this.getUTCDate(), 2) + 'T' + PadInt(this.getUTCHours(), 2) + - ':' + PadInt(this.getUTCMinutes(), 2) + ':' + PadInt(this.getUTCSeconds(), 2) + + return this.getUTCFullYear() + + '-' + PadInt(this.getUTCMonth() + 1, 2) + + '-' + PadInt(this.getUTCDate(), 2) + + 'T' + PadInt(this.getUTCHours(), 2) + + ':' + PadInt(this.getUTCMinutes(), 2) + + ':' + PadInt(this.getUTCSeconds(), 2) + '.' + PadInt(this.getUTCMilliseconds(), 3) + 'Z'; } function DateToJSON(key) { - return CheckJSONPrimitive(this.toISOString()); + var o = ToObject(this); + var tv = DefaultNumber(o); + if (IS_NUMBER(tv) && !$isFinite(tv)) { + return null; + } + return o.toISOString(); +} + + +function ResetDateCache() { + + // Reset the local_time_offset: + local_time_offset = %DateLocalTimeOffset(); + + // Reset the DST offset cache: + var cache = DST_offset_cache; + cache.offset = 0; + cache.start = 0; + cache.end = -1; + cache.increment = 0; + cache.initial_increment = 19 * msPerDay; + + // Reset the timezone cache: + timezone_cache_time = $NaN; + timezone_cache_timezone = undefined; + + // Reset the ltcache: + ltcache.key = null; + ltcache.val = null; + + // Reset the ymd_from_time_cache: + ymd_from_time_cache = [$NaN, $NaN, $NaN]; + ymd_from_time_cached_time = $NaN; + + // Reset the date cache: + cache = Date_cache; + cache.time = $NaN; + cache.year = $NaN; + cache.string = null; } diff --git a/src/debug-debugger.js b/src/debug-debugger.js index d091991a..090c661d 100644 --- a/src/debug-debugger.js +++ b/src/debug-debugger.js @@ -858,6 +858,7 @@ Debug.debuggerFlags = function() { return debugger_flags; }; +Debug.MakeMirror = MakeMirror; function MakeExecutionState(break_id) { return new ExecutionState(break_id); @@ -876,9 +877,11 @@ ExecutionState.prototype.prepareStep = function(opt_action, opt_count) { return %PrepareStep(this.break_id, action, count); } -ExecutionState.prototype.evaluateGlobal = function(source, disable_break) { - return MakeMirror( - %DebugEvaluateGlobal(this.break_id, source, Boolean(disable_break))); +ExecutionState.prototype.evaluateGlobal = function(source, disable_break, + opt_additional_context) { + return MakeMirror(%DebugEvaluateGlobal(this.break_id, source, + Boolean(disable_break), + opt_additional_context)); }; ExecutionState.prototype.frameCount = function() { @@ -1837,6 +1840,7 @@ DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) { var frame = request.arguments.frame; var global = request.arguments.global; var disable_break = request.arguments.disable_break; + var additional_context = request.arguments.additional_context; // The expression argument could be an integer so we convert it to a // string. @@ -1850,12 +1854,30 @@ DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) { if (!IS_UNDEFINED(frame) && global) { return response.failed('Arguments "frame" and "global" are exclusive'); } + + var additional_context_object; + if (additional_context) { + additional_context_object = {}; + for (var i = 0; i < additional_context.length; i++) { + var mapping = additional_context[i]; + if (!IS_STRING(mapping.name) || !IS_NUMBER(mapping.handle)) { + return response.failed("Context element #" + i + + " must contain name:string and handle:number"); + } + var context_value_mirror = LookupMirror(mapping.handle); + if (!context_value_mirror) { + return response.failed("Context object '" + mapping.name + + "' #" + mapping.handle + "# not found"); + } + additional_context_object[mapping.name] = context_value_mirror.value(); + } + } // Global evaluate. if (global) { // Evaluate in the global context. - response.body = - this.exec_state_.evaluateGlobal(expression, Boolean(disable_break)); + response.body = this.exec_state_.evaluateGlobal( + expression, Boolean(disable_break), additional_context_object); return; } @@ -1877,12 +1899,12 @@ DebugCommandProcessor.prototype.evaluateRequest_ = function(request, response) { } // Evaluate in the specified frame. response.body = this.exec_state_.frame(frame_number).evaluate( - expression, Boolean(disable_break)); + expression, Boolean(disable_break), additional_context_object); return; } else { // Evaluate in the selected frame. response.body = this.exec_state_.frame().evaluate( - expression, Boolean(disable_break)); + expression, Boolean(disable_break), additional_context_object); return; } }; diff --git a/src/debug.cc b/src/debug.cc index f3bf954d..ca3c1db7 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -35,6 +35,7 @@ #include "compilation-cache.h" #include "compiler.h" #include "debug.h" +#include "deoptimizer.h" #include "execution.h" #include "global-handles.h" #include "ic.h" @@ -140,7 +141,9 @@ void BreakLocationIterator::Next() { Address target = original_rinfo()->target_address(); Code* code = Code::GetCodeFromTargetAddress(target); if ((code->is_inline_cache_stub() && - code->kind() != Code::BINARY_OP_IC) || + !code->is_binary_op_stub() && + !code->is_type_recording_binary_op_stub() && + !code->is_compare_ic_stub()) || RelocInfo::IsConstructCall(rmode())) { break_point_++; return; @@ -855,7 +858,7 @@ bool Debug::Load() { if (caught_exception) return false; // Debugger loaded. - debug_context_ = Handle<Context>::cast(GlobalHandles::Create(*context)); + debug_context_ = context; return true; } @@ -1661,6 +1664,12 @@ bool Debug::EnsureDebugInfo(Handle<SharedFunctionInfo> shared) { // Ensure shared in compiled. Return false if this failed. if (!EnsureCompiled(shared, CLEAR_EXCEPTION)) return false; + // If preparing for the first break point make sure to deoptimize all + // functions as debugging does not work with optimized code. + if (!has_break_points_) { + Deoptimizer::DeoptimizeAll(); + } + // Create the debug info object. Handle<DebugInfo> debug_info = Factory::NewDebugInfo(shared); diff --git a/src/deoptimizer.cc b/src/deoptimizer.cc new file mode 100644 index 00000000..dd70baaa --- /dev/null +++ b/src/deoptimizer.cc @@ -0,0 +1,1147 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "codegen.h" +#include "deoptimizer.h" +#include "disasm.h" +#include "full-codegen.h" +#include "global-handles.h" +#include "macro-assembler.h" +#include "prettyprinter.h" + + +namespace v8 { +namespace internal { + +LargeObjectChunk* Deoptimizer::eager_deoptimization_entry_code_ = NULL; +LargeObjectChunk* Deoptimizer::lazy_deoptimization_entry_code_ = NULL; +Deoptimizer* Deoptimizer::current_ = NULL; +DeoptimizingCodeListNode* Deoptimizer::deoptimizing_code_list_ = NULL; + + +Deoptimizer* Deoptimizer::New(JSFunction* function, + BailoutType type, + unsigned bailout_id, + Address from, + int fp_to_sp_delta) { + Deoptimizer* deoptimizer = + new Deoptimizer(function, type, bailout_id, from, fp_to_sp_delta); + ASSERT(current_ == NULL); + current_ = deoptimizer; + return deoptimizer; +} + + +Deoptimizer* Deoptimizer::Grab() { + Deoptimizer* result = current_; + ASSERT(result != NULL); + result->DeleteFrameDescriptions(); + current_ = NULL; + return result; +} + + +void Deoptimizer::GenerateDeoptimizationEntries(MacroAssembler* masm, + int count, + BailoutType type) { + TableEntryGenerator generator(masm, type, count); + generator.Generate(); +} + + +class DeoptimizingVisitor : public OptimizedFunctionVisitor { + public: + virtual void EnterContext(Context* context) { + if (FLAG_trace_deopt) { + PrintF("[deoptimize context: %" V8PRIxPTR "]\n", + reinterpret_cast<intptr_t>(context)); + } + } + + virtual void VisitFunction(JSFunction* function) { + Deoptimizer::DeoptimizeFunction(function); + } + + virtual void LeaveContext(Context* context) { + context->ClearOptimizedFunctions(); + } +}; + + +void Deoptimizer::DeoptimizeAll() { + AssertNoAllocation no_allocation; + + if (FLAG_trace_deopt) { + PrintF("[deoptimize all contexts]\n"); + } + + DeoptimizingVisitor visitor; + VisitAllOptimizedFunctions(&visitor); +} + + +void Deoptimizer::DeoptimizeGlobalObject(JSObject* object) { + AssertNoAllocation no_allocation; + + DeoptimizingVisitor visitor; + VisitAllOptimizedFunctionsForGlobalObject(object, &visitor); +} + + +void Deoptimizer::VisitAllOptimizedFunctionsForContext( + Context* context, OptimizedFunctionVisitor* visitor) { + AssertNoAllocation no_allocation; + + ASSERT(context->IsGlobalContext()); + + visitor->EnterContext(context); + // Run through the list of optimized functions and deoptimize them. + Object* element = context->OptimizedFunctionsListHead(); + while (!element->IsUndefined()) { + JSFunction* element_function = JSFunction::cast(element); + // Get the next link before deoptimizing as deoptimizing will clear the + // next link. + element = element_function->next_function_link(); + visitor->VisitFunction(element_function); + } + visitor->LeaveContext(context); +} + + +void Deoptimizer::VisitAllOptimizedFunctionsForGlobalObject( + JSObject* object, OptimizedFunctionVisitor* visitor) { + AssertNoAllocation no_allocation; + + if (object->IsJSGlobalProxy()) { + Object* proto = object->GetPrototype(); + ASSERT(proto->IsJSGlobalObject()); + VisitAllOptimizedFunctionsForContext( + GlobalObject::cast(proto)->global_context(), visitor); + } else if (object->IsGlobalObject()) { + VisitAllOptimizedFunctionsForContext( + GlobalObject::cast(object)->global_context(), visitor); + } +} + + +void Deoptimizer::VisitAllOptimizedFunctions( + OptimizedFunctionVisitor* visitor) { + AssertNoAllocation no_allocation; + + // Run through the list of all global contexts and deoptimize. + Object* global = Heap::global_contexts_list(); + while (!global->IsUndefined()) { + VisitAllOptimizedFunctionsForGlobalObject(Context::cast(global)->global(), + visitor); + global = Context::cast(global)->get(Context::NEXT_CONTEXT_LINK); + } +} + + +void Deoptimizer::HandleWeakDeoptimizedCode( + v8::Persistent<v8::Value> obj, void* data) { + DeoptimizingCodeListNode* node = + reinterpret_cast<DeoptimizingCodeListNode*>(data); + RemoveDeoptimizingCode(*node->code()); +#ifdef DEBUG + node = Deoptimizer::deoptimizing_code_list_; + while (node != NULL) { + ASSERT(node != reinterpret_cast<DeoptimizingCodeListNode*>(data)); + node = node->next(); + } +#endif +} + + +void Deoptimizer::ComputeOutputFrames(Deoptimizer* deoptimizer) { + deoptimizer->DoComputeOutputFrames(); +} + + +Deoptimizer::Deoptimizer(JSFunction* function, + BailoutType type, + unsigned bailout_id, + Address from, + int fp_to_sp_delta) + : function_(function), + bailout_id_(bailout_id), + bailout_type_(type), + from_(from), + fp_to_sp_delta_(fp_to_sp_delta), + output_count_(0), + output_(NULL), + integer32_values_(NULL), + double_values_(NULL) { + if (FLAG_trace_deopt && type != OSR) { + PrintF("**** DEOPT: "); + function->PrintName(); + PrintF(" at bailout #%u, address 0x%" V8PRIxPTR ", frame size %d\n", + bailout_id, + reinterpret_cast<intptr_t>(from), + fp_to_sp_delta - (2 * kPointerSize)); + } else if (FLAG_trace_osr && type == OSR) { + PrintF("**** OSR: "); + function->PrintName(); + PrintF(" at ast id #%u, address 0x%" V8PRIxPTR ", frame size %d\n", + bailout_id, + reinterpret_cast<intptr_t>(from), + fp_to_sp_delta - (2 * kPointerSize)); + } + // Find the optimized code. + if (type == EAGER) { + ASSERT(from == NULL); + optimized_code_ = function_->code(); + } else if (type == LAZY) { + optimized_code_ = FindDeoptimizingCodeFromAddress(from); + ASSERT(optimized_code_ != NULL); + } else if (type == OSR) { + // The function has already been optimized and we're transitioning + // from the unoptimized shared version to the optimized one in the + // function. The return address (from) points to unoptimized code. + optimized_code_ = function_->code(); + ASSERT(optimized_code_->kind() == Code::OPTIMIZED_FUNCTION); + ASSERT(!optimized_code_->contains(from)); + } + ASSERT(Heap::allow_allocation(false)); + unsigned size = ComputeInputFrameSize(); + input_ = new(size) FrameDescription(size, function); +} + + +Deoptimizer::~Deoptimizer() { + ASSERT(input_ == NULL && output_ == NULL); + delete[] integer32_values_; + delete[] double_values_; +} + + +void Deoptimizer::DeleteFrameDescriptions() { + delete input_; + for (int i = 0; i < output_count_; ++i) { + if (output_[i] != input_) delete output_[i]; + } + delete[] output_; + input_ = NULL; + output_ = NULL; + ASSERT(!Heap::allow_allocation(true)); +} + + +Address Deoptimizer::GetDeoptimizationEntry(int id, BailoutType type) { + ASSERT(id >= 0); + if (id >= kNumberOfEntries) return NULL; + LargeObjectChunk* base = NULL; + if (type == EAGER) { + if (eager_deoptimization_entry_code_ == NULL) { + eager_deoptimization_entry_code_ = CreateCode(type); + } + base = eager_deoptimization_entry_code_; + } else { + if (lazy_deoptimization_entry_code_ == NULL) { + lazy_deoptimization_entry_code_ = CreateCode(type); + } + base = lazy_deoptimization_entry_code_; + } + return + static_cast<Address>(base->GetStartAddress()) + (id * table_entry_size_); +} + + +int Deoptimizer::GetDeoptimizationId(Address addr, BailoutType type) { + LargeObjectChunk* base = NULL; + if (type == EAGER) { + base = eager_deoptimization_entry_code_; + } else { + base = lazy_deoptimization_entry_code_; + } + if (base == NULL || + addr < base->GetStartAddress() || + addr >= base->GetStartAddress() + + (kNumberOfEntries * table_entry_size_)) { + return kNotDeoptimizationEntry; + } + ASSERT_EQ(0, + static_cast<int>(addr - base->GetStartAddress()) % table_entry_size_); + return static_cast<int>(addr - base->GetStartAddress()) / table_entry_size_; +} + + +void Deoptimizer::Setup() { + // Do nothing yet. +} + + +void Deoptimizer::TearDown() { + if (eager_deoptimization_entry_code_ != NULL) { + eager_deoptimization_entry_code_->Free(EXECUTABLE); + eager_deoptimization_entry_code_ = NULL; + } + if (lazy_deoptimization_entry_code_ != NULL) { + lazy_deoptimization_entry_code_->Free(EXECUTABLE); + lazy_deoptimization_entry_code_ = NULL; + } +} + + +unsigned Deoptimizer::GetOutputInfo(DeoptimizationOutputData* data, + unsigned id, + SharedFunctionInfo* shared) { + // TODO(kasperl): For now, we do a simple linear search for the PC + // offset associated with the given node id. This should probably be + // changed to a binary search. + int length = data->DeoptPoints(); + Smi* smi_id = Smi::FromInt(id); + for (int i = 0; i < length; i++) { + if (data->AstId(i) == smi_id) { + return data->PcAndState(i)->value(); + } + } + PrintF("[couldn't find pc offset for node=%u]\n", id); + PrintF("[method: %s]\n", *shared->DebugName()->ToCString()); + // Print the source code if available. + HeapStringAllocator string_allocator; + StringStream stream(&string_allocator); + shared->SourceCodePrint(&stream, -1); + PrintF("[source:\n%s\n]", *stream.ToCString()); + + UNREACHABLE(); + return -1; +} + + +int Deoptimizer::GetDeoptimizedCodeCount() { + int length = 0; + DeoptimizingCodeListNode* node = Deoptimizer::deoptimizing_code_list_; + while (node != NULL) { + length++; + node = node->next(); + } + return length; +} + + +void Deoptimizer::DoComputeOutputFrames() { + if (bailout_type_ == OSR) { + DoComputeOsrOutputFrame(); + return; + } + + // Print some helpful diagnostic information. + int64_t start = OS::Ticks(); + if (FLAG_trace_deopt) { + PrintF("[deoptimizing%s: begin 0x%08" V8PRIxPTR " ", + (bailout_type_ == LAZY ? " (lazy)" : ""), + reinterpret_cast<intptr_t>(function_)); + function_->PrintName(); + PrintF(" @%d]\n", bailout_id_); + } + + // Determine basic deoptimization information. The optimized frame is + // described by the input data. + DeoptimizationInputData* input_data = + DeoptimizationInputData::cast(optimized_code_->deoptimization_data()); + unsigned node_id = input_data->AstId(bailout_id_)->value(); + ByteArray* translations = input_data->TranslationByteArray(); + unsigned translation_index = + input_data->TranslationIndex(bailout_id_)->value(); + + // Do the input frame to output frame(s) translation. + TranslationIterator iterator(translations, translation_index); + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator.Next()); + ASSERT(Translation::BEGIN == opcode); + USE(opcode); + // Read the number of output frames and allocate an array for their + // descriptions. + int count = iterator.Next(); + ASSERT(output_ == NULL); + output_ = new FrameDescription*[count]; + // Per-frame lists of untagged and unboxed int32 and double values. + integer32_values_ = new List<ValueDescriptionInteger32>[count]; + double_values_ = new List<ValueDescriptionDouble>[count]; + for (int i = 0; i < count; ++i) { + output_[i] = NULL; + integer32_values_[i].Initialize(0); + double_values_[i].Initialize(0); + } + output_count_ = count; + + // Translate each output frame. + for (int i = 0; i < count; ++i) { + DoComputeFrame(&iterator, i); + } + + // Print some helpful diagnostic information. + if (FLAG_trace_deopt) { + double ms = static_cast<double>(OS::Ticks() - start) / 1000; + int index = output_count_ - 1; // Index of the topmost frame. + JSFunction* function = output_[index]->GetFunction(); + PrintF("[deoptimizing: end 0x%08" V8PRIxPTR " ", + reinterpret_cast<intptr_t>(function)); + function->PrintName(); + PrintF(" => node=%u, pc=0x%08" V8PRIxPTR ", state=%s, took %0.3f ms]\n", + node_id, + output_[index]->GetPc(), + FullCodeGenerator::State2String( + static_cast<FullCodeGenerator::State>( + output_[index]->GetState()->value())), + ms); + } +} + + +void Deoptimizer::InsertHeapNumberValues(int index, JavaScriptFrame* frame) { + // We need to adjust the stack index by one for the top-most frame. + int extra_slot_count = (index == output_count() - 1) ? 1 : 0; + List<ValueDescriptionInteger32>* ints = &integer32_values_[index]; + for (int i = 0; i < ints->length(); i++) { + ValueDescriptionInteger32 value = ints->at(i); + double val = static_cast<double>(value.int32_value()); + InsertHeapNumberValue(frame, value.stack_index(), val, extra_slot_count); + } + + // Iterate over double values and convert them to a heap number. + List<ValueDescriptionDouble>* doubles = &double_values_[index]; + for (int i = 0; i < doubles->length(); ++i) { + ValueDescriptionDouble value = doubles->at(i); + InsertHeapNumberValue(frame, value.stack_index(), value.double_value(), + extra_slot_count); + } +} + + +void Deoptimizer::InsertHeapNumberValue(JavaScriptFrame* frame, + int stack_index, + double val, + int extra_slot_count) { + // Add one to the TOS index to take the 'state' pushed before jumping + // to the stub that calls Runtime::NotifyDeoptimized into account. + int tos_index = stack_index + extra_slot_count; + int index = (frame->ComputeExpressionsCount() - 1) - tos_index; + if (FLAG_trace_deopt) PrintF("Allocating a new heap number: %e\n", val); + Handle<Object> num = Factory::NewNumber(val); + frame->SetExpression(index, *num); +} + + +void Deoptimizer::DoTranslateCommand(TranslationIterator* iterator, + int frame_index, + unsigned output_offset) { + disasm::NameConverter converter; + // A GC-safe temporary placeholder that we can put in the output frame. + const intptr_t kPlaceholder = reinterpret_cast<intptr_t>(Smi::FromInt(0)); + + // Ignore commands marked as duplicate and act on the first non-duplicate. + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator->Next()); + while (opcode == Translation::DUPLICATE) { + opcode = static_cast<Translation::Opcode>(iterator->Next()); + iterator->Skip(Translation::NumberOfOperandsFor(opcode)); + opcode = static_cast<Translation::Opcode>(iterator->Next()); + } + + switch (opcode) { + case Translation::BEGIN: + case Translation::FRAME: + case Translation::DUPLICATE: + UNREACHABLE(); + return; + + case Translation::REGISTER: { + int input_reg = iterator->Next(); + intptr_t input_value = input_->GetRegister(input_reg); + if (FLAG_trace_deopt) { + PrintF( + " 0x%08" V8PRIxPTR ": [top + %d] <- 0x%08" V8PRIxPTR " ; %s\n", + output_[frame_index]->GetTop() + output_offset, + output_offset, + input_value, + converter.NameOfCPURegister(input_reg)); + } + output_[frame_index]->SetFrameSlot(output_offset, input_value); + return; + } + + case Translation::INT32_REGISTER: { + int input_reg = iterator->Next(); + intptr_t value = input_->GetRegister(input_reg); + bool is_smi = Smi::IsValid(value); + unsigned output_index = output_offset / kPointerSize; + if (FLAG_trace_deopt) { + PrintF( + " 0x%08" V8PRIxPTR ": [top + %d] <- %" V8PRIdPTR " ; %s (%s)\n", + output_[frame_index]->GetTop() + output_offset, + output_offset, + value, + converter.NameOfCPURegister(input_reg), + is_smi ? "smi" : "heap number"); + } + if (is_smi) { + intptr_t tagged_value = + reinterpret_cast<intptr_t>(Smi::FromInt(static_cast<int>(value))); + output_[frame_index]->SetFrameSlot(output_offset, tagged_value); + } else { + // We save the untagged value on the side and store a GC-safe + // temporary placeholder in the frame. + AddInteger32Value(frame_index, + output_index, + static_cast<int32_t>(value)); + output_[frame_index]->SetFrameSlot(output_offset, kPlaceholder); + } + return; + } + + case Translation::DOUBLE_REGISTER: { + int input_reg = iterator->Next(); + double value = input_->GetDoubleRegister(input_reg); + unsigned output_index = output_offset / kPointerSize; + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": [top + %d] <- %e ; %s\n", + output_[frame_index]->GetTop() + output_offset, + output_offset, + value, + DoubleRegister::AllocationIndexToString(input_reg)); + } + // We save the untagged value on the side and store a GC-safe + // temporary placeholder in the frame. + AddDoubleValue(frame_index, output_index, value); + output_[frame_index]->SetFrameSlot(output_offset, kPlaceholder); + return; + } + + case Translation::STACK_SLOT: { + int input_slot_index = iterator->Next(); + unsigned input_offset = + input_->GetOffsetFromSlotIndex(this, input_slot_index); + intptr_t input_value = input_->GetFrameSlot(input_offset); + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": ", + output_[frame_index]->GetTop() + output_offset); + PrintF("[top + %d] <- 0x%08" V8PRIxPTR " ; [esp + %d]\n", + output_offset, + input_value, + input_offset); + } + output_[frame_index]->SetFrameSlot(output_offset, input_value); + return; + } + + case Translation::INT32_STACK_SLOT: { + int input_slot_index = iterator->Next(); + unsigned input_offset = + input_->GetOffsetFromSlotIndex(this, input_slot_index); + intptr_t value = input_->GetFrameSlot(input_offset); + bool is_smi = Smi::IsValid(value); + unsigned output_index = output_offset / kPointerSize; + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": ", + output_[frame_index]->GetTop() + output_offset); + PrintF("[top + %d] <- %" V8PRIdPTR " ; [esp + %d] (%s)\n", + output_offset, + value, + input_offset, + is_smi ? "smi" : "heap number"); + } + if (is_smi) { + intptr_t tagged_value = + reinterpret_cast<intptr_t>(Smi::FromInt(static_cast<int>(value))); + output_[frame_index]->SetFrameSlot(output_offset, tagged_value); + } else { + // We save the untagged value on the side and store a GC-safe + // temporary placeholder in the frame. + AddInteger32Value(frame_index, + output_index, + static_cast<int32_t>(value)); + output_[frame_index]->SetFrameSlot(output_offset, kPlaceholder); + } + return; + } + + case Translation::DOUBLE_STACK_SLOT: { + int input_slot_index = iterator->Next(); + unsigned input_offset = + input_->GetOffsetFromSlotIndex(this, input_slot_index); + double value = input_->GetDoubleFrameSlot(input_offset); + unsigned output_index = output_offset / kPointerSize; + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": [top + %d] <- %e ; [esp + %d]\n", + output_[frame_index]->GetTop() + output_offset, + output_offset, + value, + input_offset); + } + // We save the untagged value on the side and store a GC-safe + // temporary placeholder in the frame. + AddDoubleValue(frame_index, output_index, value); + output_[frame_index]->SetFrameSlot(output_offset, kPlaceholder); + return; + } + + case Translation::LITERAL: { + Object* literal = ComputeLiteral(iterator->Next()); + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": [top + %d] <- ", + output_[frame_index]->GetTop() + output_offset, + output_offset); + literal->ShortPrint(); + PrintF(" ; literal\n"); + } + intptr_t value = reinterpret_cast<intptr_t>(literal); + output_[frame_index]->SetFrameSlot(output_offset, value); + return; + } + + case Translation::ARGUMENTS_OBJECT: { + // Use the hole value as a sentinel and fill in the arguments object + // after the deoptimized frame is built. + ASSERT(frame_index == 0); // Only supported for first frame. + if (FLAG_trace_deopt) { + PrintF(" 0x%08" V8PRIxPTR ": [top + %d] <- ", + output_[frame_index]->GetTop() + output_offset, + output_offset); + Heap::the_hole_value()->ShortPrint(); + PrintF(" ; arguments object\n"); + } + intptr_t value = reinterpret_cast<intptr_t>(Heap::the_hole_value()); + output_[frame_index]->SetFrameSlot(output_offset, value); + return; + } + } +} + + +bool Deoptimizer::DoOsrTranslateCommand(TranslationIterator* iterator, + int* input_offset) { + disasm::NameConverter converter; + FrameDescription* output = output_[0]; + + // The input values are all part of the unoptimized frame so they + // are all tagged pointers. + uintptr_t input_value = input_->GetFrameSlot(*input_offset); + Object* input_object = reinterpret_cast<Object*>(input_value); + + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator->Next()); + bool duplicate = (opcode == Translation::DUPLICATE); + if (duplicate) { + opcode = static_cast<Translation::Opcode>(iterator->Next()); + } + + switch (opcode) { + case Translation::BEGIN: + case Translation::FRAME: + case Translation::DUPLICATE: + UNREACHABLE(); // Malformed input. + return false; + + case Translation::REGISTER: { + int output_reg = iterator->Next(); + if (FLAG_trace_osr) { + PrintF(" %s <- 0x%08" V8PRIxPTR " ; [esp + %d]\n", + converter.NameOfCPURegister(output_reg), + input_value, + *input_offset); + } + output->SetRegister(output_reg, input_value); + break; + } + + case Translation::INT32_REGISTER: { + // Abort OSR if we don't have a number. + if (!input_object->IsNumber()) return false; + + int output_reg = iterator->Next(); + int int32_value = input_object->IsSmi() + ? Smi::cast(input_object)->value() + : FastD2I(input_object->Number()); + // Abort the translation if the conversion lost information. + if (!input_object->IsSmi() && + FastI2D(int32_value) != input_object->Number()) { + if (FLAG_trace_osr) { + PrintF("**** %g could not be converted to int32 ****\n", + input_object->Number()); + } + return false; + } + if (FLAG_trace_osr) { + PrintF(" %s <- %d (int32) ; [esp + %d]\n", + converter.NameOfCPURegister(output_reg), + int32_value, + *input_offset); + } + output->SetRegister(output_reg, int32_value); + break; + } + + case Translation::DOUBLE_REGISTER: { + // Abort OSR if we don't have a number. + if (!input_object->IsNumber()) return false; + + int output_reg = iterator->Next(); + double double_value = input_object->Number(); + if (FLAG_trace_osr) { + PrintF(" %s <- %g (double) ; [esp + %d]\n", + DoubleRegister::AllocationIndexToString(output_reg), + double_value, + *input_offset); + } + output->SetDoubleRegister(output_reg, double_value); + break; + } + + case Translation::STACK_SLOT: { + int output_index = iterator->Next(); + unsigned output_offset = + output->GetOffsetFromSlotIndex(this, output_index); + if (FLAG_trace_osr) { + PrintF(" [esp + %d] <- 0x%08" V8PRIxPTR " ; [esp + %d]\n", + output_offset, + input_value, + *input_offset); + } + output->SetFrameSlot(output_offset, input_value); + break; + } + + case Translation::INT32_STACK_SLOT: { + // Abort OSR if we don't have a number. + if (!input_object->IsNumber()) return false; + + int output_index = iterator->Next(); + unsigned output_offset = + output->GetOffsetFromSlotIndex(this, output_index); + int int32_value = input_object->IsSmi() + ? Smi::cast(input_object)->value() + : DoubleToInt32(input_object->Number()); + // Abort the translation if the conversion lost information. + if (!input_object->IsSmi() && + FastI2D(int32_value) != input_object->Number()) { + if (FLAG_trace_osr) { + PrintF("**** %g could not be converted to int32 ****\n", + input_object->Number()); + } + return false; + } + if (FLAG_trace_osr) { + PrintF(" [esp + %d] <- %d (int32) ; [esp + %d]\n", + output_offset, + int32_value, + *input_offset); + } + output->SetFrameSlot(output_offset, int32_value); + break; + } + + case Translation::DOUBLE_STACK_SLOT: { + static const int kLowerOffset = 0 * kPointerSize; + static const int kUpperOffset = 1 * kPointerSize; + + // Abort OSR if we don't have a number. + if (!input_object->IsNumber()) return false; + + int output_index = iterator->Next(); + unsigned output_offset = + output->GetOffsetFromSlotIndex(this, output_index); + double double_value = input_object->Number(); + uint64_t int_value = BitCast<uint64_t, double>(double_value); + int32_t lower = static_cast<int32_t>(int_value); + int32_t upper = static_cast<int32_t>(int_value >> kBitsPerInt); + if (FLAG_trace_osr) { + PrintF(" [esp + %d] <- 0x%08x (upper bits of %g) ; [esp + %d]\n", + output_offset + kUpperOffset, + upper, + double_value, + *input_offset); + PrintF(" [esp + %d] <- 0x%08x (lower bits of %g) ; [esp + %d]\n", + output_offset + kLowerOffset, + lower, + double_value, + *input_offset); + } + output->SetFrameSlot(output_offset + kLowerOffset, lower); + output->SetFrameSlot(output_offset + kUpperOffset, upper); + break; + } + + case Translation::LITERAL: { + // Just ignore non-materialized literals. + iterator->Next(); + break; + } + + case Translation::ARGUMENTS_OBJECT: { + // Optimized code assumes that the argument object has not been + // materialized and so bypasses it when doing arguments access. + // We should have bailed out before starting the frame + // translation. + UNREACHABLE(); + return false; + } + } + + if (!duplicate) *input_offset -= kPointerSize; + return true; +} + + +unsigned Deoptimizer::ComputeInputFrameSize() const { + unsigned fixed_size = ComputeFixedSize(function_); + // The fp-to-sp delta already takes the context and the function + // into account so we have to avoid double counting them (-2). + unsigned result = fixed_size + fp_to_sp_delta_ - (2 * kPointerSize); +#ifdef DEBUG + if (bailout_type_ == OSR) { + // TODO(kasperl): It would be nice if we could verify that the + // size matches with the stack height we can compute based on the + // environment at the OSR entry. The code for that his built into + // the DoComputeOsrOutputFrame function for now. + } else { + unsigned stack_slots = optimized_code_->stack_slots(); + unsigned outgoing_size = ComputeOutgoingArgumentSize(); + ASSERT(result == fixed_size + (stack_slots * kPointerSize) + outgoing_size); + } +#endif + return result; +} + + +unsigned Deoptimizer::ComputeFixedSize(JSFunction* function) const { + // The fixed part of the frame consists of the return address, frame + // pointer, function, context, and all the incoming arguments. + static const unsigned kFixedSlotSize = 4 * kPointerSize; + return ComputeIncomingArgumentSize(function) + kFixedSlotSize; +} + + +unsigned Deoptimizer::ComputeIncomingArgumentSize(JSFunction* function) const { + // The incoming arguments is the values for formal parameters and + // the receiver. Every slot contains a pointer. + unsigned arguments = function->shared()->formal_parameter_count() + 1; + return arguments * kPointerSize; +} + + +unsigned Deoptimizer::ComputeOutgoingArgumentSize() const { + DeoptimizationInputData* data = DeoptimizationInputData::cast( + optimized_code_->deoptimization_data()); + unsigned height = data->ArgumentsStackHeight(bailout_id_)->value(); + return height * kPointerSize; +} + + +Object* Deoptimizer::ComputeLiteral(int index) const { + DeoptimizationInputData* data = DeoptimizationInputData::cast( + optimized_code_->deoptimization_data()); + FixedArray* literals = data->LiteralArray(); + return literals->get(index); +} + + +void Deoptimizer::AddInteger32Value(int frame_index, + int slot_index, + int32_t value) { + ValueDescriptionInteger32 value_desc(slot_index, value); + integer32_values_[frame_index].Add(value_desc); +} + + +void Deoptimizer::AddDoubleValue(int frame_index, + int slot_index, + double value) { + ValueDescriptionDouble value_desc(slot_index, value); + double_values_[frame_index].Add(value_desc); +} + + +LargeObjectChunk* Deoptimizer::CreateCode(BailoutType type) { + // We cannot run this if the serializer is enabled because this will + // cause us to emit relocation information for the external + // references. This is fine because the deoptimizer's code section + // isn't meant to be serialized at all. + ASSERT(!Serializer::enabled()); + bool old_debug_code = FLAG_debug_code; + FLAG_debug_code = false; + + MacroAssembler masm(NULL, 16 * KB); + GenerateDeoptimizationEntries(&masm, kNumberOfEntries, type); + CodeDesc desc; + masm.GetCode(&desc); + ASSERT(desc.reloc_size == 0); + + LargeObjectChunk* chunk = LargeObjectChunk::New(desc.instr_size, EXECUTABLE); + memcpy(chunk->GetStartAddress(), desc.buffer, desc.instr_size); + CPU::FlushICache(chunk->GetStartAddress(), desc.instr_size); + FLAG_debug_code = old_debug_code; + return chunk; +} + + +Code* Deoptimizer::FindDeoptimizingCodeFromAddress(Address addr) { + DeoptimizingCodeListNode* node = Deoptimizer::deoptimizing_code_list_; + while (node != NULL) { + if (node->code()->contains(addr)) return *node->code(); + node = node->next(); + } + return NULL; +} + + +void Deoptimizer::RemoveDeoptimizingCode(Code* code) { + ASSERT(deoptimizing_code_list_ != NULL); + // Run through the code objects to find this one and remove it. + DeoptimizingCodeListNode* prev = NULL; + DeoptimizingCodeListNode* current = deoptimizing_code_list_; + while (current != NULL) { + if (*current->code() == code) { + // Unlink from list. If prev is NULL we are looking at the first element. + if (prev == NULL) { + deoptimizing_code_list_ = current->next(); + } else { + prev->set_next(current->next()); + } + delete current; + return; + } + // Move to next in list. + prev = current; + current = current->next(); + } + // Deoptimizing code is removed through weak callback. Each object is expected + // to be removed once and only once. + UNREACHABLE(); +} + + +FrameDescription::FrameDescription(uint32_t frame_size, + JSFunction* function) + : frame_size_(frame_size), + function_(function), + top_(kZapUint32), + pc_(kZapUint32), + fp_(kZapUint32) { + // Zap all the registers. + for (int r = 0; r < Register::kNumRegisters; r++) { + SetRegister(r, kZapUint32); + } + + // Zap all the slots. + for (unsigned o = 0; o < frame_size; o += kPointerSize) { + SetFrameSlot(o, kZapUint32); + } +} + + +unsigned FrameDescription::GetOffsetFromSlotIndex(Deoptimizer* deoptimizer, + int slot_index) { + if (slot_index >= 0) { + // Local or spill slots. Skip the fixed part of the frame + // including all arguments. + unsigned base = static_cast<unsigned>( + GetFrameSize() - deoptimizer->ComputeFixedSize(GetFunction())); + return base - ((slot_index + 1) * kPointerSize); + } else { + // Incoming parameter. + unsigned base = static_cast<unsigned>(GetFrameSize() - + deoptimizer->ComputeIncomingArgumentSize(GetFunction())); + return base - ((slot_index + 1) * kPointerSize); + } +} + + +void TranslationBuffer::Add(int32_t value) { + // Encode the sign bit in the least significant bit. + bool is_negative = (value < 0); + uint32_t bits = ((is_negative ? -value : value) << 1) | + static_cast<int32_t>(is_negative); + // Encode the individual bytes using the least significant bit of + // each byte to indicate whether or not more bytes follow. + do { + uint32_t next = bits >> 7; + contents_.Add(((bits << 1) & 0xFF) | (next != 0)); + bits = next; + } while (bits != 0); +} + + +int32_t TranslationIterator::Next() { + ASSERT(HasNext()); + // Run through the bytes until we reach one with a least significant + // bit of zero (marks the end). + uint32_t bits = 0; + for (int i = 0; true; i += 7) { + uint8_t next = buffer_->get(index_++); + bits |= (next >> 1) << i; + if ((next & 1) == 0) break; + } + // The bits encode the sign in the least significant bit. + bool is_negative = (bits & 1) == 1; + int32_t result = bits >> 1; + return is_negative ? -result : result; +} + + +Handle<ByteArray> TranslationBuffer::CreateByteArray() { + int length = contents_.length(); + Handle<ByteArray> result = Factory::NewByteArray(length, TENURED); + memcpy(result->GetDataStartAddress(), contents_.ToVector().start(), length); + return result; +} + + +void Translation::BeginFrame(int node_id, int literal_id, unsigned height) { + buffer_->Add(FRAME); + buffer_->Add(node_id); + buffer_->Add(literal_id); + buffer_->Add(height); +} + + +void Translation::StoreRegister(Register reg) { + buffer_->Add(REGISTER); + buffer_->Add(reg.code()); +} + + +void Translation::StoreInt32Register(Register reg) { + buffer_->Add(INT32_REGISTER); + buffer_->Add(reg.code()); +} + + +void Translation::StoreDoubleRegister(DoubleRegister reg) { + buffer_->Add(DOUBLE_REGISTER); + buffer_->Add(DoubleRegister::ToAllocationIndex(reg)); +} + + +void Translation::StoreStackSlot(int index) { + buffer_->Add(STACK_SLOT); + buffer_->Add(index); +} + + +void Translation::StoreInt32StackSlot(int index) { + buffer_->Add(INT32_STACK_SLOT); + buffer_->Add(index); +} + + +void Translation::StoreDoubleStackSlot(int index) { + buffer_->Add(DOUBLE_STACK_SLOT); + buffer_->Add(index); +} + + +void Translation::StoreLiteral(int literal_id) { + buffer_->Add(LITERAL); + buffer_->Add(literal_id); +} + + +void Translation::StoreArgumentsObject() { + buffer_->Add(ARGUMENTS_OBJECT); +} + + +void Translation::MarkDuplicate() { + buffer_->Add(DUPLICATE); +} + + +int Translation::NumberOfOperandsFor(Opcode opcode) { + switch (opcode) { + case ARGUMENTS_OBJECT: + case DUPLICATE: + return 0; + case BEGIN: + case REGISTER: + case INT32_REGISTER: + case DOUBLE_REGISTER: + case STACK_SLOT: + case INT32_STACK_SLOT: + case DOUBLE_STACK_SLOT: + case LITERAL: + return 1; + case FRAME: + return 3; + } + UNREACHABLE(); + return -1; +} + + +#ifdef OBJECT_PRINT + +const char* Translation::StringFor(Opcode opcode) { + switch (opcode) { + case BEGIN: + return "BEGIN"; + case FRAME: + return "FRAME"; + case REGISTER: + return "REGISTER"; + case INT32_REGISTER: + return "INT32_REGISTER"; + case DOUBLE_REGISTER: + return "DOUBLE_REGISTER"; + case STACK_SLOT: + return "STACK_SLOT"; + case INT32_STACK_SLOT: + return "INT32_STACK_SLOT"; + case DOUBLE_STACK_SLOT: + return "DOUBLE_STACK_SLOT"; + case LITERAL: + return "LITERAL"; + case ARGUMENTS_OBJECT: + return "ARGUMENTS_OBJECT"; + case DUPLICATE: + return "DUPLICATE"; + } + UNREACHABLE(); + return ""; +} + +#endif + + +DeoptimizingCodeListNode::DeoptimizingCodeListNode(Code* code): next_(NULL) { + // Globalize the code object and make it weak. + code_ = Handle<Code>::cast((GlobalHandles::Create(code))); + GlobalHandles::MakeWeak(reinterpret_cast<Object**>(code_.location()), + this, + Deoptimizer::HandleWeakDeoptimizedCode); +} + + +DeoptimizingCodeListNode::~DeoptimizingCodeListNode() { + GlobalHandles::Destroy(reinterpret_cast<Object**>(code_.location())); +} + + +} } // namespace v8::internal diff --git a/src/deoptimizer.h b/src/deoptimizer.h new file mode 100644 index 00000000..2d7dfc89 --- /dev/null +++ b/src/deoptimizer.h @@ -0,0 +1,511 @@ +// Copyright 2010 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. + +#ifndef V8_DEOPTIMIZER_H_ +#define V8_DEOPTIMIZER_H_ + +#include "v8.h" + +#include "macro-assembler.h" +#include "zone-inl.h" + + +namespace v8 { +namespace internal { + +class FrameDescription; +class TranslationIterator; +class DeoptimizingCodeListNode; + + +class ValueDescription BASE_EMBEDDED { + public: + explicit ValueDescription(int index) : stack_index_(index) { } + int stack_index() const { return stack_index_; } + + private: + // Offset relative to the top of the stack. + int stack_index_; +}; + + +class ValueDescriptionInteger32: public ValueDescription { + public: + ValueDescriptionInteger32(int index, int32_t value) + : ValueDescription(index), int32_value_(value) { } + int32_t int32_value() const { return int32_value_; } + + private: + // Raw value. + int32_t int32_value_; +}; + + +class ValueDescriptionDouble: public ValueDescription { + public: + ValueDescriptionDouble(int index, double value) + : ValueDescription(index), double_value_(value) { } + double double_value() const { return double_value_; } + + private: + // Raw value. + double double_value_; +}; + + +class OptimizedFunctionVisitor BASE_EMBEDDED { + public: + virtual ~OptimizedFunctionVisitor() {} + + // Function which is called before iteration of any optimized functions + // from given global context. + virtual void EnterContext(Context* context) = 0; + + virtual void VisitFunction(JSFunction* function) = 0; + + // Function which is called after iteration of all optimized functions + // from given global context. + virtual void LeaveContext(Context* context) = 0; +}; + + +class Deoptimizer : public Malloced { + public: + enum BailoutType { + EAGER, + LAZY, + OSR + }; + + int output_count() const { return output_count_; } + + static Deoptimizer* New(JSFunction* function, + BailoutType type, + unsigned bailout_id, + Address from, + int fp_to_sp_delta); + static Deoptimizer* Grab(); + + // Deoptimize the function now. Its current optimized code will never be run + // again and any activations of the optimized code will get deoptimized when + // execution returns. + static void DeoptimizeFunction(JSFunction* function); + + // Deoptimize all functions in the heap. + static void DeoptimizeAll(); + + static void DeoptimizeGlobalObject(JSObject* object); + + static void VisitAllOptimizedFunctionsForContext( + Context* context, OptimizedFunctionVisitor* visitor); + + static void VisitAllOptimizedFunctionsForGlobalObject( + JSObject* object, OptimizedFunctionVisitor* visitor); + + static void VisitAllOptimizedFunctions(OptimizedFunctionVisitor* visitor); + + // Given the relocation info of a call to the stack check stub, patch the + // code so as to go unconditionally to the on-stack replacement builtin + // instead. + static void PatchStackCheckCode(RelocInfo* rinfo, Code* replacement_code); + + // Given the relocation info of a call to the on-stack replacement + // builtin, patch the code back to the original stack check code. + static void RevertStackCheckCode(RelocInfo* rinfo, Code* check_code); + + ~Deoptimizer(); + + void InsertHeapNumberValues(int index, JavaScriptFrame* frame); + + static void ComputeOutputFrames(Deoptimizer* deoptimizer); + + static Address GetDeoptimizationEntry(int id, BailoutType type); + static int GetDeoptimizationId(Address addr, BailoutType type); + static unsigned GetOutputInfo(DeoptimizationOutputData* data, + unsigned node_id, + SharedFunctionInfo* shared); + + static void Setup(); + static void TearDown(); + + // Code generation support. + static int input_offset() { return OFFSET_OF(Deoptimizer, input_); } + static int output_count_offset() { + return OFFSET_OF(Deoptimizer, output_count_); + } + static int output_offset() { return OFFSET_OF(Deoptimizer, output_); } + + static int GetDeoptimizedCodeCount(); + + static const int kNotDeoptimizationEntry = -1; + + // Generators for the deoptimization entry code. + class EntryGenerator BASE_EMBEDDED { + public: + EntryGenerator(MacroAssembler* masm, BailoutType type) + : masm_(masm), type_(type) { } + virtual ~EntryGenerator() { } + + void Generate(); + + protected: + MacroAssembler* masm() const { return masm_; } + BailoutType type() const { return type_; } + + virtual void GeneratePrologue() { } + + private: + MacroAssembler* masm_; + Deoptimizer::BailoutType type_; + }; + + class TableEntryGenerator : public EntryGenerator { + public: + TableEntryGenerator(MacroAssembler* masm, BailoutType type, int count) + : EntryGenerator(masm, type), count_(count) { } + + protected: + virtual void GeneratePrologue(); + + private: + int count() const { return count_; } + + int count_; + }; + + private: + static const int kNumberOfEntries = 4096; + + Deoptimizer(JSFunction* function, + BailoutType type, + unsigned bailout_id, + Address from, + int fp_to_sp_delta); + void DeleteFrameDescriptions(); + + void DoComputeOutputFrames(); + void DoComputeOsrOutputFrame(); + void DoComputeFrame(TranslationIterator* iterator, int frame_index); + void DoTranslateCommand(TranslationIterator* iterator, + int frame_index, + unsigned output_offset); + // Translate a command for OSR. Updates the input offset to be used for + // the next command. Returns false if translation of the command failed + // (e.g., a number conversion failed) and may or may not have updated the + // input offset. + bool DoOsrTranslateCommand(TranslationIterator* iterator, + int* input_offset); + + unsigned ComputeInputFrameSize() const; + unsigned ComputeFixedSize(JSFunction* function) const; + + unsigned ComputeIncomingArgumentSize(JSFunction* function) const; + unsigned ComputeOutgoingArgumentSize() const; + + Object* ComputeLiteral(int index) const; + + void InsertHeapNumberValue(JavaScriptFrame* frame, + int stack_index, + double val, + int extra_slot_count); + + void AddInteger32Value(int frame_index, int slot_index, int32_t value); + void AddDoubleValue(int frame_index, int slot_index, double value); + + static LargeObjectChunk* CreateCode(BailoutType type); + static void GenerateDeoptimizationEntries( + MacroAssembler* masm, int count, BailoutType type); + + // Weak handle callback for deoptimizing code objects. + static void HandleWeakDeoptimizedCode( + v8::Persistent<v8::Value> obj, void* data); + static Code* FindDeoptimizingCodeFromAddress(Address addr); + static void RemoveDeoptimizingCode(Code* code); + + static LargeObjectChunk* eager_deoptimization_entry_code_; + static LargeObjectChunk* lazy_deoptimization_entry_code_; + static Deoptimizer* current_; + + // List of deoptimized code which still have references from active stack + // frames. These code objects are needed by the deoptimizer when deoptimizing + // a frame for which the code object for the function function has been + // changed from the code present when deoptimizing was done. + static DeoptimizingCodeListNode* deoptimizing_code_list_; + + JSFunction* function_; + Code* optimized_code_; + unsigned bailout_id_; + BailoutType bailout_type_; + Address from_; + int fp_to_sp_delta_; + + // Input frame description. + FrameDescription* input_; + // Number of output frames. + int output_count_; + // Array of output frame descriptions. + FrameDescription** output_; + + List<ValueDescriptionInteger32>* integer32_values_; + List<ValueDescriptionDouble>* double_values_; + + static int table_entry_size_; + + friend class FrameDescription; + friend class DeoptimizingCodeListNode; +}; + + +class FrameDescription { + public: + FrameDescription(uint32_t frame_size, + JSFunction* function); + + void* operator new(size_t size, uint32_t frame_size) { + return malloc(size + frame_size); + } + + void operator delete(void* description) { + free(description); + } + + intptr_t GetFrameSize() const { return frame_size_; } + + JSFunction* GetFunction() const { return function_; } + + unsigned GetOffsetFromSlotIndex(Deoptimizer* deoptimizer, int slot_index); + + intptr_t GetFrameSlot(unsigned offset) { + return *GetFrameSlotPointer(offset); + } + + double GetDoubleFrameSlot(unsigned offset) { + return *reinterpret_cast<double*>(GetFrameSlotPointer(offset)); + } + + void SetFrameSlot(unsigned offset, intptr_t value) { + *GetFrameSlotPointer(offset) = value; + } + + intptr_t GetRegister(unsigned n) const { + ASSERT(n < ARRAY_SIZE(registers_)); + return registers_[n]; + } + + double GetDoubleRegister(unsigned n) const { + ASSERT(n < ARRAY_SIZE(double_registers_)); + return double_registers_[n]; + } + + void SetRegister(unsigned n, intptr_t value) { + ASSERT(n < ARRAY_SIZE(registers_)); + registers_[n] = value; + } + + void SetDoubleRegister(unsigned n, double value) { + ASSERT(n < ARRAY_SIZE(double_registers_)); + double_registers_[n] = value; + } + + intptr_t GetTop() const { return top_; } + void SetTop(intptr_t top) { top_ = top; } + + intptr_t GetPc() const { return pc_; } + void SetPc(intptr_t pc) { pc_ = pc; } + + intptr_t GetFp() const { return fp_; } + void SetFp(intptr_t fp) { fp_ = fp; } + + Smi* GetState() const { return state_; } + void SetState(Smi* state) { state_ = state; } + + void SetContinuation(intptr_t pc) { continuation_ = pc; } + + static int registers_offset() { + return OFFSET_OF(FrameDescription, registers_); + } + + static int double_registers_offset() { + return OFFSET_OF(FrameDescription, double_registers_); + } + + static int frame_size_offset() { + return OFFSET_OF(FrameDescription, frame_size_); + } + + static int pc_offset() { + return OFFSET_OF(FrameDescription, pc_); + } + + static int state_offset() { + return OFFSET_OF(FrameDescription, state_); + } + + static int continuation_offset() { + return OFFSET_OF(FrameDescription, continuation_); + } + + static int frame_content_offset() { + return sizeof(FrameDescription); + } + + private: + static const uint32_t kZapUint32 = 0xbeeddead; + + uintptr_t frame_size_; // Number of bytes. + JSFunction* function_; + intptr_t registers_[Register::kNumRegisters]; + double double_registers_[DoubleRegister::kNumAllocatableRegisters]; + intptr_t top_; + intptr_t pc_; + intptr_t fp_; + Smi* state_; + + // Continuation is the PC where the execution continues after + // deoptimizing. + intptr_t continuation_; + + intptr_t* GetFrameSlotPointer(unsigned offset) { + ASSERT(offset < frame_size_); + return reinterpret_cast<intptr_t*>( + reinterpret_cast<Address>(this) + frame_content_offset() + offset); + } +}; + + +class TranslationBuffer BASE_EMBEDDED { + public: + TranslationBuffer() : contents_(256) { } + + int CurrentIndex() const { return contents_.length(); } + void Add(int32_t value); + + Handle<ByteArray> CreateByteArray(); + + private: + ZoneList<uint8_t> contents_; +}; + + +class TranslationIterator BASE_EMBEDDED { + public: + TranslationIterator(ByteArray* buffer, int index) + : buffer_(buffer), index_(index) { + ASSERT(index >= 0 && index < buffer->length()); + } + + int32_t Next(); + + bool HasNext() const { return index_ >= 0; } + + void Done() { index_ = -1; } + + void Skip(int n) { + for (int i = 0; i < n; i++) Next(); + } + + private: + ByteArray* buffer_; + int index_; +}; + + +class Translation BASE_EMBEDDED { + public: + enum Opcode { + BEGIN, + FRAME, + REGISTER, + INT32_REGISTER, + DOUBLE_REGISTER, + STACK_SLOT, + INT32_STACK_SLOT, + DOUBLE_STACK_SLOT, + LITERAL, + ARGUMENTS_OBJECT, + + // A prefix indicating that the next command is a duplicate of the one + // that follows it. + DUPLICATE + }; + + Translation(TranslationBuffer* buffer, int frame_count) + : buffer_(buffer), + index_(buffer->CurrentIndex()) { + buffer_->Add(BEGIN); + buffer_->Add(frame_count); + } + + int index() const { return index_; } + + // Commands. + void BeginFrame(int node_id, int literal_id, unsigned height); + void StoreRegister(Register reg); + void StoreInt32Register(Register reg); + void StoreDoubleRegister(DoubleRegister reg); + void StoreStackSlot(int index); + void StoreInt32StackSlot(int index); + void StoreDoubleStackSlot(int index); + void StoreLiteral(int literal_id); + void StoreArgumentsObject(); + void MarkDuplicate(); + + static int NumberOfOperandsFor(Opcode opcode); + +#ifdef OBJECT_PRINT + static const char* StringFor(Opcode opcode); +#endif + + private: + TranslationBuffer* buffer_; + int index_; +}; + + +// Linked list holding deoptimizing code objects. The deoptimizing code objects +// are kept as weak handles until they are no longer activated on the stack. +class DeoptimizingCodeListNode : public Malloced { + public: + explicit DeoptimizingCodeListNode(Code* code); + ~DeoptimizingCodeListNode(); + + DeoptimizingCodeListNode* next() const { return next_; } + void set_next(DeoptimizingCodeListNode* next) { next_ = next; } + Handle<Code> code() const { return code_; } + + private: + // Global (weak) handle to the deoptimizing code object. + Handle<Code> code_; + + // Next pointer for linked list. + DeoptimizingCodeListNode* next_; +}; + + +} } // namespace v8::internal + +#endif // V8_DEOPTIMIZER_H_ diff --git a/src/disassembler.cc b/src/disassembler.cc index 2a4ea74e..bb0a0722 100644 --- a/src/disassembler.cc +++ b/src/disassembler.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -30,6 +30,7 @@ #include "code-stubs.h" #include "codegen-inl.h" #include "debug.h" +#include "deoptimizer.h" #include "disasm.h" #include "disassembler.h" #include "macro-assembler.h" @@ -277,6 +278,15 @@ static int DecodeIt(FILE* f, } else { out.AddFormatted(" %s", Code::Kind2String(kind)); } + } else if (rmode == RelocInfo::RUNTIME_ENTRY) { + // A runtime entry reloinfo might be a deoptimization bailout. + Address addr = relocinfo.target_address(); + int id = Deoptimizer::GetDeoptimizationId(addr, Deoptimizer::EAGER); + if (id == Deoptimizer::kNotDeoptimizationEntry) { + out.AddFormatted(" ;; %s", RelocInfo::RelocModeName(rmode)); + } else { + out.AddFormatted(" ;; deoptimization bailout %d", id); + } } else { out.AddFormatted(" ;; %s", RelocInfo::RelocModeName(rmode)); } @@ -299,8 +309,17 @@ int Disassembler::Decode(FILE* f, byte* begin, byte* end) { // Called by Code::CodePrint. void Disassembler::Decode(FILE* f, Code* code) { - byte* begin = Code::cast(code)->instruction_start(); - byte* end = begin + Code::cast(code)->instruction_size(); + int decode_size = (code->kind() == Code::OPTIMIZED_FUNCTION) + ? static_cast<int>(code->safepoint_table_start()) + : code->instruction_size(); + // If there might be a stack check table, stop before reaching it. + if (code->kind() == Code::FUNCTION) { + decode_size = + Min(decode_size, static_cast<int>(code->stack_check_table_start())); + } + + byte* begin = code->instruction_start(); + byte* end = begin + decode_size; V8NameConverter v8NameConverter(code); DecodeIt(f, v8NameConverter, begin, end); } diff --git a/src/execution.cc b/src/execution.cc index 691d5695..11dacfee 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -33,8 +33,10 @@ #include "bootstrapper.h" #include "codegen-inl.h" #include "debug.h" +#include "runtime-profiler.h" #include "simulator.h" #include "v8threads.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -295,6 +297,25 @@ void StackGuard::TerminateExecution() { } +bool StackGuard::IsRuntimeProfilerTick() { + ExecutionAccess access; + return thread_local_.interrupt_flags_ & RUNTIME_PROFILER_TICK; +} + + +void StackGuard::RequestRuntimeProfilerTick() { + // Ignore calls if we're not optimizing or if we can't get the lock. + if (FLAG_opt && ExecutionAccess::TryLock()) { + thread_local_.interrupt_flags_ |= RUNTIME_PROFILER_TICK; + if (thread_local_.postpone_interrupts_nesting_ == 0) { + thread_local_.jslimit_ = thread_local_.climit_ = kInterruptLimit; + Heap::SetStackLimits(); + } + ExecutionAccess::Unlock(); + } +} + + #ifdef ENABLE_DEBUGGER_SUPPORT bool StackGuard::IsDebugBreak() { ExecutionAccess access; @@ -682,6 +703,12 @@ void Execution::ProcessDebugMesssages(bool debug_command_only) { #endif MaybeObject* Execution::HandleStackGuardInterrupt() { + Counters::stack_interrupts.Increment(); + if (StackGuard::IsRuntimeProfilerTick()) { + Counters::runtime_profiler_ticks.Increment(); + StackGuard::Continue(RUNTIME_PROFILER_TICK); + RuntimeProfiler::OptimizeNow(); + } #ifdef ENABLE_DEBUGGER_SUPPORT if (StackGuard::IsDebugBreak() || StackGuard::IsDebugCommand()) { DebugBreakHelper(); @@ -693,7 +720,6 @@ MaybeObject* Execution::HandleStackGuardInterrupt() { return Top::TerminateExecution(); } if (StackGuard::IsInterrupted()) { - // interrupt StackGuard::Continue(INTERRUPT); return Top::StackOverflow(); } diff --git a/src/execution.h b/src/execution.h index a2ddc41a..af8ad9af 100644 --- a/src/execution.h +++ b/src/execution.h @@ -38,7 +38,8 @@ enum InterruptFlag { DEBUGBREAK = 1 << 1, DEBUGCOMMAND = 1 << 2, PREEMPT = 1 << 3, - TERMINATE = 1 << 4 + TERMINATE = 1 << 4, + RUNTIME_PROFILER_TICK = 1 << 5 }; class Execution : public AllStatic { @@ -175,6 +176,8 @@ class StackGuard : public AllStatic { static void Interrupt(); static bool IsTerminateExecution(); static void TerminateExecution(); + static bool IsRuntimeProfilerTick(); + static void RequestRuntimeProfilerTick(); #ifdef ENABLE_DEBUGGER_SUPPORT static bool IsDebugBreak(); static void DebugBreak(); diff --git a/src/extensions/experimental/i18n-extension.cc b/src/extensions/experimental/i18n-extension.cc new file mode 100644 index 00000000..22a1c912 --- /dev/null +++ b/src/extensions/experimental/i18n-extension.cc @@ -0,0 +1,263 @@ +// Copyright 2010 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. + +#include "i18n-extension.h" + +#include <algorithm> +#include <string> + +#include "unicode/locid.h" +#include "unicode/uloc.h" + +namespace v8 { +namespace internal { + +I18NExtension* I18NExtension::extension_ = NULL; + +// TODO(cira): maybe move JS code to a .js file and generata cc files from it? +const char* const I18NExtension::kSource = + "Locale = function(optLocale) {" + " native function NativeJSLocale();" + " var properties = NativeJSLocale(optLocale);" + " this.locale = properties.locale;" + " this.language = properties.language;" + " this.script = properties.script;" + " this.region = properties.region;" + "};" + "Locale.availableLocales = function() {" + " native function NativeJSAvailableLocales();" + " return NativeJSAvailableLocales();" + "};" + "Locale.prototype.maximizedLocale = function() {" + " native function NativeJSMaximizedLocale();" + " return new Locale(NativeJSMaximizedLocale(this.locale));" + "};" + "Locale.prototype.minimizedLocale = function() {" + " native function NativeJSMinimizedLocale();" + " return new Locale(NativeJSMinimizedLocale(this.locale));" + "};" + "Locale.prototype.displayLocale_ = function(displayLocale) {" + " var result = this.locale;" + " if (displayLocale !== undefined) {" + " result = displayLocale.locale;" + " }" + " return result;" + "};" + "Locale.prototype.displayLanguage = function(optDisplayLocale) {" + " var displayLocale = this.displayLocale_(optDisplayLocale);" + " native function NativeJSDisplayLanguage();" + " return NativeJSDisplayLanguage(this.locale, displayLocale);" + "};" + "Locale.prototype.displayScript = function(optDisplayLocale) {" + " var displayLocale = this.displayLocale_(optDisplayLocale);" + " native function NativeJSDisplayScript();" + " return NativeJSDisplayScript(this.locale, displayLocale);" + "};" + "Locale.prototype.displayRegion = function(optDisplayLocale) {" + " var displayLocale = this.displayLocale_(optDisplayLocale);" + " native function NativeJSDisplayRegion();" + " return NativeJSDisplayRegion(this.locale, displayLocale);" + "};" + "Locale.prototype.displayName = function(optDisplayLocale) {" + " var displayLocale = this.displayLocale_(optDisplayLocale);" + " native function NativeJSDisplayName();" + " return NativeJSDisplayName(this.locale, displayLocale);" + "};"; + +v8::Handle<v8::FunctionTemplate> I18NExtension::GetNativeFunction( + v8::Handle<v8::String> name) { + if (name->Equals(v8::String::New("NativeJSLocale"))) { + return v8::FunctionTemplate::New(JSLocale); + } else if (name->Equals(v8::String::New("NativeJSAvailableLocales"))) { + return v8::FunctionTemplate::New(JSAvailableLocales); + } else if (name->Equals(v8::String::New("NativeJSMaximizedLocale"))) { + return v8::FunctionTemplate::New(JSMaximizedLocale); + } else if (name->Equals(v8::String::New("NativeJSMinimizedLocale"))) { + return v8::FunctionTemplate::New(JSMinimizedLocale); + } else if (name->Equals(v8::String::New("NativeJSDisplayLanguage"))) { + return v8::FunctionTemplate::New(JSDisplayLanguage); + } else if (name->Equals(v8::String::New("NativeJSDisplayScript"))) { + return v8::FunctionTemplate::New(JSDisplayScript); + } else if (name->Equals(v8::String::New("NativeJSDisplayRegion"))) { + return v8::FunctionTemplate::New(JSDisplayRegion); + } else if (name->Equals(v8::String::New("NativeJSDisplayName"))) { + return v8::FunctionTemplate::New(JSDisplayName); + } + + return v8::Handle<v8::FunctionTemplate>(); +} + +v8::Handle<v8::Value> I18NExtension::JSLocale(const v8::Arguments& args) { + // TODO(cira): Fetch browser locale. Accept en-US as good default for now. + // We could possibly pass browser locale as a parameter in the constructor. + std::string locale_name("en-US"); + if (args.Length() == 1 && args[0]->IsString()) { + locale_name = *v8::String::Utf8Value(args[0]->ToString()); + } + + v8::Local<v8::Object> locale = v8::Object::New(); + locale->Set(v8::String::New("locale"), v8::String::New(locale_name.c_str())); + + icu::Locale icu_locale(locale_name.c_str()); + + const char* language = icu_locale.getLanguage(); + locale->Set(v8::String::New("language"), v8::String::New(language)); + + const char* script = icu_locale.getScript(); + if (strlen(script)) { + locale->Set(v8::String::New("script"), v8::String::New(script)); + } + + const char* region = icu_locale.getCountry(); + if (strlen(region)) { + locale->Set(v8::String::New("region"), v8::String::New(region)); + } + + return locale; +} + +// TODO(cira): Filter out locales that Chrome doesn't support. +v8::Handle<v8::Value> I18NExtension::JSAvailableLocales( + const v8::Arguments& args) { + v8::Local<v8::Array> all_locales = v8::Array::New(); + + int count = 0; + const Locale* icu_locales = icu::Locale::getAvailableLocales(count); + for (int i = 0; i < count; ++i) { + all_locales->Set(i, v8::String::New(icu_locales[i].getName())); + } + + return all_locales; +} + +// Use - as tag separator, not _ that ICU uses. +static std::string NormalizeLocale(const std::string& locale) { + std::string result(locale); + // TODO(cira): remove STL dependency. + std::replace(result.begin(), result.end(), '_', '-'); + return result; +} + +v8::Handle<v8::Value> I18NExtension::JSMaximizedLocale( + const v8::Arguments& args) { + if (!args.Length() || !args[0]->IsString()) { + return v8::Undefined(); + } + + UErrorCode status = U_ZERO_ERROR; + std::string locale_name = *v8::String::Utf8Value(args[0]->ToString()); + char max_locale[ULOC_FULLNAME_CAPACITY]; + uloc_addLikelySubtags(locale_name.c_str(), max_locale, + sizeof(max_locale), &status); + if (U_FAILURE(status)) { + return v8::Undefined(); + } + + return v8::String::New(NormalizeLocale(max_locale).c_str()); +} + +v8::Handle<v8::Value> I18NExtension::JSMinimizedLocale( + const v8::Arguments& args) { + if (!args.Length() || !args[0]->IsString()) { + return v8::Undefined(); + } + + UErrorCode status = U_ZERO_ERROR; + std::string locale_name = *v8::String::Utf8Value(args[0]->ToString()); + char min_locale[ULOC_FULLNAME_CAPACITY]; + uloc_minimizeSubtags(locale_name.c_str(), min_locale, + sizeof(min_locale), &status); + if (U_FAILURE(status)) { + return v8::Undefined(); + } + + return v8::String::New(NormalizeLocale(min_locale).c_str()); +} + +// Common code for JSDisplayXXX methods. +static v8::Handle<v8::Value> GetDisplayItem(const v8::Arguments& args, + const std::string& item) { + if (args.Length() != 2 || !args[0]->IsString() || !args[1]->IsString()) { + return v8::Undefined(); + } + + std::string base_locale = *v8::String::Utf8Value(args[0]->ToString()); + icu::Locale icu_locale(base_locale.c_str()); + icu::Locale display_locale = + icu::Locale(*v8::String::Utf8Value(args[1]->ToString())); + UnicodeString result; + if (item == "language") { + icu_locale.getDisplayLanguage(display_locale, result); + } else if (item == "script") { + icu_locale.getDisplayScript(display_locale, result); + } else if (item == "region") { + icu_locale.getDisplayCountry(display_locale, result); + } else if (item == "name") { + icu_locale.getDisplayName(display_locale, result); + } else { + return v8::Undefined(); + } + + if (result.length()) { + return v8::String::New( + reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()); + } + + return v8::Undefined(); +} + +v8::Handle<v8::Value> I18NExtension::JSDisplayLanguage( + const v8::Arguments& args) { + return GetDisplayItem(args, "language"); +} + +v8::Handle<v8::Value> I18NExtension::JSDisplayScript( + const v8::Arguments& args) { + return GetDisplayItem(args, "script"); +} + +v8::Handle<v8::Value> I18NExtension::JSDisplayRegion( + const v8::Arguments& args) { + return GetDisplayItem(args, "region"); +} + +v8::Handle<v8::Value> I18NExtension::JSDisplayName(const v8::Arguments& args) { + return GetDisplayItem(args, "name"); +} + +I18NExtension* I18NExtension::get() { + if (!extension_) { + extension_ = new I18NExtension(); + } + return extension_; +} + +void I18NExtension::Register() { + static v8::DeclareExtension i18n_extension_declaration(I18NExtension::get()); +} + +} } // namespace v8::internal diff --git a/src/extensions/experimental/i18n-extension.h b/src/extensions/experimental/i18n-extension.h new file mode 100644 index 00000000..629332ba --- /dev/null +++ b/src/extensions/experimental/i18n-extension.h @@ -0,0 +1,64 @@ +// Copyright 2010 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. + +#ifndef V8_EXTENSIONS_EXPERIMENTAL_I18N_EXTENSION_H_ +#define V8_EXTENSIONS_EXPERIMENTAL_I18N_EXTENSION_H_ + +#include <v8.h> + +namespace v8 { +namespace internal { + + +class I18NExtension : public v8::Extension { + public: + I18NExtension() : v8::Extension("v8/i18n", kSource) {} + virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( + v8::Handle<v8::String> name); + + // Implementations of window.Locale methods. + static v8::Handle<v8::Value> JSLocale(const v8::Arguments& args); + static v8::Handle<v8::Value> JSAvailableLocales(const v8::Arguments& args); + static v8::Handle<v8::Value> JSMaximizedLocale(const v8::Arguments& args); + static v8::Handle<v8::Value> JSMinimizedLocale(const v8::Arguments& args); + static v8::Handle<v8::Value> JSDisplayLanguage(const v8::Arguments& args); + static v8::Handle<v8::Value> JSDisplayScript(const v8::Arguments& args); + static v8::Handle<v8::Value> JSDisplayRegion(const v8::Arguments& args); + static v8::Handle<v8::Value> JSDisplayName(const v8::Arguments& args); + + // V8 code prefers Register, while Chrome and WebKit use get kind of methods. + static void Register(); + static I18NExtension* get(); + + private: + static const char* const kSource; + static I18NExtension* extension_; +}; + +} } // namespace v8::internal + +#endif // V8_EXTENSIONS_EXPERIMENTAL_I18N_EXTENSION_H_ diff --git a/src/factory.cc b/src/factory.cc index a05ff6cc..83af447d 100644 --- a/src/factory.cc +++ b/src/factory.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -32,6 +32,7 @@ #include "execution.h" #include "factory.h" #include "macro-assembler.h" +#include "objects.h" #include "objects-visiting.h" namespace v8 { @@ -73,6 +74,26 @@ Handle<DescriptorArray> Factory::NewDescriptorArray(int number_of_descriptors) { } +Handle<DeoptimizationInputData> Factory::NewDeoptimizationInputData( + int deopt_entry_count, + PretenureFlag pretenure) { + ASSERT(deopt_entry_count > 0); + CALL_HEAP_FUNCTION(DeoptimizationInputData::Allocate(deopt_entry_count, + pretenure), + DeoptimizationInputData); +} + + +Handle<DeoptimizationOutputData> Factory::NewDeoptimizationOutputData( + int deopt_entry_count, + PretenureFlag pretenure) { + ASSERT(deopt_entry_count > 0); + CALL_HEAP_FUNCTION(DeoptimizationOutputData::Allocate(deopt_entry_count, + pretenure), + DeoptimizationOutputData); +} + + // Symbols are created in the old generation (data space). Handle<String> Factory::LookupSymbol(Vector<const char> string) { CALL_HEAP_FUNCTION(Heap::LookupSymbol(string), String); @@ -243,6 +264,13 @@ Handle<ExternalArray> Factory::NewExternalArray(int length, } +Handle<JSGlobalPropertyCell> Factory::NewJSGlobalPropertyCell( + Handle<Object> value) { + CALL_HEAP_FUNCTION(Heap::AllocateJSGlobalPropertyCell(*value), + JSGlobalPropertyCell); +} + + Handle<Map> Factory::NewMap(InstanceType type, int instance_size) { CALL_HEAP_FUNCTION(Heap::AllocateMap(type, instance_size), Map); } @@ -333,6 +361,15 @@ Handle<JSFunction> Factory::NewFunctionFromSharedFunctionInfo( context->global_context()); } result->set_literals(*literals); + result->set_next_function_link(Heap::undefined_value()); + + if (V8::UseCrankshaft() && + FLAG_always_opt && + result->is_compiled() && + !function_info->is_toplevel() && + function_info->allows_lazy_compilation()) { + result->MarkForLazyRecompilation(); + } return result; } diff --git a/src/factory.h b/src/factory.h index c014986f..b7a2882e 100644 --- a/src/factory.h +++ b/src/factory.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -53,6 +53,12 @@ class Factory : public AllStatic { static Handle<StringDictionary> NewStringDictionary(int at_least_space_for); static Handle<DescriptorArray> NewDescriptorArray(int number_of_descriptors); + static Handle<DeoptimizationInputData> NewDeoptimizationInputData( + int deopt_entry_count, + PretenureFlag pretenure); + static Handle<DeoptimizationOutputData> NewDeoptimizationOutputData( + int deopt_entry_count, + PretenureFlag pretenure); static Handle<String> LookupSymbol(Vector<const char> str); static Handle<String> LookupAsciiSymbol(const char* str) { @@ -169,6 +175,9 @@ class Factory : public AllStatic { void* external_pointer, PretenureFlag pretenure = NOT_TENURED); + static Handle<JSGlobalPropertyCell> NewJSGlobalPropertyCell( + Handle<Object> value); + static Handle<Map> NewMap(InstanceType type, int instance_size); static Handle<JSObject> NewFunctionPrototype(Handle<JSFunction> function); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 46feea77..f160a85a 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -1,4 +1,4 @@ -// Copyright 2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -96,9 +96,57 @@ private: // #define FLAG FLAG_FULL +// Flags for Crankshaft. +#ifdef V8_TARGET_ARCH_IA32 +DEFINE_bool(crankshaft, true, "use crankshaft") +#else +DEFINE_bool(crankshaft, false, "use crankshaft") +#endif +DEFINE_string(hydrogen_filter, "", "hydrogen use/trace filter") +DEFINE_bool(use_hydrogen, true, "use generated hydrogen for compilation") +DEFINE_bool(build_lithium, true, "use lithium chunk builder") +DEFINE_bool(alloc_lithium, true, "use lithium register allocator") +DEFINE_bool(use_lithium, true, "use lithium code generator") +DEFINE_bool(use_range, true, "use hydrogen range analysis") +DEFINE_bool(eliminate_dead_phis, true, "eliminate dead phis") +DEFINE_bool(use_gvn, true, "use hydrogen global value numbering") +DEFINE_bool(use_peeling, false, "use loop peeling") +DEFINE_bool(use_canonicalizing, true, "use hydrogen instruction canonicalizing") +DEFINE_bool(use_inlining, true, "use function inlining") +DEFINE_bool(limit_inlining, true, "limit code size growth from inlining") +DEFINE_bool(eliminate_empty_blocks, true, "eliminate empty blocks") +DEFINE_bool(loop_invariant_code_motion, true, "loop invariant code motion") +DEFINE_bool(time_hydrogen, false, "timing for hydrogen") +DEFINE_bool(trace_hydrogen, false, "trace generated hydrogen to file") +DEFINE_bool(trace_inlining, false, "trace inlining decisions") +DEFINE_bool(trace_alloc, false, "trace register allocator") +DEFINE_bool(trace_range, false, "trace range analysis") +DEFINE_bool(trace_gvn, false, "trace global value numbering") +DEFINE_bool(trace_environment, false, "trace lithium environments") +DEFINE_bool(trace_representation, false, "trace representation types") +DEFINE_bool(stress_pointer_maps, false, "pointer map for every instruction") +DEFINE_bool(stress_environments, false, "environment for every instruction") +DEFINE_int(deopt_every_n_times, + 0, + "deoptimize every n times a deopt point is passed") +DEFINE_bool(process_arguments_object, true, "try to deal with arguments object") +DEFINE_bool(trap_on_deopt, false, "put a break point before deoptimizing") +DEFINE_bool(deoptimize_uncommon_cases, true, "deoptimize uncommon cases") +DEFINE_bool(polymorphic_inlining, true, "polymorphic inlining") +DEFINE_bool(aggressive_loop_invariant_motion, true, + "aggressive motion of instructions out of loops") +#ifdef V8_TARGET_ARCH_IA32 +DEFINE_bool(use_osr, true, "use on-stack replacement") +#else +DEFINE_bool(use_osr, false, "use on-stack replacement") +#endif +DEFINE_bool(trace_osr, false, "trace on-stack replacement") +DEFINE_int(stress_runs, 0, "number of stress runs") + // assembler-ia32.cc / assembler-arm.cc / assembler-x64.cc DEFINE_bool(debug_code, false, - "generate extra code (comments, assertions) for debugging") + "generate extra code (assertions) for debugging") +DEFINE_bool(code_comments, false, "emit comments in code disassembly") DEFINE_bool(emit_branch_hints, false, "emit branch hints") DEFINE_bool(peephole_optimization, true, "perform peephole optimizations in assembly code") @@ -146,7 +194,15 @@ DEFINE_bool(mask_constants_with_cookie, // codegen.cc DEFINE_bool(lazy, true, "use lazy compilation") +DEFINE_bool(trace_opt, false, "trace lazy optimization") +DEFINE_bool(trace_opt_stats, false, "trace lazy optimization statistics") +DEFINE_bool(opt, true, "use adaptive optimizations") +DEFINE_bool(opt_eagerly, false, "be more eager when adaptively optimizing") +DEFINE_bool(always_opt, false, "always try to optimize functions") +DEFINE_bool(prepare_always_opt, false, "prepare for turning on always opt") DEFINE_bool(debug_info, true, "add debug information to compiled functions") +DEFINE_bool(deopt, true, "support deoptimization") +DEFINE_bool(trace_deopt, false, "trace deoptimization") // compiler.cc DEFINE_bool(strict, false, "strict error checking") @@ -240,6 +296,9 @@ DEFINE_int(max_map_space_pages, MapSpace::kMaxMapPageIndex - 1, DEFINE_bool(h, false, "print this message") DEFINE_bool(new_snapshot, true, "use new snapshot implementation") +// objects.cc +DEFINE_bool(use_verbose_printer, true, "allows verbose printing") + // parser.cc DEFINE_bool(allow_natives_syntax, false, "allow natives syntax") @@ -365,6 +424,9 @@ DEFINE_bool(collect_heap_spill_statistics, false, "report heap spill statistics along with heap_stats " "(requires heap_stats)") +// VM state +DEFINE_bool(log_state_changes, false, "Log state changes.") + // Regexp DEFINE_bool(regexp_possessive_quantifier, false, @@ -397,11 +459,8 @@ DEFINE_bool(log_gc, false, DEFINE_bool(log_handles, false, "Log global handle events.") DEFINE_bool(log_snapshot_positions, false, "log positions of (de)serialized objects in the snapshot.") -DEFINE_bool(log_state_changes, false, "Log state changes.") DEFINE_bool(log_suspect, false, "Log suspect operations.") DEFINE_bool(log_producers, false, "Log stack traces of JS objects allocations.") -DEFINE_bool(compress_log, false, - "Compress log to save space (makes log less human-readable).") DEFINE_bool(prof, false, "Log statistical profiling information (implies --log-code).") DEFINE_bool(prof_auto, true, @@ -446,6 +505,10 @@ DEFINE_bool(print_code_stubs, false, "print code stubs") // codegen-ia32.cc / codegen-arm.cc DEFINE_bool(print_code, false, "print generated code") +DEFINE_bool(print_opt_code, false, "print optimized code") +DEFINE_bool(print_unopt_code, false, "print unoptimized code before " + "printing optimized code based on it") +DEFINE_bool(print_code_verbose, false, "print more information for code") DEFINE_bool(print_builtin_code, false, "print generated code for builtins") // Cleanup... diff --git a/src/flags.cc b/src/flags.cc index bbe6bb72..c20f5ee0 100644 --- a/src/flags.cc +++ b/src/flags.cc @@ -279,7 +279,7 @@ static void SplitArgument(const char* arg, *value = NULL; *is_bool = false; - if (*arg == '-') { + if (arg != NULL && *arg == '-') { // find the begin of the flag name arg++; // remove 1st '-' if (*arg == '-') { diff --git a/src/frame-element.h b/src/frame-element.h index 48bb354a..3b91b9d3 100644 --- a/src/frame-element.h +++ b/src/frame-element.h @@ -262,8 +262,8 @@ class FrameElement BASE_EMBEDDED { class CopiedField: public BitField<bool, 3, 1> {}; class SyncedField: public BitField<bool, 4, 1> {}; class UntaggedInt32Field: public BitField<bool, 5, 1> {}; - class TypeInfoField: public BitField<int, 6, 6> {}; - class DataField: public BitField<uint32_t, 12, 32 - 12> {}; + class TypeInfoField: public BitField<int, 6, 7> {}; + class DataField: public BitField<uint32_t, 13, 32 - 13> {}; friend class VirtualFrame; }; diff --git a/src/frames.cc b/src/frames.cc index 3cdb0157..3af72887 100644 --- a/src/frames.cc +++ b/src/frames.cc @@ -27,8 +27,12 @@ #include "v8.h" +#include "ast.h" +#include "deoptimizer.h" #include "frames-inl.h" +#include "full-codegen.h" #include "mark-compact.h" +#include "safepoint-table.h" #include "scopeinfo.h" #include "string-stream.h" #include "top.h" @@ -324,11 +328,33 @@ void SafeStackTraceFrameIterator::Advance() { #endif +Code* StackFrame::GetSafepointData(Address pc, + uint8_t** safepoint_entry, + unsigned* stack_slots) { + PcToCodeCache::PcToCodeCacheEntry* entry = PcToCodeCache::GetCacheEntry(pc); + uint8_t* cached_safepoint_entry = entry->safepoint_entry; + if (cached_safepoint_entry == NULL) { + cached_safepoint_entry = entry->code->GetSafepointEntry(pc); + ASSERT(cached_safepoint_entry != NULL); // No safepoint found. + entry->safepoint_entry = cached_safepoint_entry; + } else { + ASSERT(cached_safepoint_entry == entry->code->GetSafepointEntry(pc)); + } + + // Fill in the results and return the code. + Code* code = entry->code; + *safepoint_entry = cached_safepoint_entry; + *stack_slots = code->stack_slots(); + return code; +} + + bool StackFrame::HasHandler() const { StackHandlerIterator it(this, top_handler()); return !it.done(); } + void StackFrame::IteratePc(ObjectVisitor* v, Address* pc_address, Code* holder) { @@ -355,7 +381,16 @@ StackFrame::Type StackFrame::ComputeType(State* state) { // really the function. const int offset = StandardFrameConstants::kMarkerOffset; Object* marker = Memory::Object_at(state->fp + offset); - if (!marker->IsSmi()) return JAVA_SCRIPT; + if (!marker->IsSmi()) { + // If we're using a "safe" stack iterator, we treat optimized + // frames as normal JavaScript frames to avoid having to look + // into the heap to determine the state. This is safe as long + // as nobody tries to GC... + if (SafeStackFrameIterator::is_active()) return JAVA_SCRIPT; + Code::Kind kind = GetContainingCode(*(state->pc_address))->kind(); + ASSERT(kind == Code::FUNCTION || kind == Code::OPTIMIZED_FUNCTION); + return (kind == Code::OPTIMIZED_FUNCTION) ? OPTIMIZED : JAVA_SCRIPT; + } return static_cast<StackFrame::Type>(Smi::cast(marker)->value()); } @@ -488,6 +523,70 @@ bool StandardFrame::IsExpressionInsideHandler(int n) const { } +void OptimizedFrame::Iterate(ObjectVisitor* v) const { +#ifdef DEBUG + // Make sure that optimized frames do not contain any stack handlers. + StackHandlerIterator it(this, top_handler()); + ASSERT(it.done()); +#endif + + // Make sure that we're not doing "safe" stack frame iteration. We cannot + // possibly find pointers in optimized frames in that state. + ASSERT(!SafeStackFrameIterator::is_active()); + + // Compute the safepoint information. + unsigned stack_slots = 0; + uint8_t* safepoint_entry = NULL; + Code* code = StackFrame::GetSafepointData( + pc(), &safepoint_entry, &stack_slots); + unsigned slot_space = stack_slots * kPointerSize; + + // Visit the outgoing parameters. This is usually dealt with by the + // callee, but while GC'ing we artificially lower the number of + // arguments to zero and let the caller deal with it. + Object** parameters_base = &Memory::Object_at(sp()); + Object** parameters_limit = &Memory::Object_at( + fp() + JavaScriptFrameConstants::kFunctionOffset - slot_space); + + // Visit the registers that contain pointers if any. + if (SafepointTable::HasRegisters(safepoint_entry)) { + for (int i = kNumSafepointRegisters - 1; i >=0; i--) { + if (SafepointTable::HasRegisterAt(safepoint_entry, i)) { + int reg_stack_index = MacroAssembler::SafepointRegisterStackIndex(i); + v->VisitPointer(parameters_base + reg_stack_index); + } + } + // Skip the words containing the register values. + parameters_base += kNumSafepointRegisters; + } + + // We're done dealing with the register bits. + safepoint_entry += kNumSafepointRegisters >> kBitsPerByteLog2; + + // Visit the rest of the parameters. + v->VisitPointers(parameters_base, parameters_limit); + + // Visit pointer spill slots and locals. + for (unsigned index = 0; index < stack_slots; index++) { + int byte_index = index >> kBitsPerByteLog2; + int bit_index = index & (kBitsPerByte - 1); + if ((safepoint_entry[byte_index] & (1U << bit_index)) != 0) { + v->VisitPointer(parameters_limit + index); + } + } + + // Visit the context and the function. + Object** fixed_base = &Memory::Object_at( + fp() + JavaScriptFrameConstants::kFunctionOffset); + Object** fixed_limit = &Memory::Object_at(fp()); + v->VisitPointers(fixed_base, fixed_limit); + + // Visit the return address in the callee and incoming arguments. + IteratePc(v, pc_address(), code); + IterateArguments(v); +} + + Object* JavaScriptFrame::GetParameter(int index) const { ASSERT(index >= 0 && index < ComputeParametersCount()); const int offset = JavaScriptFrameConstants::kParam0Offset; @@ -547,6 +646,185 @@ Address JavaScriptFrame::GetCallerStackPointer() const { } +void JavaScriptFrame::GetFunctions(List<JSFunction*>* functions) { + ASSERT(functions->length() == 0); + functions->Add(JSFunction::cast(function())); +} + + +void JavaScriptFrame::Summarize(List<FrameSummary>* functions) { + ASSERT(functions->length() == 0); + Code* code_pointer = code(); + int offset = static_cast<int>(pc() - code_pointer->address()); + FrameSummary summary(receiver(), + JSFunction::cast(function()), + code_pointer, + offset, + IsConstructor()); + functions->Add(summary); +} + + +void FrameSummary::Print() { + PrintF("receiver: "); + receiver_->ShortPrint(); + PrintF("\nfunction: "); + function_->shared()->DebugName()->ShortPrint(); + PrintF("\ncode: "); + code_->ShortPrint(); + if (code_->kind() == Code::FUNCTION) PrintF(" NON-OPT"); + if (code_->kind() == Code::OPTIMIZED_FUNCTION) PrintF(" OPT"); + PrintF("\npc: %d\n", offset_); +} + + +void OptimizedFrame::Summarize(List<FrameSummary>* frames) { + ASSERT(frames->length() == 0); + ASSERT(is_optimized()); + + int deopt_index = AstNode::kNoNumber; + DeoptimizationInputData* data = GetDeoptimizationData(&deopt_index); + + // BUG(3243555): Since we don't have a lazy-deopt registered at + // throw-statements, we can't use the translation at the call-site of + // throw. An entry with no deoptimization index indicates a call-site + // without a lazy-deopt. As a consequence we are not allowed to inline + // functions containing throw. + if (deopt_index == Safepoint::kNoDeoptimizationIndex) { + JavaScriptFrame::Summarize(frames); + return; + } + + TranslationIterator it(data->TranslationByteArray(), + data->TranslationIndex(deopt_index)->value()); + Translation::Opcode opcode = static_cast<Translation::Opcode>(it.Next()); + ASSERT(opcode == Translation::BEGIN); + int frame_count = it.Next(); + + // We create the summary in reverse order because the frames + // in the deoptimization translation are ordered bottom-to-top. + int i = frame_count; + while (i > 0) { + opcode = static_cast<Translation::Opcode>(it.Next()); + if (opcode == Translation::FRAME) { + // We don't inline constructor calls, so only the first, outermost + // frame can be a constructor frame in case of inlining. + bool is_constructor = (i == frame_count) && IsConstructor(); + + i--; + int ast_id = it.Next(); + int function_id = it.Next(); + it.Next(); // Skip height. + JSFunction* function = + JSFunction::cast(data->LiteralArray()->get(function_id)); + + // The translation commands are ordered and the receiver is always + // at the first position. Since we are always at a call when we need + // to construct a stack trace, the receiver is always in a stack slot. + opcode = static_cast<Translation::Opcode>(it.Next()); + ASSERT(opcode == Translation::STACK_SLOT); + int input_slot_index = it.Next(); + + // Get the correct receiver in the optimized frame. + Object* receiver = NULL; + // Positive index means the value is spilled to the locals area. Negative + // means it is stored in the incoming parameter area. + if (input_slot_index >= 0) { + receiver = GetExpression(input_slot_index); + } else { + // Index -1 overlaps with last parameter, -n with the first parameter, + // (-n - 1) with the receiver with n being the number of parameters + // of the outermost, optimized frame. + int parameter_count = ComputeParametersCount(); + int parameter_index = input_slot_index + parameter_count; + receiver = (parameter_index == -1) + ? this->receiver() + : this->GetParameter(parameter_index); + } + + Code* code = function->shared()->code(); + DeoptimizationOutputData* output_data = + DeoptimizationOutputData::cast(code->deoptimization_data()); + unsigned entry = Deoptimizer::GetOutputInfo(output_data, + ast_id, + function->shared()); + unsigned pc_offset = + FullCodeGenerator::PcField::decode(entry) + Code::kHeaderSize; + ASSERT(pc_offset > 0); + + FrameSummary summary(receiver, function, code, pc_offset, is_constructor); + frames->Add(summary); + } else { + // Skip over operands to advance to the next opcode. + it.Skip(Translation::NumberOfOperandsFor(opcode)); + } + } +} + + +DeoptimizationInputData* OptimizedFrame::GetDeoptimizationData( + int* deopt_index) { + ASSERT(is_optimized()); + + JSFunction* opt_function = JSFunction::cast(function()); + Code* code = opt_function->code(); + + // The code object may have been replaced by lazy deoptimization. Fall + // back to a slow search in this case to find the original optimized + // code object. + if (!code->contains(pc())) { + code = PcToCodeCache::GcSafeFindCodeForPc(pc()); + } + ASSERT(code != NULL); + ASSERT(code->kind() == Code::OPTIMIZED_FUNCTION); + + SafepointTable table(code); + unsigned pc_offset = static_cast<unsigned>(pc() - code->instruction_start()); + for (unsigned i = 0; i < table.length(); i++) { + if (table.GetPcOffset(i) == pc_offset) { + *deopt_index = table.GetDeoptimizationIndex(i); + break; + } + } + ASSERT(*deopt_index != AstNode::kNoNumber); + + return DeoptimizationInputData::cast(code->deoptimization_data()); +} + + +void OptimizedFrame::GetFunctions(List<JSFunction*>* functions) { + ASSERT(functions->length() == 0); + ASSERT(is_optimized()); + + int deopt_index = AstNode::kNoNumber; + DeoptimizationInputData* data = GetDeoptimizationData(&deopt_index); + + TranslationIterator it(data->TranslationByteArray(), + data->TranslationIndex(deopt_index)->value()); + Translation::Opcode opcode = static_cast<Translation::Opcode>(it.Next()); + ASSERT(opcode == Translation::BEGIN); + int frame_count = it.Next(); + + // We insert the frames in reverse order because the frames + // in the deoptimization translation are ordered bottom-to-top. + while (frame_count > 0) { + opcode = static_cast<Translation::Opcode>(it.Next()); + if (opcode == Translation::FRAME) { + frame_count--; + it.Next(); // Skip ast id. + int function_id = it.Next(); + it.Next(); // Skip height. + JSFunction* function = + JSFunction::cast(data->LiteralArray()->get(function_id)); + functions->Add(function); + } else { + // Skip over operands to advance to the next opcode. + it.Skip(Translation::NumberOfOperandsFor(opcode)); + } + } +} + + Address ArgumentsAdaptorFrame::GetCallerStackPointer() const { const int arguments = Smi::cast(GetExpression(0))->value(); const int offset = StandardFrameConstants::kCallerSPOffset; @@ -789,7 +1067,11 @@ void StandardFrame::IterateExpressions(ObjectVisitor* v) const { void JavaScriptFrame::Iterate(ObjectVisitor* v) const { IterateExpressions(v); IteratePc(v, pc_address(), code()); + IterateArguments(v); +} + +void JavaScriptFrame::IterateArguments(ObjectVisitor* v) const { // Traverse callee-saved registers, receiver, and parameters. const int kBaseOffset = JavaScriptFrameConstants::kSavedRegistersOffset; const int kLimitOffset = JavaScriptFrameConstants::kReceiverOffset; @@ -851,6 +1133,7 @@ Code* PcToCodeCache::GcSafeFindCodeForPc(Address pc) { } } + PcToCodeCache::PcToCodeCacheEntry* PcToCodeCache::GetCacheEntry(Address pc) { Counters::pc_to_code.Increment(); ASSERT(IsPowerOf2(kPcToCodeCacheSize)); @@ -867,6 +1150,7 @@ PcToCodeCache::PcToCodeCacheEntry* PcToCodeCache::GetCacheEntry(Address pc) { // been set. Otherwise, we risk trying to use a cache entry before // the code has been computed. entry->code = GcSafeFindCodeForPc(pc); + entry->safepoint_entry = NULL; entry->pc = pc; } return entry; diff --git a/src/frames.h b/src/frames.h index 2d4f338a..778f9d24 100644 --- a/src/frames.h +++ b/src/frames.h @@ -51,6 +51,7 @@ class PcToCodeCache : AllStatic { struct PcToCodeCacheEntry { Address pc; Code* code; + uint8_t* safepoint_entry; }; static PcToCodeCacheEntry* cache(int index) { @@ -115,6 +116,7 @@ class StackHandler BASE_EMBEDDED { V(ENTRY_CONSTRUCT, EntryConstructFrame) \ V(EXIT, ExitFrame) \ V(JAVA_SCRIPT, JavaScriptFrame) \ + V(OPTIMIZED, OptimizedFrame) \ V(INTERNAL, InternalFrame) \ V(CONSTRUCT, ConstructFrame) \ V(ARGUMENTS_ADAPTOR, ArgumentsAdaptorFrame) @@ -158,12 +160,17 @@ class StackFrame BASE_EMBEDDED { bool is_entry() const { return type() == ENTRY; } bool is_entry_construct() const { return type() == ENTRY_CONSTRUCT; } bool is_exit() const { return type() == EXIT; } - bool is_java_script() const { return type() == JAVA_SCRIPT; } + bool is_optimized() const { return type() == OPTIMIZED; } bool is_arguments_adaptor() const { return type() == ARGUMENTS_ADAPTOR; } bool is_internal() const { return type() == INTERNAL; } bool is_construct() const { return type() == CONSTRUCT; } virtual bool is_standard() const { return false; } + bool is_java_script() const { + Type type = this->type(); + return (type == JAVA_SCRIPT) || (type == OPTIMIZED); + } + // Accessors. Address sp() const { return state_.sp; } Address fp() const { return state_.fp; } @@ -193,10 +200,17 @@ class StackFrame BASE_EMBEDDED { Code* code() const { return GetContainingCode(pc()); } // Get the code object that contains the given pc. - Code* GetContainingCode(Address pc) const { + static Code* GetContainingCode(Address pc) { return PcToCodeCache::GetCacheEntry(pc)->code; } + // Get the code object containing the given pc and fill in the + // safepoint entry and the number of stack slots. The pc must be at + // a safepoint. + static Code* GetSafepointData(Address pc, + uint8_t** safepoint_entry, + unsigned* stack_slots); + virtual void Iterate(ObjectVisitor* v) const = 0; static void IteratePc(ObjectVisitor* v, Address* pc_address, Code* holder); @@ -393,6 +407,36 @@ class StandardFrame: public StackFrame { }; +class FrameSummary BASE_EMBEDDED { + public: + FrameSummary(Object* receiver, + JSFunction* function, + Code* code, + int offset, + bool is_constructor) + : receiver_(receiver), + function_(function), + code_(code), + offset_(offset), + is_constructor_(is_constructor) { } + Handle<Object> receiver() { return receiver_; } + Handle<JSFunction> function() { return function_; } + Handle<Code> code() { return code_; } + Address pc() { return reinterpret_cast<Address>(*code_) + offset_; } + int offset() { return offset_; } + bool is_constructor() { return is_constructor_; } + + void Print(); + + private: + Handle<Object> receiver_; + Handle<JSFunction> function_; + Handle<Code> code_; + int offset_; + bool is_constructor_; +}; + + class JavaScriptFrame: public StandardFrame { public: virtual Type type() const { return JAVA_SCRIPT; } @@ -431,6 +475,12 @@ class JavaScriptFrame: public StandardFrame { // Determine the code for the frame. virtual Code* unchecked_code() const; + // Return a list with JSFunctions of this frame. + virtual void GetFunctions(List<JSFunction*>* functions); + + // Build a list with summaries for this frame including all inlined frames. + virtual void Summarize(List<FrameSummary>* frames); + static JavaScriptFrame* cast(StackFrame* frame) { ASSERT(frame->is_java_script()); return static_cast<JavaScriptFrame*>(frame); @@ -442,6 +492,10 @@ class JavaScriptFrame: public StandardFrame { virtual Address GetCallerStackPointer() const; + // Garbage collection support. Iterates over incoming arguments, + // receiver, and any callee-saved registers. + void IterateArguments(ObjectVisitor* v) const; + private: inline Object* function_slot_object() const; @@ -450,6 +504,31 @@ class JavaScriptFrame: public StandardFrame { }; +class OptimizedFrame : public JavaScriptFrame { + public: + virtual Type type() const { return OPTIMIZED; } + + // GC support. + virtual void Iterate(ObjectVisitor* v) const; + + // Return a list with JSFunctions of this frame. + // The functions are ordered bottom-to-top (i.e. functions.last() + // is the top-most activation) + virtual void GetFunctions(List<JSFunction*>* functions); + + virtual void Summarize(List<FrameSummary>* frames); + + DeoptimizationInputData* GetDeoptimizationData(int* deopt_index); + + protected: + explicit OptimizedFrame(StackFrameIterator* iterator) + : JavaScriptFrame(iterator) { } + + private: + friend class StackFrameIterator; +}; + + // Arguments adaptor frames are automatically inserted below // JavaScript frames when the actual number of parameters does not // match the formal number of parameters. diff --git a/src/full-codegen.cc b/src/full-codegen.cc index 55aa2301..58540f07 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -29,12 +29,13 @@ #include "codegen-inl.h" #include "compiler.h" +#include "debug.h" #include "full-codegen.h" +#include "liveedit.h" #include "macro-assembler.h" +#include "prettyprinter.h" #include "scopes.h" #include "stub-cache.h" -#include "debug.h" -#include "liveedit.h" namespace v8 { namespace internal { @@ -166,10 +167,6 @@ void BreakableStatementChecker::VisitConditional(Conditional* expr) { } -void BreakableStatementChecker::VisitSlot(Slot* expr) { -} - - void BreakableStatementChecker::VisitVariableProxy(VariableProxy* expr) { } @@ -283,6 +280,9 @@ bool FullCodeGenerator::MakeCode(CompilationInfo* info) { int len = String::cast(script->source())->length(); Counters::total_full_codegen_source_size.Increment(len); } + if (FLAG_trace_codegen) { + PrintF("Full Compiler - "); + } CodeGenerator::MakeCodePrologue(info); const int kInitialBufferSize = 4 * KB; MacroAssembler masm(NULL, kInitialBufferSize); @@ -293,14 +293,105 @@ bool FullCodeGenerator::MakeCode(CompilationInfo* info) { ASSERT(!Top::has_pending_exception()); return false; } + unsigned table_offset = cgen.EmitStackCheckTable(); Code::Flags flags = Code::ComputeFlags(Code::FUNCTION, NOT_IN_LOOP); Handle<Code> code = CodeGenerator::MakeCodeEpilogue(&masm, flags, info); + code->set_optimizable(info->IsOptimizable()); + cgen.PopulateDeoptimizationData(code); + code->set_has_deoptimization_support(info->HasDeoptimizationSupport()); + code->set_allow_osr_at_loop_nesting_level(0); + code->set_stack_check_table_start(table_offset); + CodeGenerator::PrintCode(code, info); info->SetCode(code); // may be an empty handle. return !code.is_null(); } +unsigned FullCodeGenerator::EmitStackCheckTable() { + // The stack check table consists of a length (in number of entries) + // field, and then a sequence of entries. Each entry is a pair of AST id + // and code-relative pc offset. + masm()->Align(kIntSize); + masm()->RecordComment("[ Stack check table"); + unsigned offset = masm()->pc_offset(); + unsigned length = stack_checks_.length(); + __ dd(length); + for (unsigned i = 0; i < length; ++i) { + __ dd(stack_checks_[i].id); + __ dd(stack_checks_[i].pc_and_state); + } + masm()->RecordComment("]"); + return offset; +} + + +void FullCodeGenerator::PopulateDeoptimizationData(Handle<Code> code) { + // Fill in the deoptimization information. + ASSERT(info_->HasDeoptimizationSupport() || bailout_entries_.is_empty()); + if (!info_->HasDeoptimizationSupport()) return; + int length = bailout_entries_.length(); + Handle<DeoptimizationOutputData> data = + Factory::NewDeoptimizationOutputData(length, TENURED); + for (int i = 0; i < length; i++) { + data->SetAstId(i, Smi::FromInt(bailout_entries_[i].id)); + data->SetPcAndState(i, Smi::FromInt(bailout_entries_[i].pc_and_state)); + } + code->set_deoptimization_data(*data); +} + + +void FullCodeGenerator::PrepareForBailout(AstNode* node, State state) { + PrepareForBailoutForId(node->id(), state); +} + + +void FullCodeGenerator::RecordJSReturnSite(Call* call) { + // We record the offset of the function return so we can rebuild the frame + // if the function was inlined, i.e., this is the return address in the + // inlined function's frame. + // + // The state is ignored. We defensively set it to TOS_REG, which is the + // real state of the unoptimized code at the return site. + PrepareForBailoutForId(call->ReturnId(), TOS_REG); +#ifdef DEBUG + // In debug builds, mark the return so we can verify that this function + // was called. + ASSERT(!call->return_is_recorded_); + call->return_is_recorded_ = true; +#endif +} + + +void FullCodeGenerator::PrepareForBailoutForId(int id, State state) { + // There's no need to prepare this code for bailouts from already optimized + // code or code that can't be optimized. + if (!FLAG_deopt || !info_->HasDeoptimizationSupport()) return; + unsigned pc_and_state = + StateField::encode(state) | PcField::encode(masm_->pc_offset()); + BailoutEntry entry = { id, pc_and_state }; +#ifdef DEBUG + // Assert that we don't have multiple bailout entries for the same node. + for (int i = 0; i < bailout_entries_.length(); i++) { + if (bailout_entries_.at(i).id == entry.id) { + AstPrinter printer; + PrintF("%s", printer.PrintProgram(info_->function())); + UNREACHABLE(); + } + } +#endif // DEBUG + bailout_entries_.Add(entry); +} + + +void FullCodeGenerator::RecordStackCheck(int ast_id) { + // The pc offset does not need to be encoded and packed together with a + // state. + BailoutEntry entry = { ast_id, masm_->pc_offset() }; + stack_checks_.Add(entry); +} + + int FullCodeGenerator::SlotOffset(Slot* slot) { ASSERT(slot != NULL); // Offset is negative because higher indexes are at lower addresses. @@ -335,13 +426,11 @@ void FullCodeGenerator::EffectContext::Plug(Register reg) const { void FullCodeGenerator::AccumulatorValueContext::Plug(Register reg) const { - // Move value into place. __ Move(result_register(), reg); } void FullCodeGenerator::StackValueContext::Plug(Register reg) const { - // Move value into place. __ push(reg); } @@ -349,6 +438,7 @@ void FullCodeGenerator::StackValueContext::Plug(Register reg) const { void FullCodeGenerator::TestContext::Plug(Register reg) const { // For simplicity we always test the accumulator register. __ Move(result_register(), reg); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } @@ -370,6 +460,7 @@ void FullCodeGenerator::StackValueContext::PlugTOS() const { void FullCodeGenerator::TestContext::PlugTOS() const { // For simplicity we always test the accumulator register. __ pop(result_register()); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } @@ -558,10 +649,9 @@ void FullCodeGenerator::SetStatementPosition(int pos) { } -void FullCodeGenerator::SetSourcePosition( - int pos, PositionRecordingType recording_type) { +void FullCodeGenerator::SetSourcePosition(int pos) { if (FLAG_debug_info && pos != RelocInfo::kNoPosition) { - masm_->positions_recorder()->RecordPosition(pos, recording_type); + masm_->positions_recorder()->RecordPosition(pos); } } @@ -581,8 +671,12 @@ const FullCodeGenerator::InlineFunctionGenerator FullCodeGenerator::InlineFunctionGenerator FullCodeGenerator::FindInlineFunctionGenerator(Runtime::FunctionId id) { - return kInlineFunctionGenerators[ - static_cast<int>(id) - static_cast<int>(Runtime::kFirstInlineFunction)]; + int lookup_index = + static_cast<int>(id) - static_cast<int>(Runtime::kFirstInlineFunction); + ASSERT(lookup_index >= 0); + ASSERT(static_cast<size_t>(lookup_index) < + ARRAY_SIZE(kInlineFunctionGenerators)); + return kInlineFunctionGenerators[lookup_index]; } @@ -594,7 +688,6 @@ void FullCodeGenerator::EmitInlineRuntimeCall(CallRuntime* node) { ASSERT(function->intrinsic_type == Runtime::INLINE); InlineFunctionGenerator generator = FindInlineFunctionGenerator(function->function_id); - ASSERT(generator != NULL); ((*this).*(generator))(args); } @@ -615,7 +708,8 @@ void FullCodeGenerator::VisitBinaryOperation(BinaryOperation* expr) { switch (op) { case Token::COMMA: VisitForEffect(left); - Visit(right); + if (context()->IsTest()) ForwardBailoutToChild(expr); + context()->HandleExpression(right); break; case Token::OR: @@ -670,8 +764,10 @@ void FullCodeGenerator::EmitLogicalOperation(BinaryOperation* expr) { context()->EmitLogicalLeft(expr, &eval_right, &done); + PrepareForBailoutForId(expr->RightId(), NO_REGISTERS); __ bind(&eval_right); - Visit(expr->right()); + if (context()->IsTest()) ForwardBailoutToChild(expr); + context()->HandleExpression(expr->right()); __ bind(&done); } @@ -693,15 +789,17 @@ void FullCodeGenerator::AccumulatorValueContext::EmitLogicalLeft( BinaryOperation* expr, Label* eval_right, Label* done) const { - codegen()->Visit(expr->left()); + HandleExpression(expr->left()); // We want the value in the accumulator for the test, and on the stack in case // we need it. __ push(result_register()); Label discard, restore; if (expr->op() == Token::OR) { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(&restore, &discard, &restore); } else { ASSERT(expr->op() == Token::AND); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(&discard, &restore, &restore); } __ bind(&restore); @@ -722,9 +820,11 @@ void FullCodeGenerator::StackValueContext::EmitLogicalLeft( __ push(result_register()); Label discard; if (expr->op() == Token::OR) { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(done, &discard, &discard); } else { ASSERT(expr->op() == Token::AND); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(&discard, done, &discard); } __ bind(&discard); @@ -746,12 +846,66 @@ void FullCodeGenerator::TestContext::EmitLogicalLeft(BinaryOperation* expr, } +void FullCodeGenerator::ForwardBailoutToChild(Expression* expr) { + if (!info_->HasDeoptimizationSupport()) return; + ASSERT(context()->IsTest()); + ASSERT(expr == forward_bailout_stack_->expr()); + forward_bailout_pending_ = forward_bailout_stack_; +} + + +void FullCodeGenerator::EffectContext::HandleExpression( + Expression* expr) const { + codegen()->HandleInNonTestContext(expr, NO_REGISTERS); +} + + +void FullCodeGenerator::AccumulatorValueContext::HandleExpression( + Expression* expr) const { + codegen()->HandleInNonTestContext(expr, TOS_REG); +} + + +void FullCodeGenerator::StackValueContext::HandleExpression( + Expression* expr) const { + codegen()->HandleInNonTestContext(expr, NO_REGISTERS); +} + + +void FullCodeGenerator::TestContext::HandleExpression(Expression* expr) const { + codegen()->VisitInTestContext(expr); +} + + +void FullCodeGenerator::HandleInNonTestContext(Expression* expr, State state) { + ASSERT(forward_bailout_pending_ == NULL); + AstVisitor::Visit(expr); + PrepareForBailout(expr, state); + // Forwarding bailouts to children is a one shot operation. It + // should have been processed at this point. + ASSERT(forward_bailout_pending_ == NULL); +} + + +void FullCodeGenerator::VisitInTestContext(Expression* expr) { + ForwardBailoutStack stack(expr, forward_bailout_pending_); + ForwardBailoutStack* saved = forward_bailout_stack_; + forward_bailout_pending_ = NULL; + forward_bailout_stack_ = &stack; + AstVisitor::Visit(expr); + forward_bailout_stack_ = saved; +} + + void FullCodeGenerator::VisitBlock(Block* stmt) { Comment cmnt(masm_, "[ Block"); Breakable nested_statement(this, stmt); SetStatementPosition(stmt); + + PrepareForBailoutForId(stmt->EntryId(), TOS_REG); VisitStatements(stmt->statements()); __ bind(nested_statement.break_target()); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); } @@ -775,18 +929,24 @@ void FullCodeGenerator::VisitIfStatement(IfStatement* stmt) { if (stmt->HasElseStatement()) { VisitForControl(stmt->condition(), &then_part, &else_part, &then_part); + PrepareForBailoutForId(stmt->ThenId(), NO_REGISTERS); __ bind(&then_part); Visit(stmt->then_statement()); __ jmp(&done); + PrepareForBailoutForId(stmt->ElseId(), NO_REGISTERS); __ bind(&else_part); Visit(stmt->else_statement()); } else { VisitForControl(stmt->condition(), &then_part, &done, &then_part); + PrepareForBailoutForId(stmt->ThenId(), NO_REGISTERS); __ bind(&then_part); Visit(stmt->then_statement()); + + PrepareForBailoutForId(stmt->ElseId(), NO_REGISTERS); } __ bind(&done); + PrepareForBailoutForId(stmt->id(), NO_REGISTERS); } @@ -883,7 +1043,7 @@ void FullCodeGenerator::VisitWithExitStatement(WithExitStatement* stmt) { void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) { Comment cmnt(masm_, "[ DoWhileStatement"); SetStatementPosition(stmt); - Label body, stack_limit_hit, stack_check_success, done; + Label body, stack_check; Iteration loop_statement(this, stmt); increment_loop_depth(); @@ -891,75 +1051,65 @@ void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) { __ bind(&body); Visit(stmt->body()); - // Check stack before looping. - __ bind(loop_statement.continue_target()); - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_success); - // Record the position of the do while condition and make sure it is // possible to break on the condition. + __ bind(loop_statement.continue_target()); + PrepareForBailoutForId(stmt->ContinueId(), NO_REGISTERS); SetExpressionPosition(stmt->cond(), stmt->condition_position()); VisitForControl(stmt->cond(), - &body, + &stack_check, loop_statement.break_target(), - loop_statement.break_target()); + &stack_check); - __ bind(loop_statement.break_target()); - __ jmp(&done); - - __ bind(&stack_limit_hit); - StackCheckStub stack_stub; - __ CallStub(&stack_stub); - __ jmp(&stack_check_success); + // Check stack before looping. + PrepareForBailoutForId(stmt->BackEdgeId(), NO_REGISTERS); + __ bind(&stack_check); + EmitStackCheck(stmt); + __ jmp(&body); - __ bind(&done); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ bind(loop_statement.break_target()); decrement_loop_depth(); } void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) { Comment cmnt(masm_, "[ WhileStatement"); - Label body, stack_limit_hit, stack_check_success, done; + Label test, body; Iteration loop_statement(this, stmt); increment_loop_depth(); // Emit the test at the bottom of the loop. - __ jmp(loop_statement.continue_target()); + __ jmp(&test); + PrepareForBailoutForId(stmt->BodyId(), NO_REGISTERS); __ bind(&body); Visit(stmt->body()); - __ bind(loop_statement.continue_target()); // Emit the statement position here as this is where the while // statement code starts. + __ bind(loop_statement.continue_target()); SetStatementPosition(stmt); // Check stack before looping. - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_success); + EmitStackCheck(stmt); + __ bind(&test); VisitForControl(stmt->cond(), &body, loop_statement.break_target(), loop_statement.break_target()); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); __ bind(loop_statement.break_target()); - __ jmp(&done); - - __ bind(&stack_limit_hit); - StackCheckStub stack_stub; - __ CallStub(&stack_stub); - __ jmp(&stack_check_success); - - __ bind(&done); decrement_loop_depth(); } void FullCodeGenerator::VisitForStatement(ForStatement* stmt) { Comment cmnt(masm_, "[ ForStatement"); - Label test, body, stack_limit_hit, stack_check_success; + Label test, body; Iteration loop_statement(this, stmt); if (stmt->init() != NULL) { @@ -970,30 +1120,25 @@ void FullCodeGenerator::VisitForStatement(ForStatement* stmt) { // Emit the test at the bottom of the loop (even if empty). __ jmp(&test); - __ bind(&stack_limit_hit); - StackCheckStub stack_stub; - __ CallStub(&stack_stub); - __ jmp(&stack_check_success); - + PrepareForBailoutForId(stmt->BodyId(), NO_REGISTERS); __ bind(&body); Visit(stmt->body()); + PrepareForBailoutForId(stmt->ContinueId(), NO_REGISTERS); __ bind(loop_statement.continue_target()); - SetStatementPosition(stmt); if (stmt->next() != NULL) { Visit(stmt->next()); } - __ bind(&test); // Emit the statement position here as this is where the for // statement code starts. SetStatementPosition(stmt); // Check stack before looping. - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_success); + EmitStackCheck(stmt); + __ bind(&test); if (stmt->cond() != NULL) { VisitForControl(stmt->cond(), &body, @@ -1003,6 +1148,7 @@ void FullCodeGenerator::VisitForStatement(ForStatement* stmt) { __ jmp(&body); } + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); __ bind(loop_statement.break_target()); decrement_loop_depth(); } @@ -1134,6 +1280,7 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) { Label true_case, false_case, done; VisitForControl(expr->condition(), &true_case, &false_case, &true_case); + PrepareForBailoutForId(expr->ThenId(), NO_REGISTERS); __ bind(&true_case); SetExpressionPosition(expr->then_expression(), expr->then_expression_position()); @@ -1144,14 +1291,16 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) { for_test->false_label(), NULL); } else { - Visit(expr->then_expression()); + context()->HandleExpression(expr->then_expression()); __ jmp(&done); } + PrepareForBailoutForId(expr->ElseId(), NO_REGISTERS); __ bind(&false_case); + if (context()->IsTest()) ForwardBailoutToChild(expr); SetExpressionPosition(expr->else_expression(), expr->else_expression_position()); - Visit(expr->else_expression()); + context()->HandleExpression(expr->else_expression()); // If control flow falls through Visit, merge it with true case here. if (!context()->IsTest()) { __ bind(&done); @@ -1159,12 +1308,6 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) { } -void FullCodeGenerator::VisitSlot(Slot* expr) { - // Slots do not appear directly in the AST. - UNREACHABLE(); -} - - void FullCodeGenerator::VisitLiteral(Literal* expr) { Comment cmnt(masm_, "[ Literal"); context()->Plug(expr->handle()); diff --git a/src/full-codegen.h b/src/full-codegen.h index 02335a92..0482ee8d 100644 --- a/src/full-codegen.h +++ b/src/full-codegen.h @@ -31,11 +31,16 @@ #include "v8.h" #include "ast.h" +#include "code-stubs.h" +#include "codegen.h" #include "compiler.h" namespace v8 { namespace internal { +// Forward declarations. +class JumpPatchSite; + // AST node visitor which can tell whether a given statement will be breakable // when the code is compiled by the full compiler in the debugger. This means // that there will be an IC (load/store/call) in the code generated for the @@ -66,17 +71,39 @@ class BreakableStatementChecker: public AstVisitor { class FullCodeGenerator: public AstVisitor { public: + enum State { + NO_REGISTERS, + TOS_REG + }; + explicit FullCodeGenerator(MacroAssembler* masm) : masm_(masm), info_(NULL), nesting_stack_(NULL), loop_depth_(0), - context_(NULL) { + context_(NULL), + bailout_entries_(0), + stack_checks_(2), // There's always at least one. + forward_bailout_stack_(NULL), + forward_bailout_pending_(NULL) { } static bool MakeCode(CompilationInfo* info); void Generate(CompilationInfo* info); + void PopulateDeoptimizationData(Handle<Code> code); + + class StateField : public BitField<State, 0, 8> { }; + class PcField : public BitField<unsigned, 8, 32-8> { }; + + static const char* State2String(State state) { + switch (state) { + case NO_REGISTERS: return "NO_REGISTERS"; + case TOS_REG: return "TOS_REG"; + } + UNREACHABLE(); + return NULL; + } private: class Breakable; @@ -229,6 +256,24 @@ class FullCodeGenerator: public AstVisitor { DISALLOW_COPY_AND_ASSIGN(ForIn); }; + // The forward bailout stack keeps track of the expressions that can + // bail out to just before the control flow is split in a child + // node. The stack elements are linked together through the parent + // link when visiting expressions in test contexts after requesting + // bailout in child forwarding. + class ForwardBailoutStack BASE_EMBEDDED { + public: + ForwardBailoutStack(Expression* expr, ForwardBailoutStack* parent) + : expr_(expr), parent_(parent) { } + + Expression* expr() const { return expr_; } + ForwardBailoutStack* parent() const { return parent_; } + + private: + Expression* const expr_; + ForwardBailoutStack* const parent_; + }; + enum ConstantOperand { kNoConstants, kLeftConstant, @@ -278,19 +323,23 @@ class FullCodeGenerator: public AstVisitor { // register. MemOperand EmitSlotSearch(Slot* slot, Register scratch); + // Forward the bailout responsibility for the given expression to + // the next child visited (which must be in a test context). + void ForwardBailoutToChild(Expression* expr); + void VisitForEffect(Expression* expr) { EffectContext context(this); - Visit(expr); + HandleInNonTestContext(expr, NO_REGISTERS); } void VisitForAccumulatorValue(Expression* expr) { AccumulatorValueContext context(this); - Visit(expr); + HandleInNonTestContext(expr, TOS_REG); } void VisitForStackValue(Expression* expr) { StackValueContext context(this); - Visit(expr); + HandleInNonTestContext(expr, NO_REGISTERS); } void VisitForControl(Expression* expr, @@ -298,9 +347,15 @@ class FullCodeGenerator: public AstVisitor { Label* if_false, Label* fall_through) { TestContext context(this, if_true, if_false, fall_through); - Visit(expr); + VisitInTestContext(expr); + // Forwarding bailouts to children is a one shot operation. It + // should have been processed at this point. + ASSERT(forward_bailout_pending_ == NULL); } + void HandleInNonTestContext(Expression* expr, State state); + void VisitInTestContext(Expression* expr); + void VisitDeclarations(ZoneList<Declaration*>* declarations); void DeclareGlobals(Handle<FixedArray> pairs); @@ -314,12 +369,39 @@ class FullCodeGenerator: public AstVisitor { Label* if_false, Label* fall_through); + // Bailout support. + void PrepareForBailout(AstNode* node, State state); + void PrepareForBailoutForId(int id, State state); + + // Record a call's return site offset, used to rebuild the frame if the + // called function was inlined at the site. + void RecordJSReturnSite(Call* call); + + // Prepare for bailout before a test (or compare) and branch. If + // should_normalize, then the following comparison will not handle the + // canonical JS true value so we will insert a (dead) test against true at + // the actual bailout target from the optimized code. If not + // should_normalize, the true and false labels are ignored. + void PrepareForBailoutBeforeSplit(State state, + bool should_normalize, + Label* if_true, + Label* if_false); + // Platform-specific code for a variable, constant, or function // declaration. Functions have an initial value. void EmitDeclaration(Variable* variable, Variable::Mode mode, FunctionLiteral* function); + // Platform-specific code for checking the stack limit at the back edge of + // a loop. + void EmitStackCheck(IterationStatement* stmt); + // Record the OSR AST id corresponding to a stack check in the code. + void RecordStackCheck(int osr_ast_id); + // Emit a table of stack check ids and pcs into the code stream. Return + // the offset of the start of the table. + unsigned EmitStackCheckTable(); + // Platform-specific return sequence void EmitReturnSequence(); @@ -406,7 +488,7 @@ class FullCodeGenerator: public AstVisitor { // Assign to the given expression as if via '='. The right-hand-side value // is expected in the accumulator. - void EmitAssignment(Expression* expr); + void EmitAssignment(Expression* expr, int bailout_ast_id); // Complete a variable assignment. The right-hand-side value is expected // in the accumulator. @@ -427,9 +509,7 @@ class FullCodeGenerator: public AstVisitor { void SetStatementPosition(Statement* stmt); void SetExpressionPosition(Expression* expr, int pos); void SetStatementPosition(int pos); - void SetSourcePosition( - int pos, - PositionRecordingType recording_type = NORMAL_POSITION); + void SetSourcePosition(int pos); // Non-local control flow support. void EnterFinallyBlock(); @@ -460,6 +540,10 @@ class FullCodeGenerator: public AstVisitor { // Helper for calling an IC stub. void EmitCallIC(Handle<Code> ic, RelocInfo::Mode mode); + // Calling an IC stub with a patch site. Passing NULL for patch_site + // indicates no inlined smi code and emits a nop after the IC call. + void EmitCallIC(Handle<Code> ic, JumpPatchSite* patch_site); + // Set fields in the stack frame. Offsets are the frame pointer relative // offsets defined in, e.g., StandardFrameConstants. void StoreToFrameField(int frame_offset, Register value); @@ -477,14 +561,13 @@ class FullCodeGenerator: public AstVisitor { void VisitForTypeofValue(Expression* expr); - MacroAssembler* masm_; - CompilationInfo* info_; + struct BailoutEntry { + unsigned id; + unsigned pc_and_state; + }; - Label return_label_; - NestedStatement* nesting_stack_; - int loop_depth_; - class ExpressionContext { + class ExpressionContext BASE_EMBEDDED { public: explicit ExpressionContext(FullCodeGenerator* codegen) : masm_(codegen->masm()), old_(codegen->context()), codegen_(codegen) { @@ -510,7 +593,8 @@ class FullCodeGenerator: public AstVisitor { // Emit code to convert pure control flow to a pair of unbound labels into // the result expected according to this expression context. The - // implementation may decide to bind either of the labels. + // implementation will bind both labels unless it's a TestContext, which + // won't bind them at this point. virtual void Plug(Label* materialize_true, Label* materialize_false) const = 0; @@ -532,12 +616,14 @@ class FullCodeGenerator: public AstVisitor { Label** if_false, Label** fall_through) const = 0; + virtual void HandleExpression(Expression* expr) const = 0; + // Returns true if we are evaluating only for side effects (ie if the result - // will be discarded. + // will be discarded). virtual bool IsEffect() const { return false; } // Returns true if we are branching on the value rather than materializing - // it. + // it. Only used for asserts. virtual bool IsTest() const { return false; } protected: @@ -571,6 +657,7 @@ class FullCodeGenerator: public AstVisitor { Label** if_true, Label** if_false, Label** fall_through) const; + virtual void HandleExpression(Expression* expr) const; }; class StackValueContext : public ExpressionContext { @@ -594,6 +681,7 @@ class FullCodeGenerator: public AstVisitor { Label** if_true, Label** if_false, Label** fall_through) const; + virtual void HandleExpression(Expression* expr) const; }; class TestContext : public ExpressionContext { @@ -632,6 +720,7 @@ class FullCodeGenerator: public AstVisitor { Label** if_true, Label** if_false, Label** fall_through) const; + virtual void HandleExpression(Expression* expr) const; virtual bool IsTest() const { return true; } private: @@ -661,10 +750,20 @@ class FullCodeGenerator: public AstVisitor { Label** if_true, Label** if_false, Label** fall_through) const; + virtual void HandleExpression(Expression* expr) const; virtual bool IsEffect() const { return true; } }; + MacroAssembler* masm_; + CompilationInfo* info_; + Label return_label_; + NestedStatement* nesting_stack_; + int loop_depth_; const ExpressionContext* context_; + ZoneList<BailoutEntry> bailout_entries_; + ZoneList<BailoutEntry> stack_checks_; + ForwardBailoutStack* forward_bailout_stack_; + ForwardBailoutStack* forward_bailout_pending_; friend class NestedStatement; diff --git a/src/global-handles.cc b/src/global-handles.cc index 53398409..18cdc5a3 100644 --- a/src/global-handles.cc +++ b/src/global-handles.cc @@ -30,6 +30,8 @@ #include "api.h" #include "global-handles.h" +#include "vm-state-inl.h" + namespace v8 { namespace internal { diff --git a/src/globals.h b/src/globals.h index 88c3e780..35156ae6 100644 --- a/src/globals.h +++ b/src/globals.h @@ -28,6 +28,8 @@ #ifndef V8_GLOBALS_H_ #define V8_GLOBALS_H_ +#include "../include/v8stdint.h" + namespace v8 { namespace internal { @@ -147,13 +149,16 @@ typedef byte* Address; #ifdef _MSC_VER #define V8_UINT64_C(x) (x ## UI64) #define V8_INT64_C(x) (x ## I64) +#define V8_INTPTR_C(x) (x ## I64) #define V8_PTR_PREFIX "ll" #else // _MSC_VER #define V8_UINT64_C(x) (x ## UL) #define V8_INT64_C(x) (x ## L) +#define V8_INTPTR_C(x) (x ## L) #define V8_PTR_PREFIX "l" #endif // _MSC_VER #else // V8_HOST_ARCH_64_BIT +#define V8_INTPTR_C(x) (x) #define V8_PTR_PREFIX "" #endif // V8_HOST_ARCH_64_BIT @@ -223,6 +228,7 @@ const int kBinary32MinExponent = 0x01; const int kBinary32MantissaBits = 23; const int kBinary32ExponentShift = 23; + // The expression OFFSET_OF(type, field) computes the byte-offset // of the specified field relative to the containing type. This // corresponds to 'offsetof' (in stddef.h), except that it doesn't diff --git a/src/handles.cc b/src/handles.cc index 37a5011c..68c61b5c 100644 --- a/src/handles.cc +++ b/src/handles.cc @@ -39,6 +39,7 @@ #include "runtime.h" #include "string-search.h" #include "stub-cache.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -224,13 +225,7 @@ void FlattenString(Handle<String> string) { Handle<String> FlattenGetString(Handle<String> string) { - Handle<String> result; - CALL_AND_RETRY(string->TryFlatten(), - { result = Handle<String>(String::cast(__object__)); - break; }, - return Handle<String>()); - ASSERT(string->IsFlat()); - return result; + CALL_HEAP_FUNCTION(string->TryFlatten(), String); } @@ -803,7 +798,7 @@ bool EnsureCompiled(Handle<SharedFunctionInfo> shared, static bool CompileLazyHelper(CompilationInfo* info, ClearExceptionFlag flag) { // Compile the source information to a code object. - ASSERT(!info->shared_info()->is_compiled()); + ASSERT(info->IsOptimizing() || !info->shared_info()->is_compiled()); bool result = Compiler::CompileLazy(info); ASSERT(result != Top::has_pending_exception()); if (!result && flag == CLEAR_EXCEPTION) Top::clear_pending_exception(); @@ -820,36 +815,47 @@ bool CompileLazyShared(Handle<SharedFunctionInfo> shared, bool CompileLazy(Handle<JSFunction> function, ClearExceptionFlag flag) { + bool result = true; if (function->shared()->is_compiled()) { - function->set_code(function->shared()->code()); - PROFILE(FunctionCreateEvent(*function)); + function->ReplaceCode(function->shared()->code()); function->shared()->set_code_age(0); - return true; } else { CompilationInfo info(function); - bool result = CompileLazyHelper(&info, flag); + result = CompileLazyHelper(&info, flag); ASSERT(!result || function->is_compiled()); + } + if (result && function->is_compiled()) { PROFILE(FunctionCreateEvent(*function)); - return result; } + return result; } bool CompileLazyInLoop(Handle<JSFunction> function, ClearExceptionFlag flag) { + bool result = true; if (function->shared()->is_compiled()) { - function->set_code(function->shared()->code()); - PROFILE(FunctionCreateEvent(*function)); + function->ReplaceCode(function->shared()->code()); function->shared()->set_code_age(0); - return true; } else { CompilationInfo info(function); info.MarkAsInLoop(); - bool result = CompileLazyHelper(&info, flag); + result = CompileLazyHelper(&info, flag); ASSERT(!result || function->is_compiled()); + } + if (result && function->is_compiled()) { PROFILE(FunctionCreateEvent(*function)); - return result; } + return result; +} + + +bool CompileOptimized(Handle<JSFunction> function, int osr_ast_id) { + CompilationInfo info(function); + info.SetOptimizing(osr_ast_id); + bool result = CompileLazyHelper(&info, KEEP_EXCEPTION); + if (result) PROFILE(FunctionCreateEvent(*function)); + return result; } diff --git a/src/handles.h b/src/handles.h index 2e18ab34..8fd25dc9 100644 --- a/src/handles.h +++ b/src/handles.h @@ -342,6 +342,8 @@ bool CompileLazy(Handle<JSFunction> function, ClearExceptionFlag flag); bool CompileLazyInLoop(Handle<JSFunction> function, ClearExceptionFlag flag); +bool CompileOptimized(Handle<JSFunction> function, int osr_ast_id); + class NoHandleAllocation BASE_EMBEDDED { public: #ifndef DEBUG diff --git a/src/heap-inl.h b/src/heap-inl.h index ba50c0f7..ef839988 100644 --- a/src/heap-inl.h +++ b/src/heap-inl.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2006-2010 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: @@ -409,8 +409,8 @@ void Heap::SetLastScriptId(Object* last_script_id) { v8::internal::V8::FatalProcessOutOfMemory("CALL_AND_RETRY_0", true);\ } \ if (!__maybe_object__->IsRetryAfterGC()) RETURN_EMPTY; \ - Heap::CollectGarbage(Failure::cast(__maybe_object__)-> \ - allocation_space()); \ + Heap::CollectGarbage( \ + Failure::cast(__maybe_object__)->allocation_space()); \ __maybe_object__ = FUNCTION_CALL; \ if (__maybe_object__->ToObject(&__object__)) RETURN_VALUE; \ if (__maybe_object__->IsOutOfMemory()) { \ diff --git a/src/heap-profiler.cc b/src/heap-profiler.cc index 91ac9867..dfda7c6f 100644 --- a/src/heap-profiler.cc +++ b/src/heap-profiler.cc @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2009-2010 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: @@ -348,30 +348,37 @@ void HeapProfiler::TearDown() { #ifdef ENABLE_LOGGING_AND_PROFILING -HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name, int type) { +HeapSnapshot* HeapProfiler::TakeSnapshot(const char* name, + int type, + v8::ActivityControl* control) { ASSERT(singleton_ != NULL); - return singleton_->TakeSnapshotImpl(name, type); + return singleton_->TakeSnapshotImpl(name, type, control); } -HeapSnapshot* HeapProfiler::TakeSnapshot(String* name, int type) { +HeapSnapshot* HeapProfiler::TakeSnapshot(String* name, + int type, + v8::ActivityControl* control) { ASSERT(singleton_ != NULL); - return singleton_->TakeSnapshotImpl(name, type); + return singleton_->TakeSnapshotImpl(name, type, control); } -HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name, int type) { - Heap::CollectAllGarbage(true); +HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name, + int type, + v8::ActivityControl* control) { HeapSnapshot::Type s_type = static_cast<HeapSnapshot::Type>(type); HeapSnapshot* result = snapshots_->NewSnapshot(s_type, name, next_snapshot_uid_++); + bool generation_completed = true; switch (s_type) { case HeapSnapshot::kFull: { - HeapSnapshotGenerator generator(result); - generator.GenerateSnapshot(); + HeapSnapshotGenerator generator(result, control); + generation_completed = generator.GenerateSnapshot(); break; } case HeapSnapshot::kAggregated: { + Heap::CollectAllGarbage(true); AggregatedHeapSnapshot agg_snapshot; AggregatedHeapSnapshotGenerator generator(&agg_snapshot); generator.GenerateSnapshot(); @@ -381,13 +388,19 @@ HeapSnapshot* HeapProfiler::TakeSnapshotImpl(const char* name, int type) { default: UNREACHABLE(); } - snapshots_->SnapshotGenerationFinished(); + if (!generation_completed) { + delete result; + result = NULL; + } + snapshots_->SnapshotGenerationFinished(result); return result; } -HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name, int type) { - return TakeSnapshotImpl(snapshots_->GetName(name), type); +HeapSnapshot* HeapProfiler::TakeSnapshotImpl(String* name, + int type, + v8::ActivityControl* control) { + return TakeSnapshotImpl(snapshots_->GetName(name), type, control); } @@ -795,7 +808,7 @@ void AggregatedHeapSnapshotGenerator::CollectStats(HeapObject* obj) { void AggregatedHeapSnapshotGenerator::GenerateSnapshot() { - HeapIterator iterator(HeapIterator::kPreciseFiltering); + HeapIterator iterator(HeapIterator::kFilterFreeListNodes); for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { CollectStats(obj); agg_snapshot_->js_cons_profile()->CollectStats(obj); diff --git a/src/heap-profiler.h b/src/heap-profiler.h index 2ef081ee..90c664ed 100644 --- a/src/heap-profiler.h +++ b/src/heap-profiler.h @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2009-2010 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: @@ -56,8 +56,12 @@ class HeapProfiler { static void TearDown(); #ifdef ENABLE_LOGGING_AND_PROFILING - static HeapSnapshot* TakeSnapshot(const char* name, int type); - static HeapSnapshot* TakeSnapshot(String* name, int type); + static HeapSnapshot* TakeSnapshot(const char* name, + int type, + v8::ActivityControl* control); + static HeapSnapshot* TakeSnapshot(String* name, + int type, + v8::ActivityControl* control); static int GetSnapshotsCount(); static HeapSnapshot* GetSnapshot(int index); static HeapSnapshot* FindSnapshot(unsigned uid); @@ -75,8 +79,12 @@ class HeapProfiler { private: HeapProfiler(); ~HeapProfiler(); - HeapSnapshot* TakeSnapshotImpl(const char* name, int type); - HeapSnapshot* TakeSnapshotImpl(String* name, int type); + HeapSnapshot* TakeSnapshotImpl(const char* name, + int type, + v8::ActivityControl* control); + HeapSnapshot* TakeSnapshotImpl(String* name, + int type, + v8::ActivityControl* control); HeapSnapshotsCollection* snapshots_; unsigned next_snapshot_uid_; diff --git a/src/heap.cc b/src/heap.cc index 16415ad3..1e999916 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -38,10 +38,12 @@ #include "mark-compact.h" #include "natives.h" #include "objects-visiting.h" +#include "runtime-profiler.h" #include "scanner-base.h" #include "scopeinfo.h" #include "snapshot.h" #include "v8threads.h" +#include "vm-state-inl.h" #if V8_TARGET_ARCH_ARM && !V8_INTERPRETED_REGEXP #include "regexp-macro-assembler.h" #include "arm/regexp-macro-assembler-arm.h" @@ -839,6 +841,8 @@ void Heap::MarkCompactPrologue(bool is_compacting) { ContextSlotCache::Clear(); DescriptorLookupCache::Clear(); + RuntimeProfiler::MarkCompactPrologue(is_compacting); + CompilationCache::MarkCompactPrologue(); CompletelyClearInstanceofCache(); @@ -1049,6 +1053,14 @@ void Heap::Scavenge() { // Scavenge object reachable from the global contexts list directly. scavenge_visitor.VisitPointer(BitCast<Object**>(&global_contexts_list_)); + // Scavenge objects reachable from the runtime-profiler sampler + // window directly. + Object** sampler_window_address = RuntimeProfiler::SamplerWindowAddress(); + int sampler_window_size = RuntimeProfiler::SamplerWindowSize(); + scavenge_visitor.VisitPointers( + sampler_window_address, + sampler_window_address + sampler_window_size); + new_space_front = DoScavenge(&scavenge_visitor, new_space_front); UpdateNewSpaceReferencesInExternalStringTable( @@ -1116,6 +1128,40 @@ void Heap::UpdateNewSpaceReferencesInExternalStringTable( } +static Object* ProcessFunctionWeakReferences(Object* function, + WeakObjectRetainer* retainer) { + Object* head = Heap::undefined_value(); + JSFunction* tail = NULL; + Object* candidate = function; + while (!candidate->IsUndefined()) { + // Check whether to keep the candidate in the list. + JSFunction* candidate_function = reinterpret_cast<JSFunction*>(candidate); + Object* retain = retainer->RetainAs(candidate); + if (retain != NULL) { + if (head->IsUndefined()) { + // First element in the list. + head = candidate_function; + } else { + // Subsequent elements in the list. + ASSERT(tail != NULL); + tail->set_next_function_link(candidate_function); + } + // Retained function is new tail. + tail = candidate_function; + } + // Move to next element in the list. + candidate = candidate_function->next_function_link(); + } + + // Terminate the list if there is one or more elements. + if (tail != NULL) { + tail->set_next_function_link(Heap::undefined_value()); + } + + return head; +} + + void Heap::ProcessWeakReferences(WeakObjectRetainer* retainer) { Object* head = undefined_value(); Context* tail = NULL; @@ -1137,6 +1183,15 @@ void Heap::ProcessWeakReferences(WeakObjectRetainer* retainer) { } // Retained context is new tail. tail = candidate_context; + + // Process the weak list of optimized functions for the context. + Object* function_list_head = + ProcessFunctionWeakReferences( + candidate_context->get(Context::OPTIMIZED_FUNCTIONS_LIST), + retainer); + candidate_context->set_unchecked(Context::OPTIMIZED_FUNCTIONS_LIST, + function_list_head, + UPDATE_WRITE_BARRIER); } // Move to next element in the list. candidate = candidate_context->get(Context::NEXT_CONTEXT_LINK); @@ -1651,6 +1706,11 @@ bool Heap::CreateInitialMaps() { } set_byte_array_map(Map::cast(obj)); + { MaybeObject* maybe_obj = AllocateByteArray(0, TENURED); + if (!maybe_obj->ToObject(&obj)) return false; + } + set_empty_byte_array(ByteArray::cast(obj)); + { MaybeObject* maybe_obj = AllocateMap(PIXEL_ARRAY_TYPE, PixelArray::kAlignedSize); if (!maybe_obj->ToObject(&obj)) return false; @@ -2245,9 +2305,11 @@ MaybeObject* Heap::AllocateSharedFunctionInfo(Object* name) { share->set_debug_info(undefined_value()); share->set_inferred_name(empty_string()); share->set_compiler_hints(0); + share->set_deopt_counter(Smi::FromInt(FLAG_deopt_every_n_times)); share->set_initial_map(undefined_value()); share->set_this_property_assignments_count(0); share->set_this_property_assignments(undefined_value()); + share->set_opt_count(0); share->set_num_literals(0); share->set_end_position(0); share->set_function_token_position(0); @@ -2666,6 +2728,7 @@ MaybeObject* Heap::CreateCode(const CodeDesc& desc, code->set_instruction_size(desc.instr_size); code->set_relocation_info(ByteArray::cast(reloc_info)); code->set_flags(flags); + code->set_deoptimization_data(empty_fixed_array()); // Allow self references to created code object by patching the handle to // point to the newly allocated Code object. if (!self_reference.is_null()) { @@ -2794,6 +2857,7 @@ MaybeObject* Heap::InitializeFunction(JSFunction* function, function->set_prototype_or_initial_map(prototype); function->set_context(undefined_value()); function->set_literals(empty_fixed_array()); + function->set_next_function_link(undefined_value()); return function; } @@ -4419,7 +4483,7 @@ void Heap::RecordStats(HeapStats* stats, bool take_snapshot) { MemoryAllocator::Size() + MemoryAllocator::Available(); *stats->os_error = OS::GetLastError(); if (take_snapshot) { - HeapIterator iterator(HeapIterator::kPreciseFiltering); + HeapIterator iterator(HeapIterator::kFilterFreeListNodes); for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { @@ -4853,13 +4917,20 @@ ObjectIterator* SpaceIterator::CreateIterator() { } -class FreeListNodesFilter { +class HeapObjectsFilter { + public: + virtual ~HeapObjectsFilter() {} + virtual bool SkipObject(HeapObject* object) = 0; +}; + + +class FreeListNodesFilter : public HeapObjectsFilter { public: FreeListNodesFilter() { MarkFreeListNodes(); } - inline bool IsFreeListNode(HeapObject* object) { + bool SkipObject(HeapObject* object) { if (object->IsMarked()) { object->ClearMark(); return true; @@ -4891,6 +4962,65 @@ class FreeListNodesFilter { }; +class UnreachableObjectsFilter : public HeapObjectsFilter { + public: + UnreachableObjectsFilter() { + MarkUnreachableObjects(); + } + + bool SkipObject(HeapObject* object) { + if (object->IsMarked()) { + object->ClearMark(); + return true; + } else { + return false; + } + } + + private: + class UnmarkingVisitor : public ObjectVisitor { + public: + UnmarkingVisitor() : list_(10) {} + + void VisitPointers(Object** start, Object** end) { + for (Object** p = start; p < end; p++) { + if (!(*p)->IsHeapObject()) continue; + HeapObject* obj = HeapObject::cast(*p); + if (obj->IsMarked()) { + obj->ClearMark(); + list_.Add(obj); + } + } + } + + bool can_process() { return !list_.is_empty(); } + + void ProcessNext() { + HeapObject* obj = list_.RemoveLast(); + obj->Iterate(this); + } + + private: + List<HeapObject*> list_; + }; + + void MarkUnreachableObjects() { + HeapIterator iterator; + for (HeapObject* obj = iterator.next(); + obj != NULL; + obj = iterator.next()) { + obj->SetMark(); + } + UnmarkingVisitor visitor; + Heap::IterateRoots(&visitor, VISIT_ONLY_STRONG); + while (visitor.can_process()) + visitor.ProcessNext(); + } + + AssertNoAllocation no_alloc; +}; + + HeapIterator::HeapIterator() : filtering_(HeapIterator::kNoFiltering), filter_(NULL) { @@ -4898,7 +5028,7 @@ HeapIterator::HeapIterator() } -HeapIterator::HeapIterator(HeapIterator::FreeListNodesFiltering filtering) +HeapIterator::HeapIterator(HeapIterator::HeapObjectsFiltering filtering) : filtering_(filtering), filter_(NULL) { Init(); @@ -4912,12 +5042,17 @@ HeapIterator::~HeapIterator() { void HeapIterator::Init() { // Start the iteration. - if (filtering_ == kPreciseFiltering) { - filter_ = new FreeListNodesFilter; - space_iterator_ = - new SpaceIterator(MarkCompactCollector::SizeOfMarkedObject); - } else { - space_iterator_ = new SpaceIterator; + space_iterator_ = filtering_ == kNoFiltering ? new SpaceIterator : + new SpaceIterator(MarkCompactCollector::SizeOfMarkedObject); + switch (filtering_) { + case kFilterFreeListNodes: + filter_ = new FreeListNodesFilter; + break; + case kFilterUnreachable: + filter_ = new UnreachableObjectsFilter; + break; + default: + break; } object_iterator_ = space_iterator_->next(); } @@ -4925,9 +5060,9 @@ void HeapIterator::Init() { void HeapIterator::Shutdown() { #ifdef DEBUG - // Assert that in precise mode we have iterated through all + // Assert that in filtering mode we have iterated through all // objects. Otherwise, heap will be left in an inconsistent state. - if (filtering_ == kPreciseFiltering) { + if (filtering_ != kNoFiltering) { ASSERT(object_iterator_ == NULL); } #endif @@ -4944,7 +5079,7 @@ HeapObject* HeapIterator::next() { if (filter_ == NULL) return NextObject(); HeapObject* obj = NextObject(); - while (obj != NULL && filter_->IsFreeListNode(obj)) obj = NextObject(); + while (obj != NULL && filter_->SkipObject(obj)) obj = NextObject(); return obj; } @@ -62,6 +62,7 @@ namespace internal { V(Object, termination_exception, TerminationException) \ V(Map, hash_table_map, HashTableMap) \ V(FixedArray, empty_fixed_array, EmptyFixedArray) \ + V(ByteArray, empty_byte_array, EmptyByteArray) \ V(Map, string_map, StringMap) \ V(Map, ascii_string_map, AsciiStringMap) \ V(Map, symbol_map, SymbolMap) \ @@ -173,6 +174,8 @@ namespace internal { V(value_of_symbol, "valueOf") \ V(InitializeVarGlobal_symbol, "InitializeVarGlobal") \ V(InitializeConstGlobal_symbol, "InitializeConstGlobal") \ + V(KeyedLoadSpecialized_symbol, "KeyedLoadSpecialized") \ + V(KeyedStoreSpecialized_symbol, "KeyedStoreSpecialized") \ V(stack_overflow_symbol, "kStackOverflowBoilerplate") \ V(illegal_access_symbol, "illegal access") \ V(out_of_memory_symbol, "out-of-memory") \ @@ -1116,9 +1119,9 @@ class Heap : public AllStatic { static int contexts_disposed_; #if defined(V8_TARGET_ARCH_X64) - static const int kMaxObjectSizeInNewSpace = 512*KB; + static const int kMaxObjectSizeInNewSpace = 1024*KB; #else - static const int kMaxObjectSizeInNewSpace = 256*KB; + static const int kMaxObjectSizeInNewSpace = 512*KB; #endif static NewSpace new_space_; @@ -1582,17 +1585,18 @@ class SpaceIterator : public Malloced { // nodes filtering uses GC marks, it can't be used during MS/MC GC // phases. Also, it is forbidden to interrupt iteration in this mode, // as this will leave heap objects marked (and thus, unusable). -class FreeListNodesFilter; +class HeapObjectsFilter; class HeapIterator BASE_EMBEDDED { public: - enum FreeListNodesFiltering { + enum HeapObjectsFiltering { kNoFiltering, - kPreciseFiltering + kFilterFreeListNodes, + kFilterUnreachable }; HeapIterator(); - explicit HeapIterator(FreeListNodesFiltering filtering); + explicit HeapIterator(HeapObjectsFiltering filtering); ~HeapIterator(); HeapObject* next(); @@ -1605,8 +1609,8 @@ class HeapIterator BASE_EMBEDDED { void Shutdown(); HeapObject* NextObject(); - FreeListNodesFiltering filtering_; - FreeListNodesFilter* filter_; + HeapObjectsFiltering filtering_; + HeapObjectsFilter* filter_; // Space iterator for iterating all the spaces. SpaceIterator* space_iterator_; // Object iterator for the space currently being iterated. @@ -1965,6 +1969,8 @@ class GCTracer BASE_EMBEDDED { class TranscendentalCache { public: enum Type {ACOS, ASIN, ATAN, COS, EXP, LOG, SIN, TAN, kNumberOfCaches}; + static const int kTranscendentalTypeBits = 3; + STATIC_ASSERT((1 << kTranscendentalTypeBits) >= kNumberOfCaches); explicit TranscendentalCache(Type t); @@ -2051,7 +2057,7 @@ class TranscendentalCache { // Allow access to the caches_ array as an ExternalReference. friend class ExternalReference; - // Inline implementation of the caching. + // Inline implementation of the cache. friend class TranscendentalCacheStub; static TranscendentalCache* caches_[kNumberOfCaches]; diff --git a/src/hydrogen-instructions.cc b/src/hydrogen-instructions.cc new file mode 100644 index 00000000..3f39888e --- /dev/null +++ b/src/hydrogen-instructions.cc @@ -0,0 +1,1446 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "factory.h" +#include "hydrogen.h" + +#if V8_TARGET_ARCH_IA32 +#include "ia32/lithium-ia32.h" +#elif V8_TARGET_ARCH_X64 +#include "x64/lithium-x64.h" +#elif V8_TARGET_ARCH_ARM +#include "arm/lithium-arm.h" +#else +#error Unsupported target architecture. +#endif + +namespace v8 { +namespace internal { + +#define DEFINE_COMPILE(type) \ + LInstruction* H##type::CompileToLithium(LChunkBuilder* builder) { \ + return builder->Do##type(this); \ + } +HYDROGEN_CONCRETE_INSTRUCTION_LIST(DEFINE_COMPILE) +#undef DEFINE_COMPILE + + +const char* Representation::Mnemonic() const { + switch (kind_) { + case kNone: return "v"; + case kTagged: return "t"; + case kDouble: return "d"; + case kInteger32: return "i"; + default: + UNREACHABLE(); + return NULL; + } +} + + +static int32_t ConvertAndSetOverflow(int64_t result, bool* overflow) { + if (result > kMaxInt) { + *overflow = true; + return kMaxInt; + } + if (result < kMinInt) { + *overflow = true; + return kMinInt; + } + return static_cast<int32_t>(result); +} + + +static int32_t AddWithoutOverflow(int32_t a, int32_t b, bool* overflow) { + int64_t result = static_cast<int64_t>(a) + static_cast<int64_t>(b); + return ConvertAndSetOverflow(result, overflow); +} + + +static int32_t SubWithoutOverflow(int32_t a, int32_t b, bool* overflow) { + int64_t result = static_cast<int64_t>(a) - static_cast<int64_t>(b); + return ConvertAndSetOverflow(result, overflow); +} + + +static int32_t MulWithoutOverflow(int32_t a, int32_t b, bool* overflow) { + int64_t result = static_cast<int64_t>(a) * static_cast<int64_t>(b); + return ConvertAndSetOverflow(result, overflow); +} + + +int32_t Range::Mask() const { + if (lower_ == upper_) return lower_; + if (lower_ >= 0) { + int32_t res = 1; + while (res < upper_) { + res = (res << 1) | 1; + } + return res; + } + return 0xffffffff; +} + + +void Range::AddConstant(int32_t value) { + if (value == 0) return; + bool may_overflow = false; // Overflow is ignored here. + lower_ = AddWithoutOverflow(lower_, value, &may_overflow); + upper_ = AddWithoutOverflow(upper_, value, &may_overflow); + Verify(); +} + + +bool Range::AddAndCheckOverflow(Range* other) { + bool may_overflow = false; + lower_ = AddWithoutOverflow(lower_, other->lower(), &may_overflow); + upper_ = AddWithoutOverflow(upper_, other->upper(), &may_overflow); + KeepOrder(); + Verify(); + return may_overflow; +} + + +bool Range::SubAndCheckOverflow(Range* other) { + bool may_overflow = false; + lower_ = SubWithoutOverflow(lower_, other->upper(), &may_overflow); + upper_ = SubWithoutOverflow(upper_, other->lower(), &may_overflow); + KeepOrder(); + Verify(); + return may_overflow; +} + + +void Range::KeepOrder() { + if (lower_ > upper_) { + int32_t tmp = lower_; + lower_ = upper_; + upper_ = tmp; + } +} + + +void Range::Verify() const { + ASSERT(lower_ <= upper_); +} + + +bool Range::MulAndCheckOverflow(Range* other) { + bool may_overflow = false; + int v1 = MulWithoutOverflow(lower_, other->lower(), &may_overflow); + int v2 = MulWithoutOverflow(lower_, other->upper(), &may_overflow); + int v3 = MulWithoutOverflow(upper_, other->lower(), &may_overflow); + int v4 = MulWithoutOverflow(upper_, other->upper(), &may_overflow); + lower_ = Min(Min(v1, v2), Min(v3, v4)); + upper_ = Max(Max(v1, v2), Max(v3, v4)); + Verify(); + return may_overflow; +} + + +const char* HType::ToString() { + switch (type_) { + case kTagged: return "tagged"; + case kTaggedPrimitive: return "primitive"; + case kTaggedNumber: return "number"; + case kSmi: return "smi"; + case kHeapNumber: return "heap-number"; + case kString: return "string"; + case kBoolean: return "boolean"; + case kNonPrimitive: return "non-primitive"; + case kJSArray: return "array"; + case kJSObject: return "object"; + case kUninitialized: return "uninitialized"; + } + UNREACHABLE(); + return "Unreachable code"; +} + + +const char* HType::ToShortString() { + switch (type_) { + case kTagged: return "t"; + case kTaggedPrimitive: return "p"; + case kTaggedNumber: return "n"; + case kSmi: return "m"; + case kHeapNumber: return "h"; + case kString: return "s"; + case kBoolean: return "b"; + case kNonPrimitive: return "r"; + case kJSArray: return "a"; + case kJSObject: return "o"; + case kUninitialized: return "z"; + } + UNREACHABLE(); + return "Unreachable code"; +} + + +HType HType::TypeFromValue(Handle<Object> value) { + HType result = HType::Tagged(); + if (value->IsSmi()) { + result = HType::Smi(); + } else if (value->IsHeapNumber()) { + result = HType::HeapNumber(); + } else if (value->IsString()) { + result = HType::String(); + } else if (value->IsBoolean()) { + result = HType::Boolean(); + } else if (value->IsJSObject()) { + result = HType::JSObject(); + } else if (value->IsJSArray()) { + result = HType::JSArray(); + } + return result; +} + + +int HValue::LookupOperandIndex(int occurrence_index, HValue* op) const { + for (int i = 0; i < OperandCount(); ++i) { + if (OperandAt(i) == op) { + if (occurrence_index == 0) return i; + --occurrence_index; + } + } + return -1; +} + + +bool HValue::IsDefinedAfter(HBasicBlock* other) const { + return block()->block_id() > other->block_id(); +} + + +bool HValue::UsesMultipleTimes(HValue* op) const { + bool seen = false; + for (int i = 0; i < OperandCount(); ++i) { + if (OperandAt(i) == op) { + if (seen) return true; + seen = true; + } + } + return false; +} + + +bool HValue::Equals(HValue* other) const { + if (other->opcode() != opcode()) return false; + if (!other->representation().Equals(representation())) return false; + if (!other->type_.Equals(type_)) return false; + if (OperandCount() != other->OperandCount()) return false; + for (int i = 0; i < OperandCount(); ++i) { + if (OperandAt(i)->id() != other->OperandAt(i)->id()) return false; + } + bool result = DataEquals(other); + ASSERT(!result || Hashcode() == other->Hashcode()); + return result; +} + + +intptr_t HValue::Hashcode() const { + intptr_t result = opcode(); + int count = OperandCount(); + for (int i = 0; i < count; ++i) { + result = result * 19 + OperandAt(i)->id() + (result >> 7); + } + return result; +} + + +void HValue::SetOperandAt(int index, HValue* value) { + ASSERT(value == NULL || !value->representation().IsNone()); + RegisterUse(index, value); + InternalSetOperandAt(index, value); +} + + +void HValue::ReplaceAndDelete(HValue* other) { + ReplaceValue(other); + Delete(); +} + + +void HValue::ReplaceValue(HValue* other) { + ZoneList<HValue*> start_uses(2); + for (int i = 0; i < uses_.length(); ++i) { + HValue* use = uses_.at(i); + if (!use->block()->IsStartBlock()) { + InternalReplaceAtUse(use, other); + other->uses_.Add(use); + } else { + start_uses.Add(use); + } + } + uses_.Clear(); + uses_.AddAll(start_uses); +} + + +void HValue::ClearOperands() { + for (int i = 0; i < OperandCount(); ++i) { + SetOperandAt(i, NULL); + } +} + + +void HValue::Delete() { + ASSERT(HasNoUses()); + ClearOperands(); + DeleteFromGraph(); +} + + +void HValue::ReplaceAtUse(HValue* use, HValue* other) { + for (int i = 0; i < use->OperandCount(); ++i) { + if (use->OperandAt(i) == this) { + use->SetOperandAt(i, other); + } + } +} + + +void HValue::ReplaceFirstAtUse(HValue* use, HValue* other, Representation r) { + for (int i = 0; i < use->OperandCount(); ++i) { + if (use->RequiredInputRepresentation(i).Equals(r) && + use->OperandAt(i) == this) { + use->SetOperandAt(i, other); + return; + } + } +} + + +void HValue::InternalReplaceAtUse(HValue* use, HValue* other) { + for (int i = 0; i < use->OperandCount(); ++i) { + if (use->OperandAt(i) == this) { + // Call internal method that does not update use lists. The caller is + // responsible for doing so. + use->InternalSetOperandAt(i, other); + } + } +} + + +void HValue::SetBlock(HBasicBlock* block) { + ASSERT(block_ == NULL || block == NULL); + block_ = block; + if (id_ == kNoNumber && block != NULL) { + id_ = block->graph()->GetNextValueID(this); + } +} + + +void HValue::PrintTypeTo(HType type, StringStream* stream) { + stream->Add(type.ToShortString()); +} + + +void HValue::PrintNameTo(StringStream* stream) { + stream->Add("%s%d", representation_.Mnemonic(), id()); +} + + +bool HValue::UpdateInferredType() { + HType type = CalculateInferredType(); + bool result = (!type.Equals(type_)); + type_ = type; + return result; +} + + +void HValue::RegisterUse(int index, HValue* new_value) { + HValue* old_value = OperandAt(index); + if (old_value == new_value) return; + if (old_value != NULL) { + ASSERT(old_value->uses_.Contains(this)); + old_value->uses_.RemoveElement(this); + } + if (new_value != NULL) { + new_value->uses_.Add(this); + } +} + + +void HValue::AddNewRange(Range* r) { + if (!HasRange()) ComputeInitialRange(); + if (!HasRange()) range_ = new Range(); + ASSERT(HasRange()); + r->StackUpon(range_); + range_ = r; +} + + +void HValue::RemoveLastAddedRange() { + ASSERT(HasRange()); + ASSERT(range_->next() != NULL); + range_ = range_->next(); +} + + +void HValue::ComputeInitialRange() { + ASSERT(!HasRange()); + range_ = InferRange(); + ASSERT(HasRange()); +} + + +void HInstruction::PrintTo(StringStream* stream) const { + stream->Add("%s", Mnemonic()); + if (HasSideEffects()) stream->Add("*"); + stream->Add(" "); + PrintDataTo(stream); + + if (range() != NULL) { + stream->Add(" range[%d,%d,m0=%d]", + range()->lower(), + range()->upper(), + static_cast<int>(range()->CanBeMinusZero())); + } + + int changes_flags = (flags() & HValue::ChangesFlagsMask()); + if (changes_flags != 0) { + stream->Add(" changes[0x%x]", changes_flags); + } + + if (representation().IsTagged() && !type().Equals(HType::Tagged())) { + stream->Add(" type[%s]", type().ToString()); + } +} + + +void HInstruction::Unlink() { + ASSERT(IsLinked()); + ASSERT(!IsControlInstruction()); // Must never move control instructions. + clear_block(); + if (previous_ != NULL) previous_->next_ = next_; + if (next_ != NULL) next_->previous_ = previous_; +} + + +void HInstruction::InsertBefore(HInstruction* next) { + ASSERT(!IsLinked()); + ASSERT(!next->IsBlockEntry()); + ASSERT(!IsControlInstruction()); + ASSERT(!next->block()->IsStartBlock()); + ASSERT(next->previous_ != NULL); + HInstruction* prev = next->previous(); + prev->next_ = this; + next->previous_ = this; + next_ = next; + previous_ = prev; + SetBlock(next->block()); +} + + +void HInstruction::InsertAfter(HInstruction* previous) { + ASSERT(!IsLinked()); + ASSERT(!previous->IsControlInstruction()); + ASSERT(!IsControlInstruction() || previous->next_ == NULL); + HBasicBlock* block = previous->block(); + // Never insert anything except constants into the start block after finishing + // it. + if (block->IsStartBlock() && block->IsFinished() && !IsConstant()) { + ASSERT(block->end()->SecondSuccessor() == NULL); + InsertAfter(block->end()->FirstSuccessor()->first()); + return; + } + + // If we're inserting after an instruction with side-effects that is + // followed by a simulate instruction, we need to insert after the + // simulate instruction instead. + HInstruction* next = previous->next_; + if (previous->HasSideEffects() && next != NULL) { + ASSERT(next->IsSimulate()); + previous = next; + next = previous->next_; + } + + previous_ = previous; + next_ = next; + SetBlock(block); + previous->next_ = this; + if (next != NULL) next->previous_ = this; +} + + +#ifdef DEBUG +void HInstruction::Verify() const { + // Verify that input operands are defined before use. + HBasicBlock* cur_block = block(); + for (int i = 0; i < OperandCount(); ++i) { + HValue* other_operand = OperandAt(i); + HBasicBlock* other_block = other_operand->block(); + if (cur_block == other_block) { + if (!other_operand->IsPhi()) { + HInstruction* cur = cur_block->first(); + while (cur != NULL) { + ASSERT(cur != this); // We should reach other_operand before! + if (cur == other_operand) break; + cur = cur->next(); + } + // Must reach other operand in the same block! + ASSERT(cur == other_operand); + } + } else { + ASSERT(other_block->Dominates(cur_block)); + } + } + + // Verify that instructions that may have side-effects are followed + // by a simulate instruction. + if (HasSideEffects() && !IsOsrEntry()) { + ASSERT(next()->IsSimulate()); + } +} +#endif + + +HCall::HCall(int count) : arguments_(Zone::NewArray<HValue*>(count), count) { + for (int i = 0; i < count; ++i) arguments_[i] = NULL; + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); +} + + +void HCall::PrintDataTo(StringStream* stream) const { + stream->Add("("); + for (int i = 0; i < arguments_.length(); ++i) { + if (i != 0) stream->Add(", "); + arguments_.at(i)->PrintNameTo(stream); + } + stream->Add(")"); +} + + +void HClassOfTest::PrintDataTo(StringStream* stream) const { + stream->Add("class_of_test("); + value()->PrintTo(stream); + stream->Add(", \"%o\")", *class_name()); +} + + +void HAccessArgumentsAt::PrintDataTo(StringStream* stream) const { + arguments()->PrintNameTo(stream); + stream->Add("["); + index()->PrintNameTo(stream); + stream->Add("], length "); + length()->PrintNameTo(stream); +} + + +void HCall::SetArgumentAt(int index, HPushArgument* push_argument) { + push_argument->set_argument_index(index); + SetOperandAt(index, push_argument); +} + + +void HCallConstantFunction::PrintDataTo(StringStream* stream) const { + if (IsApplyFunction()) { + stream->Add("SPECIAL function: apply"); + } else { + stream->Add("%s", *(function()->shared()->DebugName()->ToCString())); + } + HCall::PrintDataTo(stream); +} + + +void HBranch::PrintDataTo(StringStream* stream) const { + int first_id = FirstSuccessor()->block_id(); + int second_id = SecondSuccessor()->block_id(); + stream->Add("on "); + value()->PrintNameTo(stream); + stream->Add(" (B%d, B%d)", first_id, second_id); +} + + +void HCompareMapAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("on "); + value()->PrintNameTo(stream); + stream->Add(" (%p)", *map()); +} + + +void HGoto::PrintDataTo(StringStream* stream) const { + stream->Add("B%d", FirstSuccessor()->block_id()); +} + + +void HReturn::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); +} + + +void HThrow::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); +} + + +const char* HUnaryMathOperation::OpName() const { + switch (op()) { + case kMathFloor: return "floor"; + case kMathRound: return "round"; + case kMathCeil: return "ceil"; + case kMathAbs: return "abs"; + case kMathLog: return "log"; + case kMathSin: return "sin"; + case kMathCos: return "cos"; + case kMathTan: return "tan"; + case kMathASin: return "asin"; + case kMathACos: return "acos"; + case kMathATan: return "atan"; + case kMathExp: return "exp"; + case kMathSqrt: return "sqrt"; + default: break; + } + return "(unknown operation)"; +} + + +void HUnaryMathOperation::PrintDataTo(StringStream* stream) const { + const char* name = OpName(); + stream->Add("%s ", name); + value()->PrintNameTo(stream); +} + + +void HUnaryOperation::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); +} + + +void HHasInstanceType::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); + switch (from_) { + case FIRST_JS_OBJECT_TYPE: + if (to_ == LAST_TYPE) stream->Add(" spec_object"); + break; + case JS_REGEXP_TYPE: + if (to_ == JS_REGEXP_TYPE) stream->Add(" reg_exp"); + break; + case JS_ARRAY_TYPE: + if (to_ == JS_ARRAY_TYPE) stream->Add(" array"); + break; + case JS_FUNCTION_TYPE: + if (to_ == JS_FUNCTION_TYPE) stream->Add(" function"); + break; + default: + break; + } +} + + +void HTypeofIs::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); + stream->Add(" == "); + stream->Add(type_literal_->ToAsciiVector()); +} + + +void HPushArgument::PrintDataTo(StringStream* stream) const { + HUnaryOperation::PrintDataTo(stream); + if (argument_index() != -1) { + stream->Add(" [%d]", argument_index_); + } +} + + +void HChange::PrintDataTo(StringStream* stream) const { + HUnaryOperation::PrintDataTo(stream); + stream->Add(" %s to %s", from_.Mnemonic(), to_.Mnemonic()); + + if (CanTruncateToInt32()) stream->Add(" truncating-int32"); + if (CheckFlag(kBailoutOnMinusZero)) stream->Add(" -0?"); +} + + +HCheckInstanceType* HCheckInstanceType::NewIsJSObjectOrJSFunction( + HValue* value) { + STATIC_ASSERT((LAST_JS_OBJECT_TYPE + 1) == JS_FUNCTION_TYPE); + return new HCheckInstanceType(value, FIRST_JS_OBJECT_TYPE, JS_FUNCTION_TYPE); +} + + +void HCheckMap::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); + stream->Add(" %p", *map()); +} + + +void HCheckFunction::PrintDataTo(StringStream* stream) const { + value()->PrintNameTo(stream); + stream->Add(" %p", *target()); +} + + +void HCallKeyed::PrintDataTo(StringStream* stream) const { + stream->Add("["); + key()->PrintNameTo(stream); + stream->Add("]("); + for (int i = 1; i < arguments_.length(); ++i) { + if (i != 1) stream->Add(", "); + arguments_.at(i)->PrintNameTo(stream); + } + stream->Add(")"); +} + + +void HCallNamed::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s ", *name_string); + HCall::PrintDataTo(stream); +} + + +void HCallGlobal::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s ", *name_string); + HCall::PrintDataTo(stream); +} + + +void HCallRuntime::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s ", *name_string); + HCall::PrintDataTo(stream); +} + +void HCallStub::PrintDataTo(StringStream* stream) const { + stream->Add("%s(%d)", + CodeStub::MajorName(major_key_, false), + argument_count_); +} + + +Range* HValue::InferRange() { + if (representation().IsTagged()) { + // Tagged values are always in int32 range when converted to integer, + // but they can contain -0. + Range* result = new Range(); + result->set_can_be_minus_zero(true); + return result; + } else if (representation().IsNone()) { + return NULL; + } else { + return new Range(); + } +} + + +Range* HConstant::InferRange() { + if (has_int32_value_) { + Range* result = new Range(int32_value_, int32_value_); + result->set_can_be_minus_zero(false); + return result; + } + return HInstruction::InferRange(); +} + + +Range* HPhi::InferRange() { + if (representation().IsInteger32()) { + if (block()->IsLoopHeader()) { + Range* range = new Range(kMinInt, kMaxInt); + return range; + } else { + Range* range = OperandAt(0)->range()->Copy(); + for (int i = 1; i < OperandCount(); ++i) { + range->Union(OperandAt(i)->range()); + } + return range; + } + } else { + return HValue::InferRange(); + } +} + + +Range* HAdd::InferRange() { + if (representation().IsInteger32()) { + Range* a = left()->range(); + Range* b = right()->range(); + Range* res = a->Copy(); + if (!res->AddAndCheckOverflow(b)) { + ClearFlag(kCanOverflow); + } + bool m0 = a->CanBeMinusZero() && b->CanBeMinusZero(); + res->set_can_be_minus_zero(m0); + return res; + } else { + return HArithmeticBinaryOperation::InferRange(); + } +} + + +Range* HSub::InferRange() { + if (representation().IsInteger32()) { + Range* a = left()->range(); + Range* b = right()->range(); + Range* res = a->Copy(); + if (!res->SubAndCheckOverflow(b)) { + ClearFlag(kCanOverflow); + } + res->set_can_be_minus_zero(a->CanBeMinusZero() && b->CanBeZero()); + return res; + } else { + return HArithmeticBinaryOperation::InferRange(); + } +} + + +Range* HMul::InferRange() { + if (representation().IsInteger32()) { + Range* a = left()->range(); + Range* b = right()->range(); + Range* res = a->Copy(); + if (!res->MulAndCheckOverflow(b)) { + ClearFlag(kCanOverflow); + } + bool m0 = (a->CanBeZero() && b->CanBeNegative()) || + (a->CanBeNegative() && b->CanBeZero()); + res->set_can_be_minus_zero(m0); + return res; + } else { + return HArithmeticBinaryOperation::InferRange(); + } +} + + +Range* HDiv::InferRange() { + if (representation().IsInteger32()) { + Range* result = new Range(); + if (left()->range()->CanBeMinusZero()) { + result->set_can_be_minus_zero(true); + } + + if (left()->range()->CanBeZero() && right()->range()->CanBeNegative()) { + result->set_can_be_minus_zero(true); + } + + if (right()->range()->Includes(-1) && left()->range()->Includes(kMinInt)) { + SetFlag(HValue::kCanOverflow); + } + + if (!right()->range()->CanBeZero()) { + ClearFlag(HValue::kCanBeDivByZero); + } + return result; + } else { + return HArithmeticBinaryOperation::InferRange(); + } +} + + +Range* HMod::InferRange() { + if (representation().IsInteger32()) { + Range* a = left()->range(); + Range* result = new Range(); + if (a->CanBeMinusZero() || a->CanBeNegative()) { + result->set_can_be_minus_zero(true); + } + if (!right()->range()->CanBeZero()) { + ClearFlag(HValue::kCanBeDivByZero); + } + return result; + } else { + return HArithmeticBinaryOperation::InferRange(); + } +} + + +void HPhi::PrintTo(StringStream* stream) const { + stream->Add("["); + for (int i = 0; i < OperandCount(); ++i) { + HValue* value = OperandAt(i); + stream->Add(" "); + value->PrintNameTo(stream); + stream->Add(" "); + } + stream->Add(" uses%d_%di_%dd_%dt]", + uses()->length(), + int32_non_phi_uses() + int32_indirect_uses(), + double_non_phi_uses() + double_indirect_uses(), + tagged_non_phi_uses() + tagged_indirect_uses()); +} + + +void HPhi::AddInput(HValue* value) { + inputs_.Add(NULL); + SetOperandAt(OperandCount() - 1, value); + // Mark phis that may have 'arguments' directly or indirectly as an operand. + if (!CheckFlag(kIsArguments) && value->CheckFlag(kIsArguments)) { + SetFlag(kIsArguments); + } +} + + +bool HPhi::HasReceiverOperand() { + for (int i = 0; i < OperandCount(); i++) { + if (OperandAt(i)->IsParameter() && + HParameter::cast(OperandAt(i))->index() == 0) { + return true; + } + } + return false; +} + + +HValue* HPhi::GetRedundantReplacement() const { + HValue* candidate = NULL; + int count = OperandCount(); + int position = 0; + while (position < count && candidate == NULL) { + HValue* current = OperandAt(position++); + if (current != this) candidate = current; + } + while (position < count) { + HValue* current = OperandAt(position++); + if (current != this && current != candidate) return NULL; + } + ASSERT(candidate != this); + return candidate; +} + + +void HPhi::DeleteFromGraph() { + ASSERT(block() != NULL); + block()->RemovePhi(this); + ASSERT(block() == NULL); +} + + +void HPhi::InitRealUses(int phi_id) { + // Initialize real uses. + phi_id_ = phi_id; + for (int j = 0; j < uses()->length(); j++) { + HValue* use = uses()->at(j); + if (!use->IsPhi()) { + int index = use->LookupOperandIndex(0, this); + Representation req_rep = use->RequiredInputRepresentation(index); + non_phi_uses_[req_rep.kind()]++; + } + } +} + + +void HPhi::AddNonPhiUsesFrom(HPhi* other) { + for (int i = 0; i < Representation::kNumRepresentations; i++) { + indirect_uses_[i] += other->non_phi_uses_[i]; + } +} + + +void HPhi::AddIndirectUsesTo(int* dest) { + for (int i = 0; i < Representation::kNumRepresentations; i++) { + dest[i] += indirect_uses_[i]; + } +} + + +void HSimulate::PrintDataTo(StringStream* stream) const { + stream->Add("id=%d ", ast_id()); + if (pop_count_ > 0) stream->Add("pop %d", pop_count_); + if (values_.length() > 0) { + if (pop_count_ > 0) stream->Add(" /"); + for (int i = 0; i < values_.length(); ++i) { + if (!HasAssignedIndexAt(i)) { + stream->Add(" push "); + } else { + stream->Add(" var[%d] = ", GetAssignedIndexAt(i)); + } + values_[i]->PrintNameTo(stream); + } + } +} + + +void HEnterInlined::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name = function()->debug_name()->ToCString(); + stream->Add("%s, id=%d", *name, function()->id()); +} + + +HConstant::HConstant(Handle<Object> handle, Representation r) + : handle_(handle), + constant_type_(HType::TypeFromValue(handle)), + has_int32_value_(false), + int32_value_(0), + has_double_value_(false), + double_value_(0) { + set_representation(r); + SetFlag(kUseGVN); + if (handle_->IsNumber()) { + double n = handle_->Number(); + has_int32_value_ = static_cast<double>(static_cast<int32_t>(n)) == n; + if (has_int32_value_) int32_value_ = static_cast<int32_t>(n); + double_value_ = n; + has_double_value_ = true; + } +} + + +HConstant* HConstant::CopyToRepresentation(Representation r) const { + if (r.IsInteger32() && !has_int32_value_) return NULL; + if (r.IsDouble() && !has_double_value_) return NULL; + return new HConstant(handle_, r); +} + + +HConstant* HConstant::CopyToTruncatedInt32() const { + if (!has_double_value_) return NULL; + int32_t truncated = NumberToInt32(*handle_); + return new HConstant(Factory::NewNumberFromInt(truncated), + Representation::Integer32()); +} + + +void HConstant::PrintDataTo(StringStream* stream) const { + handle()->ShortPrint(stream); +} + + +bool HArrayLiteral::IsCopyOnWrite() const { + return constant_elements()->map() == Heap::fixed_cow_array_map(); +} + + +void HBinaryOperation::PrintDataTo(StringStream* stream) const { + left()->PrintNameTo(stream); + stream->Add(" "); + right()->PrintNameTo(stream); + if (CheckFlag(kCanOverflow)) stream->Add(" !"); + if (CheckFlag(kBailoutOnMinusZero)) stream->Add(" -0?"); +} + + +Range* HBitAnd::InferRange() { + Range* a = left()->range(); + Range* b = right()->range(); + int32_t a_mask = 0xffffffff; + int32_t b_mask = 0xffffffff; + if (a != NULL) a_mask = a->Mask(); + if (b != NULL) b_mask = b->Mask(); + int32_t result_mask = a_mask & b_mask; + if (result_mask >= 0) { + return new Range(0, result_mask); + } else { + return HBinaryOperation::InferRange(); + } +} + + +Range* HBitOr::InferRange() { + Range* a = left()->range(); + Range* b = right()->range(); + int32_t a_mask = 0xffffffff; + int32_t b_mask = 0xffffffff; + if (a != NULL) a_mask = a->Mask(); + if (b != NULL) b_mask = b->Mask(); + int32_t result_mask = a_mask | b_mask; + if (result_mask >= 0) { + return new Range(0, result_mask); + } else { + return HBinaryOperation::InferRange(); + } +} + + +Range* HSar::InferRange() { + if (right()->IsConstant()) { + HConstant* c = HConstant::cast(right()); + if (c->HasInteger32Value()) { + int32_t val = c->Integer32Value(); + Range* result = NULL; + Range* left_range = left()->range(); + if (left_range == NULL) { + result = new Range(); + } else { + result = left_range->Copy(); + } + result->Sar(val); + return result; + } + } + + return HBinaryOperation::InferRange(); +} + + +Range* HShl::InferRange() { + if (right()->IsConstant()) { + HConstant* c = HConstant::cast(right()); + if (c->HasInteger32Value()) { + int32_t val = c->Integer32Value(); + Range* result = NULL; + Range* left_range = left()->range(); + if (left_range == NULL) { + result = new Range(); + } else { + result = left_range->Copy(); + } + result->Shl(val); + return result; + } + } + + return HBinaryOperation::InferRange(); +} + + + +void HCompare::PrintDataTo(StringStream* stream) const { + stream->Add(Token::Name(token())); + stream->Add(" "); + HBinaryOperation::PrintDataTo(stream); +} + + +void HCompare::SetInputRepresentation(Representation r) { + input_representation_ = r; + if (r.IsTagged()) { + SetFlagMask(AllSideEffects()); + ClearFlag(kUseGVN); + } else { + ClearFlagMask(AllSideEffects()); + SetFlag(kUseGVN); + } +} + + +void HParameter::PrintDataTo(StringStream* stream) const { + stream->Add("%u", index()); +} + + +void HLoadNamedField::PrintDataTo(StringStream* stream) const { + object()->PrintNameTo(stream); + stream->Add(" @%d%s", offset(), is_in_object() ? "[in-object]" : ""); +} + + +void HLoadKeyed::PrintDataTo(StringStream* stream) const { + object()->PrintNameTo(stream); + stream->Add("["); + key()->PrintNameTo(stream); + stream->Add("]"); +} + + +void HStoreNamed::PrintDataTo(StringStream* stream) const { + object()->PrintNameTo(stream); + stream->Add("."); + ASSERT(name()->IsString()); + stream->Add(*String::cast(*name())->ToCString()); + stream->Add(" = "); + value()->PrintNameTo(stream); +} + + +void HStoreNamedField::PrintDataTo(StringStream* stream) const { + HStoreNamed::PrintDataTo(stream); + if (!transition().is_null()) { + stream->Add(" (transition map %p)", *transition()); + } +} + + +void HStoreKeyed::PrintDataTo(StringStream* stream) const { + object()->PrintNameTo(stream); + stream->Add("["); + key()->PrintNameTo(stream); + stream->Add("] = "); + value()->PrintNameTo(stream); +} + + +void HLoadGlobal::PrintDataTo(StringStream* stream) const { + stream->Add("[%p]", *cell()); + if (check_hole_value()) stream->Add(" (deleteable/read-only)"); +} + + +void HStoreGlobal::PrintDataTo(StringStream* stream) const { + stream->Add("[%p] = ", *cell()); + value()->PrintNameTo(stream); +} + + +// Implementation of type inference and type conversions. Calculates +// the inferred type of this instruction based on the input operands. + +HType HValue::CalculateInferredType() const { + return type_; +} + + +HType HCheckMap::CalculateInferredType() const { + return value()->type(); +} + + +HType HCheckFunction::CalculateInferredType() const { + return value()->type(); +} + + +HType HCheckNonSmi::CalculateInferredType() const { + // TODO(kasperl): Is there any way to signal that this isn't a smi? + return HType::Tagged(); +} + + +HType HCheckSmi::CalculateInferredType() const { + return HType::Smi(); +} + + +HType HPhi::CalculateInferredType() const { + HType result = HType::Uninitialized(); + for (int i = 0; i < OperandCount(); ++i) { + HType current = OperandAt(i)->type(); + result = result.Combine(current); + } + return result; +} + + +HType HConstant::CalculateInferredType() const { + return constant_type_; +} + + +HType HCompare::CalculateInferredType() const { + return HType::Boolean(); +} + + +HType HCompareJSObjectEq::CalculateInferredType() const { + return HType::Boolean(); +} + + +HType HUnaryPredicate::CalculateInferredType() const { + return HType::Boolean(); +} + + +HType HArithmeticBinaryOperation::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HAdd::CalculateInferredType() const { + return HType::Tagged(); +} + + +HType HBitAnd::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HBitXor::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HBitOr::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HBitNot::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HUnaryMathOperation::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HShl::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HShr::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HType HSar::CalculateInferredType() const { + return HType::TaggedNumber(); +} + + +HValue* HUnaryMathOperation::EnsureAndPropagateNotMinusZero( + BitVector* visited) { + visited->Add(id()); + if (representation().IsInteger32() && + !value()->representation().IsInteger32()) { + if (value()->range() == NULL || value()->range()->CanBeMinusZero()) { + SetFlag(kBailoutOnMinusZero); + } + } + if (RequiredInputRepresentation(0).IsInteger32() && + representation().IsInteger32()) { + return value(); + } + return NULL; +} + + + +HValue* HChange::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + if (from().IsInteger32()) return NULL; + if (CanTruncateToInt32()) return NULL; + if (value()->range() == NULL || value()->range()->CanBeMinusZero()) { + SetFlag(kBailoutOnMinusZero); + } + ASSERT(!from().IsInteger32() || !to().IsInteger32()); + return NULL; +} + + +HValue* HMod::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + if (range() == NULL || range()->CanBeMinusZero()) { + SetFlag(kBailoutOnMinusZero); + return left(); + } + return NULL; +} + + +HValue* HDiv::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + if (range() == NULL || range()->CanBeMinusZero()) { + SetFlag(kBailoutOnMinusZero); + } + return NULL; +} + + +HValue* HMul::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + if (range() == NULL || range()->CanBeMinusZero()) { + SetFlag(kBailoutOnMinusZero); + } + return NULL; +} + + +HValue* HSub::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + // Propagate to the left argument. If the left argument cannot be -0, then + // the result of the add operation cannot be either. + if (range() == NULL || range()->CanBeMinusZero()) { + return left(); + } + return NULL; +} + + +HValue* HAdd::EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + // Propagate to the left argument. If the left argument cannot be -0, then + // the result of the sub operation cannot be either. + if (range() == NULL || range()->CanBeMinusZero()) { + return left(); + } + return NULL; +} + + +// Node-specific verification code is only included in debug mode. +#ifdef DEBUG + +void HPhi::Verify() const { + ASSERT(OperandCount() == block()->predecessors()->length()); + for (int i = 0; i < OperandCount(); ++i) { + HValue* value = OperandAt(i); + HBasicBlock* defining_block = value->block(); + HBasicBlock* predecessor_block = block()->predecessors()->at(i); + ASSERT(defining_block == predecessor_block || + defining_block->Dominates(predecessor_block)); + } +} + + +void HSimulate::Verify() const { + HInstruction::Verify(); + ASSERT(HasAstId()); +} + + +void HBoundsCheck::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckSmi::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckNonSmi::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckInstanceType::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckMap::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckFunction::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + + +void HCheckPrototypeMaps::Verify() const { + HInstruction::Verify(); + ASSERT(HasNoUses()); +} + +#endif + +} } // namespace v8::internal diff --git a/src/hydrogen-instructions.h b/src/hydrogen-instructions.h new file mode 100644 index 00000000..cbbe8fcc --- /dev/null +++ b/src/hydrogen-instructions.h @@ -0,0 +1,2953 @@ +// Copyright 2010 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. + +#ifndef V8_HYDROGEN_INSTRUCTIONS_H_ +#define V8_HYDROGEN_INSTRUCTIONS_H_ + +#include "v8.h" +#include "code-stubs.h" +#include "string-stream.h" +#include "zone.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class HBasicBlock; +class HEnvironment; +class HInstruction; +class HLoopInformation; +class HValue; +class LInstruction; +class LChunkBuilder; + + +// Type hierarchy: +// +// HValue +// HInstruction +// HAccessArgumentsAt +// HApplyArguments +// HArgumentsElements +// HArgumentsLength +// HArgumentsObject +// HBinaryOperation +// HArithmeticBinaryOperation +// HAdd +// HDiv +// HMod +// HMul +// HSub +// HBitwiseBinaryOperation +// HBitAnd +// HBitOr +// HBitXor +// HSar +// HShl +// HShr +// HBoundsCheck +// HCompare +// HCompareJSObjectEq +// HInstanceOf +// HLoadKeyed +// HLoadKeyedFastElement +// HLoadKeyedGeneric +// HLoadNamedGeneric +// HPower +// HStoreNamed +// HStoreNamedField +// HStoreNamedGeneric +// HBlockEntry +// HCall +// HCallConstantFunction +// HCallFunction +// HCallGlobal +// HCallKeyed +// HCallKnownGlobal +// HCallNamed +// HCallNew +// HCallRuntime +// HCallStub +// HConstant +// HControlInstruction +// HDeoptimize +// HGoto +// HUnaryControlInstruction +// HBranch +// HCompareMapAndBranch +// HReturn +// HThrow +// HEnterInlined +// HFunctionLiteral +// HGlobalObject +// HGlobalReceiver +// HLeaveInlined +// HLoadGlobal +// HMaterializedLiteral +// HArrayLiteral +// HObjectLiteral +// HRegExpLiteral +// HOsrEntry +// HParameter +// HSimulate +// HStackCheck +// HStoreKeyed +// HStoreKeyedFastElement +// HStoreKeyedGeneric +// HUnaryOperation +// HArrayLength +// HBitNot +// HChange +// HCheckFunction +// HCheckInstanceType +// HCheckMap +// HCheckNonSmi +// HCheckPrototypeMaps +// HCheckSmi +// HDeleteProperty +// HLoadElements +// HTypeofIs +// HLoadNamedField +// HPushArgument +// HTypeof +// HUnaryMathOperation +// HUnaryPredicate +// HClassOfTest +// HHasCachedArrayIndex +// HHasInstanceType +// HIsNull +// HIsObject +// HIsSmi +// HValueOf +// HUnknownOSRValue +// HPhi + +#define HYDROGEN_ALL_INSTRUCTION_LIST(V) \ + V(ArithmeticBinaryOperation) \ + V(BinaryOperation) \ + V(BitwiseBinaryOperation) \ + V(Call) \ + V(ControlInstruction) \ + V(Instruction) \ + V(LoadKeyed) \ + V(MaterializedLiteral) \ + V(Phi) \ + V(StoreKeyed) \ + V(StoreNamed) \ + V(UnaryControlInstruction) \ + V(UnaryOperation) \ + HYDROGEN_CONCRETE_INSTRUCTION_LIST(V) + + +#define HYDROGEN_CONCRETE_INSTRUCTION_LIST(V) \ + V(AccessArgumentsAt) \ + V(Add) \ + V(ApplyArguments) \ + V(ArgumentsElements) \ + V(ArgumentsLength) \ + V(ArgumentsObject) \ + V(ArrayLength) \ + V(ArrayLiteral) \ + V(BitAnd) \ + V(BitNot) \ + V(BitOr) \ + V(BitXor) \ + V(BlockEntry) \ + V(BoundsCheck) \ + V(Branch) \ + V(CallConstantFunction) \ + V(CallFunction) \ + V(CallGlobal) \ + V(CallKeyed) \ + V(CallKnownGlobal) \ + V(CallNamed) \ + V(CallNew) \ + V(CallRuntime) \ + V(CallStub) \ + V(Change) \ + V(CheckFunction) \ + V(CheckInstanceType) \ + V(CheckMap) \ + V(CheckNonSmi) \ + V(CheckPrototypeMaps) \ + V(CheckSmi) \ + V(Compare) \ + V(CompareJSObjectEq) \ + V(CompareMapAndBranch) \ + V(Constant) \ + V(DeleteProperty) \ + V(Deoptimize) \ + V(Div) \ + V(EnterInlined) \ + V(FunctionLiteral) \ + V(GlobalObject) \ + V(GlobalReceiver) \ + V(Goto) \ + V(InstanceOf) \ + V(IsNull) \ + V(IsObject) \ + V(IsSmi) \ + V(HasInstanceType) \ + V(HasCachedArrayIndex) \ + V(ClassOfTest) \ + V(LeaveInlined) \ + V(LoadElements) \ + V(LoadGlobal) \ + V(LoadKeyedFastElement) \ + V(LoadKeyedGeneric) \ + V(LoadNamedField) \ + V(LoadNamedGeneric) \ + V(Mod) \ + V(Mul) \ + V(ObjectLiteral) \ + V(OsrEntry) \ + V(Parameter) \ + V(Power) \ + V(PushArgument) \ + V(RegExpLiteral) \ + V(Return) \ + V(Sar) \ + V(Shl) \ + V(Shr) \ + V(Simulate) \ + V(StackCheck) \ + V(StoreGlobal) \ + V(StoreKeyedFastElement) \ + V(StoreKeyedGeneric) \ + V(StoreNamedField) \ + V(StoreNamedGeneric) \ + V(Sub) \ + V(Throw) \ + V(Typeof) \ + V(TypeofIs) \ + V(UnaryMathOperation) \ + V(UnknownOSRValue) \ + V(ValueOf) + +#define GVN_FLAG_LIST(V) \ + V(Calls) \ + V(InobjectFields) \ + V(BackingStoreFields) \ + V(ArrayElements) \ + V(GlobalVars) \ + V(Maps) \ + V(ArrayLengths) \ + V(OsrEntries) + +#define DECLARE_INSTRUCTION(type) \ + virtual bool Is##type() const { return true; } \ + static H##type* cast(HValue* value) { \ + ASSERT(value->Is##type()); \ + return reinterpret_cast<H##type*>(value); \ + } \ + Opcode opcode() const { return HValue::k##type; } + + +#define DECLARE_CONCRETE_INSTRUCTION(type, mnemonic) \ + virtual LInstruction* CompileToLithium(LChunkBuilder* builder); \ + virtual const char* Mnemonic() const { return mnemonic; } \ + DECLARE_INSTRUCTION(type) + + + +template<int kSize> +class HOperandVector : public EmbeddedVector<HValue*, kSize> { + public: + HOperandVector() : EmbeddedVector<HValue*, kSize>(NULL) { } +}; + + +class Range: public ZoneObject { + public: + Range() : lower_(kMinInt), + upper_(kMaxInt), + next_(NULL), + can_be_minus_zero_(false) { } + + Range(int32_t lower, int32_t upper) + : lower_(lower), upper_(upper), next_(NULL), can_be_minus_zero_(false) { } + + bool IsInSmiRange() const { + return lower_ >= Smi::kMinValue && upper_ <= Smi::kMaxValue; + } + void KeepOrder(); + void Verify() const; + int32_t upper() const { return upper_; } + int32_t lower() const { return lower_; } + Range* next() const { return next_; } + Range* CopyClearLower() const { return new Range(kMinInt, upper_); } + Range* CopyClearUpper() const { return new Range(lower_, kMaxInt); } + void ClearLower() { lower_ = kMinInt; } + void ClearUpper() { upper_ = kMaxInt; } + Range* Copy() const { return new Range(lower_, upper_); } + bool IsMostGeneric() const { return lower_ == kMinInt && upper_ == kMaxInt; } + int32_t Mask() const; + void set_can_be_minus_zero(bool b) { can_be_minus_zero_ = b; } + bool CanBeMinusZero() const { return CanBeZero() && can_be_minus_zero_; } + bool CanBeZero() const { return upper_ >= 0 && lower_ <= 0; } + bool CanBeNegative() const { return lower_ < 0; } + bool Includes(int value) const { + return lower_ <= value && upper_ >= value; + } + + void Sar(int32_t value) { + int32_t bits = value & 0x1F; + lower_ = lower_ >> bits; + upper_ = upper_ >> bits; + set_can_be_minus_zero(false); + } + + void Shl(int32_t value) { + int32_t bits = value & 0x1F; + int old_lower = lower_; + int old_upper = upper_; + lower_ = lower_ << bits; + upper_ = upper_ << bits; + if (old_lower != lower_ >> bits || old_upper != upper_ >> bits) { + upper_ = kMaxInt; + lower_ = kMinInt; + } + set_can_be_minus_zero(false); + } + + // Adds a constant to the lower and upper bound of the range. + void AddConstant(int32_t value); + + void StackUpon(Range* other) { + Intersect(other); + next_ = other; + } + + void Intersect(Range* other) { + upper_ = Min(upper_, other->upper_); + lower_ = Max(lower_, other->lower_); + bool b = CanBeMinusZero() && other->CanBeMinusZero(); + set_can_be_minus_zero(b); + } + + void Union(Range* other) { + upper_ = Max(upper_, other->upper_); + lower_ = Min(lower_, other->lower_); + bool b = CanBeMinusZero() || other->CanBeMinusZero(); + set_can_be_minus_zero(b); + } + + // Compute a new result range and return true, if the operation + // can overflow. + bool AddAndCheckOverflow(Range* other); + bool SubAndCheckOverflow(Range* other); + bool MulAndCheckOverflow(Range* other); + + private: + int32_t lower_; + int32_t upper_; + Range* next_; + bool can_be_minus_zero_; +}; + + +class Representation { + public: + enum Kind { + kNone, + kTagged, + kDouble, + kInteger32, + kNumRepresentations + }; + + Representation() : kind_(kNone) { } + + static Representation None() { return Representation(kNone); } + static Representation Tagged() { return Representation(kTagged); } + static Representation Integer32() { return Representation(kInteger32); } + static Representation Double() { return Representation(kDouble); } + + bool Equals(const Representation& other) const { + return kind_ == other.kind_; + } + + Kind kind() const { return kind_; } + bool IsNone() const { return kind_ == kNone; } + bool IsTagged() const { return kind_ == kTagged; } + bool IsInteger32() const { return kind_ == kInteger32; } + bool IsDouble() const { return kind_ == kDouble; } + bool IsSpecialization() const { + return kind_ == kInteger32 || kind_ == kDouble; + } + const char* Mnemonic() const; + + private: + explicit Representation(Kind k) : kind_(k) { } + + Kind kind_; +}; + + +class HType { + public: + HType() : type_(kUninitialized) { } + + static HType Tagged() { return HType(kTagged); } + static HType TaggedPrimitive() { return HType(kTaggedPrimitive); } + static HType TaggedNumber() { return HType(kTaggedNumber); } + static HType Smi() { return HType(kSmi); } + static HType HeapNumber() { return HType(kHeapNumber); } + static HType String() { return HType(kString); } + static HType Boolean() { return HType(kBoolean); } + static HType NonPrimitive() { return HType(kNonPrimitive); } + static HType JSArray() { return HType(kJSArray); } + static HType JSObject() { return HType(kJSObject); } + static HType Uninitialized() { return HType(kUninitialized); } + + // Return the weakest (least precise) common type. + HType Combine(HType other) { + return HType(static_cast<Type>(type_ & other.type_)); + } + + bool Equals(const HType& other) { + return type_ == other.type_; + } + + bool IsSubtypeOf(const HType& other) { + return Combine(other).Equals(other); + } + + bool IsTagged() { + ASSERT(type_ != kUninitialized); + return ((type_ & kTagged) == kTagged); + } + + bool IsTaggedPrimitive() { + ASSERT(type_ != kUninitialized); + return ((type_ & kTaggedPrimitive) == kTaggedPrimitive); + } + + bool IsTaggedNumber() { + ASSERT(type_ != kUninitialized); + return ((type_ & kTaggedNumber) == kTaggedNumber); + } + + bool IsSmi() { + ASSERT(type_ != kUninitialized); + return ((type_ & kSmi) == kSmi); + } + + bool IsHeapNumber() { + ASSERT(type_ != kUninitialized); + return ((type_ & kHeapNumber) == kHeapNumber); + } + + bool IsString() { + ASSERT(type_ != kUninitialized); + return ((type_ & kString) == kString); + } + + bool IsBoolean() { + ASSERT(type_ != kUninitialized); + return ((type_ & kBoolean) == kBoolean); + } + + bool IsNonPrimitive() { + ASSERT(type_ != kUninitialized); + return ((type_ & kNonPrimitive) == kNonPrimitive); + } + + bool IsJSArray() { + ASSERT(type_ != kUninitialized); + return ((type_ & kJSArray) == kJSArray); + } + + bool IsJSObject() { + ASSERT(type_ != kUninitialized); + return ((type_ & kJSObject) == kJSObject); + } + + bool IsUninitialized() { + return type_ == kUninitialized; + } + + static HType TypeFromValue(Handle<Object> value); + + const char* ToString(); + const char* ToShortString(); + + private: + enum Type { + kTagged = 0x1, // 0000 0000 0000 0001 + kTaggedPrimitive = 0x5, // 0000 0000 0000 0101 + kTaggedNumber = 0xd, // 0000 0000 0000 1101 + kSmi = 0x1d, // 0000 0000 0001 1101 + kHeapNumber = 0x2d, // 0000 0000 0010 1101 + kString = 0x45, // 0000 0000 0100 0101 + kBoolean = 0x85, // 0000 0000 1000 0101 + kNonPrimitive = 0x101, // 0000 0001 0000 0001 + kJSObject = 0x301, // 0000 0011 0000 0001 + kJSArray = 0x701, // 0000 0111 1000 0001 + kUninitialized = 0x1fff // 0001 1111 1111 1111 + }; + + explicit HType(Type t) : type_(t) { } + + Type type_; +}; + + +class HValue: public ZoneObject { + public: + static const int kNoNumber = -1; + + // There must be one corresponding kDepends flag for every kChanges flag and + // the order of the kChanges flags must be exactly the same as of the kDepends + // flags. + enum Flag { + // Declare global value numbering flags. + #define DECLARE_DO(type) kChanges##type, kDependsOn##type, + GVN_FLAG_LIST(DECLARE_DO) + #undef DECLARE_DO + kFlexibleRepresentation, + kUseGVN, + kCanOverflow, + kBailoutOnMinusZero, + kCanBeDivByZero, + kIsArguments, + kTruncatingToInt32, + kLastFlag = kTruncatingToInt32 + }; + + STATIC_ASSERT(kLastFlag < kBitsPerInt); + + static const int kChangesToDependsFlagsLeftShift = 1; + + static int ChangesFlagsMask() { + int result = 0; + // Create changes mask. +#define DECLARE_DO(type) result |= (1 << kChanges##type); + GVN_FLAG_LIST(DECLARE_DO) +#undef DECLARE_DO + return result; + } + + static int DependsFlagsMask() { + return ConvertChangesToDependsFlags(ChangesFlagsMask()); + } + + static int ConvertChangesToDependsFlags(int flags) { + return flags << kChangesToDependsFlagsLeftShift; + } + + // A flag mask to mark an instruction as having arbitrary side effects. + static int AllSideEffects() { + return ChangesFlagsMask() & ~(1 << kChangesOsrEntries); + } + + static HValue* cast(HValue* value) { return value; } + + enum Opcode { + // Declare a unique enum value for each hydrogen instruction. + #define DECLARE_DO(type) k##type, + HYDROGEN_ALL_INSTRUCTION_LIST(DECLARE_DO) + #undef DECLARE_DO + kMaxInstructionClass + }; + + HValue() : block_(NULL), + id_(kNoNumber), + uses_(2), + type_(HType::Tagged()), + range_(NULL), + flags_(0) {} + virtual ~HValue() {} + + HBasicBlock* block() const { return block_; } + void SetBlock(HBasicBlock* block); + + int id() const { return id_; } + void set_id(int id) { id_ = id; } + + const ZoneList<HValue*>* uses() const { return &uses_; } + + virtual bool EmitAtUses() const { return false; } + Representation representation() const { return representation_; } + void ChangeRepresentation(Representation r) { + // Representation was already set and is allowed to be changed. + ASSERT(!representation_.IsNone()); + ASSERT(!r.IsNone()); + ASSERT(CheckFlag(kFlexibleRepresentation)); + RepresentationChanged(r); + representation_ = r; + } + + HType type() const { return type_; } + void set_type(HType type) { + ASSERT(uses_.length() == 0); + type_ = type; + } + + // An operation needs to override this function iff: + // 1) it can produce an int32 output. + // 2) the true value of its output can potentially be minus zero. + // The implementation must set a flag so that it bails out in the case where + // it would otherwise output what should be a minus zero as an int32 zero. + // If the operation also exists in a form that takes int32 and outputs int32 + // then the operation should return its input value so that we can propagate + // back. There are two operations that need to propagate back to more than + // one input. They are phi and binary add. They always return NULL and + // expect the caller to take care of things. + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited) { + visited->Add(id()); + return NULL; + } + + bool HasSideEffects() const { + return (flags_ & AllSideEffects()) != 0; + } + bool IsDefinedAfter(HBasicBlock* other) const; + + // Operands. + virtual int OperandCount() const { return 0; } + virtual HValue* OperandAt(int index) const { + UNREACHABLE(); + return NULL; + } + void SetOperandAt(int index, HValue* value); + + int LookupOperandIndex(int occurrence_index, HValue* op) const; + bool UsesMultipleTimes(HValue* op) const; + + void ReplaceAndDelete(HValue* other); + void ReplaceValue(HValue* other); + void ReplaceAtUse(HValue* use, HValue* other); + void ReplaceFirstAtUse(HValue* use, HValue* other, Representation r); + bool HasNoUses() const { return uses_.is_empty(); } + void ClearOperands(); + void Delete(); + + int flags() const { return flags_; } + void SetFlagMask(int mask) { flags_ |= mask; } + void SetFlag(Flag f) { SetFlagMask(1 << f); } + void ClearFlagMask(int mask) { flags_ &= ~mask; } + void ClearFlag(Flag f) { ClearFlagMask(1 << f); } + bool CheckFlag(Flag f) const { return CheckFlagMask(1 << f); } + bool CheckFlagMask(int mask) const { return (flags_ & mask) != 0; } + + Range* range() const { return range_; } + bool HasRange() const { return range_ != NULL; } + void AddNewRange(Range* r); + void RemoveLastAddedRange(); + void ComputeInitialRange(); + + // Representation helpers. + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::None(); + } + virtual Representation InferredRepresentation() const { + return representation(); + } + + // This gives the instruction an opportunity to replace itself with an + // instruction that does the same in some better way. To replace an + // instruction with a new one, first add the new instruction to the graph, + // then return it. Return NULL to have the instruction deleted. + virtual HValue* Canonicalize() { return this; } + + // Declare virtual type testers. +#define DECLARE_DO(type) virtual bool Is##type() const { return false; } + HYDROGEN_ALL_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + bool Equals(HValue* other) const; + virtual intptr_t Hashcode() const; + + // Printing support. + virtual void PrintTo(StringStream* stream) const = 0; + void PrintNameTo(StringStream* stream); + static void PrintTypeTo(HType type, StringStream* stream); + + virtual const char* Mnemonic() const = 0; + virtual Opcode opcode() const = 0; + + // Updated the inferred type of this instruction and returns true if + // it has changed. + bool UpdateInferredType(); + + virtual HType CalculateInferredType() const; + + // Helper for type conversions used by normal and phi instructions. + void InsertInputConversion(HInstruction* previous, int index, HType type); + +#ifdef DEBUG + virtual void Verify() const = 0; +#endif + + protected: + virtual bool DataEquals(HValue* other) const { return true; } + virtual void RepresentationChanged(Representation to) { } + virtual Range* InferRange(); + virtual void DeleteFromGraph() = 0; + virtual void InternalSetOperandAt(int index, HValue* value) { UNREACHABLE(); } + void clear_block() { + ASSERT(block_ != NULL); + block_ = NULL; + } + + void set_representation(Representation r) { + // Representation is set-once. + ASSERT(representation_.IsNone() && !r.IsNone()); + representation_ = r; + } + + private: + void InternalReplaceAtUse(HValue* use, HValue* other); + void RegisterUse(int index, HValue* new_value); + + HBasicBlock* block_; + + // The id of this instruction in the hydrogen graph, assigned when first + // added to the graph. Reflects creation order. + int id_; + + Representation representation_; + ZoneList<HValue*> uses_; + HType type_; + Range* range_; + int flags_; + + DISALLOW_COPY_AND_ASSIGN(HValue); +}; + + +class HInstruction: public HValue { + public: + HInstruction* next() const { return next_; } + HInstruction* previous() const { return previous_; } + + void PrintTo(StringStream* stream) const; + virtual void PrintDataTo(StringStream* stream) const {} + + bool IsLinked() const { return block() != NULL; } + void Unlink(); + void InsertBefore(HInstruction* next); + void InsertAfter(HInstruction* previous); + + int position() const { return position_; } + bool has_position() const { return position_ != RelocInfo::kNoPosition; } + void set_position(int position) { position_ = position; } + + virtual LInstruction* CompileToLithium(LChunkBuilder* builder) = 0; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + DECLARE_INSTRUCTION(Instruction) + + protected: + HInstruction() + : next_(NULL), + previous_(NULL), + position_(RelocInfo::kNoPosition) { + SetFlag(kDependsOnOsrEntries); + } + + virtual void DeleteFromGraph() { Unlink(); } + + private: + void InitializeAsFirst(HBasicBlock* block) { + ASSERT(!IsLinked()); + SetBlock(block); + } + + HInstruction* next_; + HInstruction* previous_; + int position_; + + friend class HBasicBlock; +}; + + +class HBlockEntry: public HInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(BlockEntry, "block_entry") +}; + + +class HControlInstruction: public HInstruction { + public: + virtual HBasicBlock* FirstSuccessor() const { return NULL; } + virtual HBasicBlock* SecondSuccessor() const { return NULL; } + + DECLARE_INSTRUCTION(ControlInstruction) +}; + + +class HDeoptimize: public HControlInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(Deoptimize, "deoptimize") +}; + + +class HGoto: public HControlInstruction { + public: + explicit HGoto(HBasicBlock* destination) + : destination_(destination), + include_stack_check_(false) {} + + virtual HBasicBlock* FirstSuccessor() const { return destination_; } + void set_include_stack_check(bool include_stack_check) { + include_stack_check_ = include_stack_check; + } + bool include_stack_check() const { return include_stack_check_; } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Goto, "goto") + + private: + HBasicBlock* destination_; + bool include_stack_check_; +}; + + +class HUnaryControlInstruction: public HControlInstruction { + public: + explicit HUnaryControlInstruction(HValue* value) { + SetOperandAt(0, value); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + HValue* value() const { return OperandAt(0); } + virtual int OperandCount() const { return 1; } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + DECLARE_INSTRUCTION(UnaryControlInstruction) + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<1> operands_; +}; + + +class HBranch: public HUnaryControlInstruction { + public: + HBranch(HBasicBlock* true_destination, + HBasicBlock* false_destination, + HValue* boolean_value) + : HUnaryControlInstruction(boolean_value), + true_destination_(true_destination), + false_destination_(false_destination) { + ASSERT(true_destination != NULL && false_destination != NULL); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::None(); + } + + virtual HBasicBlock* FirstSuccessor() const { return true_destination_; } + virtual HBasicBlock* SecondSuccessor() const { return false_destination_; } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Branch, "branch") + + private: + HBasicBlock* true_destination_; + HBasicBlock* false_destination_; +}; + + +class HCompareMapAndBranch: public HUnaryControlInstruction { + public: + HCompareMapAndBranch(HValue* result, + Handle<Map> map, + HBasicBlock* true_destination, + HBasicBlock* false_destination) + : HUnaryControlInstruction(result), + map_(map), + true_destination_(true_destination), + false_destination_(false_destination) { + ASSERT(true_destination != NULL); + ASSERT(false_destination != NULL); + ASSERT(!map.is_null()); + } + + virtual HBasicBlock* FirstSuccessor() const { return true_destination_; } + virtual HBasicBlock* SecondSuccessor() const { return false_destination_; } + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<Map> map() const { return map_; } + + DECLARE_CONCRETE_INSTRUCTION(CompareMapAndBranch, "compare_map_and_branch") + + private: + Handle<Map> map_; + HBasicBlock* true_destination_; + HBasicBlock* false_destination_; +}; + + +class HReturn: public HUnaryControlInstruction { + public: + explicit HReturn(HValue* result) : HUnaryControlInstruction(result) { } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Return, "return") +}; + + +class HThrow: public HUnaryControlInstruction { + public: + explicit HThrow(HValue* value) : HUnaryControlInstruction(value) { } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Throw, "throw") +}; + + +class HUnaryOperation: public HInstruction { + public: + explicit HUnaryOperation(HValue* value) { + SetOperandAt(0, value); + } + + HValue* value() const { return OperandAt(0); } + virtual void PrintDataTo(StringStream* stream) const; + virtual int OperandCount() const { return 1; } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + DECLARE_INSTRUCTION(UnaryOperation) + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<1> operands_; +}; + + +class HChange: public HUnaryOperation { + public: + HChange(HValue* value, + Representation from, + Representation to) + : HUnaryOperation(value), from_(from), to_(to) { + ASSERT(!from.IsNone() && !to.IsNone()); + ASSERT(!from.Equals(to)); + set_representation(to); + SetFlag(kUseGVN); + + if (from.IsInteger32() && to.IsTagged() && value->range() != NULL && + value->range()->IsInSmiRange()) { + set_type(HType::Smi()); + } + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + Representation from() const { return from_; } + Representation to() const { return to_; } + virtual Representation RequiredInputRepresentation(int index) const { + return from_; + } + + bool CanTruncateToInt32() const { + for (int i = 0; i < uses()->length(); ++i) { + if (!uses()->at(i)->CheckFlag(HValue::kTruncatingToInt32)) return false; + } + return true; + } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Change, + CanTruncateToInt32() ? "truncate" : "change") + + protected: + virtual bool DataEquals(HValue* other) const { + if (!other->IsChange()) return false; + HChange* change = HChange::cast(other); + return value() == change->value() + && to().Equals(change->to()) + && CanTruncateToInt32() == change->CanTruncateToInt32(); + } + + private: + Representation from_; + Representation to_; +}; + + +class HSimulate: public HInstruction { + public: + HSimulate(int ast_id, int pop_count, int environment_height) + : ast_id_(ast_id), + pop_count_(pop_count), + environment_height_(environment_height), + values_(2), + assigned_indexes_(2) {} + virtual ~HSimulate() {} + + virtual void PrintDataTo(StringStream* stream) const; + + bool HasAstId() const { return ast_id_ != AstNode::kNoNumber; } + int ast_id() const { return ast_id_; } + void set_ast_id(int id) { + ASSERT(!HasAstId()); + ast_id_ = id; + } + + int environment_height() const { return environment_height_; } + int pop_count() const { return pop_count_; } + const ZoneList<HValue*>* values() const { return &values_; } + int GetAssignedIndexAt(int index) const { + ASSERT(HasAssignedIndexAt(index)); + return assigned_indexes_[index]; + } + bool HasAssignedIndexAt(int index) const { + return assigned_indexes_[index] != kNoIndex; + } + void AddAssignedValue(int index, HValue* value) { + AddValue(index, value); + } + void AddPushedValue(HValue* value) { + AddValue(kNoIndex, value); + } + virtual int OperandCount() const { return values_.length(); } + virtual HValue* OperandAt(int index) const { return values_[index]; } + + DECLARE_CONCRETE_INSTRUCTION(Simulate, "simulate") + +#ifdef DEBUG + virtual void Verify() const; +#endif + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + values_[index] = value; + } + + private: + static const int kNoIndex = -1; + void AddValue(int index, HValue* value) { + assigned_indexes_.Add(index); + // Resize the list of pushed values. + values_.Add(NULL); + // Set the operand through the base method in HValue to make sure that the + // use lists are correctly updated. + SetOperandAt(values_.length() - 1, value); + } + int ast_id_; + int pop_count_; + int environment_height_; + ZoneList<HValue*> values_; + ZoneList<int> assigned_indexes_; +}; + + +class HStackCheck: public HInstruction { + public: + HStackCheck() { } + + DECLARE_CONCRETE_INSTRUCTION(Throw, "stack_check") +}; + + +class HEnterInlined: public HInstruction { + public: + HEnterInlined(Handle<JSFunction> closure, FunctionLiteral* function) + : closure_(closure), function_(function) { + } + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<JSFunction> closure() const { return closure_; } + FunctionLiteral* function() const { return function_; } + + DECLARE_CONCRETE_INSTRUCTION(EnterInlined, "enter_inlined") + + private: + Handle<JSFunction> closure_; + FunctionLiteral* function_; +}; + + +class HLeaveInlined: public HInstruction { + public: + HLeaveInlined() {} + + DECLARE_CONCRETE_INSTRUCTION(LeaveInlined, "leave_inlined") +}; + + +class HPushArgument: public HUnaryOperation { + public: + explicit HPushArgument(HValue* value) + : HUnaryOperation(value), argument_index_(-1) { + set_representation(Representation::Tagged()); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + virtual void PrintDataTo(StringStream* stream) const; + HValue* argument() const { return OperandAt(0); } + int argument_index() const { return argument_index_; } + void set_argument_index(int index) { + ASSERT(argument_index_ == -1 || index == argument_index_); + argument_index_ = index; + } + + DECLARE_CONCRETE_INSTRUCTION(PushArgument, "push_argument") + + private: + int argument_index_; +}; + + +class HGlobalObject: public HInstruction { + public: + HGlobalObject() { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnCalls); + } + + DECLARE_CONCRETE_INSTRUCTION(GlobalObject, "global_object") +}; + + +class HGlobalReceiver: public HInstruction { + public: + HGlobalReceiver() { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnCalls); + } + + DECLARE_CONCRETE_INSTRUCTION(GlobalReceiver, "global_receiver") +}; + + +class HCall: public HInstruction { + public: + // Construct a call with uninitialized arguments. The argument count + // includes the receiver. + explicit HCall(int count); + + virtual HType CalculateInferredType() const { return HType::Tagged(); } + + // TODO(3190496): This needs a cleanup. We don't want the arguments + // be operands of the call instruction. This results in bad code quality. + virtual int argument_count() const { return arguments_.length(); } + virtual int OperandCount() const { return argument_count(); } + virtual HValue* OperandAt(int index) const { return arguments_[index]; } + virtual HPushArgument* PushArgumentAt(int index) const { + return HPushArgument::cast(OperandAt(index)); + } + virtual HValue* ArgumentAt(int index) const { + return PushArgumentAt(index)->argument(); + } + virtual void SetArgumentAt(int index, HPushArgument* push_argument); + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_INSTRUCTION(Call) + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + arguments_[index] = value; + } + + int argument_count_; + Vector<HValue*> arguments_; +}; + + +class HCallConstantFunction: public HCall { + public: + HCallConstantFunction(Handle<JSFunction> function, int argument_count) + : HCall(argument_count), function_(function) { } + + Handle<JSFunction> function() const { return function_; } + bool IsApplyFunction() const { + return function_->code() == Builtins::builtin(Builtins::FunctionApply); + } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction, "call_constant_function") + + private: + Handle<JSFunction> function_; +}; + + +class HCallKeyed: public HCall { + public: + HCallKeyed(HValue* key, int argument_count) + : HCall(argument_count + 1) { + SetOperandAt(0, key); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + // TODO(3190496): This is a hack to get an additional operand that + // is not an argument to work with the current setup. This _needs_ a cleanup. + // (see HCall) + virtual void PrintDataTo(StringStream* stream) const; + HValue* key() const { return OperandAt(0); } + virtual int argument_count() const { return arguments_.length() - 1; } + virtual int OperandCount() const { return arguments_.length(); } + virtual HValue* OperandAt(int index) const { return arguments_[index]; } + virtual HPushArgument* PushArgumentAt(int index) const { + return HPushArgument::cast(OperandAt(index + 1)); + } + virtual void SetArgumentAt(int index, HPushArgument* push_argument) { + HCall::SetArgumentAt(index + 1, push_argument); + } + + DECLARE_CONCRETE_INSTRUCTION(CallKeyed, "call_keyed") +}; + + +class HCallNamed: public HCall { + public: + HCallNamed(Handle<String> name, int argument_count) + : HCall(argument_count), name_(name) { } + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const { return name_; } + + DECLARE_CONCRETE_INSTRUCTION(CallNamed, "call_named") + + private: + Handle<String> name_; +}; + + +class HCallFunction: public HCall { + public: + explicit HCallFunction(int argument_count) : HCall(argument_count) { } + + DECLARE_CONCRETE_INSTRUCTION(CallFunction, "call_function") +}; + + +class HCallGlobal: public HCall { + public: + HCallGlobal(Handle<String> name, int argument_count) + : HCall(argument_count), name_(name) { } + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const { return name_; } + + DECLARE_CONCRETE_INSTRUCTION(CallGlobal, "call_global") + + private: + Handle<String> name_; +}; + + +class HCallKnownGlobal: public HCall { + public: + HCallKnownGlobal(Handle<JSFunction> target, + int argument_count) + : HCall(argument_count), target_(target) { } + + Handle<JSFunction> target() const { return target_; } + + DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal, "call_known_global") + + private: + Handle<JSFunction> target_; +}; + + +class HCallNew: public HCall { + public: + explicit HCallNew(int argument_count) : HCall(argument_count) { } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + HValue* constructor() const { return ArgumentAt(0); } + + DECLARE_CONCRETE_INSTRUCTION(CallNew, "call_new") +}; + + +class HCallRuntime: public HCall { + public: + HCallRuntime(Handle<String> name, + Runtime::Function* c_function, + int argument_count) + : HCall(argument_count), c_function_(c_function), name_(name) { } + virtual void PrintDataTo(StringStream* stream) const; + + Runtime::Function* function() const { return c_function_; } + Handle<String> name() const { return name_; } + + DECLARE_CONCRETE_INSTRUCTION(CallRuntime, "call_runtime") + + private: + Runtime::Function* c_function_; + Handle<String> name_; +}; + + +class HArrayLength: public HUnaryOperation { + public: + explicit HArrayLength(HValue* value) : HUnaryOperation(value) { + // The length of an array is stored as a tagged value in the array + // object. It is guaranteed to be 32 bit integer, but it can be + // represented as either a smi or heap number. + set_representation(Representation::Tagged()); + SetFlag(kDependsOnArrayLengths); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(ArrayLength, "array_length") +}; + + +class HBitNot: public HUnaryOperation { + public: + explicit HBitNot(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Integer32()); + SetFlag(kUseGVN); + SetFlag(kTruncatingToInt32); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Integer32(); + } + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(BitNot, "bit_not") +}; + + +class HUnaryMathOperation: public HUnaryOperation { + public: + HUnaryMathOperation(HValue* value, BuiltinFunctionId op) + : HUnaryOperation(value), op_(op) { + switch (op) { + case kMathFloor: + case kMathRound: + case kMathCeil: + set_representation(Representation::Integer32()); + break; + case kMathAbs: + set_representation(Representation::Tagged()); + SetFlag(kFlexibleRepresentation); + break; + case kMathSqrt: + case kMathPowHalf: + case kMathLog: + case kMathSin: + case kMathCos: + set_representation(Representation::Double()); + break; + default: + UNREACHABLE(); + } + SetFlag(kUseGVN); + } + + virtual void PrintDataTo(StringStream* stream) const; + + virtual HType CalculateInferredType() const; + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + virtual Representation RequiredInputRepresentation(int index) const { + switch (op_) { + case kMathFloor: + case kMathRound: + case kMathCeil: + case kMathSqrt: + case kMathPowHalf: + case kMathLog: + case kMathSin: + case kMathCos: + return Representation::Double(); + break; + case kMathAbs: + return representation(); + break; + default: + return Representation::None(); + } + } + + virtual HValue* Canonicalize() { + // If the input is integer32 then we replace the floor instruction + // with its inputs. This happens before the representation changes are + // introduced. + if (op() == kMathFloor) { + if (value()->representation().IsInteger32()) return value(); + } + return this; + } + + BuiltinFunctionId op() const { return op_; } + const char* OpName() const; + + DECLARE_CONCRETE_INSTRUCTION(UnaryMathOperation, "unary_math_operation") + + protected: + virtual bool DataEquals(HValue* other) const { + HUnaryMathOperation* b = HUnaryMathOperation::cast(other); + return op_ == b->op(); + } + + private: + BuiltinFunctionId op_; +}; + + +class HLoadElements: public HUnaryOperation { + public: + explicit HLoadElements(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnMaps); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(LoadElements, "load-elements") +}; + + +class HCheckMap: public HUnaryOperation { + public: + HCheckMap(HValue* value, Handle<Map> map) + : HUnaryOperation(value), map_(map) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnMaps); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + virtual HType CalculateInferredType() const; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + Handle<Map> map() const { return map_; } + + DECLARE_CONCRETE_INSTRUCTION(CheckMap, "check_map") + + protected: + virtual bool DataEquals(HValue* other) const { + HCheckMap* b = HCheckMap::cast(other); + return map_.is_identical_to(b->map()); + } + + private: + Handle<Map> map_; +}; + + +class HCheckFunction: public HUnaryOperation { + public: + HCheckFunction(HValue* value, Handle<JSFunction> function) + : HUnaryOperation(value), target_(function) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + virtual HType CalculateInferredType() const; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + Handle<JSFunction> target() const { return target_; } + + DECLARE_CONCRETE_INSTRUCTION(CheckFunction, "check_function") + + protected: + virtual bool DataEquals(HValue* other) const { + HCheckFunction* b = HCheckFunction::cast(other); + return target_.is_identical_to(b->target()); + } + + private: + Handle<JSFunction> target_; +}; + + +class HCheckInstanceType: public HUnaryOperation { + public: + // Check that the instance type is in the range [first, last] where + // both first and last are included. + HCheckInstanceType(HValue* value, InstanceType first, InstanceType last) + : HUnaryOperation(value), first_(first), last_(last) { + ASSERT(first <= last); + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + +#ifdef DEBUG + virtual void Verify() const; +#endif + + static HCheckInstanceType* NewIsJSObjectOrJSFunction(HValue* value); + + InstanceType first() const { return first_; } + InstanceType last() const { return last_; } + + DECLARE_CONCRETE_INSTRUCTION(CheckInstanceType, "check_instance_type") + + protected: + // TODO(ager): It could be nice to allow the ommision of instance + // type checks if we have already performed an instance type check + // with a larger range. + virtual bool DataEquals(HValue* other) const { + HCheckInstanceType* b = HCheckInstanceType::cast(other); + return (first_ == b->first()) && (last_ == b->last()); + } + + private: + InstanceType first_; + InstanceType last_; +}; + + +class HCheckNonSmi: public HUnaryOperation { + public: + explicit HCheckNonSmi(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + virtual HType CalculateInferredType() const; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + DECLARE_CONCRETE_INSTRUCTION(CheckNonSmi, "check_non_smi") +}; + + +class HCheckPrototypeMaps: public HUnaryOperation { + public: + HCheckPrototypeMaps(HValue* value, + Handle<JSObject> holder, + Handle<Map> receiver_map) + : HUnaryOperation(value), + holder_(holder), + receiver_map_(receiver_map) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnMaps); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + +#ifdef DEBUG + virtual void Verify() const; +#endif + + Handle<JSObject> holder() const { return holder_; } + Handle<Map> receiver_map() const { return receiver_map_; } + + DECLARE_CONCRETE_INSTRUCTION(CheckPrototypeMaps, "check_prototype_maps") + + protected: + virtual bool DataEquals(HValue* other) const { + HCheckPrototypeMaps* b = HCheckPrototypeMaps::cast(other); + return holder_.is_identical_to(b->holder()) && + receiver_map_.is_identical_to(b->receiver_map()); + } + + private: + Handle<JSObject> holder_; + Handle<Map> receiver_map_; +}; + + +class HCheckSmi: public HUnaryOperation { + public: + explicit HCheckSmi(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual HType CalculateInferredType() const; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + DECLARE_CONCRETE_INSTRUCTION(CheckSmi, "check_smi") +}; + + +class HPhi: public HValue { + public: + explicit HPhi(int merged_index) + : inputs_(2), + merged_index_(merged_index), + phi_id_(-1) { + for (int i = 0; i < Representation::kNumRepresentations; i++) { + non_phi_uses_[i] = 0; + indirect_uses_[i] = 0; + } + ASSERT(merged_index >= 0); + set_representation(Representation::Tagged()); + SetFlag(kFlexibleRepresentation); + } + + virtual Representation InferredRepresentation() const { + bool double_occurred = false; + bool int32_occurred = false; + for (int i = 0; i < OperandCount(); ++i) { + HValue* value = OperandAt(i); + if (value->representation().IsDouble()) double_occurred = true; + if (value->representation().IsInteger32()) int32_occurred = true; + if (value->representation().IsTagged()) return Representation::Tagged(); + } + + if (double_occurred) return Representation::Double(); + if (int32_occurred) return Representation::Integer32(); + return Representation::None(); + } + + virtual Range* InferRange(); + virtual Representation RequiredInputRepresentation(int index) const { + return representation(); + } + virtual HType CalculateInferredType() const; + virtual int OperandCount() const { return inputs_.length(); } + virtual HValue* OperandAt(int index) const { return inputs_[index]; } + HValue* GetRedundantReplacement() const; + void AddInput(HValue* value); + + bool HasReceiverOperand(); + + int merged_index() const { return merged_index_; } + + virtual const char* Mnemonic() const { return "phi"; } + + virtual void PrintTo(StringStream* stream) const; + +#ifdef DEBUG + virtual void Verify() const; +#endif + + DECLARE_INSTRUCTION(Phi) + + void InitRealUses(int id); + void AddNonPhiUsesFrom(HPhi* other); + void AddIndirectUsesTo(int* use_count); + + int tagged_non_phi_uses() const { + return non_phi_uses_[Representation::kTagged]; + } + int int32_non_phi_uses() const { + return non_phi_uses_[Representation::kInteger32]; + } + int double_non_phi_uses() const { + return non_phi_uses_[Representation::kDouble]; + } + int tagged_indirect_uses() const { + return indirect_uses_[Representation::kTagged]; + } + int int32_indirect_uses() const { + return indirect_uses_[Representation::kInteger32]; + } + int double_indirect_uses() const { + return indirect_uses_[Representation::kDouble]; + } + int phi_id() { return phi_id_; } + + protected: + virtual void DeleteFromGraph(); + virtual void InternalSetOperandAt(int index, HValue* value) { + inputs_[index] = value; + } + + private: + ZoneList<HValue*> inputs_; + int merged_index_; + + int non_phi_uses_[Representation::kNumRepresentations]; + int indirect_uses_[Representation::kNumRepresentations]; + int phi_id_; +}; + + +class HArgumentsObject: public HInstruction { + public: + HArgumentsObject() { + set_representation(Representation::Tagged()); + SetFlag(kIsArguments); + } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsObject, "arguments-object") +}; + + +class HConstant: public HInstruction { + public: + HConstant(Handle<Object> handle, Representation r); + + Handle<Object> handle() const { return handle_; } + + virtual bool EmitAtUses() const { return !representation().IsDouble(); } + virtual void PrintDataTo(StringStream* stream) const; + virtual HType CalculateInferredType() const; + bool IsInteger() const { return handle_->IsSmi(); } + HConstant* CopyToRepresentation(Representation r) const; + HConstant* CopyToTruncatedInt32() const; + bool HasInteger32Value() const { return has_int32_value_; } + int32_t Integer32Value() const { + ASSERT(HasInteger32Value()); + return int32_value_; + } + bool HasDoubleValue() const { return has_double_value_; } + double DoubleValue() const { + ASSERT(HasDoubleValue()); + return double_value_; + } + bool HasStringValue() const { return handle_->IsString(); } + + virtual intptr_t Hashcode() const { + ASSERT(!Heap::allow_allocation(false)); + return reinterpret_cast<intptr_t>(*handle()); + } + +#ifdef DEBUG + virtual void Verify() const { } +#endif + + DECLARE_CONCRETE_INSTRUCTION(Constant, "constant") + + protected: + virtual Range* InferRange(); + + virtual bool DataEquals(HValue* other) const { + HConstant* other_constant = HConstant::cast(other); + return handle().is_identical_to(other_constant->handle()); + } + + private: + Handle<Object> handle_; + HType constant_type_; + + // The following two values represent the int32 and the double value of the + // given constant if there is a lossless conversion between the constant + // and the specific representation. + bool has_int32_value_; + int32_t int32_value_; + bool has_double_value_; + double double_value_; +}; + + +class HBinaryOperation: public HInstruction { + public: + HBinaryOperation(HValue* left, HValue* right) { + ASSERT(left != NULL && right != NULL); + SetOperandAt(0, left); + SetOperandAt(1, right); + } + + HValue* left() const { return OperandAt(0); } + HValue* right() const { return OperandAt(1); } + + // TODO(kasperl): Move these helpers to the IA-32 Lithium + // instruction sequence builder. + HValue* LeastConstantOperand() const { + if (IsCommutative() && left()->IsConstant()) return right(); + return left(); + } + HValue* MostConstantOperand() const { + if (IsCommutative() && left()->IsConstant()) return left(); + return right(); + } + + virtual bool IsCommutative() const { return false; } + + virtual void PrintDataTo(StringStream* stream) const; + virtual int OperandCount() const { return operands_.length(); } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + DECLARE_INSTRUCTION(BinaryOperation) + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<2> operands_; +}; + + +class HApplyArguments: public HInstruction { + public: + HApplyArguments(HValue* function, + HValue* receiver, + HValue* length, + HValue* elements) { + set_representation(Representation::Tagged()); + SetOperandAt(0, function); + SetOperandAt(1, receiver); + SetOperandAt(2, length); + SetOperandAt(3, elements); + } + + virtual Representation RequiredInputRepresentation(int index) const { + // The length is untagged, all other inputs are tagged. + return (index == 2) + ? Representation::Integer32() + : Representation::Tagged(); + } + + HValue* function() const { return OperandAt(0); } + HValue* receiver() const { return OperandAt(1); } + HValue* length() const { return OperandAt(2); } + HValue* elements() const { return OperandAt(3); } + + virtual int OperandCount() const { return operands_.length(); } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply_arguments") + + + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<4> operands_; +}; + + +class HArgumentsElements: public HInstruction { + public: + HArgumentsElements() { + // The value produced by this instruction is a pointer into the stack + // that looks as if it was a smi because of alignment. + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsElements, "arguments_elements") +}; + + +class HArgumentsLength: public HUnaryOperation { + public: + explicit HArgumentsLength(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Integer32()); + SetFlag(kUseGVN); + } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsLength, "arguments_length") +}; + + +class HAccessArgumentsAt: public HInstruction { + public: + HAccessArgumentsAt(HValue* arguments, HValue* length, HValue* index) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetOperandAt(0, arguments); + SetOperandAt(1, length); + SetOperandAt(2, index); + } + + virtual void PrintDataTo(StringStream* stream) const; + + virtual Representation RequiredInputRepresentation(int index) const { + // The arguments elements is considered tagged. + return index == 0 + ? Representation::Tagged() + : Representation::Integer32(); + } + + HValue* arguments() const { return operands_[0]; } + HValue* length() const { return operands_[1]; } + HValue* index() const { return operands_[2]; } + + virtual int OperandCount() const { return operands_.length(); } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + DECLARE_CONCRETE_INSTRUCTION(AccessArgumentsAt, "access_arguments_at") + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<3> operands_; +}; + + +class HBoundsCheck: public HBinaryOperation { + public: + HBoundsCheck(HValue* index, HValue* length) + : HBinaryOperation(index, length) { + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Integer32(); + } + +#ifdef DEBUG + virtual void Verify() const; +#endif + + HValue* index() const { return left(); } + HValue* length() const { return right(); } + + DECLARE_CONCRETE_INSTRUCTION(BoundsCheck, "bounds_check") +}; + + +class HBitwiseBinaryOperation: public HBinaryOperation { + public: + HBitwiseBinaryOperation(HValue* left, HValue* right) + : HBinaryOperation(left, right) { + // Default to truncating, Integer32, UseGVN. + set_representation(Representation::Integer32()); + SetFlag(kTruncatingToInt32); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Integer32(); + } + + DECLARE_INSTRUCTION(BitwiseBinaryOperation) +}; + + +class HArithmeticBinaryOperation: public HBinaryOperation { + public: + HArithmeticBinaryOperation(HValue* left, HValue* right) + : HBinaryOperation(left, right) { + set_representation(Representation::Tagged()); + SetFlag(kFlexibleRepresentation); + SetFlagMask(AllSideEffects()); + } + + virtual void RepresentationChanged(Representation to) { + if (!to.IsTagged()) { + ClearFlagMask(AllSideEffects()); + SetFlag(kUseGVN); + } + } + + virtual HType CalculateInferredType() const; + virtual Representation RequiredInputRepresentation(int index) const { + return representation(); + } + virtual Representation InferredRepresentation() const { + if (left()->representation().Equals(right()->representation())) { + return left()->representation(); + } + return HValue::InferredRepresentation(); + } + + DECLARE_INSTRUCTION(ArithmeticBinaryOperation) +}; + + +class HCompare: public HBinaryOperation { + public: + HCompare(HValue* left, HValue* right, Token::Value token) + : HBinaryOperation(left, right), token_(token) { + ASSERT(Token::IsCompareOp(token)); + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); + } + + void SetInputRepresentation(Representation r); + virtual bool EmitAtUses() const { return uses()->length() <= 1; } + virtual Representation RequiredInputRepresentation(int index) const { + return input_representation_; + } + Representation GetInputRepresentation() const { + return input_representation_; + } + Token::Value token() const { return token_; } + virtual void PrintDataTo(StringStream* stream) const; + + virtual HType CalculateInferredType() const; + + virtual intptr_t Hashcode() const { + return HValue::Hashcode() * 7 + token_; + } + + DECLARE_CONCRETE_INSTRUCTION(Compare, "compare") + + protected: + virtual bool DataEquals(HValue* other) const { + HCompare* comp = HCompare::cast(other); + return token_ == comp->token(); + } + + private: + Representation input_representation_; + Token::Value token_; +}; + + +class HCompareJSObjectEq: public HBinaryOperation { + public: + HCompareJSObjectEq(HValue* left, HValue* right) + : HBinaryOperation(left, right) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + + virtual bool EmitAtUses() const { return uses()->length() <= 1; } + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(CompareJSObjectEq, "compare-js-object-eq") +}; + + +class HUnaryPredicate: public HUnaryOperation { + public: + explicit HUnaryPredicate(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + } + virtual bool EmitAtUses() const { return uses()->length() <= 1; } + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual HType CalculateInferredType() const; +}; + + +class HIsNull: public HUnaryPredicate { + public: + HIsNull(HValue* value, bool is_strict) + : HUnaryPredicate(value), is_strict_(is_strict) { } + + bool is_strict() const { return is_strict_; } + + DECLARE_CONCRETE_INSTRUCTION(IsNull, "is_null") + + protected: + virtual bool DataEquals(HValue* other) const { + HIsNull* b = HIsNull::cast(other); + return is_strict_ == b->is_strict(); + } + + private: + bool is_strict_; +}; + + +class HIsObject: public HUnaryPredicate { + public: + explicit HIsObject(HValue* value) : HUnaryPredicate(value) { } + + DECLARE_CONCRETE_INSTRUCTION(IsObject, "is_object") +}; + + +class HIsSmi: public HUnaryPredicate { + public: + explicit HIsSmi(HValue* value) : HUnaryPredicate(value) { } + + DECLARE_CONCRETE_INSTRUCTION(IsSmi, "is_smi") +}; + + +class HHasInstanceType: public HUnaryPredicate { + public: + HHasInstanceType(HValue* value, InstanceType type) + : HUnaryPredicate(value), from_(type), to_(type) { } + HHasInstanceType(HValue* value, InstanceType from, InstanceType to) + : HUnaryPredicate(value), from_(from), to_(to) { + ASSERT(to == LAST_TYPE); // Others not implemented yet in backend. + } + + InstanceType from() { return from_; } + InstanceType to() { return to_; } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceType, "has_instance_type") + + protected: + virtual bool DataEquals(HValue* other) const { + HHasInstanceType* b = HHasInstanceType::cast(other); + return (from_ == b->from()) && (to_ == b->to()); + } + + private: + InstanceType from_; + InstanceType to_; // Inclusive range, not all combinations work. +}; + + +class HHasCachedArrayIndex: public HUnaryPredicate { + public: + explicit HHasCachedArrayIndex(HValue* value) : HUnaryPredicate(value) { } + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndex, "has_cached_array_index") +}; + + +class HClassOfTest: public HUnaryPredicate { + public: + HClassOfTest(HValue* value, Handle<String> class_name) + : HUnaryPredicate(value), class_name_(class_name) { } + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTest, "class_of_test") + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> class_name() const { return class_name_; } + + protected: + virtual bool DataEquals(HValue* other) const { + HClassOfTest* b = HClassOfTest::cast(other); + return class_name_.is_identical_to(b->class_name_); + } + + private: + Handle<String> class_name_; +}; + + +class HTypeofIs: public HUnaryPredicate { + public: + HTypeofIs(HValue* value, Handle<String> type_literal) + : HUnaryPredicate(value), type_literal_(type_literal) { } + + Handle<String> type_literal() { return type_literal_; } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(TypeofIs, "typeof_is") + + protected: + virtual bool DataEquals(HValue* other) const { + HTypeofIs* b = HTypeofIs::cast(other); + return type_literal_.is_identical_to(b->type_literal_); + } + + private: + Handle<String> type_literal_; +}; + + +class HInstanceOf: public HBinaryOperation { + public: + HInstanceOf(HValue* left, HValue* right) : HBinaryOperation(left, right) { + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); + } + + virtual bool EmitAtUses() const { return uses()->length() <= 1; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOf, "instance_of") +}; + + +class HPower: public HBinaryOperation { + public: + HPower(HValue* left, HValue* right) + : HBinaryOperation(left, right) { + set_representation(Representation::Double()); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return (index == 1) ? Representation::None() : Representation::Double(); + } + + DECLARE_CONCRETE_INSTRUCTION(Power, "power") +}; + + +class HAdd: public HArithmeticBinaryOperation { + public: + HAdd(HValue* left, HValue* right) : HArithmeticBinaryOperation(left, right) { + SetFlag(kCanOverflow); + } + + // Add is only commutative if two integer values are added and not if two + // tagged values are added (because it might be a String concatenation). + virtual bool IsCommutative() const { + return !representation().IsTagged(); + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(Add, "add") + + protected: + virtual Range* InferRange(); +}; + + +class HSub: public HArithmeticBinaryOperation { + public: + HSub(HValue* left, HValue* right) : HArithmeticBinaryOperation(left, right) { + SetFlag(kCanOverflow); + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + DECLARE_CONCRETE_INSTRUCTION(Sub, "sub") + + protected: + virtual Range* InferRange(); +}; + + +class HMul: public HArithmeticBinaryOperation { + public: + HMul(HValue* left, HValue* right) : HArithmeticBinaryOperation(left, right) { + SetFlag(kCanOverflow); + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + // Only commutative if it is certain that not two objects are multiplicated. + virtual bool IsCommutative() const { + return !representation().IsTagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(Mul, "mul") + + protected: + virtual Range* InferRange(); +}; + + +class HMod: public HArithmeticBinaryOperation { + public: + HMod(HValue* left, HValue* right) : HArithmeticBinaryOperation(left, right) { + SetFlag(kCanBeDivByZero); + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + DECLARE_CONCRETE_INSTRUCTION(Mod, "mod") + + protected: + virtual Range* InferRange(); +}; + + +class HDiv: public HArithmeticBinaryOperation { + public: + HDiv(HValue* left, HValue* right) : HArithmeticBinaryOperation(left, right) { + SetFlag(kCanBeDivByZero); + SetFlag(kCanOverflow); + } + + virtual HValue* EnsureAndPropagateNotMinusZero(BitVector* visited); + + DECLARE_CONCRETE_INSTRUCTION(Div, "div") + + protected: + virtual Range* InferRange(); +}; + + +class HBitAnd: public HBitwiseBinaryOperation { + public: + HBitAnd(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual bool IsCommutative() const { return true; } + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(BitAnd, "bit_and") + + protected: + virtual Range* InferRange(); +}; + + +class HBitXor: public HBitwiseBinaryOperation { + public: + HBitXor(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual bool IsCommutative() const { return true; } + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(BitXor, "bit_xor") +}; + + +class HBitOr: public HBitwiseBinaryOperation { + public: + HBitOr(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual bool IsCommutative() const { return true; } + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(BitOr, "bit_or") + + protected: + virtual Range* InferRange(); +}; + + +class HShl: public HBitwiseBinaryOperation { + public: + HShl(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual Range* InferRange(); + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(Shl, "shl") +}; + + +class HShr: public HBitwiseBinaryOperation { + public: + HShr(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(Shr, "shr") +}; + + +class HSar: public HBitwiseBinaryOperation { + public: + HSar(HValue* left, HValue* right) + : HBitwiseBinaryOperation(left, right) { } + + virtual Range* InferRange(); + virtual HType CalculateInferredType() const; + + DECLARE_CONCRETE_INSTRUCTION(Sar, "sar") +}; + + +class HOsrEntry: public HInstruction { + public: + explicit HOsrEntry(int ast_id) : ast_id_(ast_id) { + SetFlag(kChangesOsrEntries); + } + + int ast_id() const { return ast_id_; } + + DECLARE_CONCRETE_INSTRUCTION(OsrEntry, "osr_entry") + + private: + int ast_id_; +}; + + +class HParameter: public HInstruction { + public: + explicit HParameter(unsigned index) : index_(index) { + set_representation(Representation::Tagged()); + } + + unsigned index() const { return index_; } + + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(Parameter, "parameter") + + private: + unsigned index_; +}; + + +class HCallStub: public HInstruction { + public: + HCallStub(CodeStub::Major major_key, int argument_count) + : major_key_(major_key), + argument_count_(argument_count), + transcendental_type_(TranscendentalCache::kNumberOfCaches) { + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); + } + + CodeStub::Major major_key() { return major_key_; } + int argument_count() { return argument_count_; } + + void set_transcendental_type(TranscendentalCache::Type transcendental_type) { + transcendental_type_ = transcendental_type; + } + TranscendentalCache::Type transcendental_type() { + return transcendental_type_; + } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(CallStub, "call_stub") + + private: + CodeStub::Major major_key_; + int argument_count_; + TranscendentalCache::Type transcendental_type_; +}; + + +class HUnknownOSRValue: public HInstruction { + public: + HUnknownOSRValue() { set_representation(Representation::Tagged()); } + + DECLARE_CONCRETE_INSTRUCTION(UnknownOSRValue, "unknown_osr_value") +}; + + +class HLoadGlobal: public HInstruction { + public: + HLoadGlobal(Handle<JSGlobalPropertyCell> cell, bool check_hole_value) + : cell_(cell), check_hole_value_(check_hole_value) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + SetFlag(kDependsOnGlobalVars); + } + + Handle<JSGlobalPropertyCell> cell() const { return cell_; } + bool check_hole_value() const { return check_hole_value_; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + + virtual intptr_t Hashcode() const { + ASSERT(!Heap::allow_allocation(false)); + return reinterpret_cast<intptr_t>(*cell_); + } + + DECLARE_CONCRETE_INSTRUCTION(LoadGlobal, "load_global") + + protected: + virtual bool DataEquals(HValue* other) const { + HLoadGlobal* b = HLoadGlobal::cast(other); + return cell_.is_identical_to(b->cell()); + } + + private: + Handle<JSGlobalPropertyCell> cell_; + bool check_hole_value_; +}; + + +class HStoreGlobal: public HUnaryOperation { + public: + HStoreGlobal(HValue* value, Handle<JSGlobalPropertyCell> cell) + : HUnaryOperation(value), cell_(cell) { + SetFlag(kChangesGlobalVars); + } + + Handle<JSGlobalPropertyCell> cell() const { return cell_; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(StoreGlobal, "store_global") + + protected: + virtual bool DataEquals(HValue* other) const { + HStoreGlobal* b = HStoreGlobal::cast(other); + return cell_.is_identical_to(b->cell()); + } + + private: + Handle<JSGlobalPropertyCell> cell_; +}; + + +class HLoadNamedField: public HUnaryOperation { + public: + HLoadNamedField(HValue* object, bool is_in_object, int offset) + : HUnaryOperation(object), + is_in_object_(is_in_object), + offset_(offset) { + set_representation(Representation::Tagged()); + SetFlag(kUseGVN); + if (is_in_object) { + SetFlag(kDependsOnInobjectFields); + } else { + SetFlag(kDependsOnBackingStoreFields); + } + } + + HValue* object() const { return OperandAt(0); } + bool is_in_object() const { return is_in_object_; } + int offset() const { return offset_; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedField, "load_named_field") + + protected: + virtual bool DataEquals(HValue* other) const { + HLoadNamedField* b = HLoadNamedField::cast(other); + return is_in_object_ == b->is_in_object_ && offset_ == b->offset_; + } + + private: + bool is_in_object_; + int offset_; +}; + + +class HLoadNamedGeneric: public HUnaryOperation { + public: + HLoadNamedGeneric(HValue* object, Handle<Object> name) + : HUnaryOperation(object), name_(name) { + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); + } + + HValue* object() const { return OperandAt(0); } + Handle<Object> name() const { return name_; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedGeneric, "load_named_generic") + + protected: + virtual bool DataEquals(HValue* other) const { + HLoadNamedGeneric* b = HLoadNamedGeneric::cast(other); + return name_.is_identical_to(b->name_); + } + + private: + Handle<Object> name_; +}; + + +class HLoadKeyed: public HBinaryOperation { + public: + HLoadKeyed(HValue* obj, HValue* key) : HBinaryOperation(obj, key) { + set_representation(Representation::Tagged()); + } + + virtual void PrintDataTo(StringStream* stream) const; + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + HValue* object() const { return OperandAt(0); } + HValue* key() const { return OperandAt(1); } + + DECLARE_INSTRUCTION(LoadKeyed) +}; + + +class HLoadKeyedFastElement: public HLoadKeyed { + public: + HLoadKeyedFastElement(HValue* obj, HValue* key) : HLoadKeyed(obj, key) { + SetFlag(kDependsOnArrayElements); + SetFlag(kUseGVN); + } + + virtual Representation RequiredInputRepresentation(int index) const { + // The key is supposed to be Integer32. + return (index == 1) ? Representation::Integer32() + : Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedFastElement, + "load_keyed_fast_element") +}; + + +class HLoadKeyedGeneric: public HLoadKeyed { + public: + HLoadKeyedGeneric(HValue* obj, HValue* key) : HLoadKeyed(obj, key) { + SetFlagMask(AllSideEffects()); + } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedGeneric, "load_keyed_generic") +}; + + +class HStoreNamed: public HBinaryOperation { + public: + HStoreNamed(HValue* obj, Handle<Object> name, HValue* val) + : HBinaryOperation(obj, val), name_(name) { + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + virtual void PrintDataTo(StringStream* stream) const; + + HValue* object() const { return OperandAt(0); } + Handle<Object> name() const { return name_; } + HValue* value() const { return OperandAt(1); } + void set_value(HValue* value) { SetOperandAt(1, value); } + + DECLARE_INSTRUCTION(StoreNamed) + + protected: + virtual bool DataEquals(HValue* other) const { + HStoreNamed* b = HStoreNamed::cast(other); + return name_.is_identical_to(b->name_); + } + + private: + Handle<Object> name_; +}; + + +class HStoreNamedField: public HStoreNamed { + public: + HStoreNamedField(HValue* obj, + Handle<Object> name, + HValue* val, + bool in_object, + int offset) + : HStoreNamed(obj, name, val), + is_in_object_(in_object), + offset_(offset) { + if (is_in_object_) { + SetFlag(kChangesInobjectFields); + } else { + SetFlag(kChangesBackingStoreFields); + } + } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedField, "store_named_field") + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + virtual void PrintDataTo(StringStream* stream) const; + + bool is_in_object() const { return is_in_object_; } + int offset() const { return offset_; } + Handle<Map> transition() const { return transition_; } + void set_transition(Handle<Map> map) { transition_ = map; } + + private: + bool is_in_object_; + int offset_; + Handle<Map> transition_; +}; + + +class HStoreNamedGeneric: public HStoreNamed { + public: + HStoreNamedGeneric(HValue* obj, Handle<Object> name, HValue* val) + : HStoreNamed(obj, name, val) { + SetFlagMask(AllSideEffects()); + } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedGeneric, "store_named_generic") +}; + + +class HStoreKeyed: public HInstruction { + public: + HStoreKeyed(HValue* obj, HValue* key, HValue* val) { + SetOperandAt(0, obj); + SetOperandAt(1, key); + SetOperandAt(2, val); + } + + virtual void PrintDataTo(StringStream* stream) const; + virtual int OperandCount() const { return operands_.length(); } + virtual HValue* OperandAt(int index) const { return operands_[index]; } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + HValue* object() const { return OperandAt(0); } + HValue* key() const { return OperandAt(1); } + HValue* value() const { return OperandAt(2); } + + DECLARE_INSTRUCTION(StoreKeyed) + + protected: + virtual void InternalSetOperandAt(int index, HValue* value) { + operands_[index] = value; + } + + private: + HOperandVector<3> operands_; +}; + + +class HStoreKeyedFastElement: public HStoreKeyed { + public: + HStoreKeyedFastElement(HValue* obj, HValue* key, HValue* val) + : HStoreKeyed(obj, key, val) { + SetFlag(kChangesArrayElements); + } + + bool NeedsWriteBarrier() const { + return !value()->type().IsSmi(); + } + + virtual Representation RequiredInputRepresentation(int index) const { + // The key is supposed to be Integer32. + return (index == 1) ? Representation::Integer32() + : Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFastElement, + "store_keyed_fast_element") +}; + + +class HStoreKeyedGeneric: public HStoreKeyed { + public: + HStoreKeyedGeneric(HValue* obj, HValue* key, HValue* val) + : HStoreKeyed(obj, key, val) { + SetFlagMask(AllSideEffects()); + } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedGeneric, "store_keyed_generic") +}; + + +class HMaterializedLiteral: public HInstruction { + public: + HMaterializedLiteral(int index, int depth) + : literal_index_(index), depth_(depth) { + set_representation(Representation::Tagged()); + } + + int literal_index() const { return literal_index_; } + int depth() const { return depth_; } + + DECLARE_INSTRUCTION(MaterializedLiteral) + + private: + int literal_index_; + int depth_; +}; + + +class HArrayLiteral: public HMaterializedLiteral { + public: + HArrayLiteral(Handle<FixedArray> constant_elements, + int length, + int literal_index, + int depth) + : HMaterializedLiteral(literal_index, depth), + length_(length), + constant_elements_(constant_elements) {} + + Handle<FixedArray> constant_elements() const { return constant_elements_; } + int length() const { return length_; } + + bool IsCopyOnWrite() const; + + DECLARE_CONCRETE_INSTRUCTION(ArrayLiteral, "array_literal") + + private: + int length_; + Handle<FixedArray> constant_elements_; +}; + + +class HObjectLiteral: public HMaterializedLiteral { + public: + HObjectLiteral(Handle<FixedArray> constant_properties, + bool fast_elements, + int literal_index, + int depth) + : HMaterializedLiteral(literal_index, depth), + constant_properties_(constant_properties), + fast_elements_(fast_elements) {} + + Handle<FixedArray> constant_properties() const { + return constant_properties_; + } + bool fast_elements() const { return fast_elements_; } + + DECLARE_CONCRETE_INSTRUCTION(ObjectLiteral, "object_literal") + + private: + Handle<FixedArray> constant_properties_; + bool fast_elements_; +}; + + +class HRegExpLiteral: public HMaterializedLiteral { + public: + HRegExpLiteral(Handle<String> pattern, + Handle<String> flags, + int literal_index) + : HMaterializedLiteral(literal_index, 0), + pattern_(pattern), + flags_(flags) { } + + Handle<String> pattern() { return pattern_; } + Handle<String> flags() { return flags_; } + + DECLARE_CONCRETE_INSTRUCTION(RegExpLiteral, "regexp_literal") + + private: + Handle<String> pattern_; + Handle<String> flags_; +}; + + +class HFunctionLiteral: public HInstruction { + public: + HFunctionLiteral(Handle<SharedFunctionInfo> shared, bool pretenure) + : shared_info_(shared), pretenure_(pretenure) { + set_representation(Representation::Tagged()); + } + + DECLARE_CONCRETE_INSTRUCTION(FunctionLiteral, "function_literal") + + Handle<SharedFunctionInfo> shared_info() const { return shared_info_; } + bool pretenure() const { return pretenure_; } + + private: + Handle<SharedFunctionInfo> shared_info_; + bool pretenure_; +}; + + +class HTypeof: public HUnaryOperation { + public: + explicit HTypeof(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + } + + DECLARE_CONCRETE_INSTRUCTION(Typeof, "typeof") +}; + + +class HValueOf: public HUnaryOperation { + public: + explicit HValueOf(HValue* value) : HUnaryOperation(value) { + set_representation(Representation::Tagged()); + } + + DECLARE_CONCRETE_INSTRUCTION(ValueOf, "value_of") +}; + + +class HDeleteProperty: public HBinaryOperation { + public: + HDeleteProperty(HValue* obj, HValue* key) + : HBinaryOperation(obj, key) { + set_representation(Representation::Tagged()); + SetFlagMask(AllSideEffects()); + } + + virtual Representation RequiredInputRepresentation(int index) const { + return Representation::Tagged(); + } + + DECLARE_CONCRETE_INSTRUCTION(DeleteProperty, "delete_property") + + HValue* object() const { return left(); } + HValue* key() const { return right(); } +}; + +#undef DECLARE_INSTRUCTION +#undef DECLARE_CONCRETE_INSTRUCTION + +} } // namespace v8::internal + +#endif // V8_HYDROGEN_INSTRUCTIONS_H_ diff --git a/src/hydrogen.cc b/src/hydrogen.cc new file mode 100644 index 00000000..e34acd67 --- /dev/null +++ b/src/hydrogen.cc @@ -0,0 +1,5686 @@ +// Copyright 2010 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. + +#include "hydrogen.h" + +#include "codegen.h" +#include "data-flow.h" +#include "full-codegen.h" +#include "hashmap.h" +#include "lithium-allocator.h" +#include "parser.h" +#include "scopes.h" + +#if V8_TARGET_ARCH_IA32 +#include "ia32/lithium-codegen-ia32.h" +#elif V8_TARGET_ARCH_X64 +#include "x64/lithium-codegen-x64.h" +#elif V8_TARGET_ARCH_ARM +#include "arm/lithium-codegen-arm.h" +#else +#error Unsupported target architecture. +#endif + +namespace v8 { +namespace internal { + +HBasicBlock::HBasicBlock(HGraph* graph) + : block_id_(graph->GetNextBlockID()), + graph_(graph), + phis_(4), + first_(NULL), + last_(NULL), + end_(NULL), + loop_information_(NULL), + predecessors_(2), + dominator_(NULL), + dominated_blocks_(4), + last_environment_(NULL), + argument_count_(-1), + first_instruction_index_(-1), + last_instruction_index_(-1), + deleted_phis_(4), + is_inline_return_target_(false) { +} + + +void HBasicBlock::AttachLoopInformation() { + ASSERT(!IsLoopHeader()); + loop_information_ = new HLoopInformation(this); +} + + +void HBasicBlock::DetachLoopInformation() { + ASSERT(IsLoopHeader()); + loop_information_ = NULL; +} + + +void HBasicBlock::AddPhi(HPhi* phi) { + ASSERT(!IsStartBlock()); + phis_.Add(phi); + phi->SetBlock(this); +} + + +void HBasicBlock::RemovePhi(HPhi* phi) { + ASSERT(phi->block() == this); + ASSERT(phis_.Contains(phi)); + ASSERT(phi->HasNoUses()); + phi->ClearOperands(); + phis_.RemoveElement(phi); + phi->SetBlock(NULL); +} + + +void HBasicBlock::AddInstruction(HInstruction* instr) { + ASSERT(!IsStartBlock() || !IsFinished()); + ASSERT(!instr->IsLinked()); + ASSERT(!IsFinished()); + if (first_ == NULL) { + HBlockEntry* entry = new HBlockEntry(); + entry->InitializeAsFirst(this); + first_ = entry; + } + instr->InsertAfter(GetLastInstruction()); +} + + +HInstruction* HBasicBlock::GetLastInstruction() { + if (end_ != NULL) return end_->previous(); + if (first_ == NULL) return NULL; + if (last_ == NULL) last_ = first_; + while (last_->next() != NULL) last_ = last_->next(); + return last_; +} + + +HSimulate* HBasicBlock::CreateSimulate(int id) { + ASSERT(HasEnvironment()); + HEnvironment* environment = last_environment(); + ASSERT(id == AstNode::kNoNumber || + environment->closure()->shared()->VerifyBailoutId(id)); + + int push_count = environment->push_count(); + int pop_count = environment->pop_count(); + + int length = environment->values()->length(); + HSimulate* instr = new HSimulate(id, pop_count, length); + for (int i = push_count - 1; i >= 0; --i) { + instr->AddPushedValue(environment->ExpressionStackAt(i)); + } + for (int i = 0; i < environment->assigned_variables()->length(); ++i) { + int index = environment->assigned_variables()->at(i); + instr->AddAssignedValue(index, environment->Lookup(index)); + } + environment->ClearHistory(); + return instr; +} + + +void HBasicBlock::Finish(HControlInstruction* end) { + ASSERT(!IsFinished()); + AddInstruction(end); + end_ = end; + if (end->FirstSuccessor() != NULL) { + end->FirstSuccessor()->RegisterPredecessor(this); + if (end->SecondSuccessor() != NULL) { + end->SecondSuccessor()->RegisterPredecessor(this); + } + } +} + + +void HBasicBlock::Goto(HBasicBlock* block, bool include_stack_check) { + AddSimulate(AstNode::kNoNumber); + HGoto* instr = new HGoto(block); + instr->set_include_stack_check(include_stack_check); + Finish(instr); +} + + +void HBasicBlock::SetInitialEnvironment(HEnvironment* env) { + ASSERT(!HasEnvironment()); + ASSERT(first() == NULL); + UpdateEnvironment(env); +} + + +void HBasicBlock::SetJoinId(int id) { + int length = predecessors_.length(); + ASSERT(length > 0); + for (int i = 0; i < length; i++) { + HBasicBlock* predecessor = predecessors_[i]; + ASSERT(predecessor->end()->IsGoto()); + HSimulate* simulate = HSimulate::cast(predecessor->GetLastInstruction()); + // We only need to verify the ID once. + ASSERT(i != 0 || + predecessor->last_environment()->closure()->shared() + ->VerifyBailoutId(id)); + simulate->set_ast_id(id); + } +} + + +bool HBasicBlock::Dominates(HBasicBlock* other) const { + HBasicBlock* current = other->dominator(); + while (current != NULL) { + if (current == this) return true; + current = current->dominator(); + } + return false; +} + + +void HBasicBlock::PostProcessLoopHeader(IterationStatement* stmt) { + ASSERT(IsLoopHeader()); + + SetJoinId(stmt->EntryId()); + if (predecessors()->length() == 1) { + // This is a degenerated loop. + DetachLoopInformation(); + return; + } + + // Only the first entry into the loop is from outside the loop. All other + // entries must be back edges. + for (int i = 1; i < predecessors()->length(); ++i) { + loop_information()->RegisterBackEdge(predecessors()->at(i)); + } +} + + +void HBasicBlock::RegisterPredecessor(HBasicBlock* pred) { + if (!predecessors_.is_empty()) { + // Only loop header blocks can have a predecessor added after + // instructions have been added to the block (they have phis for all + // values in the environment, these phis may be eliminated later). + ASSERT(IsLoopHeader() || first_ == NULL); + HEnvironment* incoming_env = pred->last_environment(); + if (IsLoopHeader()) { + ASSERT(phis()->length() == incoming_env->values()->length()); + for (int i = 0; i < phis_.length(); ++i) { + phis_[i]->AddInput(incoming_env->values()->at(i)); + } + } else { + last_environment()->AddIncomingEdge(this, pred->last_environment()); + } + } else if (!HasEnvironment() && !IsFinished()) { + ASSERT(!IsLoopHeader()); + SetInitialEnvironment(pred->last_environment()->Copy()); + } + + predecessors_.Add(pred); +} + + +void HBasicBlock::AddDominatedBlock(HBasicBlock* block) { + ASSERT(!dominated_blocks_.Contains(block)); + // Keep the list of dominated blocks sorted such that if there is two + // succeeding block in this list, the predecessor is before the successor. + int index = 0; + while (index < dominated_blocks_.length() && + dominated_blocks_[index]->block_id() < block->block_id()) { + ++index; + } + dominated_blocks_.InsertAt(index, block); +} + + +void HBasicBlock::AssignCommonDominator(HBasicBlock* other) { + if (dominator_ == NULL) { + dominator_ = other; + other->AddDominatedBlock(this); + } else if (other->dominator() != NULL) { + HBasicBlock* first = dominator_; + HBasicBlock* second = other; + + while (first != second) { + if (first->block_id() > second->block_id()) { + first = first->dominator(); + } else { + second = second->dominator(); + } + ASSERT(first != NULL && second != NULL); + } + + if (dominator_ != first) { + ASSERT(dominator_->dominated_blocks_.Contains(this)); + dominator_->dominated_blocks_.RemoveElement(this); + dominator_ = first; + first->AddDominatedBlock(this); + } + } +} + + +int HBasicBlock::PredecessorIndexOf(HBasicBlock* predecessor) const { + for (int i = 0; i < predecessors_.length(); ++i) { + if (predecessors_[i] == predecessor) return i; + } + UNREACHABLE(); + return -1; +} + + +#ifdef DEBUG +void HBasicBlock::Verify() { + // Check that every block is finished. + ASSERT(IsFinished()); + ASSERT(block_id() >= 0); + + // Verify that all blocks targetting a branch target, have the same boolean + // value on top of their expression stack. + if (!cond().is_null()) { + ASSERT(predecessors()->length() > 0); + for (int i = 1; i < predecessors()->length(); i++) { + HBasicBlock* pred = predecessors()->at(i); + HValue* top = pred->last_environment()->Top(); + ASSERT(top->IsConstant()); + Object* a = *HConstant::cast(top)->handle(); + Object* b = *cond(); + ASSERT(a == b); + } + } +} +#endif + + +void HLoopInformation::RegisterBackEdge(HBasicBlock* block) { + this->back_edges_.Add(block); + AddBlock(block); +} + + +HBasicBlock* HLoopInformation::GetLastBackEdge() const { + int max_id = -1; + HBasicBlock* result = NULL; + for (int i = 0; i < back_edges_.length(); ++i) { + HBasicBlock* cur = back_edges_[i]; + if (cur->block_id() > max_id) { + max_id = cur->block_id(); + result = cur; + } + } + return result; +} + + +void HLoopInformation::AddBlock(HBasicBlock* block) { + if (block == loop_header()) return; + if (block->parent_loop_header() == loop_header()) return; + if (block->parent_loop_header() != NULL) { + AddBlock(block->parent_loop_header()); + } else { + block->set_parent_loop_header(loop_header()); + blocks_.Add(block); + for (int i = 0; i < block->predecessors()->length(); ++i) { + AddBlock(block->predecessors()->at(i)); + } + } +} + + +#ifdef DEBUG + +// Checks reachability of the blocks in this graph and stores a bit in +// the BitVector "reachable()" for every block that can be reached +// from the start block of the graph. If "dont_visit" is non-null, the given +// block is treated as if it would not be part of the graph. "visited_count()" +// returns the number of reachable blocks. +class ReachabilityAnalyzer BASE_EMBEDDED { + public: + ReachabilityAnalyzer(HBasicBlock* entry_block, + int block_count, + HBasicBlock* dont_visit) + : visited_count_(0), + stack_(16), + reachable_(block_count), + dont_visit_(dont_visit) { + PushBlock(entry_block); + Analyze(); + } + + int visited_count() const { return visited_count_; } + const BitVector* reachable() const { return &reachable_; } + + private: + void PushBlock(HBasicBlock* block) { + if (block != NULL && block != dont_visit_ && + !reachable_.Contains(block->block_id())) { + reachable_.Add(block->block_id()); + stack_.Add(block); + visited_count_++; + } + } + + void Analyze() { + while (!stack_.is_empty()) { + HControlInstruction* end = stack_.RemoveLast()->end(); + PushBlock(end->FirstSuccessor()); + PushBlock(end->SecondSuccessor()); + } + } + + int visited_count_; + ZoneList<HBasicBlock*> stack_; + BitVector reachable_; + HBasicBlock* dont_visit_; +}; + + +void HGraph::Verify() const { + for (int i = 0; i < blocks_.length(); i++) { + HBasicBlock* block = blocks_.at(i); + + block->Verify(); + + // Check that every block contains at least one node and that only the last + // node is a control instruction. + HInstruction* current = block->first(); + ASSERT(current != NULL && current->IsBlockEntry()); + while (current != NULL) { + ASSERT((current->next() == NULL) == current->IsControlInstruction()); + ASSERT(current->block() == block); + current->Verify(); + current = current->next(); + } + + // Check that successors are correctly set. + HBasicBlock* first = block->end()->FirstSuccessor(); + HBasicBlock* second = block->end()->SecondSuccessor(); + ASSERT(second == NULL || first != NULL); + + // Check that the predecessor array is correct. + if (first != NULL) { + ASSERT(first->predecessors()->Contains(block)); + if (second != NULL) { + ASSERT(second->predecessors()->Contains(block)); + } + } + + // Check that phis have correct arguments. + for (int j = 0; j < block->phis()->length(); j++) { + HPhi* phi = block->phis()->at(j); + phi->Verify(); + } + + // Check that all join blocks have predecessors that end with an + // unconditional goto and agree on their environment node id. + if (block->predecessors()->length() >= 2) { + int id = block->predecessors()->first()->last_environment()->ast_id(); + for (int k = 0; k < block->predecessors()->length(); k++) { + HBasicBlock* predecessor = block->predecessors()->at(k); + ASSERT(predecessor->end()->IsGoto()); + ASSERT(predecessor->last_environment()->ast_id() == id); + } + } + } + + // Check special property of first block to have no predecessors. + ASSERT(blocks_.at(0)->predecessors()->is_empty()); + + // Check that the graph is fully connected. + ReachabilityAnalyzer analyzer(entry_block_, blocks_.length(), NULL); + ASSERT(analyzer.visited_count() == blocks_.length()); + + // Check that entry block dominator is NULL. + ASSERT(entry_block_->dominator() == NULL); + + // Check dominators. + for (int i = 0; i < blocks_.length(); ++i) { + HBasicBlock* block = blocks_.at(i); + if (block->dominator() == NULL) { + // Only start block may have no dominator assigned to. + ASSERT(i == 0); + } else { + // Assert that block is unreachable if dominator must not be visited. + ReachabilityAnalyzer dominator_analyzer(entry_block_, + blocks_.length(), + block->dominator()); + ASSERT(!dominator_analyzer.reachable()->Contains(block->block_id())); + } + } +} + +#endif + + +HConstant* HGraph::GetConstant(SetOncePointer<HConstant>* pointer, + Object* value) { + if (!pointer->is_set()) { + HConstant* constant = new HConstant(Handle<Object>(value), + Representation::Tagged()); + constant->InsertAfter(GetConstantUndefined()); + pointer->set(constant); + } + return pointer->get(); +} + + +HConstant* HGraph::GetConstant1() { + return GetConstant(&constant_1_, Smi::FromInt(1)); +} + + +HConstant* HGraph::GetConstantMinus1() { + return GetConstant(&constant_minus1_, Smi::FromInt(-1)); +} + + +HConstant* HGraph::GetConstantTrue() { + return GetConstant(&constant_true_, Heap::true_value()); +} + + +HConstant* HGraph::GetConstantFalse() { + return GetConstant(&constant_false_, Heap::false_value()); +} + + +void HSubgraph::AppendOptional(HSubgraph* graph, + bool on_true_branch, + HValue* boolean_value) { + ASSERT(HasExit() && graph->HasExit()); + HBasicBlock* other_block = graph_->CreateBasicBlock(); + HBasicBlock* join_block = graph_->CreateBasicBlock(); + + HBasicBlock* true_branch = other_block; + HBasicBlock* false_branch = graph->entry_block(); + if (on_true_branch) { + true_branch = graph->entry_block(); + false_branch = other_block; + } + + exit_block_->Finish(new HBranch(true_branch, false_branch, boolean_value)); + other_block->Goto(join_block); + graph->exit_block()->Goto(join_block); + exit_block_ = join_block; +} + + +void HSubgraph::AppendJoin(HSubgraph* then_graph, + HSubgraph* else_graph, + AstNode* node) { + if (then_graph->HasExit() && else_graph->HasExit()) { + // We need to merge, create new merge block. + HBasicBlock* join_block = graph_->CreateBasicBlock(); + then_graph->exit_block()->Goto(join_block); + else_graph->exit_block()->Goto(join_block); + join_block->SetJoinId(node->id()); + exit_block_ = join_block; + } else if (then_graph->HasExit()) { + exit_block_ = then_graph->exit_block_; + } else if (else_graph->HasExit()) { + exit_block_ = else_graph->exit_block_; + } else { + exit_block_ = NULL; + } +} + + +void HSubgraph::ResolveContinue(IterationStatement* statement) { + HBasicBlock* continue_block = BundleContinue(statement); + if (continue_block != NULL) { + exit_block_ = JoinBlocks(exit_block(), + continue_block, + statement->ContinueId()); + } +} + + +HBasicBlock* HSubgraph::BundleBreak(BreakableStatement* statement) { + return BundleBreakContinue(statement, false, statement->ExitId()); +} + + +HBasicBlock* HSubgraph::BundleContinue(IterationStatement* statement) { + return BundleBreakContinue(statement, true, statement->ContinueId()); +} + + +HBasicBlock* HSubgraph::BundleBreakContinue(BreakableStatement* statement, + bool is_continue, + int join_id) { + HBasicBlock* result = NULL; + const ZoneList<BreakContinueInfo*>* infos = break_continue_info(); + for (int i = 0; i < infos->length(); ++i) { + BreakContinueInfo* info = infos->at(i); + if (info->is_continue() == is_continue && + info->target() == statement && + !info->IsResolved()) { + if (result == NULL) { + result = graph_->CreateBasicBlock(); + } + info->block()->Goto(result); + info->Resolve(); + } + } + + if (result != NULL) result->SetJoinId(join_id); + + return result; +} + + +HBasicBlock* HSubgraph::JoinBlocks(HBasicBlock* a, HBasicBlock* b, int id) { + if (a == NULL) return b; + if (b == NULL) return a; + HBasicBlock* target = graph_->CreateBasicBlock(); + a->Goto(target); + b->Goto(target); + target->SetJoinId(id); + return target; +} + + +void HSubgraph::AppendEndless(HSubgraph* body, IterationStatement* statement) { + ConnectExitTo(body->entry_block()); + body->ResolveContinue(statement); + body->ConnectExitTo(body->entry_block(), true); + exit_block_ = body->BundleBreak(statement); + body->entry_block()->PostProcessLoopHeader(statement); +} + + +void HSubgraph::AppendDoWhile(HSubgraph* body, + IterationStatement* statement, + HSubgraph* go_back, + HSubgraph* exit) { + ConnectExitTo(body->entry_block()); + go_back->ConnectExitTo(body->entry_block(), true); + + HBasicBlock* break_block = body->BundleBreak(statement); + exit_block_ = + JoinBlocks(exit->exit_block(), break_block, statement->ExitId()); + body->entry_block()->PostProcessLoopHeader(statement); +} + + +void HSubgraph::AppendWhile(HSubgraph* condition, + HSubgraph* body, + IterationStatement* statement, + HSubgraph* continue_subgraph, + HSubgraph* exit) { + ConnectExitTo(condition->entry_block()); + + HBasicBlock* break_block = body->BundleBreak(statement); + exit_block_ = + JoinBlocks(exit->exit_block(), break_block, statement->ExitId()); + + if (continue_subgraph != NULL) { + body->ConnectExitTo(continue_subgraph->entry_block(), true); + continue_subgraph->entry_block()->SetJoinId(statement->EntryId()); + exit_block_ = JoinBlocks(exit_block_, + continue_subgraph->exit_block(), + statement->ExitId()); + } else { + body->ConnectExitTo(condition->entry_block(), true); + } + condition->entry_block()->PostProcessLoopHeader(statement); +} + + +void HSubgraph::Append(HSubgraph* next, BreakableStatement* stmt) { + exit_block_->Goto(next->entry_block()); + exit_block_ = next->exit_block_; + + if (stmt != NULL) { + next->entry_block()->SetJoinId(stmt->EntryId()); + HBasicBlock* break_block = next->BundleBreak(stmt); + exit_block_ = JoinBlocks(exit_block(), break_block, stmt->ExitId()); + } +} + + +void HSubgraph::FinishExit(HControlInstruction* instruction) { + ASSERT(HasExit()); + exit_block_->Finish(instruction); + exit_block_->ClearEnvironment(); + exit_block_ = NULL; +} + + +void HSubgraph::FinishBreakContinue(BreakableStatement* target, + bool is_continue) { + ASSERT(!exit_block_->IsFinished()); + BreakContinueInfo* info = new BreakContinueInfo(target, exit_block_, + is_continue); + break_continue_info_.Add(info); + exit_block_ = NULL; +} + + +HGraph::HGraph(CompilationInfo* info) + : HSubgraph(this), + next_block_id_(0), + info_(info), + blocks_(8), + values_(16), + phi_list_(NULL) { + start_environment_ = new HEnvironment(NULL, info->scope(), info->closure()); + start_environment_->set_ast_id(info->function()->id()); +} + + +Handle<Code> HGraph::Compile() { + int values = GetMaximumValueID(); + if (values > LAllocator::max_initial_value_ids()) { + if (FLAG_trace_bailout) PrintF("Function is too big\n"); + return Handle<Code>::null(); + } + + LAllocator allocator(values, this); + LChunkBuilder builder(this, &allocator); + LChunk* chunk = builder.Build(); + if (chunk == NULL) return Handle<Code>::null(); + + if (!FLAG_alloc_lithium) return Handle<Code>::null(); + + allocator.Allocate(chunk); + + if (!FLAG_use_lithium) return Handle<Code>::null(); + + MacroAssembler assembler(NULL, 0); + LCodeGen generator(chunk, &assembler, info()); + + if (FLAG_eliminate_empty_blocks) { + chunk->MarkEmptyBlocks(); + } + + if (generator.GenerateCode()) { + if (FLAG_trace_codegen) { + PrintF("Crankshaft Compiler - "); + } + CodeGenerator::MakeCodePrologue(info()); + Code::Flags flags = + Code::ComputeFlags(Code::OPTIMIZED_FUNCTION, NOT_IN_LOOP); + Handle<Code> code = + CodeGenerator::MakeCodeEpilogue(&assembler, flags, info()); + generator.FinishCode(code); + CodeGenerator::PrintCode(code, info()); + return code; + } + return Handle<Code>::null(); +} + + +HBasicBlock* HGraph::CreateBasicBlock() { + HBasicBlock* result = new HBasicBlock(this); + blocks_.Add(result); + return result; +} + + +void HGraph::Canonicalize() { + HPhase phase("Canonicalize", this); + if (FLAG_use_canonicalizing) { + for (int i = 0; i < blocks()->length(); ++i) { + HBasicBlock* b = blocks()->at(i); + for (HInstruction* insn = b->first(); insn != NULL; insn = insn->next()) { + HValue* value = insn->Canonicalize(); + if (value != insn) { + if (value != NULL) { + insn->ReplaceAndDelete(value); + } else { + insn->Delete(); + } + } + } + } + } +} + + +void HGraph::OrderBlocks() { + HPhase phase("Block ordering"); + BitVector visited(blocks_.length()); + + ZoneList<HBasicBlock*> reverse_result(8); + HBasicBlock* start = blocks_[0]; + Postorder(start, &visited, &reverse_result, NULL); + + blocks_.Clear(); + int index = 0; + for (int i = reverse_result.length() - 1; i >= 0; --i) { + HBasicBlock* b = reverse_result[i]; + blocks_.Add(b); + b->set_block_id(index++); + } +} + + +void HGraph::PostorderLoopBlocks(HLoopInformation* loop, + BitVector* visited, + ZoneList<HBasicBlock*>* order, + HBasicBlock* loop_header) { + for (int i = 0; i < loop->blocks()->length(); ++i) { + HBasicBlock* b = loop->blocks()->at(i); + Postorder(b->end()->SecondSuccessor(), visited, order, loop_header); + Postorder(b->end()->FirstSuccessor(), visited, order, loop_header); + if (b->IsLoopHeader() && b != loop->loop_header()) { + PostorderLoopBlocks(b->loop_information(), visited, order, loop_header); + } + } +} + + +void HGraph::Postorder(HBasicBlock* block, + BitVector* visited, + ZoneList<HBasicBlock*>* order, + HBasicBlock* loop_header) { + if (block == NULL || visited->Contains(block->block_id())) return; + if (block->parent_loop_header() != loop_header) return; + visited->Add(block->block_id()); + if (block->IsLoopHeader()) { + PostorderLoopBlocks(block->loop_information(), visited, order, loop_header); + Postorder(block->end()->SecondSuccessor(), visited, order, block); + Postorder(block->end()->FirstSuccessor(), visited, order, block); + } else { + Postorder(block->end()->SecondSuccessor(), visited, order, loop_header); + Postorder(block->end()->FirstSuccessor(), visited, order, loop_header); + } + ASSERT(block->end()->FirstSuccessor() == NULL || + order->Contains(block->end()->FirstSuccessor()) || + block->end()->FirstSuccessor()->IsLoopHeader()); + ASSERT(block->end()->SecondSuccessor() == NULL || + order->Contains(block->end()->SecondSuccessor()) || + block->end()->SecondSuccessor()->IsLoopHeader()); + order->Add(block); +} + + +void HGraph::AssignDominators() { + HPhase phase("Assign dominators", this); + for (int i = 0; i < blocks_.length(); ++i) { + if (blocks_[i]->IsLoopHeader()) { + blocks_[i]->AssignCommonDominator(blocks_[i]->predecessors()->first()); + } else { + for (int j = 0; j < blocks_[i]->predecessors()->length(); ++j) { + blocks_[i]->AssignCommonDominator(blocks_[i]->predecessors()->at(j)); + } + } + } +} + + +void HGraph::EliminateRedundantPhis() { + HPhase phase("Phi elimination", this); + ZoneList<HValue*> uses_to_replace(2); + + // Worklist of phis that can potentially be eliminated. Initialized + // with all phi nodes. When elimination of a phi node modifies + // another phi node the modified phi node is added to the worklist. + ZoneList<HPhi*> worklist(blocks_.length()); + for (int i = 0; i < blocks_.length(); ++i) { + worklist.AddAll(*blocks_[i]->phis()); + } + + while (!worklist.is_empty()) { + HPhi* phi = worklist.RemoveLast(); + HBasicBlock* block = phi->block(); + + // Skip phi node if it was already replaced. + if (block == NULL) continue; + + // Get replacement value if phi is redundant. + HValue* value = phi->GetRedundantReplacement(); + + if (value != NULL) { + // Iterate through uses finding the ones that should be + // replaced. + const ZoneList<HValue*>* uses = phi->uses(); + for (int i = 0; i < uses->length(); ++i) { + HValue* use = uses->at(i); + if (!use->block()->IsStartBlock()) { + uses_to_replace.Add(use); + } + } + // Replace the uses and add phis modified to the work list. + for (int i = 0; i < uses_to_replace.length(); ++i) { + HValue* use = uses_to_replace[i]; + phi->ReplaceAtUse(use, value); + if (use->IsPhi()) worklist.Add(HPhi::cast(use)); + } + uses_to_replace.Rewind(0); + block->RemovePhi(phi); + } else if (phi->HasNoUses() && + !phi->HasReceiverOperand() && + FLAG_eliminate_dead_phis) { + // We can't eliminate phis that have the receiver as an operand + // because in case of throwing an error we need the correct + // receiver value in the environment to construct a corrent + // stack trace. + block->RemovePhi(phi); + block->RecordDeletedPhi(phi->merged_index()); + } + } +} + + +bool HGraph::CollectPhis() { + const ZoneList<HBasicBlock*>* blocks = graph_->blocks(); + phi_list_ = new ZoneList<HPhi*>(blocks->length()); + for (int i = 0; i < blocks->length(); ++i) { + for (int j = 0; j < blocks->at(i)->phis()->length(); j++) { + HPhi* phi = blocks->at(i)->phis()->at(j); + phi_list_->Add(phi); + // We don't support phi uses of arguments for now. + if (phi->CheckFlag(HValue::kIsArguments)) return false; + } + } + return true; +} + + +void HGraph::InferTypes(ZoneList<HValue*>* worklist) { + BitVector in_worklist(GetMaximumValueID()); + for (int i = 0; i < worklist->length(); ++i) { + ASSERT(!in_worklist.Contains(worklist->at(i)->id())); + in_worklist.Add(worklist->at(i)->id()); + } + + while (!worklist->is_empty()) { + HValue* current = worklist->RemoveLast(); + in_worklist.Remove(current->id()); + if (current->UpdateInferredType()) { + for (int j = 0; j < current->uses()->length(); j++) { + HValue* use = current->uses()->at(j); + if (!in_worklist.Contains(use->id())) { + in_worklist.Add(use->id()); + worklist->Add(use); + } + } + } + } +} + + +class HRangeAnalysis BASE_EMBEDDED { + public: + explicit HRangeAnalysis(HGraph* graph) : graph_(graph), changed_ranges_(16) {} + + void Analyze(); + + private: + void TraceRange(const char* msg, ...); + void Analyze(HBasicBlock* block); + void InferControlFlowRange(HBranch* branch, HBasicBlock* dest); + void InferControlFlowRange(Token::Value op, HValue* value, HValue* other); + void InferPhiRange(HPhi* phi); + void InferRange(HValue* value); + void RollBackTo(int index); + void AddRange(HValue* value, Range* range); + + HGraph* graph_; + ZoneList<HValue*> changed_ranges_; +}; + + +void HRangeAnalysis::TraceRange(const char* msg, ...) { + if (FLAG_trace_range) { + va_list arguments; + va_start(arguments, msg); + OS::VPrint(msg, arguments); + va_end(arguments); + } +} + + +void HRangeAnalysis::Analyze() { + HPhase phase("Range analysis", graph_); + Analyze(graph_->blocks()->at(0)); +} + + +void HRangeAnalysis::Analyze(HBasicBlock* block) { + TraceRange("Analyzing block B%d\n", block->block_id()); + + int last_changed_range = changed_ranges_.length() - 1; + + // Infer range based on control flow. + if (block->predecessors()->length() == 1) { + HBasicBlock* pred = block->predecessors()->first(); + if (pred->end()->IsBranch()) { + InferControlFlowRange(HBranch::cast(pred->end()), block); + } + } + + // Process phi instructions. + for (int i = 0; i < block->phis()->length(); ++i) { + HPhi* phi = block->phis()->at(i); + InferPhiRange(phi); + } + + // Go through all instructions of the current block. + HInstruction* instr = block->first(); + while (instr != block->end()) { + InferRange(instr); + instr = instr->next(); + } + + // Continue analysis in all dominated blocks. + for (int i = 0; i < block->dominated_blocks()->length(); ++i) { + Analyze(block->dominated_blocks()->at(i)); + } + + RollBackTo(last_changed_range); +} + + +void HRangeAnalysis::InferControlFlowRange(HBranch* branch, HBasicBlock* dest) { + ASSERT(branch->FirstSuccessor() == dest || branch->SecondSuccessor() == dest); + ASSERT(branch->FirstSuccessor() != dest || branch->SecondSuccessor() != dest); + + if (branch->value()->IsCompare()) { + HCompare* compare = HCompare::cast(branch->value()); + Token::Value op = compare->token(); + if (branch->SecondSuccessor() == dest) { + op = Token::NegateCompareOp(op); + } + Token::Value inverted_op = Token::InvertCompareOp(op); + InferControlFlowRange(op, compare->left(), compare->right()); + InferControlFlowRange(inverted_op, compare->right(), compare->left()); + } +} + + +// We know that value [op] other. Use this information to update the range on +// value. +void HRangeAnalysis::InferControlFlowRange(Token::Value op, + HValue* value, + HValue* other) { + Range* range = other->range(); + if (range == NULL) range = new Range(); + Range* new_range = NULL; + + TraceRange("Control flow range infer %d %s %d\n", + value->id(), + Token::Name(op), + other->id()); + + if (op == Token::EQ || op == Token::EQ_STRICT) { + // The same range has to apply for value. + new_range = range->Copy(); + } else if (op == Token::LT || op == Token::LTE) { + new_range = range->CopyClearLower(); + if (op == Token::LT) { + new_range->AddConstant(-1); + } + } else if (op == Token::GT || op == Token::GTE) { + new_range = range->CopyClearUpper(); + if (op == Token::GT) { + new_range->AddConstant(1); + } + } + + if (new_range != NULL && !new_range->IsMostGeneric()) { + AddRange(value, new_range); + } +} + + +void HRangeAnalysis::InferPhiRange(HPhi* phi) { + // TODO(twuerthinger): Infer loop phi ranges. + InferRange(phi); +} + + +void HRangeAnalysis::InferRange(HValue* value) { + ASSERT(!value->HasRange()); + if (!value->representation().IsNone()) { + value->ComputeInitialRange(); + Range* range = value->range(); + TraceRange("Initial inferred range of %d (%s) set to [%d,%d]\n", + value->id(), + value->Mnemonic(), + range->lower(), + range->upper()); + } +} + + +void HRangeAnalysis::RollBackTo(int index) { + for (int i = index + 1; i < changed_ranges_.length(); ++i) { + changed_ranges_[i]->RemoveLastAddedRange(); + } + changed_ranges_.Rewind(index + 1); +} + + +void HRangeAnalysis::AddRange(HValue* value, Range* range) { + Range* original_range = value->range(); + value->AddNewRange(range); + changed_ranges_.Add(value); + Range* new_range = value->range(); + TraceRange("Updated range of %d set to [%d,%d]\n", + value->id(), + new_range->lower(), + new_range->upper()); + if (original_range != NULL) { + TraceRange("Original range was [%d,%d]\n", + original_range->lower(), + original_range->upper()); + } + TraceRange("New information was [%d,%d]\n", + range->lower(), + range->upper()); +} + + +void TraceGVN(const char* msg, ...) { + if (FLAG_trace_gvn) { + va_list arguments; + va_start(arguments, msg); + OS::VPrint(msg, arguments); + va_end(arguments); + } +} + + +HValueMap::HValueMap(const HValueMap* other) + : array_size_(other->array_size_), + lists_size_(other->lists_size_), + count_(other->count_), + present_flags_(other->present_flags_), + array_(Zone::NewArray<HValueMapListElement>(other->array_size_)), + lists_(Zone::NewArray<HValueMapListElement>(other->lists_size_)), + free_list_head_(other->free_list_head_) { + memcpy(array_, other->array_, array_size_ * sizeof(HValueMapListElement)); + memcpy(lists_, other->lists_, lists_size_ * sizeof(HValueMapListElement)); +} + + +void HValueMap::Kill(int flags) { + int depends_flags = HValue::ConvertChangesToDependsFlags(flags); + if ((present_flags_ & depends_flags) == 0) return; + present_flags_ = 0; + for (int i = 0; i < array_size_; ++i) { + HValue* value = array_[i].value; + if (value != NULL) { + // Clear list of collisions first, so we know if it becomes empty. + int kept = kNil; // List of kept elements. + int next; + for (int current = array_[i].next; current != kNil; current = next) { + next = lists_[current].next; + if ((lists_[current].value->flags() & depends_flags) != 0) { + // Drop it. + count_--; + lists_[current].next = free_list_head_; + free_list_head_ = current; + } else { + // Keep it. + lists_[current].next = kept; + kept = current; + present_flags_ |= lists_[current].value->flags(); + } + } + array_[i].next = kept; + + // Now possibly drop directly indexed element. + if ((array_[i].value->flags() & depends_flags) != 0) { // Drop it. + count_--; + int head = array_[i].next; + if (head == kNil) { + array_[i].value = NULL; + } else { + array_[i].value = lists_[head].value; + array_[i].next = lists_[head].next; + lists_[head].next = free_list_head_; + free_list_head_ = head; + } + } else { + present_flags_ |= array_[i].value->flags(); // Keep it. + } + } + } +} + + +HValue* HValueMap::Lookup(HValue* value) const { + uint32_t hash = static_cast<uint32_t>(value->Hashcode()); + uint32_t pos = Bound(hash); + if (array_[pos].value != NULL) { + if (array_[pos].value->Equals(value)) return array_[pos].value; + int next = array_[pos].next; + while (next != kNil) { + if (lists_[next].value->Equals(value)) return lists_[next].value; + next = lists_[next].next; + } + } + return NULL; +} + + +void HValueMap::Resize(int new_size) { + ASSERT(new_size > count_); + // Hashing the values into the new array has no more collisions than in the + // old hash map, so we can use the existing lists_ array, if we are careful. + + // Make sure we have at least one free element. + if (free_list_head_ == kNil) { + ResizeLists(lists_size_ << 1); + } + + HValueMapListElement* new_array = + Zone::NewArray<HValueMapListElement>(new_size); + memset(new_array, 0, sizeof(HValueMapListElement) * new_size); + + HValueMapListElement* old_array = array_; + int old_size = array_size_; + + int old_count = count_; + count_ = 0; + // Do not modify present_flags_. It is currently correct. + array_size_ = new_size; + array_ = new_array; + + if (old_array != NULL) { + // Iterate over all the elements in lists, rehashing them. + for (int i = 0; i < old_size; ++i) { + if (old_array[i].value != NULL) { + int current = old_array[i].next; + while (current != kNil) { + Insert(lists_[current].value); + int next = lists_[current].next; + lists_[current].next = free_list_head_; + free_list_head_ = current; + current = next; + } + // Rehash the directly stored value. + Insert(old_array[i].value); + } + } + } + USE(old_count); + ASSERT(count_ == old_count); +} + + +void HValueMap::ResizeLists(int new_size) { + ASSERT(new_size > lists_size_); + + HValueMapListElement* new_lists = + Zone::NewArray<HValueMapListElement>(new_size); + memset(new_lists, 0, sizeof(HValueMapListElement) * new_size); + + HValueMapListElement* old_lists = lists_; + int old_size = lists_size_; + + lists_size_ = new_size; + lists_ = new_lists; + + if (old_lists != NULL) { + memcpy(lists_, old_lists, old_size * sizeof(HValueMapListElement)); + } + for (int i = old_size; i < lists_size_; ++i) { + lists_[i].next = free_list_head_; + free_list_head_ = i; + } +} + + +void HValueMap::Insert(HValue* value) { + ASSERT(value != NULL); + // Resizing when half of the hashtable is filled up. + if (count_ >= array_size_ >> 1) Resize(array_size_ << 1); + ASSERT(count_ < array_size_); + count_++; + uint32_t pos = Bound(static_cast<uint32_t>(value->Hashcode())); + if (array_[pos].value == NULL) { + array_[pos].value = value; + array_[pos].next = kNil; + } else { + if (free_list_head_ == kNil) { + ResizeLists(lists_size_ << 1); + } + int new_element_pos = free_list_head_; + ASSERT(new_element_pos != kNil); + free_list_head_ = lists_[free_list_head_].next; + lists_[new_element_pos].value = value; + lists_[new_element_pos].next = array_[pos].next; + ASSERT(array_[pos].next == kNil || lists_[array_[pos].next].value != NULL); + array_[pos].next = new_element_pos; + } +} + + +class HStackCheckEliminator BASE_EMBEDDED { + public: + explicit HStackCheckEliminator(HGraph* graph) : graph_(graph) { } + + void Process(); + + private: + void RemoveStackCheck(HBasicBlock* block); + + HGraph* graph_; +}; + + +void HStackCheckEliminator::Process() { + // For each loop block walk the dominator tree from the backwards branch to + // the loop header. If a call instruction is encountered the backwards branch + // is dominated by a call and the stack check in the backwards branch can be + // removed. + for (int i = 0; i < graph_->blocks()->length(); i++) { + HBasicBlock* block = graph_->blocks()->at(i); + if (block->IsLoopHeader()) { + HBasicBlock* back_edge = block->loop_information()->GetLastBackEdge(); + HBasicBlock* dominator = back_edge; + bool back_edge_dominated_by_call = false; + while (dominator != block && !back_edge_dominated_by_call) { + HInstruction* instr = dominator->first(); + while (instr != NULL && !back_edge_dominated_by_call) { + if (instr->IsCall()) { + RemoveStackCheck(back_edge); + back_edge_dominated_by_call = true; + } + instr = instr->next(); + } + dominator = dominator->dominator(); + } + } + } +} + + +void HStackCheckEliminator::RemoveStackCheck(HBasicBlock* block) { + HInstruction* instr = block->first(); + while (instr != NULL) { + if (instr->IsGoto()) { + HGoto::cast(instr)->set_include_stack_check(false); + return; + } + instr = instr->next(); + } +} + + +class HGlobalValueNumberer BASE_EMBEDDED { + public: + explicit HGlobalValueNumberer(HGraph* graph) + : graph_(graph), + block_side_effects_(graph_->blocks()->length()), + loop_side_effects_(graph_->blocks()->length()) { + ASSERT(Heap::allow_allocation(false)); + block_side_effects_.AddBlock(0, graph_->blocks()->length()); + loop_side_effects_.AddBlock(0, graph_->blocks()->length()); + } + ~HGlobalValueNumberer() { + ASSERT(!Heap::allow_allocation(true)); + } + + void Analyze(); + + private: + void AnalyzeBlock(HBasicBlock* block, HValueMap* map); + void ComputeBlockSideEffects(); + void LoopInvariantCodeMotion(); + void ProcessLoopBlock(HBasicBlock* block, + HBasicBlock* before_loop, + int loop_kills); + bool ShouldMove(HInstruction* instr, HBasicBlock* loop_header); + + HGraph* graph_; + + // A map of block IDs to their side effects. + ZoneList<int> block_side_effects_; + + // A map of loop header block IDs to their loop's side effects. + ZoneList<int> loop_side_effects_; +}; + + +void HGlobalValueNumberer::Analyze() { + ComputeBlockSideEffects(); + if (FLAG_loop_invariant_code_motion) { + LoopInvariantCodeMotion(); + } + HValueMap* map = new HValueMap(); + AnalyzeBlock(graph_->blocks()->at(0), map); +} + + +void HGlobalValueNumberer::ComputeBlockSideEffects() { + for (int i = graph_->blocks()->length() - 1; i >= 0; --i) { + // Compute side effects for the block. + HBasicBlock* block = graph_->blocks()->at(i); + HInstruction* instr = block->first(); + int id = block->block_id(); + int side_effects = 0; + while (instr != NULL) { + side_effects |= (instr->flags() & HValue::ChangesFlagsMask()); + instr = instr->next(); + } + block_side_effects_[id] |= side_effects; + + // Loop headers are part of their loop. + if (block->IsLoopHeader()) { + loop_side_effects_[id] |= side_effects; + } + + // Propagate loop side effects upwards. + if (block->HasParentLoopHeader()) { + int header_id = block->parent_loop_header()->block_id(); + loop_side_effects_[header_id] |= + block->IsLoopHeader() ? loop_side_effects_[id] : side_effects; + } + } +} + + +void HGlobalValueNumberer::LoopInvariantCodeMotion() { + for (int i = graph_->blocks()->length() - 1; i >= 0; --i) { + HBasicBlock* block = graph_->blocks()->at(i); + if (block->IsLoopHeader()) { + int side_effects = loop_side_effects_[block->block_id()]; + TraceGVN("Try loop invariant motion for block B%d effects=0x%x\n", + block->block_id(), + side_effects); + + HBasicBlock* last = block->loop_information()->GetLastBackEdge(); + for (int j = block->block_id(); j <= last->block_id(); ++j) { + ProcessLoopBlock(graph_->blocks()->at(j), block, side_effects); + } + } + } +} + + +void HGlobalValueNumberer::ProcessLoopBlock(HBasicBlock* block, + HBasicBlock* loop_header, + int loop_kills) { + HBasicBlock* pre_header = loop_header->predecessors()->at(0); + int depends_flags = HValue::ConvertChangesToDependsFlags(loop_kills); + TraceGVN("Loop invariant motion for B%d depends_flags=0x%x\n", + block->block_id(), + depends_flags); + HInstruction* instr = block->first(); + while (instr != NULL) { + HInstruction* next = instr->next(); + if (instr->CheckFlag(HValue::kUseGVN) && + (instr->flags() & depends_flags) == 0) { + TraceGVN("Checking instruction %d (%s)\n", + instr->id(), + instr->Mnemonic()); + bool inputs_loop_invariant = true; + for (int i = 0; i < instr->OperandCount(); ++i) { + if (instr->OperandAt(i)->IsDefinedAfter(pre_header)) { + inputs_loop_invariant = false; + } + } + + if (inputs_loop_invariant && ShouldMove(instr, loop_header)) { + TraceGVN("Found loop invariant instruction %d\n", instr->id()); + // Move the instruction out of the loop. + instr->Unlink(); + instr->InsertBefore(pre_header->end()); + } + } + instr = next; + } +} + +// Only move instructions that postdominate the loop header (i.e. are +// always executed inside the loop). This is to avoid unnecessary +// deoptimizations assuming the loop is executed at least once. +// TODO(fschneider): Better type feedback should give us information +// about code that was never executed. +bool HGlobalValueNumberer::ShouldMove(HInstruction* instr, + HBasicBlock* loop_header) { + if (!instr->IsChange() && + FLAG_aggressive_loop_invariant_motion) return true; + HBasicBlock* block = instr->block(); + bool result = true; + if (block != loop_header) { + for (int i = 1; i < loop_header->predecessors()->length(); ++i) { + bool found = false; + HBasicBlock* pred = loop_header->predecessors()->at(i); + while (pred != loop_header) { + if (pred == block) found = true; + pred = pred->dominator(); + } + if (!found) { + result = false; + break; + } + } + } + return result; +} + + +void HGlobalValueNumberer::AnalyzeBlock(HBasicBlock* block, HValueMap* map) { + TraceGVN("Analyzing block B%d\n", block->block_id()); + + // If this is a loop header kill everything killed by the loop. + if (block->IsLoopHeader()) { + map->Kill(loop_side_effects_[block->block_id()]); + } + + // Go through all instructions of the current block. + HInstruction* instr = block->first(); + while (instr != NULL) { + HInstruction* next = instr->next(); + int flags = (instr->flags() & HValue::ChangesFlagsMask()); + if (flags != 0) { + ASSERT(!instr->CheckFlag(HValue::kUseGVN)); + // Clear all instructions in the map that are affected by side effects. + map->Kill(flags); + TraceGVN("Instruction %d kills\n", instr->id()); + } else if (instr->CheckFlag(HValue::kUseGVN)) { + HValue* other = map->Lookup(instr); + if (other != NULL) { + ASSERT(instr->Equals(other) && other->Equals(instr)); + TraceGVN("Replacing value %d (%s) with value %d (%s)\n", + instr->id(), + instr->Mnemonic(), + other->id(), + other->Mnemonic()); + instr->ReplaceValue(other); + instr->Delete(); + } else { + map->Add(instr); + } + } + instr = next; + } + + // Recursively continue analysis for all immediately dominated blocks. + int length = block->dominated_blocks()->length(); + for (int i = 0; i < length; ++i) { + HBasicBlock* dominated = block->dominated_blocks()->at(i); + // No need to copy the map for the last child in the dominator tree. + HValueMap* successor_map = (i == length - 1) ? map : map->Copy(); + + // If the dominated block is not a successor to this block we have to + // kill everything killed on any path between this block and the + // dominated block. Note we rely on the block ordering. + bool is_successor = false; + int predecessor_count = dominated->predecessors()->length(); + for (int j = 0; !is_successor && j < predecessor_count; ++j) { + is_successor = (dominated->predecessors()->at(j) == block); + } + + if (!is_successor) { + int side_effects = 0; + for (int j = block->block_id() + 1; j < dominated->block_id(); ++j) { + side_effects |= block_side_effects_[j]; + } + successor_map->Kill(side_effects); + } + + AnalyzeBlock(dominated, successor_map); + } +} + + +class HInferRepresentation BASE_EMBEDDED { + public: + explicit HInferRepresentation(HGraph* graph) + : graph_(graph), worklist_(8), in_worklist_(graph->GetMaximumValueID()) {} + + void Analyze(); + + private: + Representation TryChange(HValue* current); + void AddToWorklist(HValue* current); + void InferBasedOnInputs(HValue* current); + void AddDependantsToWorklist(HValue* current); + void InferBasedOnUses(HValue* current); + + HGraph* graph_; + ZoneList<HValue*> worklist_; + BitVector in_worklist_; +}; + + +void HInferRepresentation::AddToWorklist(HValue* current) { + if (current->representation().IsSpecialization()) return; + if (!current->CheckFlag(HValue::kFlexibleRepresentation)) return; + if (in_worklist_.Contains(current->id())) return; + worklist_.Add(current); + in_worklist_.Add(current->id()); +} + + +// This method tries to specialize the representation type of the value +// given as a parameter. The value is asked to infer its representation type +// based on its inputs. If the inferred type is more specialized, then this +// becomes the new representation type of the node. +void HInferRepresentation::InferBasedOnInputs(HValue* current) { + Representation r = current->representation(); + if (r.IsSpecialization()) return; + ASSERT(current->CheckFlag(HValue::kFlexibleRepresentation)); + Representation inferred = current->InferredRepresentation(); + if (inferred.IsSpecialization()) { + current->ChangeRepresentation(inferred); + AddDependantsToWorklist(current); + } +} + + +void HInferRepresentation::AddDependantsToWorklist(HValue* current) { + for (int i = 0; i < current->uses()->length(); ++i) { + AddToWorklist(current->uses()->at(i)); + } + for (int i = 0; i < current->OperandCount(); ++i) { + AddToWorklist(current->OperandAt(i)); + } +} + + +// This method calculates whether specializing the representation of the value +// given as the parameter has a benefit in terms of less necessary type +// conversions. If there is a benefit, then the representation of the value is +// specialized. +void HInferRepresentation::InferBasedOnUses(HValue* current) { + Representation r = current->representation(); + if (r.IsSpecialization() || current->HasNoUses()) return; + ASSERT(current->CheckFlag(HValue::kFlexibleRepresentation)); + Representation new_rep = TryChange(current); + if (!new_rep.IsNone()) { + if (!current->representation().Equals(new_rep)) { + current->ChangeRepresentation(new_rep); + AddDependantsToWorklist(current); + } + } +} + + +Representation HInferRepresentation::TryChange(HValue* current) { + // Array of use counts for each representation. + int use_count[Representation::kNumRepresentations]; + for (int i = 0; i < Representation::kNumRepresentations; i++) { + use_count[i] = 0; + } + + for (int i = 0; i < current->uses()->length(); ++i) { + HValue* use = current->uses()->at(i); + int index = use->LookupOperandIndex(0, current); + Representation req_rep = use->RequiredInputRepresentation(index); + if (req_rep.IsNone()) continue; + if (use->IsPhi()) { + HPhi* phi = HPhi::cast(use); + phi->AddIndirectUsesTo(&use_count[0]); + } + use_count[req_rep.kind()]++; + } + int tagged_count = use_count[Representation::kTagged]; + int double_count = use_count[Representation::kDouble]; + int int32_count = use_count[Representation::kInteger32]; + int non_tagged_count = double_count + int32_count; + + // If a non-loop phi has tagged uses, don't convert it to untagged. + if (current->IsPhi() && !current->block()->IsLoopHeader()) { + if (tagged_count > 0) return Representation::None(); + } + + if (non_tagged_count >= tagged_count) { + // More untagged than tagged. + if (double_count > 0) { + // There is at least one usage that is a double => guess that the + // correct representation is double. + return Representation::Double(); + } else if (int32_count > 0) { + return Representation::Integer32(); + } + } + return Representation::None(); +} + + +void HInferRepresentation::Analyze() { + HPhase phase("Infer representations", graph_); + + // (1) Initialize bit vectors and count real uses. Each phi + // gets a bit-vector of length <number of phis>. + const ZoneList<HPhi*>* phi_list = graph_->phi_list(); + int num_phis = phi_list->length(); + ScopedVector<BitVector*> connected_phis(num_phis); + for (int i = 0; i < num_phis; i++) { + phi_list->at(i)->InitRealUses(i); + connected_phis[i] = new BitVector(num_phis); + connected_phis[i]->Add(i); + } + + // (2) Do a fixed point iteration to find the set of connected phis. + // A phi is connected to another phi if its value is used either + // directly or indirectly through a transitive closure of the def-use + // relation. + bool change = true; + while (change) { + change = false; + for (int i = 0; i < num_phis; i++) { + HPhi* phi = phi_list->at(i); + for (int j = 0; j < phi->uses()->length(); j++) { + HValue* use = phi->uses()->at(j); + if (use->IsPhi()) { + int phi_use = HPhi::cast(use)->phi_id(); + if (connected_phis[i]->UnionIsChanged(*connected_phis[phi_use])) { + change = true; + } + } + } + } + } + + // (3) Sum up the non-phi use counts of all connected phis. + // Don't include the non-phi uses of the phi itself. + for (int i = 0; i < num_phis; i++) { + HPhi* phi = phi_list->at(i); + for (BitVector::Iterator it(connected_phis.at(i)); + !it.Done(); + it.Advance()) { + int index = it.Current(); + if (index != i) { + HPhi* it_use = phi_list->at(it.Current()); + phi->AddNonPhiUsesFrom(it_use); + } + } + } + + for (int i = 0; i < graph_->blocks()->length(); ++i) { + HBasicBlock* block = graph_->blocks()->at(i); + const ZoneList<HPhi*>* phis = block->phis(); + for (int j = 0; j < phis->length(); ++j) { + AddToWorklist(phis->at(j)); + } + + HInstruction* current = block->first(); + while (current != NULL) { + AddToWorklist(current); + current = current->next(); + } + } + + while (!worklist_.is_empty()) { + HValue* current = worklist_.RemoveLast(); + in_worklist_.Remove(current->id()); + InferBasedOnInputs(current); + InferBasedOnUses(current); + } +} + + +void HGraph::InitializeInferredTypes() { + HPhase phase("Inferring types", this); + InitializeInferredTypes(0, this->blocks_.length() - 1); +} + + +void HGraph::InitializeInferredTypes(int from_inclusive, int to_inclusive) { + for (int i = from_inclusive; i <= to_inclusive; ++i) { + HBasicBlock* block = blocks_[i]; + + const ZoneList<HPhi*>* phis = block->phis(); + for (int j = 0; j < phis->length(); j++) { + phis->at(j)->UpdateInferredType(); + } + + HInstruction* current = block->first(); + while (current != NULL) { + current->UpdateInferredType(); + current = current->next(); + } + + if (block->IsLoopHeader()) { + HBasicBlock* last_back_edge = + block->loop_information()->GetLastBackEdge(); + InitializeInferredTypes(i + 1, last_back_edge->block_id()); + // Skip all blocks already processed by the recursive call. + i = last_back_edge->block_id(); + // Update phis of the loop header now after the whole loop body is + // guaranteed to be processed. + ZoneList<HValue*> worklist(block->phis()->length()); + for (int j = 0; j < block->phis()->length(); ++j) { + worklist.Add(block->phis()->at(j)); + } + InferTypes(&worklist); + } + } +} + + +void HGraph::PropagateMinusZeroChecks(HValue* value, BitVector* visited) { + HValue* current = value; + while (current != NULL) { + if (visited->Contains(current->id())) return; + + // For phis, we must propagate the check to all of its inputs. + if (current->IsPhi()) { + visited->Add(current->id()); + HPhi* phi = HPhi::cast(current); + for (int i = 0; i < phi->OperandCount(); ++i) { + PropagateMinusZeroChecks(phi->OperandAt(i), visited); + } + break; + } + + // For multiplication and division, we must propagate to the left and + // the right side. + if (current->IsMul()) { + HMul* mul = HMul::cast(current); + mul->EnsureAndPropagateNotMinusZero(visited); + PropagateMinusZeroChecks(mul->left(), visited); + PropagateMinusZeroChecks(mul->right(), visited); + } else if (current->IsDiv()) { + HDiv* div = HDiv::cast(current); + div->EnsureAndPropagateNotMinusZero(visited); + PropagateMinusZeroChecks(div->left(), visited); + PropagateMinusZeroChecks(div->right(), visited); + } + + current = current->EnsureAndPropagateNotMinusZero(visited); + } +} + + +void HGraph::InsertRepresentationChangeForUse(HValue* value, + HValue* use, + Representation to, + bool is_truncating) { + // Propagate flags for negative zero checks upwards from conversions + // int32-to-tagged and int32-to-double. + Representation from = value->representation(); + if (from.IsInteger32()) { + ASSERT(to.IsTagged() || to.IsDouble()); + BitVector visited(GetMaximumValueID()); + PropagateMinusZeroChecks(value, &visited); + } + + // Insert the representation change right before its use. For phi-uses we + // insert at the end of the corresponding predecessor. + HBasicBlock* insert_block = use->block(); + if (use->IsPhi()) { + int index = 0; + while (use->OperandAt(index) != value) ++index; + insert_block = insert_block->predecessors()->at(index); + } + + HInstruction* next = (insert_block == use->block()) + ? HInstruction::cast(use) + : insert_block->end(); + + // For constants we try to make the representation change at compile + // time. When a representation change is not possible without loss of + // information we treat constants like normal instructions and insert the + // change instructions for them. + HInstruction* new_value = NULL; + if (value->IsConstant()) { + HConstant* constant = HConstant::cast(value); + // Try to create a new copy of the constant with the new representation. + new_value = is_truncating + ? constant->CopyToTruncatedInt32() + : constant->CopyToRepresentation(to); + } + + if (new_value == NULL) { + new_value = new HChange(value, value->representation(), to); + } + + new_value->InsertBefore(next); + value->ReplaceFirstAtUse(use, new_value, to); +} + + +int CompareConversionUses(HValue* a, + HValue* b, + Representation a_rep, + Representation b_rep) { + if (a_rep.kind() > b_rep.kind()) { + // Make sure specializations are separated in the result array. + return 1; + } + // Put truncating conversions before non-truncating conversions. + bool a_truncate = a->CheckFlag(HValue::kTruncatingToInt32); + bool b_truncate = b->CheckFlag(HValue::kTruncatingToInt32); + if (a_truncate != b_truncate) { + return a_truncate ? -1 : 1; + } + // Sort by increasing block ID. + return a->block()->block_id() - b->block()->block_id(); +} + + +void HGraph::InsertRepresentationChanges(HValue* current) { + Representation r = current->representation(); + if (r.IsNone()) return; + if (current->uses()->length() == 0) return; + + // Collect the representation changes in a sorted list. This allows + // us to avoid duplicate changes without searching the list. + ZoneList<HValue*> to_convert(2); + ZoneList<Representation> to_convert_reps(2); + for (int i = 0; i < current->uses()->length(); ++i) { + HValue* use = current->uses()->at(i); + // The occurrences index means the index within the operand array of "use" + // at which "current" is used. While iterating through the use array we + // also have to iterate over the different occurrence indices. + int occurrence_index = 0; + if (use->UsesMultipleTimes(current)) { + occurrence_index = current->uses()->CountOccurrences(use, 0, i - 1); + if (FLAG_trace_representation) { + PrintF("Instruction %d is used multiple times at %d; occurrence=%d\n", + current->id(), + use->id(), + occurrence_index); + } + } + int operand_index = use->LookupOperandIndex(occurrence_index, current); + Representation req = use->RequiredInputRepresentation(operand_index); + if (req.IsNone() || req.Equals(r)) continue; + int index = 0; + while (to_convert.length() > index && + CompareConversionUses(to_convert[index], + use, + to_convert_reps[index], + req) < 0) { + ++index; + } + if (FLAG_trace_representation) { + PrintF("Inserting a representation change to %s of %d for use at %d\n", + req.Mnemonic(), + current->id(), + use->id()); + } + to_convert.InsertAt(index, use); + to_convert_reps.InsertAt(index, req); + } + + for (int i = 0; i < to_convert.length(); ++i) { + HValue* use = to_convert[i]; + Representation r_to = to_convert_reps[i]; + bool is_truncating = use->CheckFlag(HValue::kTruncatingToInt32); + InsertRepresentationChangeForUse(current, use, r_to, is_truncating); + } + + if (current->uses()->is_empty()) { + ASSERT(current->IsConstant()); + current->Delete(); + } +} + + +void HGraph::InsertRepresentationChanges() { + HPhase phase("Insert representation changes", this); + + + // Compute truncation flag for phis: Initially assume that all + // int32-phis allow truncation and iteratively remove the ones that + // are used in an operation that does not allow a truncating + // conversion. + // TODO(fschneider): Replace this with a worklist-based iteration. + for (int i = 0; i < phi_list()->length(); i++) { + HPhi* phi = phi_list()->at(i); + if (phi->representation().IsInteger32()) { + phi->SetFlag(HValue::kTruncatingToInt32); + } + } + bool change = true; + while (change) { + change = false; + for (int i = 0; i < phi_list()->length(); i++) { + HPhi* phi = phi_list()->at(i); + if (!phi->CheckFlag(HValue::kTruncatingToInt32)) continue; + for (int j = 0; j < phi->uses()->length(); j++) { + HValue* use = phi->uses()->at(j); + if (!use->CheckFlag(HValue::kTruncatingToInt32)) { + phi->ClearFlag(HValue::kTruncatingToInt32); + change = true; + break; + } + } + } + } + + for (int i = 0; i < blocks_.length(); ++i) { + // Process phi instructions first. + for (int j = 0; j < blocks_[i]->phis()->length(); j++) { + HPhi* phi = blocks_[i]->phis()->at(j); + InsertRepresentationChanges(phi); + } + + // Process normal instructions. + HInstruction* current = blocks_[i]->first(); + while (current != NULL) { + InsertRepresentationChanges(current); + current = current->next(); + } + } +} + + +// Implementation of utility classes to represent an expression's context in +// the AST. +AstContext::AstContext(HGraphBuilder* owner, Expression::Context kind) + : owner_(owner), kind_(kind), outer_(owner->ast_context()) { + owner->set_ast_context(this); // Push. +#ifdef DEBUG + original_count_ = owner->environment()->total_count(); +#endif +} + + +AstContext::~AstContext() { + owner_->set_ast_context(outer_); // Pop. +} + + +EffectContext::~EffectContext() { + ASSERT(owner()->HasStackOverflow() || + !owner()->subgraph()->HasExit() || + owner()->environment()->total_count() == original_count_); +} + + +ValueContext::~ValueContext() { + ASSERT(owner()->HasStackOverflow() || + !owner()->subgraph()->HasExit() || + owner()->environment()->total_count() == original_count_ + 1); +} + + +void EffectContext::ReturnValue(HValue* value) { + // The value is simply ignored. +} + + +void ValueContext::ReturnValue(HValue* value) { + // The value is tracked in the bailout environment, and communicated + // through the environment as the result of the expression. + owner()->Push(value); +} + + +void TestContext::ReturnValue(HValue* value) { + BuildBranch(value); +} + + +void EffectContext::ReturnInstruction(HInstruction* instr, int ast_id) { + owner()->AddInstruction(instr); + if (instr->HasSideEffects()) owner()->AddSimulate(ast_id); +} + + +void ValueContext::ReturnInstruction(HInstruction* instr, int ast_id) { + owner()->AddInstruction(instr); + owner()->Push(instr); + if (instr->HasSideEffects()) owner()->AddSimulate(ast_id); +} + + +void TestContext::ReturnInstruction(HInstruction* instr, int ast_id) { + HGraphBuilder* builder = owner(); + builder->AddInstruction(instr); + // We expect a simulate after every expression with side effects, though + // this one isn't actually needed (and wouldn't work if it were targeted). + if (instr->HasSideEffects()) { + builder->Push(instr); + builder->AddSimulate(ast_id); + builder->Pop(); + } + BuildBranch(instr); +} + + +void TestContext::BuildBranch(HValue* value) { + // We expect the graph to be in edge-split form: there is no edge that + // connects a branch node to a join node. We conservatively ensure that + // property by always adding an empty block on the outgoing edges of this + // branch. + HGraphBuilder* builder = owner(); + HBasicBlock* empty_true = builder->graph()->CreateBasicBlock(); + HBasicBlock* empty_false = builder->graph()->CreateBasicBlock(); + HBranch* branch = new HBranch(empty_true, empty_false, value); + builder->CurrentBlock()->Finish(branch); + + HValue* const no_return_value = NULL; + HBasicBlock* true_target = if_true(); + if (true_target->IsInlineReturnTarget()) { + empty_true->AddLeaveInlined(no_return_value, true_target); + } else { + empty_true->Goto(true_target); + } + + HBasicBlock* false_target = if_false(); + if (false_target->IsInlineReturnTarget()) { + empty_false->AddLeaveInlined(no_return_value, false_target); + } else { + empty_false->Goto(false_target); + } + builder->subgraph()->set_exit_block(NULL); +} + + +// HGraphBuilder infrastructure for bailing out and checking bailouts. +#define BAILOUT(reason) \ + do { \ + Bailout(reason); \ + return; \ + } while (false) + + +#define CHECK_BAILOUT \ + do { \ + if (HasStackOverflow()) return; \ + } while (false) + + +#define VISIT_FOR_EFFECT(expr) \ + do { \ + VisitForEffect(expr); \ + if (HasStackOverflow()) return; \ + } while (false) + + +#define VISIT_FOR_VALUE(expr) \ + do { \ + VisitForValue(expr); \ + if (HasStackOverflow()) return; \ + } while (false) + + +#define VISIT_FOR_CONTROL(expr, true_block, false_block) \ + do { \ + VisitForControl(expr, true_block, false_block); \ + if (HasStackOverflow()) return; \ + } while (false) + + +// 'thing' could be an expression, statement, or list of statements. +#define ADD_TO_SUBGRAPH(graph, thing) \ + do { \ + AddToSubgraph(graph, thing); \ + if (HasStackOverflow()) return; \ + } while (false) + + +class HGraphBuilder::SubgraphScope BASE_EMBEDDED { + public: + SubgraphScope(HGraphBuilder* builder, HSubgraph* new_subgraph) + : builder_(builder) { + old_subgraph_ = builder_->current_subgraph_; + subgraph_ = new_subgraph; + builder_->current_subgraph_ = subgraph_; + } + + ~SubgraphScope() { + old_subgraph_->AddBreakContinueInfo(subgraph_); + builder_->current_subgraph_ = old_subgraph_; + } + + HSubgraph* subgraph() const { return subgraph_; } + + private: + HGraphBuilder* builder_; + HSubgraph* old_subgraph_; + HSubgraph* subgraph_; +}; + + +void HGraphBuilder::Bailout(const char* reason) { + if (FLAG_trace_bailout) { + SmartPointer<char> debug_name = graph()->debug_name()->ToCString(); + PrintF("Bailout in HGraphBuilder: @\"%s\": %s\n", *debug_name, reason); + } + SetStackOverflow(); +} + + +void HGraphBuilder::VisitForEffect(Expression* expr) { + EffectContext for_effect(this); + Visit(expr); +} + + +void HGraphBuilder::VisitForValue(Expression* expr) { + ValueContext for_value(this); + Visit(expr); +} + + +void HGraphBuilder::VisitForControl(Expression* expr, + HBasicBlock* true_block, + HBasicBlock* false_block) { + TestContext for_test(this, true_block, false_block); + Visit(expr); +} + + +HValue* HGraphBuilder::VisitArgument(Expression* expr) { + VisitForValue(expr); + if (HasStackOverflow() || !subgraph()->HasExit()) return NULL; + return environment()->Top(); +} + + +void HGraphBuilder::VisitArgumentList(ZoneList<Expression*>* arguments) { + for (int i = 0; i < arguments->length(); i++) { + VisitArgument(arguments->at(i)); + if (HasStackOverflow() || !current_subgraph_->HasExit()) return; + } +} + + +HGraph* HGraphBuilder::CreateGraph(CompilationInfo* info) { + ASSERT(current_subgraph_ == NULL); + graph_ = new HGraph(info); + + { + HPhase phase("Block building"); + graph_->Initialize(CreateBasicBlock(graph_->start_environment())); + current_subgraph_ = graph_; + + Scope* scope = info->scope(); + SetupScope(scope); + VisitDeclarations(scope->declarations()); + + AddInstruction(new HStackCheck()); + + ZoneList<Statement*>* stmts = info->function()->body(); + HSubgraph* body = CreateGotoSubgraph(environment()); + AddToSubgraph(body, stmts); + if (HasStackOverflow()) return NULL; + current_subgraph_->Append(body, NULL); + body->entry_block()->SetJoinId(info->function()->id()); + + if (graph_->HasExit()) { + graph_->FinishExit(new HReturn(graph_->GetConstantUndefined())); + } + } + + graph_->OrderBlocks(); + graph_->AssignDominators(); + graph_->EliminateRedundantPhis(); + if (!graph_->CollectPhis()) { + Bailout("Phi-use of arguments object"); + return NULL; + } + + HInferRepresentation rep(graph_); + rep.Analyze(); + + if (FLAG_use_range) { + HRangeAnalysis rangeAnalysis(graph_); + rangeAnalysis.Analyze(); + } + + graph_->InitializeInferredTypes(); + graph_->Canonicalize(); + graph_->InsertRepresentationChanges(); + + // Eliminate redundant stack checks on backwards branches. + HStackCheckEliminator sce(graph_); + sce.Process(); + + // Perform common subexpression elimination and loop-invariant code motion. + if (FLAG_use_gvn) { + HPhase phase("Global value numbering", graph_); + HGlobalValueNumberer gvn(graph_); + gvn.Analyze(); + } + + return graph_; +} + + +void HGraphBuilder::AddToSubgraph(HSubgraph* graph, Statement* stmt) { + SubgraphScope scope(this, graph); + Visit(stmt); +} + + +void HGraphBuilder::AddToSubgraph(HSubgraph* graph, Expression* expr) { + SubgraphScope scope(this, graph); + VisitForValue(expr); +} + + +void HGraphBuilder::AddToSubgraph(HSubgraph* graph, + ZoneList<Statement*>* stmts) { + SubgraphScope scope(this, graph); + VisitStatements(stmts); +} + + +HInstruction* HGraphBuilder::AddInstruction(HInstruction* instr) { + ASSERT(current_subgraph_->HasExit()); + current_subgraph_->exit_block()->AddInstruction(instr); + return instr; +} + + +void HGraphBuilder::AddSimulate(int id) { + ASSERT(current_subgraph_->HasExit()); + current_subgraph_->exit_block()->AddSimulate(id); +} + + +void HGraphBuilder::AddPhi(HPhi* instr) { + ASSERT(current_subgraph_->HasExit()); + current_subgraph_->exit_block()->AddPhi(instr); +} + + +void HGraphBuilder::PushAndAdd(HInstruction* instr) { + Push(instr); + AddInstruction(instr); +} + + +void HGraphBuilder::PushArgumentsForStubCall(int argument_count) { + const int kMaxStubArguments = 4; + ASSERT_GE(kMaxStubArguments, argument_count); + // Push the arguments on the stack. + HValue* arguments[kMaxStubArguments]; + for (int i = argument_count - 1; i >= 0; i--) { + arguments[i] = Pop(); + } + for (int i = 0; i < argument_count; i++) { + AddInstruction(new HPushArgument(arguments[i])); + } +} + + +void HGraphBuilder::ProcessCall(HCall* call) { + for (int i = call->argument_count() - 1; i >= 0; --i) { + HValue* value = Pop(); + HPushArgument* push = new HPushArgument(value); + call->SetArgumentAt(i, push); + } + + for (int i = 0; i < call->argument_count(); ++i) { + AddInstruction(call->PushArgumentAt(i)); + } +} + + +void HGraphBuilder::SetupScope(Scope* scope) { + // We don't yet handle the function name for named function expressions. + if (scope->function() != NULL) BAILOUT("named function expression"); + + // We can't handle heap-allocated locals. + if (scope->num_heap_slots() > 0) BAILOUT("heap allocated locals"); + + HConstant* undefined_constant = + new HConstant(Factory::undefined_value(), Representation::Tagged()); + AddInstruction(undefined_constant); + graph_->set_undefined_constant(undefined_constant); + + // Set the initial values of parameters including "this". "This" has + // parameter index 0. + int count = scope->num_parameters() + 1; + for (int i = 0; i < count; ++i) { + HInstruction* parameter = AddInstruction(new HParameter(i)); + environment()->Bind(i, parameter); + } + + // Set the initial values of stack-allocated locals. + for (int i = count; i < environment()->values()->length(); ++i) { + environment()->Bind(i, undefined_constant); + } + + // Handle the arguments and arguments shadow variables specially (they do + // not have declarations). + if (scope->arguments() != NULL) { + HArgumentsObject* object = new HArgumentsObject; + AddInstruction(object); + graph()->SetArgumentsObject(object); + environment()->Bind(scope->arguments(), object); + environment()->Bind(scope->arguments_shadow(), object); + } +} + + +void HGraphBuilder::VisitStatements(ZoneList<Statement*>* statements) { + for (int i = 0; i < statements->length(); i++) { + Visit(statements->at(i)); + if (HasStackOverflow() || !current_subgraph_->HasExit()) break; + } +} + + +HBasicBlock* HGraphBuilder::CreateBasicBlock(HEnvironment* env) { + HBasicBlock* b = graph()->CreateBasicBlock(); + b->SetInitialEnvironment(env); + return b; +} + + +HSubgraph* HGraphBuilder::CreateInlinedSubgraph(HEnvironment* outer, + Handle<JSFunction> target, + FunctionLiteral* function) { + HConstant* undefined = graph()->GetConstantUndefined(); + HEnvironment* inner = + outer->CopyForInlining(target, function, true, undefined); + HSubgraph* subgraph = new HSubgraph(graph()); + subgraph->Initialize(CreateBasicBlock(inner)); + return subgraph; +} + + +HSubgraph* HGraphBuilder::CreateGotoSubgraph(HEnvironment* env) { + HSubgraph* subgraph = new HSubgraph(graph()); + HEnvironment* new_env = env->CopyWithoutHistory(); + subgraph->Initialize(CreateBasicBlock(new_env)); + return subgraph; +} + + +HSubgraph* HGraphBuilder::CreateEmptySubgraph() { + HSubgraph* subgraph = new HSubgraph(graph()); + subgraph->Initialize(graph()->CreateBasicBlock()); + return subgraph; +} + + +HSubgraph* HGraphBuilder::CreateBranchSubgraph(HEnvironment* env) { + HSubgraph* subgraph = new HSubgraph(graph()); + HEnvironment* new_env = env->Copy(); + subgraph->Initialize(CreateBasicBlock(new_env)); + return subgraph; +} + + +HSubgraph* HGraphBuilder::CreateLoopHeaderSubgraph(HEnvironment* env) { + HSubgraph* subgraph = new HSubgraph(graph()); + HBasicBlock* block = graph()->CreateBasicBlock(); + HEnvironment* new_env = env->CopyAsLoopHeader(block); + block->SetInitialEnvironment(new_env); + subgraph->Initialize(block); + subgraph->entry_block()->AttachLoopInformation(); + return subgraph; +} + + +void HGraphBuilder::VisitBlock(Block* stmt) { + if (stmt->labels() != NULL) { + HSubgraph* block_graph = CreateGotoSubgraph(environment()); + ADD_TO_SUBGRAPH(block_graph, stmt->statements()); + current_subgraph_->Append(block_graph, stmt); + } else { + VisitStatements(stmt->statements()); + } +} + + +void HGraphBuilder::VisitExpressionStatement(ExpressionStatement* stmt) { + VisitForEffect(stmt->expression()); +} + + +void HGraphBuilder::VisitEmptyStatement(EmptyStatement* stmt) { +} + + +void HGraphBuilder::VisitIfStatement(IfStatement* stmt) { + if (stmt->condition()->ToBooleanIsTrue()) { + AddSimulate(stmt->ThenId()); + Visit(stmt->then_statement()); + } else if (stmt->condition()->ToBooleanIsFalse()) { + AddSimulate(stmt->ElseId()); + Visit(stmt->else_statement()); + } else { + HSubgraph* then_graph = CreateEmptySubgraph(); + HSubgraph* else_graph = CreateEmptySubgraph(); + VISIT_FOR_CONTROL(stmt->condition(), + then_graph->entry_block(), + else_graph->entry_block()); + + then_graph->entry_block()->SetJoinId(stmt->ThenId()); + ADD_TO_SUBGRAPH(then_graph, stmt->then_statement()); + + else_graph->entry_block()->SetJoinId(stmt->ElseId()); + ADD_TO_SUBGRAPH(else_graph, stmt->else_statement()); + + current_subgraph_->AppendJoin(then_graph, else_graph, stmt); + } +} + + +void HGraphBuilder::VisitContinueStatement(ContinueStatement* stmt) { + current_subgraph_->FinishBreakContinue(stmt->target(), true); +} + + +void HGraphBuilder::VisitBreakStatement(BreakStatement* stmt) { + current_subgraph_->FinishBreakContinue(stmt->target(), false); +} + + +void HGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) { + AstContext* context = call_context(); + if (context == NULL) { + // Not an inlined return, so an actual one. + VISIT_FOR_VALUE(stmt->expression()); + HValue* result = environment()->Pop(); + subgraph()->FinishExit(new HReturn(result)); + } else { + // Return from an inlined function, visit the subexpression in the + // expression context of the call. + if (context->IsTest()) { + TestContext* test = TestContext::cast(context); + VisitForControl(stmt->expression(), + test->if_true(), + test->if_false()); + } else { + HValue* return_value = NULL; + if (context->IsEffect()) { + VISIT_FOR_EFFECT(stmt->expression()); + return_value = graph()->GetConstantUndefined(); + } else { + ASSERT(context->IsValue()); + VISIT_FOR_VALUE(stmt->expression()); + return_value = environment()->Pop(); + } + subgraph()->exit_block()->AddLeaveInlined(return_value, + function_return_); + subgraph()->set_exit_block(NULL); + } + } +} + + +void HGraphBuilder::VisitWithEnterStatement(WithEnterStatement* stmt) { + BAILOUT("WithEnterStatement"); +} + + +void HGraphBuilder::VisitWithExitStatement(WithExitStatement* stmt) { + BAILOUT("WithExitStatement"); +} + + +HCompare* HGraphBuilder::BuildSwitchCompare(HSubgraph* subgraph, + HValue* switch_value, + CaseClause* clause) { + AddToSubgraph(subgraph, clause->label()); + if (HasStackOverflow()) return NULL; + HValue* clause_value = subgraph->environment()->Pop(); + HCompare* compare = new HCompare(switch_value, + clause_value, + Token::EQ_STRICT); + compare->SetInputRepresentation(Representation::Integer32()); + subgraph->exit_block()->AddInstruction(compare); + return compare; +} + + +void HGraphBuilder::VisitSwitchStatement(SwitchStatement* stmt) { + VISIT_FOR_VALUE(stmt->tag()); + // TODO(3168478): simulate added for tag should be enough. + AddSimulate(stmt->EntryId()); + HValue* switch_value = Pop(); + + ZoneList<CaseClause*>* clauses = stmt->cases(); + int num_clauses = clauses->length(); + if (num_clauses == 0) return; + if (num_clauses > 128) BAILOUT("SwitchStatement: too many clauses"); + + int num_smi_clauses = num_clauses; + for (int i = 0; i < num_clauses; i++) { + CaseClause* clause = clauses->at(i); + if (clause->is_default()) continue; + clause->RecordTypeFeedback(oracle()); + if (!clause->IsSmiCompare()) { + if (i == 0) BAILOUT("SwitchStatement: no smi compares"); + // We will deoptimize if the first non-smi compare is reached. + num_smi_clauses = i; + break; + } + if (!clause->label()->IsSmiLiteral()) { + BAILOUT("SwitchStatement: non-literal switch label"); + } + } + + // The single exit block of the whole switch statement. + HBasicBlock* single_exit_block = graph_->CreateBasicBlock(); + + // Build a series of empty subgraphs for the comparisons. + // The default clause does not have a comparison subgraph. + ZoneList<HSubgraph*> compare_graphs(num_smi_clauses); + for (int i = 0; i < num_smi_clauses; i++) { + if (clauses->at(i)->is_default()) { + compare_graphs.Add(NULL); + } else { + compare_graphs.Add(CreateEmptySubgraph()); + } + } + + HSubgraph* prev_graph = current_subgraph_; + HCompare* prev_compare_inst = NULL; + for (int i = 0; i < num_smi_clauses; i++) { + CaseClause* clause = clauses->at(i); + if (clause->is_default()) continue; + + // Finish the previous graph by connecting it to the current. + HSubgraph* subgraph = compare_graphs.at(i); + if (prev_compare_inst == NULL) { + ASSERT(prev_graph == current_subgraph_); + prev_graph->exit_block()->Finish(new HGoto(subgraph->entry_block())); + } else { + HBasicBlock* empty = graph()->CreateBasicBlock(); + prev_graph->exit_block()->Finish(new HBranch(empty, + subgraph->entry_block(), + prev_compare_inst)); + } + + // Build instructions for current subgraph. + ASSERT(clause->IsSmiCompare()); + prev_compare_inst = BuildSwitchCompare(subgraph, switch_value, clause); + if (HasStackOverflow()) return; + + prev_graph = subgraph; + } + + // Finish last comparison if there was at least one comparison. + // last_false_block is the (empty) false-block of the last comparison. If + // there are no comparisons at all (a single default clause), it is just + // the last block of the current subgraph. + HBasicBlock* last_false_block = current_subgraph_->exit_block(); + if (prev_graph != current_subgraph_) { + last_false_block = graph()->CreateBasicBlock(); + HBasicBlock* empty = graph()->CreateBasicBlock(); + prev_graph->exit_block()->Finish(new HBranch(empty, + last_false_block, + prev_compare_inst)); + } + + // If we have a non-smi compare clause, we deoptimize after trying + // all the previous compares. + if (num_smi_clauses < num_clauses) { + last_false_block->Finish(new HDeoptimize); + } + + // Build statement blocks, connect them to their comparison block and + // to the previous statement block, if there is a fall-through. + HSubgraph* previous_subgraph = NULL; + for (int i = 0; i < num_clauses; i++) { + CaseClause* clause = clauses->at(i); + // Subgraph for the statements of the clause is only created when + // it's reachable either from the corresponding compare or as a + // fall-through from previous statements. + HSubgraph* subgraph = NULL; + + if (i < num_smi_clauses) { + if (clause->is_default()) { + if (!last_false_block->IsFinished()) { + // Default clause: Connect it to the last false block. + subgraph = CreateEmptySubgraph(); + last_false_block->Finish(new HGoto(subgraph->entry_block())); + } + } else { + ASSERT(clause->IsSmiCompare()); + // Connect with the corresponding comparison. + subgraph = CreateEmptySubgraph(); + HBasicBlock* empty = + compare_graphs.at(i)->exit_block()->end()->FirstSuccessor(); + empty->Finish(new HGoto(subgraph->entry_block())); + } + } + + // Check for fall-through from previous statement block. + if (previous_subgraph != NULL && previous_subgraph->HasExit()) { + if (subgraph == NULL) subgraph = CreateEmptySubgraph(); + previous_subgraph->exit_block()-> + Finish(new HGoto(subgraph->entry_block())); + } + + if (subgraph != NULL) { + ADD_TO_SUBGRAPH(subgraph, clause->statements()); + HBasicBlock* break_block = subgraph->BundleBreak(stmt); + if (break_block != NULL) { + break_block->Finish(new HGoto(single_exit_block)); + } + } + + previous_subgraph = subgraph; + } + + // If the last statement block has a fall-through, connect it to the + // single exit block. + if (previous_subgraph != NULL && previous_subgraph->HasExit()) { + previous_subgraph->exit_block()->Finish(new HGoto(single_exit_block)); + } + + // If there is no default clause finish the last comparison's false target. + if (!last_false_block->IsFinished()) { + last_false_block->Finish(new HGoto(single_exit_block)); + } + + if (single_exit_block->HasPredecessor()) { + current_subgraph_->set_exit_block(single_exit_block); + } else { + current_subgraph_->set_exit_block(NULL); + } +} + +bool HGraph::HasOsrEntryAt(IterationStatement* statement) { + return statement->OsrEntryId() == info()->osr_ast_id(); +} + + +void HSubgraph::PreProcessOsrEntry(IterationStatement* statement) { + if (!graph()->HasOsrEntryAt(statement)) return; + + HBasicBlock* non_osr_entry = graph()->CreateBasicBlock(); + HBasicBlock* osr_entry = graph()->CreateBasicBlock(); + HValue* true_value = graph()->GetConstantTrue(); + HBranch* branch = new HBranch(non_osr_entry, osr_entry, true_value); + exit_block()->Finish(branch); + + HBasicBlock* loop_predecessor = graph()->CreateBasicBlock(); + non_osr_entry->Goto(loop_predecessor); + + int osr_entry_id = statement->OsrEntryId(); + // We want the correct environment at the OsrEntry instruction. Build + // it explicitly. The expression stack should be empty. + int count = osr_entry->last_environment()->total_count(); + ASSERT(count == (osr_entry->last_environment()->parameter_count() + + osr_entry->last_environment()->local_count())); + for (int i = 0; i < count; ++i) { + HUnknownOSRValue* unknown = new HUnknownOSRValue; + osr_entry->AddInstruction(unknown); + osr_entry->last_environment()->Bind(i, unknown); + } + + osr_entry->AddSimulate(osr_entry_id); + osr_entry->AddInstruction(new HOsrEntry(osr_entry_id)); + osr_entry->Goto(loop_predecessor); + loop_predecessor->SetJoinId(statement->EntryId()); + set_exit_block(loop_predecessor); +} + + +void HGraphBuilder::VisitDoWhileStatement(DoWhileStatement* stmt) { + ASSERT(subgraph()->HasExit()); + subgraph()->PreProcessOsrEntry(stmt); + + HSubgraph* body_graph = CreateLoopHeaderSubgraph(environment()); + ADD_TO_SUBGRAPH(body_graph, stmt->body()); + body_graph->ResolveContinue(stmt); + + if (!body_graph->HasExit() || stmt->cond()->ToBooleanIsTrue()) { + current_subgraph_->AppendEndless(body_graph, stmt); + } else { + HSubgraph* go_back = CreateEmptySubgraph(); + HSubgraph* exit = CreateEmptySubgraph(); + { + SubgraphScope scope(this, body_graph); + VISIT_FOR_CONTROL(stmt->cond(), + go_back->entry_block(), + exit->entry_block()); + go_back->entry_block()->SetJoinId(stmt->BackEdgeId()); + exit->entry_block()->SetJoinId(stmt->ExitId()); + } + current_subgraph_->AppendDoWhile(body_graph, stmt, go_back, exit); + } +} + + +bool HGraphBuilder::ShouldPeel(HSubgraph* cond, HSubgraph* body) { + return FLAG_use_peeling; +} + + +void HGraphBuilder::VisitWhileStatement(WhileStatement* stmt) { + ASSERT(subgraph()->HasExit()); + subgraph()->PreProcessOsrEntry(stmt); + + HSubgraph* cond_graph = NULL; + HSubgraph* body_graph = NULL; + HSubgraph* exit_graph = NULL; + + // If the condition is constant true, do not generate a condition subgraph. + if (stmt->cond()->ToBooleanIsTrue()) { + body_graph = CreateLoopHeaderSubgraph(environment()); + ADD_TO_SUBGRAPH(body_graph, stmt->body()); + } else { + cond_graph = CreateLoopHeaderSubgraph(environment()); + body_graph = CreateEmptySubgraph(); + exit_graph = CreateEmptySubgraph(); + { + SubgraphScope scope(this, cond_graph); + VISIT_FOR_CONTROL(stmt->cond(), + body_graph->entry_block(), + exit_graph->entry_block()); + body_graph->entry_block()->SetJoinId(stmt->BodyId()); + exit_graph->entry_block()->SetJoinId(stmt->ExitId()); + } + ADD_TO_SUBGRAPH(body_graph, stmt->body()); + } + + body_graph->ResolveContinue(stmt); + + if (cond_graph != NULL) { + AppendPeeledWhile(stmt, cond_graph, body_graph, exit_graph); + } else { + // TODO(fschneider): Implement peeling for endless loops as well. + current_subgraph_->AppendEndless(body_graph, stmt); + } +} + + +void HGraphBuilder::AppendPeeledWhile(IterationStatement* stmt, + HSubgraph* cond_graph, + HSubgraph* body_graph, + HSubgraph* exit_graph) { + HSubgraph* loop = NULL; + if (body_graph->HasExit() && stmt != peeled_statement_ && + ShouldPeel(cond_graph, body_graph)) { + // Save the last peeled iteration statement to prevent infinite recursion. + IterationStatement* outer_peeled_statement = peeled_statement_; + peeled_statement_ = stmt; + loop = CreateGotoSubgraph(body_graph->environment()); + ADD_TO_SUBGRAPH(loop, stmt); + peeled_statement_ = outer_peeled_statement; + } + current_subgraph_->AppendWhile(cond_graph, body_graph, stmt, loop, + exit_graph); +} + + +void HGraphBuilder::VisitForStatement(ForStatement* stmt) { + // Only visit the init statement in the peeled part of the loop. + if (stmt->init() != NULL && peeled_statement_ != stmt) { + Visit(stmt->init()); + CHECK_BAILOUT; + } + ASSERT(subgraph()->HasExit()); + subgraph()->PreProcessOsrEntry(stmt); + + HSubgraph* cond_graph = NULL; + HSubgraph* body_graph = NULL; + HSubgraph* exit_graph = NULL; + if (stmt->cond() != NULL) { + cond_graph = CreateLoopHeaderSubgraph(environment()); + body_graph = CreateEmptySubgraph(); + exit_graph = CreateEmptySubgraph(); + { + SubgraphScope scope(this, cond_graph); + VISIT_FOR_CONTROL(stmt->cond(), + body_graph->entry_block(), + exit_graph->entry_block()); + body_graph->entry_block()->SetJoinId(stmt->BodyId()); + exit_graph->entry_block()->SetJoinId(stmt->ExitId()); + } + } else { + body_graph = CreateLoopHeaderSubgraph(environment()); + } + ADD_TO_SUBGRAPH(body_graph, stmt->body()); + + HSubgraph* next_graph = NULL; + body_graph->ResolveContinue(stmt); + + if (stmt->next() != NULL && body_graph->HasExit()) { + next_graph = CreateGotoSubgraph(body_graph->environment()); + ADD_TO_SUBGRAPH(next_graph, stmt->next()); + body_graph->Append(next_graph, NULL); + next_graph->entry_block()->SetJoinId(stmt->ContinueId()); + } + + if (cond_graph != NULL) { + AppendPeeledWhile(stmt, cond_graph, body_graph, exit_graph); + } else { + current_subgraph_->AppendEndless(body_graph, stmt); + } +} + + +void HGraphBuilder::VisitForInStatement(ForInStatement* stmt) { + BAILOUT("ForInStatement"); +} + + +void HGraphBuilder::VisitTryCatchStatement(TryCatchStatement* stmt) { + BAILOUT("TryCatchStatement"); +} + + +void HGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) { + BAILOUT("TryFinallyStatement"); +} + + +void HGraphBuilder::VisitDebuggerStatement(DebuggerStatement* stmt) { + BAILOUT("DebuggerStatement"); +} + + +void HGraphBuilder::VisitFunctionLiteral(FunctionLiteral* expr) { + Handle<SharedFunctionInfo> shared_info = + Compiler::BuildFunctionInfo(expr, graph_->info()->script()); + CHECK_BAILOUT; + HFunctionLiteral* instr = + new HFunctionLiteral(shared_info, expr->pretenure()); + ast_context()->ReturnInstruction(instr, expr->id()); +} + + +void HGraphBuilder::VisitSharedFunctionInfoLiteral( + SharedFunctionInfoLiteral* expr) { + BAILOUT("SharedFunctionInfoLiteral"); +} + + +void HGraphBuilder::VisitConditional(Conditional* expr) { + HSubgraph* then_graph = CreateEmptySubgraph(); + HSubgraph* else_graph = CreateEmptySubgraph(); + VISIT_FOR_CONTROL(expr->condition(), + then_graph->entry_block(), + else_graph->entry_block()); + + then_graph->entry_block()->SetJoinId(expr->ThenId()); + ADD_TO_SUBGRAPH(then_graph, expr->then_expression()); + + else_graph->entry_block()->SetJoinId(expr->ElseId()); + ADD_TO_SUBGRAPH(else_graph, expr->else_expression()); + + current_subgraph_->AppendJoin(then_graph, else_graph, expr); + ast_context()->ReturnValue(Pop()); +} + + +void HGraphBuilder::LookupGlobalPropertyCell(Variable* var, + LookupResult* lookup, + bool is_store) { + if (var->is_this()) { + BAILOUT("global this reference"); + } + if (!graph()->info()->has_global_object()) { + BAILOUT("no global object to optimize VariableProxy"); + } + Handle<GlobalObject> global(graph()->info()->global_object()); + global->Lookup(*var->name(), lookup); + if (!lookup->IsProperty()) { + BAILOUT("global variable cell not yet introduced"); + } + if (lookup->type() != NORMAL) { + BAILOUT("global variable has accessors"); + } + if (is_store && lookup->IsReadOnly()) { + BAILOUT("read-only global variable"); + } +} + + +void HGraphBuilder::VisitVariableProxy(VariableProxy* expr) { + Variable* variable = expr->AsVariable(); + if (variable == NULL) { + BAILOUT("reference to rewritten variable"); + } else if (variable->IsStackAllocated()) { + if (environment()->Lookup(variable)->CheckFlag(HValue::kIsArguments)) { + BAILOUT("unsupported context for arguments object"); + } + ast_context()->ReturnValue(environment()->Lookup(variable)); + } else if (variable->is_global()) { + LookupResult lookup; + LookupGlobalPropertyCell(variable, &lookup, false); + CHECK_BAILOUT; + + Handle<GlobalObject> global(graph()->info()->global_object()); + // TODO(3039103): Handle global property load through an IC call when access + // checks are enabled. + if (global->IsAccessCheckNeeded()) { + BAILOUT("global object requires access check"); + } + Handle<JSGlobalPropertyCell> cell(global->GetPropertyCell(&lookup)); + bool check_hole = !lookup.IsDontDelete() || lookup.IsReadOnly(); + HLoadGlobal* instr = new HLoadGlobal(cell, check_hole); + ast_context()->ReturnInstruction(instr, expr->id()); + } else { + BAILOUT("reference to non-stack-allocated/non-global variable"); + } +} + + +void HGraphBuilder::VisitLiteral(Literal* expr) { + HConstant* instr = new HConstant(expr->handle(), Representation::Tagged()); + ast_context()->ReturnInstruction(instr, expr->id()); +} + + +void HGraphBuilder::VisitRegExpLiteral(RegExpLiteral* expr) { + HRegExpLiteral* instr = new HRegExpLiteral(expr->pattern(), + expr->flags(), + expr->literal_index()); + ast_context()->ReturnInstruction(instr, expr->id()); +} + + +void HGraphBuilder::VisitObjectLiteral(ObjectLiteral* expr) { + HObjectLiteral* literal = (new HObjectLiteral(expr->constant_properties(), + expr->fast_elements(), + expr->literal_index(), + expr->depth())); + // The object is expected in the bailout environment during computation + // of the property values and is the value of the entire expression. + PushAndAdd(literal); + + expr->CalculateEmitStore(); + + for (int i = 0; i < expr->properties()->length(); i++) { + ObjectLiteral::Property* property = expr->properties()->at(i); + if (property->IsCompileTimeValue()) continue; + + Literal* key = property->key(); + Expression* value = property->value(); + + switch (property->kind()) { + case ObjectLiteral::Property::MATERIALIZED_LITERAL: + ASSERT(!CompileTimeValue::IsCompileTimeValue(value)); + // Fall through. + case ObjectLiteral::Property::COMPUTED: + if (key->handle()->IsSymbol()) { + if (property->emit_store()) { + VISIT_FOR_VALUE(value); + HValue* value = Pop(); + Handle<String> name = Handle<String>::cast(key->handle()); + AddInstruction(new HStoreNamedGeneric(literal, name, value)); + AddSimulate(key->id()); + } else { + VISIT_FOR_EFFECT(value); + } + break; + } + // Fall through. + case ObjectLiteral::Property::PROTOTYPE: + case ObjectLiteral::Property::SETTER: + case ObjectLiteral::Property::GETTER: + BAILOUT("Object literal with complex property"); + default: UNREACHABLE(); + } + } + ast_context()->ReturnValue(Pop()); +} + + +void HGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) { + ZoneList<Expression*>* subexprs = expr->values(); + int length = subexprs->length(); + + HArrayLiteral* literal = new HArrayLiteral(expr->constant_elements(), + length, + expr->literal_index(), + expr->depth()); + // The array is expected in the bailout environment during computation + // of the property values and is the value of the entire expression. + PushAndAdd(literal); + + HLoadElements* elements = NULL; + + for (int i = 0; i < length; i++) { + Expression* subexpr = subexprs->at(i); + // If the subexpression is a literal or a simple materialized literal it + // is already set in the cloned array. + if (CompileTimeValue::IsCompileTimeValue(subexpr)) continue; + + VISIT_FOR_VALUE(subexpr); + HValue* value = Pop(); + if (!Smi::IsValid(i)) BAILOUT("Non-smi key in array literal"); + + // Load the elements array before the first store. + if (elements == NULL) { + elements = new HLoadElements(literal); + AddInstruction(elements); + } + + HValue* key = AddInstruction(new HConstant(Handle<Object>(Smi::FromInt(i)), + Representation::Integer32())); + AddInstruction(new HStoreKeyedFastElement(elements, key, value)); + AddSimulate(expr->GetIdForElement(i)); + } + ast_context()->ReturnValue(Pop()); +} + + +void HGraphBuilder::VisitCatchExtensionObject(CatchExtensionObject* expr) { + BAILOUT("CatchExtensionObject"); +} + + +HBasicBlock* HGraphBuilder::BuildTypeSwitch(ZoneMapList* maps, + ZoneList<HSubgraph*>* subgraphs, + HValue* receiver, + int join_id) { + ASSERT(subgraphs->length() == (maps->length() + 1)); + + // Build map compare subgraphs for all but the first map. + ZoneList<HSubgraph*> map_compare_subgraphs(maps->length() - 1); + for (int i = maps->length() - 1; i > 0; --i) { + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + HSubgraph* else_subgraph = + (i == (maps->length() - 1)) + ? subgraphs->last() + : map_compare_subgraphs.last(); + current_subgraph_->exit_block()->Finish( + new HCompareMapAndBranch(receiver, + maps->at(i), + subgraphs->at(i)->entry_block(), + else_subgraph->entry_block())); + map_compare_subgraphs.Add(subgraph); + } + + // Generate first map check to end the current block. + AddInstruction(new HCheckNonSmi(receiver)); + HSubgraph* else_subgraph = + (maps->length() == 1) ? subgraphs->at(1) : map_compare_subgraphs.last(); + current_subgraph_->exit_block()->Finish( + new HCompareMapAndBranch(receiver, + Handle<Map>(maps->first()), + subgraphs->first()->entry_block(), + else_subgraph->entry_block())); + + // Join all the call subgraphs in a new basic block and make + // this basic block the current basic block. + HBasicBlock* join_block = graph_->CreateBasicBlock(); + for (int i = 0; i < subgraphs->length(); ++i) { + if (subgraphs->at(i)->HasExit()) { + subgraphs->at(i)->exit_block()->Goto(join_block); + } + } + + if (join_block->predecessors()->is_empty()) return NULL; + join_block->SetJoinId(join_id); + return join_block; +} + + +// Sets the lookup result and returns true if the store can be inlined. +static bool ComputeStoredField(Handle<Map> type, + Handle<String> name, + LookupResult* lookup) { + type->LookupInDescriptors(NULL, *name, lookup); + if (!lookup->IsPropertyOrTransition()) return false; + if (lookup->type() == FIELD) return true; + return (lookup->type() == MAP_TRANSITION) && + (type->unused_property_fields() > 0); +} + + +static int ComputeStoredFieldIndex(Handle<Map> type, + Handle<String> name, + LookupResult* lookup) { + ASSERT(lookup->type() == FIELD || lookup->type() == MAP_TRANSITION); + if (lookup->type() == FIELD) { + return lookup->GetLocalFieldIndexFromMap(*type); + } else { + Map* transition = lookup->GetTransitionMapFromMap(*type); + return transition->PropertyIndexFor(*name) - type->inobject_properties(); + } +} + + +HInstruction* HGraphBuilder::BuildStoreNamedField(HValue* object, + Handle<String> name, + HValue* value, + Handle<Map> type, + LookupResult* lookup, + bool smi_and_map_check) { + if (smi_and_map_check) { + AddInstruction(new HCheckNonSmi(object)); + AddInstruction(new HCheckMap(object, type)); + } + + int index = ComputeStoredFieldIndex(type, name, lookup); + bool is_in_object = index < 0; + int offset = index * kPointerSize; + if (index < 0) { + // Negative property indices are in-object properties, indexed + // from the end of the fixed part of the object. + offset += type->instance_size(); + } else { + offset += FixedArray::kHeaderSize; + } + HStoreNamedField* instr = + new HStoreNamedField(object, name, value, is_in_object, offset); + if (lookup->type() == MAP_TRANSITION) { + Handle<Map> transition(lookup->GetTransitionMapFromMap(*type)); + instr->set_transition(transition); + // TODO(fschneider): Record the new map type of the object in the IR to + // enable elimination of redundant checks after the transition store. + instr->SetFlag(HValue::kChangesMaps); + } + return instr; +} + + +HInstruction* HGraphBuilder::BuildStoreNamedGeneric(HValue* object, + Handle<String> name, + HValue* value) { + return new HStoreNamedGeneric(object, name, value); +} + + +HInstruction* HGraphBuilder::BuildStoreNamed(HValue* object, + HValue* value, + Expression* expr) { + Property* prop = (expr->AsProperty() != NULL) + ? expr->AsProperty() + : expr->AsAssignment()->target()->AsProperty(); + Literal* key = prop->key()->AsLiteral(); + Handle<String> name = Handle<String>::cast(key->handle()); + ASSERT(!name.is_null()); + + LookupResult lookup; + ZoneMapList* types = expr->GetReceiverTypes(); + bool is_monomorphic = expr->IsMonomorphic() && + ComputeStoredField(types->first(), name, &lookup); + + return is_monomorphic + ? BuildStoreNamedField(object, name, value, types->first(), &lookup, + true) // Needs smi and map check. + : BuildStoreNamedGeneric(object, name, value); +} + + +void HGraphBuilder::HandlePolymorphicStoreNamedField(Assignment* expr, + HValue* object, + HValue* value, + ZoneMapList* types, + Handle<String> name) { + int number_of_types = Min(types->length(), kMaxStorePolymorphism); + ZoneMapList maps(number_of_types); + ZoneList<HSubgraph*> subgraphs(number_of_types + 1); + bool needs_generic = (types->length() > kMaxStorePolymorphism); + + // Build subgraphs for each of the specific maps. + // + // TODO(ager): We should recognize when the prototype chains for + // different maps are identical. In that case we can avoid + // repeatedly generating the same prototype map checks. + for (int i = 0; i < number_of_types; ++i) { + Handle<Map> map = types->at(i); + LookupResult lookup; + if (ComputeStoredField(map, name, &lookup)) { + maps.Add(map); + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + HInstruction* instr = + BuildStoreNamedField(object, name, value, map, &lookup, false); + Push(value); + instr->set_position(expr->position()); + AddInstruction(instr); + subgraphs.Add(subgraph); + } else { + needs_generic = true; + } + } + + // If none of the properties were named fields we generate a + // generic store. + if (maps.length() == 0) { + HInstruction* instr = new HStoreNamedGeneric(object, name, value); + Push(value); + instr->set_position(expr->position()); + AddInstruction(instr); + if (instr->HasSideEffects()) AddSimulate(expr->id()); + } else { + // Build subgraph for generic store through IC. + { + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + if (!needs_generic && FLAG_deoptimize_uncommon_cases) { + subgraph->FinishExit(new HDeoptimize()); + } else { + HInstruction* instr = new HStoreNamedGeneric(object, name, value); + Push(value); + instr->set_position(expr->position()); + AddInstruction(instr); + } + subgraphs.Add(subgraph); + } + + HBasicBlock* new_exit_block = + BuildTypeSwitch(&maps, &subgraphs, object, expr->AssignmentId()); + subgraph()->set_exit_block(new_exit_block); + } + + if (subgraph()->HasExit()) ast_context()->ReturnValue(Pop()); +} + + +void HGraphBuilder::HandlePropertyAssignment(Assignment* expr) { + Property* prop = expr->target()->AsProperty(); + ASSERT(prop != NULL); + expr->RecordTypeFeedback(oracle()); + VISIT_FOR_VALUE(prop->obj()); + + HValue* value = NULL; + HInstruction* instr = NULL; + + if (prop->key()->IsPropertyName()) { + // Named store. + VISIT_FOR_VALUE(expr->value()); + value = Pop(); + HValue* object = Pop(); + + Literal* key = prop->key()->AsLiteral(); + Handle<String> name = Handle<String>::cast(key->handle()); + ASSERT(!name.is_null()); + + ZoneMapList* types = expr->GetReceiverTypes(); + LookupResult lookup; + + if (expr->IsMonomorphic()) { + instr = BuildStoreNamed(object, value, expr); + + } else if (types != NULL && types->length() > 1) { + HandlePolymorphicStoreNamedField(expr, object, value, types, name); + return; + + } else { + instr = new HStoreNamedGeneric(object, name, value); + } + + } else { + // Keyed store. + VISIT_FOR_VALUE(prop->key()); + VISIT_FOR_VALUE(expr->value()); + value = Pop(); + HValue* key = Pop(); + HValue* object = Pop(); + + bool is_fast_elements = expr->IsMonomorphic() && + expr->GetMonomorphicReceiverType()->has_fast_elements(); + + instr = is_fast_elements + ? BuildStoreKeyedFastElement(object, key, value, expr) + : BuildStoreKeyedGeneric(object, key, value); + } + + Push(value); + instr->set_position(expr->position()); + AddInstruction(instr); + if (instr->HasSideEffects()) AddSimulate(expr->AssignmentId()); + ast_context()->ReturnValue(Pop()); +} + + +// Because not every expression has a position and there is not common +// superclass of Assignment and CountOperation, we cannot just pass the +// owning expression instead of position and ast_id separately. +void HGraphBuilder::HandleGlobalVariableAssignment(Variable* var, + HValue* value, + int position, + int ast_id) { + LookupResult lookup; + LookupGlobalPropertyCell(var, &lookup, true); + CHECK_BAILOUT; + + Handle<GlobalObject> global(graph()->info()->global_object()); + Handle<JSGlobalPropertyCell> cell(global->GetPropertyCell(&lookup)); + HInstruction* instr = new HStoreGlobal(value, cell); + instr->set_position(position); + AddInstruction(instr); + if (instr->HasSideEffects()) AddSimulate(ast_id); +} + + +void HGraphBuilder::HandleCompoundAssignment(Assignment* expr) { + Expression* target = expr->target(); + VariableProxy* proxy = target->AsVariableProxy(); + Variable* var = proxy->AsVariable(); + Property* prop = target->AsProperty(); + ASSERT(var == NULL || prop == NULL); + + // We have a second position recorded in the FullCodeGenerator to have + // type feedback for the binary operation. + BinaryOperation* operation = expr->binary_operation(); + operation->RecordTypeFeedback(oracle()); + + if (var != NULL) { + if (!var->is_global() && !var->IsStackAllocated()) { + BAILOUT("non-stack/non-global in compound assignment"); + } + + VISIT_FOR_VALUE(operation); + + if (var->is_global()) { + HandleGlobalVariableAssignment(var, + Top(), + expr->position(), + expr->AssignmentId()); + } else { + Bind(var, Top()); + } + ast_context()->ReturnValue(Pop()); + + } else if (prop != NULL) { + prop->RecordTypeFeedback(oracle()); + + if (prop->key()->IsPropertyName()) { + // Named property. + VISIT_FOR_VALUE(prop->obj()); + HValue* obj = Top(); + + HInstruction* load = NULL; + if (prop->IsMonomorphic()) { + Handle<String> name = prop->key()->AsLiteral()->AsPropertyName(); + Handle<Map> map = prop->GetReceiverTypes()->first(); + load = BuildLoadNamed(obj, prop, map, name); + } else { + load = BuildLoadNamedGeneric(obj, prop); + } + PushAndAdd(load); + if (load->HasSideEffects()) AddSimulate(expr->CompoundLoadId()); + + VISIT_FOR_VALUE(expr->value()); + HValue* right = Pop(); + HValue* left = Pop(); + + HInstruction* instr = BuildBinaryOperation(operation, left, right); + PushAndAdd(instr); + if (instr->HasSideEffects()) AddSimulate(operation->id()); + + HInstruction* store = BuildStoreNamed(obj, instr, prop); + AddInstruction(store); + // Drop the simulated receiver and value. Return the value. + Drop(2); + Push(instr); + if (store->HasSideEffects()) AddSimulate(expr->AssignmentId()); + ast_context()->ReturnValue(Pop()); + + } else { + // Keyed property. + VISIT_FOR_VALUE(prop->obj()); + VISIT_FOR_VALUE(prop->key()); + HValue* obj = environment()->ExpressionStackAt(1); + HValue* key = environment()->ExpressionStackAt(0); + + bool is_fast_elements = prop->IsMonomorphic() && + prop->GetMonomorphicReceiverType()->has_fast_elements(); + + HInstruction* load = is_fast_elements + ? BuildLoadKeyedFastElement(obj, key, prop) + : BuildLoadKeyedGeneric(obj, key); + PushAndAdd(load); + if (load->HasSideEffects()) AddSimulate(expr->CompoundLoadId()); + + VISIT_FOR_VALUE(expr->value()); + HValue* right = Pop(); + HValue* left = Pop(); + + HInstruction* instr = BuildBinaryOperation(operation, left, right); + PushAndAdd(instr); + if (instr->HasSideEffects()) AddSimulate(operation->id()); + + HInstruction* store = is_fast_elements + ? BuildStoreKeyedFastElement(obj, key, instr, prop) + : BuildStoreKeyedGeneric(obj, key, instr); + AddInstruction(store); + // Drop the simulated receiver, key, and value. Return the value. + Drop(3); + Push(instr); + if (store->HasSideEffects()) AddSimulate(expr->AssignmentId()); + ast_context()->ReturnValue(Pop()); + } + + } else { + BAILOUT("invalid lhs in compound assignment"); + } +} + + +void HGraphBuilder::VisitAssignment(Assignment* expr) { + VariableProxy* proxy = expr->target()->AsVariableProxy(); + Variable* var = proxy->AsVariable(); + Property* prop = expr->target()->AsProperty(); + ASSERT(var == NULL || prop == NULL); + + if (expr->is_compound()) { + HandleCompoundAssignment(expr); + return; + } + + if (var != NULL) { + if (proxy->IsArguments()) BAILOUT("assignment to arguments"); + + // Handle the assignment. + if (var->is_global()) { + VISIT_FOR_VALUE(expr->value()); + HandleGlobalVariableAssignment(var, + Top(), + expr->position(), + expr->AssignmentId()); + } else { + // We allow reference to the arguments object only in assignemtns + // to local variables to make sure that the arguments object does + // not escape and is not modified. + VariableProxy* rhs = expr->value()->AsVariableProxy(); + if (rhs != NULL && + rhs->var()->IsStackAllocated() && + environment()->Lookup(rhs->var())->CheckFlag(HValue::kIsArguments)) { + Push(environment()->Lookup(rhs->var())); + } else { + VISIT_FOR_VALUE(expr->value()); + } + Bind(proxy->var(), Top()); + } + // Return the value. + ast_context()->ReturnValue(Pop()); + + } else if (prop != NULL) { + HandlePropertyAssignment(expr); + } else { + BAILOUT("unsupported invalid lhs"); + } +} + + +void HGraphBuilder::VisitThrow(Throw* expr) { + // We don't optimize functions with invalid left-hand sides in + // assignments, count operations, or for-in. Consequently throw can + // currently only occur in an effect context. + ASSERT(ast_context()->IsEffect()); + VISIT_FOR_VALUE(expr->exception()); + + HValue* value = environment()->Pop(); + HControlInstruction* instr = new HThrow(value); + instr->set_position(expr->position()); + current_subgraph_->FinishExit(instr); +} + + +void HGraphBuilder::HandlePolymorphicLoadNamedField(Property* expr, + HValue* object, + ZoneMapList* types, + Handle<String> name) { + int number_of_types = Min(types->length(), kMaxLoadPolymorphism); + ZoneMapList maps(number_of_types); + ZoneList<HSubgraph*> subgraphs(number_of_types + 1); + bool needs_generic = (types->length() > kMaxLoadPolymorphism); + + // Build subgraphs for each of the specific maps. + // + // TODO(ager): We should recognize when the prototype chains for + // different maps are identical. In that case we can avoid + // repeatedly generating the same prototype map checks. + for (int i = 0; i < number_of_types; ++i) { + Handle<Map> map = types->at(i); + LookupResult lookup; + map->LookupInDescriptors(NULL, *name, &lookup); + if (lookup.IsProperty() && lookup.type() == FIELD) { + maps.Add(map); + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + HLoadNamedField* instr = + BuildLoadNamedField(object, expr, map, &lookup, false); + instr->set_position(expr->position()); + instr->ClearFlag(HValue::kUseGVN); // Don't do GVN on polymorphic loads. + PushAndAdd(instr); + subgraphs.Add(subgraph); + } else { + needs_generic = true; + } + } + + // If none of the properties were named fields we generate a + // generic load. + if (maps.length() == 0) { + HInstruction* instr = BuildLoadNamedGeneric(object, expr); + instr->set_position(expr->position()); + PushAndAdd(instr); + if (instr->HasSideEffects()) AddSimulate(expr->id()); + } else { + // Build subgraph for generic load through IC. + { + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + if (!needs_generic && FLAG_deoptimize_uncommon_cases) { + subgraph->FinishExit(new HDeoptimize()); + } else { + HInstruction* instr = BuildLoadNamedGeneric(object, expr); + instr->set_position(expr->position()); + PushAndAdd(instr); + } + subgraphs.Add(subgraph); + } + + HBasicBlock* new_exit_block = + BuildTypeSwitch(&maps, &subgraphs, object, expr->id()); + subgraph()->set_exit_block(new_exit_block); + } + + if (subgraph()->HasExit()) ast_context()->ReturnValue(Pop()); +} + + +HLoadNamedField* HGraphBuilder::BuildLoadNamedField(HValue* object, + Property* expr, + Handle<Map> type, + LookupResult* lookup, + bool smi_and_map_check) { + if (smi_and_map_check) { + AddInstruction(new HCheckNonSmi(object)); + AddInstruction(new HCheckMap(object, type)); + } + + int index = lookup->GetLocalFieldIndexFromMap(*type); + if (index < 0) { + // Negative property indices are in-object properties, indexed + // from the end of the fixed part of the object. + int offset = (index * kPointerSize) + type->instance_size(); + return new HLoadNamedField(object, true, offset); + } else { + // Non-negative property indices are in the properties array. + int offset = (index * kPointerSize) + FixedArray::kHeaderSize; + return new HLoadNamedField(object, false, offset); + } +} + + +HInstruction* HGraphBuilder::BuildLoadNamedGeneric(HValue* obj, + Property* expr) { + ASSERT(expr->key()->IsPropertyName()); + Handle<Object> name = expr->key()->AsLiteral()->handle(); + return new HLoadNamedGeneric(obj, name); +} + + +HInstruction* HGraphBuilder::BuildLoadNamed(HValue* obj, + Property* expr, + Handle<Map> map, + Handle<String> name) { + LookupResult lookup; + map->LookupInDescriptors(NULL, *name, &lookup); + if (lookup.IsProperty() && lookup.type() == FIELD) { + return BuildLoadNamedField(obj, + expr, + map, + &lookup, + true); + } else if (lookup.IsProperty() && lookup.type() == CONSTANT_FUNCTION) { + AddInstruction(new HCheckNonSmi(obj)); + AddInstruction(new HCheckMap(obj, map)); + Handle<JSFunction> function(lookup.GetConstantFunctionFromMap(*map)); + return new HConstant(function, Representation::Tagged()); + } else { + return BuildLoadNamedGeneric(obj, expr); + } +} + + +HInstruction* HGraphBuilder::BuildLoadKeyedGeneric(HValue* object, + HValue* key) { + return new HLoadKeyedGeneric(object, key); +} + + +HInstruction* HGraphBuilder::BuildLoadKeyedFastElement(HValue* object, + HValue* key, + Property* expr) { + ASSERT(!expr->key()->IsPropertyName() && expr->IsMonomorphic()); + AddInstruction(new HCheckNonSmi(object)); + Handle<Map> map = expr->GetMonomorphicReceiverType(); + ASSERT(map->has_fast_elements()); + AddInstruction(new HCheckMap(object, map)); + HInstruction* elements = AddInstruction(new HLoadElements(object)); + HInstruction* length = AddInstruction(new HArrayLength(elements)); + AddInstruction(new HBoundsCheck(key, length)); + return new HLoadKeyedFastElement(elements, key); +} + + +HInstruction* HGraphBuilder::BuildStoreKeyedGeneric(HValue* object, + HValue* key, + HValue* value) { + return new HStoreKeyedGeneric(object, key, value); +} + + +HInstruction* HGraphBuilder::BuildStoreKeyedFastElement(HValue* object, + HValue* key, + HValue* val, + Expression* expr) { + ASSERT(expr->IsMonomorphic()); + AddInstruction(new HCheckNonSmi(object)); + Handle<Map> map = expr->GetMonomorphicReceiverType(); + ASSERT(map->has_fast_elements()); + AddInstruction(new HCheckMap(object, map)); + HInstruction* elements = AddInstruction(new HLoadElements(object)); + AddInstruction(new HCheckMap(elements, Factory::fixed_array_map())); + bool is_array = (map->instance_type() == JS_ARRAY_TYPE); + HInstruction* length = NULL; + if (is_array) { + length = AddInstruction(new HArrayLength(object)); + } else { + length = AddInstruction(new HArrayLength(elements)); + } + AddInstruction(new HBoundsCheck(key, length)); + return new HStoreKeyedFastElement(elements, key, val); +} + + +bool HGraphBuilder::TryArgumentsAccess(Property* expr) { + VariableProxy* proxy = expr->obj()->AsVariableProxy(); + if (proxy == NULL) return false; + if (!proxy->var()->IsStackAllocated()) return false; + if (!environment()->Lookup(proxy->var())->CheckFlag(HValue::kIsArguments)) { + return false; + } + + HInstruction* result = NULL; + if (expr->key()->IsPropertyName()) { + Handle<String> name = expr->key()->AsLiteral()->AsPropertyName(); + if (!name->IsEqualTo(CStrVector("length"))) return false; + HInstruction* elements = AddInstruction(new HArgumentsElements); + result = new HArgumentsLength(elements); + } else { + VisitForValue(expr->key()); + if (HasStackOverflow()) return false; + HValue* key = Pop(); + HInstruction* elements = AddInstruction(new HArgumentsElements); + HInstruction* length = AddInstruction(new HArgumentsLength(elements)); + AddInstruction(new HBoundsCheck(key, length)); + result = new HAccessArgumentsAt(elements, length, key); + } + ast_context()->ReturnInstruction(result, expr->id()); + return true; +} + + +void HGraphBuilder::VisitProperty(Property* expr) { + expr->RecordTypeFeedback(oracle()); + + if (TryArgumentsAccess(expr)) return; + CHECK_BAILOUT; + + VISIT_FOR_VALUE(expr->obj()); + + HInstruction* instr = NULL; + if (expr->IsArrayLength()) { + HValue* array = Pop(); + AddInstruction(new HCheckNonSmi(array)); + instr = new HArrayLength(array); + + } else if (expr->key()->IsPropertyName()) { + Handle<String> name = expr->key()->AsLiteral()->AsPropertyName(); + ZoneMapList* types = expr->GetReceiverTypes(); + + HValue* obj = Pop(); + if (expr->IsMonomorphic()) { + instr = BuildLoadNamed(obj, expr, types->first(), name); + } else if (types != NULL && types->length() > 1) { + HandlePolymorphicLoadNamedField(expr, obj, types, name); + return; + + } else { + instr = BuildLoadNamedGeneric(obj, expr); + } + + } else { + VISIT_FOR_VALUE(expr->key()); + + HValue* key = Pop(); + HValue* obj = Pop(); + + bool is_fast_elements = expr->IsMonomorphic() && + expr->GetMonomorphicReceiverType()->has_fast_elements(); + + instr = is_fast_elements + ? BuildLoadKeyedFastElement(obj, key, expr) + : BuildLoadKeyedGeneric(obj, key); + } + instr->set_position(expr->position()); + ast_context()->ReturnInstruction(instr, expr->id()); +} + + +void HGraphBuilder::AddCheckConstantFunction(Call* expr, + HValue* receiver, + Handle<Map> receiver_map, + bool smi_and_map_check) { + // Constant functions have the nice property that the map will change if they + // are overwritten. Therefore it is enough to check the map of the holder and + // its prototypes. + if (smi_and_map_check) { + AddInstruction(new HCheckNonSmi(receiver)); + AddInstruction(new HCheckMap(receiver, receiver_map)); + } + if (!expr->holder().is_null()) { + AddInstruction(new HCheckPrototypeMaps(receiver, + expr->holder(), + receiver_map)); + } +} + + +void HGraphBuilder::HandlePolymorphicCallNamed(Call* expr, + HValue* receiver, + ZoneMapList* types, + Handle<String> name) { + int argument_count = expr->arguments()->length() + 1; // Plus receiver. + int number_of_types = Min(types->length(), kMaxCallPolymorphism); + ZoneMapList maps(number_of_types); + ZoneList<HSubgraph*> subgraphs(number_of_types + 1); + bool needs_generic = (types->length() > kMaxCallPolymorphism); + + // Build subgraphs for each of the specific maps. + // + // TODO(ager): We should recognize when the prototype chains for different + // maps are identical. In that case we can avoid repeatedly generating the + // same prototype map checks. + for (int i = 0; i < number_of_types; ++i) { + Handle<Map> map = types->at(i); + if (expr->ComputeTarget(map, name)) { + maps.Add(map); + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + AddCheckConstantFunction(expr, receiver, map, false); + if (FLAG_trace_inlining && FLAG_polymorphic_inlining) { + PrintF("Trying to inline the polymorphic call to %s\n", + *name->ToCString()); + } + if (!FLAG_polymorphic_inlining || !TryInline(expr)) { + // Check for bailout, as trying to inline might fail due to bailout + // during hydrogen processing. + CHECK_BAILOUT; + HCall* call = new HCallConstantFunction(expr->target(), argument_count); + call->set_position(expr->position()); + ProcessCall(call); + PushAndAdd(call); + } + subgraphs.Add(subgraph); + } else { + needs_generic = true; + } + } + + // If we couldn't compute the target for any of the maps just perform an + // IC call. + if (maps.length() == 0) { + HCall* call = new HCallNamed(name, argument_count); + call->set_position(expr->position()); + ProcessCall(call); + ast_context()->ReturnInstruction(call, expr->id()); + } else { + // Build subgraph for generic call through IC. + { + HSubgraph* subgraph = CreateBranchSubgraph(environment()); + SubgraphScope scope(this, subgraph); + if (!needs_generic && FLAG_deoptimize_uncommon_cases) { + subgraph->FinishExit(new HDeoptimize()); + } else { + HCall* call = new HCallNamed(name, argument_count); + call->set_position(expr->position()); + ProcessCall(call); + PushAndAdd(call); + } + subgraphs.Add(subgraph); + } + + HBasicBlock* new_exit_block = + BuildTypeSwitch(&maps, &subgraphs, receiver, expr->id()); + subgraph()->set_exit_block(new_exit_block); + if (new_exit_block != NULL) ast_context()->ReturnValue(Pop()); + } +} + + +void HGraphBuilder::TraceInline(Handle<JSFunction> target, bool result) { + SmartPointer<char> callee = target->shared()->DebugName()->ToCString(); + SmartPointer<char> caller = + graph()->info()->function()->debug_name()->ToCString(); + if (result) { + PrintF("Inlined %s called from %s.\n", *callee, *caller); + } else { + PrintF("Do not inline %s called from %s.\n", *callee, *caller); + } +} + + +bool HGraphBuilder::TryInline(Call* expr) { + if (!FLAG_use_inlining) return false; + + // Precondition: call is monomorphic and we have found a target with the + // appropriate arity. + Handle<JSFunction> target = expr->target(); + + // Do a quick check on source code length to avoid parsing large + // inlining candidates. + if (FLAG_limit_inlining && target->shared()->SourceSize() > kMaxSourceSize) { + if (FLAG_trace_inlining) TraceInline(target, false); + return false; + } + + // Target must be inlineable. + if (!target->IsInlineable()) return false; + + // No context change required. + CompilationInfo* outer_info = graph()->info(); + if (target->context() != outer_info->closure()->context() || + outer_info->scope()->contains_with() || + outer_info->scope()->num_heap_slots() > 0) { + return false; + } + + // Don't inline deeper than two calls. + HEnvironment* env = environment(); + if (env->outer() != NULL && env->outer()->outer() != NULL) return false; + + // Don't inline recursive functions. + if (target->shared() == outer_info->closure()->shared()) return false; + + // We don't want to add more than a certain number of nodes from inlining. + if (FLAG_limit_inlining && inlined_count_ > kMaxInlinedNodes) { + if (FLAG_trace_inlining) TraceInline(target, false); + return false; + } + + int count_before = AstNode::Count(); + + // Parse and allocate variables. + Handle<SharedFunctionInfo> shared(target->shared()); + CompilationInfo inner_info(shared); + if (!ParserApi::Parse(&inner_info) || + !Scope::Analyze(&inner_info)) { + return false; + } + FunctionLiteral* function = inner_info.function(); + + // Count the number of AST nodes added by inlining this call. + int nodes_added = AstNode::Count() - count_before; + if (FLAG_limit_inlining && nodes_added > kMaxInlinedSize) { + if (FLAG_trace_inlining) TraceInline(target, false); + return false; + } + + // Check if we can handle all declarations in the inlined functions. + VisitDeclarations(inner_info.scope()->declarations()); + if (HasStackOverflow()) { + ClearStackOverflow(); + return false; + } + + // Don't inline functions that uses the arguments object or that + // have a mismatching number of parameters. + int arity = expr->arguments()->length(); + if (function->scope()->arguments() != NULL || + arity != target->shared()->formal_parameter_count()) { + return false; + } + + // All statements in the body must be inlineable. + for (int i = 0, count = function->body()->length(); i < count; ++i) { + if (!function->body()->at(i)->IsInlineable()) return false; + } + + // Generate the deoptimization data for the unoptimized version of + // the target function if we don't already have it. + if (!shared->has_deoptimization_support()) { + // Note that we compile here using the same AST that we will use for + // generating the optimized inline code. + inner_info.EnableDeoptimizationSupport(); + if (!FullCodeGenerator::MakeCode(&inner_info)) return false; + shared->EnableDeoptimizationSupport(*inner_info.code()); + Compiler::RecordFunctionCompilation( + Logger::FUNCTION_TAG, + Handle<String>(shared->DebugName()), + shared->start_position(), + &inner_info); + } + + // Save the pending call context and type feedback oracle. Set up new ones + // for the inlined function. + ASSERT(shared->has_deoptimization_support()); + AstContext* saved_call_context = call_context(); + HBasicBlock* saved_function_return = function_return(); + TypeFeedbackOracle* saved_oracle = oracle(); + // On-stack replacement cannot target inlined functions. Since we don't + // use a separate CompilationInfo structure for the inlined function, we + // save and restore the AST ID in the original compilation info. + int saved_osr_ast_id = graph()->info()->osr_ast_id(); + + TestContext* test_context = NULL; + if (ast_context()->IsTest()) { + // Inlined body is treated as if it occurs in an 'inlined' call context + // with true and false blocks that will forward to the real ones. + HBasicBlock* if_true = graph()->CreateBasicBlock(); + HBasicBlock* if_false = graph()->CreateBasicBlock(); + if_true->MarkAsInlineReturnTarget(); + if_false->MarkAsInlineReturnTarget(); + // AstContext constructor pushes on the context stack. + test_context = new TestContext(this, if_true, if_false); + function_return_ = NULL; + } else { + // Inlined body is treated as if it occurs in the original call context. + function_return_ = graph()->CreateBasicBlock(); + function_return_->MarkAsInlineReturnTarget(); + } + call_context_ = ast_context(); + TypeFeedbackOracle new_oracle(Handle<Code>(shared->code())); + oracle_ = &new_oracle; + graph()->info()->SetOsrAstId(AstNode::kNoNumber); + + HSubgraph* body = CreateInlinedSubgraph(env, target, function); + body->exit_block()->AddInstruction(new HEnterInlined(target, function)); + AddToSubgraph(body, function->body()); + if (HasStackOverflow()) { + // Bail out if the inline function did, as we cannot residualize a call + // instead. + delete test_context; + call_context_ = saved_call_context; + function_return_ = saved_function_return; + oracle_ = saved_oracle; + graph()->info()->SetOsrAstId(saved_osr_ast_id); + return false; + } + + // Update inlined nodes count. + inlined_count_ += nodes_added; + + if (FLAG_trace_inlining) TraceInline(target, true); + + if (body->HasExit()) { + // Add a return of undefined if control can fall off the body. In a + // test context, undefined is false. + HValue* return_value = graph()->GetConstantUndefined(); + if (test_context == NULL) { + ASSERT(function_return_ != NULL); + body->exit_block()->AddLeaveInlined(return_value, function_return_); + } else { + // The graph builder assumes control can reach both branches of a + // test, so we materialize the undefined value and test it rather than + // simply jumping to the false target. + // + // TODO(3168478): refactor to avoid this. + HBasicBlock* empty_true = graph()->CreateBasicBlock(); + HBasicBlock* empty_false = graph()->CreateBasicBlock(); + HBranch* branch = + new HBranch(empty_true, empty_false, return_value); + body->exit_block()->Finish(branch); + + HValue* const no_return_value = NULL; + empty_true->AddLeaveInlined(no_return_value, test_context->if_true()); + empty_false->AddLeaveInlined(no_return_value, test_context->if_false()); + } + body->set_exit_block(NULL); + } + + // Record the environment at the inlined function call. + AddSimulate(expr->ReturnId()); + + // Jump to the function entry (without re-recording the environment). + subgraph()->exit_block()->Finish(new HGoto(body->entry_block())); + + // Fix up the function exits. + if (test_context != NULL) { + HBasicBlock* if_true = test_context->if_true(); + HBasicBlock* if_false = test_context->if_false(); + if_true->SetJoinId(expr->id()); + if_false->SetJoinId(expr->id()); + ASSERT(ast_context() == test_context); + delete test_context; // Destructor pops from expression context stack. + + // Forward to the real test context. + HValue* const no_return_value = NULL; + HBasicBlock* true_target = TestContext::cast(ast_context())->if_true(); + if (true_target->IsInlineReturnTarget()) { + if_true->AddLeaveInlined(no_return_value, true_target); + } else { + if_true->Goto(true_target); + } + + HBasicBlock* false_target = TestContext::cast(ast_context())->if_false(); + if (false_target->IsInlineReturnTarget()) { + if_false->AddLeaveInlined(no_return_value, false_target); + } else { + if_false->Goto(false_target); + } + + // TODO(kmillikin): Come up with a better way to handle this. It is too + // subtle. NULL here indicates that the enclosing context has no control + // flow to handle. + subgraph()->set_exit_block(NULL); + + } else { + function_return_->SetJoinId(expr->id()); + subgraph()->set_exit_block(function_return_); + } + + call_context_ = saved_call_context; + function_return_ = saved_function_return; + oracle_ = saved_oracle; + graph()->info()->SetOsrAstId(saved_osr_ast_id); + + return true; +} + + +void HBasicBlock::AddLeaveInlined(HValue* return_value, HBasicBlock* target) { + ASSERT(target->IsInlineReturnTarget()); + AddInstruction(new HLeaveInlined); + HEnvironment* outer = last_environment()->outer(); + if (return_value != NULL) outer->Push(return_value); + UpdateEnvironment(outer); + Goto(target); +} + + +bool HGraphBuilder::TryMathFunctionInline(Call* expr) { + // Try to inline calls like Math.* as operations in the calling function. + if (!expr->target()->shared()->IsBuiltinMathFunction()) return false; + BuiltinFunctionId id = expr->target()->shared()->builtin_function_id(); + int argument_count = expr->arguments()->length() + 1; // Plus receiver. + switch (id) { + case kMathRound: + case kMathFloor: + case kMathAbs: + case kMathSqrt: + case kMathLog: + case kMathSin: + case kMathCos: + if (argument_count == 2) { + HValue* argument = Pop(); + Drop(1); // Receiver. + HUnaryMathOperation* op = new HUnaryMathOperation(argument, id); + op->set_position(expr->position()); + ast_context()->ReturnInstruction(op, expr->id()); + return true; + } + break; + case kMathPow: + if (argument_count == 3) { + HValue* right = Pop(); + HValue* left = Pop(); + Pop(); // Pop receiver. + HInstruction* result = NULL; + // Use sqrt() if exponent is 0.5 or -0.5. + if (right->IsConstant() && HConstant::cast(right)->HasDoubleValue()) { + double exponent = HConstant::cast(right)->DoubleValue(); + if (exponent == 0.5) { + result = new HUnaryMathOperation(left, kMathPowHalf); + ast_context()->ReturnInstruction(result, expr->id()); + return true; + } else if (exponent == -0.5) { + HConstant* double_one = + new HConstant(Handle<Object>(Smi::FromInt(1)), + Representation::Double()); + AddInstruction(double_one); + HUnaryMathOperation* square_root = + new HUnaryMathOperation(left, kMathPowHalf); + AddInstruction(square_root); + // MathPowHalf doesn't have side effects so there's no need for + // an environment simulation here. + ASSERT(!square_root->HasSideEffects()); + result = new HDiv(double_one, square_root); + ast_context()->ReturnInstruction(result, expr->id()); + return true; + } else if (exponent == 2.0) { + result = new HMul(left, left); + ast_context()->ReturnInstruction(result, expr->id()); + return true; + } + } else if (right->IsConstant() && + HConstant::cast(right)->HasInteger32Value() && + HConstant::cast(right)->Integer32Value() == 2) { + result = new HMul(left, left); + ast_context()->ReturnInstruction(result, expr->id()); + return true; + } + + result = new HPower(left, right); + ast_context()->ReturnInstruction(result, expr->id()); + return true; + } + break; + default: + // Not yet supported for inlining. + break; + } + return false; +} + + +bool HGraphBuilder::TryCallApply(Call* expr) { + Expression* callee = expr->expression(); + Property* prop = callee->AsProperty(); + ASSERT(prop != NULL); + + if (graph()->info()->scope()->arguments() == NULL) return false; + + Handle<String> name = prop->key()->AsLiteral()->AsPropertyName(); + if (!name->IsEqualTo(CStrVector("apply"))) return false; + + ZoneList<Expression*>* args = expr->arguments(); + if (args->length() != 2) return false; + + VariableProxy* arg_two = args->at(1)->AsVariableProxy(); + if (arg_two == NULL || !arg_two->var()->IsStackAllocated()) return false; + HValue* arg_two_value = environment()->Lookup(arg_two->var()); + if (!arg_two_value->CheckFlag(HValue::kIsArguments)) return false; + + if (!expr->IsMonomorphic()) return false; + + // Found pattern f.apply(receiver, arguments). + VisitForValue(prop->obj()); + if (HasStackOverflow()) return false; + HValue* function = Pop(); + VisitForValue(args->at(0)); + if (HasStackOverflow()) return false; + HValue* receiver = Pop(); + HInstruction* elements = AddInstruction(new HArgumentsElements); + HInstruction* length = AddInstruction(new HArgumentsLength(elements)); + AddCheckConstantFunction(expr, + function, + expr->GetReceiverTypes()->first(), + true); + HInstruction* result = + new HApplyArguments(function, receiver, length, elements); + result->set_position(expr->position()); + ast_context()->ReturnInstruction(result, expr->id()); + return true; +} + + +void HGraphBuilder::VisitCall(Call* expr) { + Expression* callee = expr->expression(); + int argument_count = expr->arguments()->length() + 1; // Plus receiver. + HCall* call = NULL; + + Property* prop = callee->AsProperty(); + if (prop != NULL) { + if (!prop->key()->IsPropertyName()) { + // Keyed function call. + VisitArgument(prop->obj()); + CHECK_BAILOUT; + + VISIT_FOR_VALUE(prop->key()); + // Push receiver and key like the non-optimized code generator expects it. + HValue* key = Pop(); + HValue* receiver = Pop(); + Push(key); + Push(receiver); + + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + call = new HCallKeyed(key, argument_count); + call->set_position(expr->position()); + ProcessCall(call); + Drop(1); // Key. + ast_context()->ReturnInstruction(call, expr->id()); + return; + } + + // Named function call. + expr->RecordTypeFeedback(oracle()); + + if (TryCallApply(expr)) return; + CHECK_BAILOUT; + + HValue* receiver = VisitArgument(prop->obj()); + CHECK_BAILOUT; + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + Handle<String> name = prop->key()->AsLiteral()->AsPropertyName(); + + expr->RecordTypeFeedback(oracle()); + ZoneMapList* types = expr->GetReceiverTypes(); + + if (expr->IsMonomorphic()) { + AddCheckConstantFunction(expr, receiver, types->first(), true); + + if (TryMathFunctionInline(expr)) { + return; + } else if (TryInline(expr)) { + if (subgraph()->HasExit()) { + HValue* return_value = Pop(); + // If we inlined a function in a test context then we need to emit + // a simulate here to shadow the ones at the end of the + // predecessor blocks. Those environments contain the return + // value on top and do not correspond to any actual state of the + // unoptimized code. + if (ast_context()->IsEffect()) AddSimulate(expr->id()); + ast_context()->ReturnValue(return_value); + } + return; + } else { + // Check for bailout, as the TryInline call in the if condition above + // might return false due to bailout during hydrogen processing. + CHECK_BAILOUT; + call = new HCallConstantFunction(expr->target(), argument_count); + } + + } else if (types != NULL && types->length() > 1) { + HandlePolymorphicCallNamed(expr, receiver, types, name); + return; + + } else { + call = new HCallNamed(name, argument_count); + } + + } else { + Variable* var = expr->expression()->AsVariableProxy()->AsVariable(); + bool global_call = (var != NULL) && var->is_global() && !var->is_this(); + + if (!global_call) { + ++argument_count; + VisitArgument(expr->expression()); + CHECK_BAILOUT; + } + + if (global_call) { + // If there is a global property cell for the name at compile time and + // access check is not enabled we assume that the function will not change + // and generate optimized code for calling the function. + CompilationInfo* info = graph()->info(); + bool known_global_function = info->has_global_object() && + !info->global_object()->IsAccessCheckNeeded() && + expr->ComputeGlobalTarget(Handle<GlobalObject>(info->global_object()), + var->name()); + if (known_global_function) { + // Push the global object instead of the global receiver because + // code generated by the full code generator expects it. + PushAndAdd(new HGlobalObject); + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + VISIT_FOR_VALUE(expr->expression()); + HValue* function = Pop(); + AddInstruction(new HCheckFunction(function, expr->target())); + + // Replace the global object with the global receiver. + HGlobalReceiver* global_receiver = new HGlobalReceiver; + // Index of the receiver from the top of the expression stack. + const int receiver_index = argument_count - 1; + AddInstruction(global_receiver); + ASSERT(environment()->ExpressionStackAt(receiver_index)-> + IsGlobalObject()); + environment()->SetExpressionStackAt(receiver_index, global_receiver); + + if (TryInline(expr)) { + if (subgraph()->HasExit()) { + HValue* return_value = Pop(); + // If we inlined a function in a test context then we need to + // emit a simulate here to shadow the ones at the end of the + // predecessor blocks. Those environments contain the return + // value on top and do not correspond to any actual state of the + // unoptimized code. + if (ast_context()->IsEffect()) AddSimulate(expr->id()); + ast_context()->ReturnValue(return_value); + } + return; + } + // Check for bailout, as trying to inline might fail due to bailout + // during hydrogen processing. + CHECK_BAILOUT; + + call = new HCallKnownGlobal(expr->target(), argument_count); + } else { + PushAndAdd(new HGlobalObject); + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + call = new HCallGlobal(var->name(), argument_count); + } + + } else { + PushAndAdd(new HGlobalReceiver); + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + call = new HCallFunction(argument_count); + } + } + + call->set_position(expr->position()); + ProcessCall(call); + ast_context()->ReturnInstruction(call, expr->id()); +} + + +void HGraphBuilder::VisitCallNew(CallNew* expr) { + // The constructor function is also used as the receiver argument to the + // JS construct call builtin. + VisitArgument(expr->expression()); + CHECK_BAILOUT; + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + int argument_count = expr->arguments()->length() + 1; // Plus constructor. + HCall* call = new HCallNew(argument_count); + call->set_position(expr->position()); + ProcessCall(call); + ast_context()->ReturnInstruction(call, expr->id()); +} + + +// Support for generating inlined runtime functions. + +// Lookup table for generators for runtime calls that are generated inline. +// Elements of the table are member pointers to functions of HGraphBuilder. +#define INLINE_FUNCTION_GENERATOR_ADDRESS(Name, argc, ressize) \ + &HGraphBuilder::Generate##Name, + +const HGraphBuilder::InlineFunctionGenerator + HGraphBuilder::kInlineFunctionGenerators[] = { + INLINE_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_ADDRESS) + INLINE_RUNTIME_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_ADDRESS) +}; +#undef INLINE_FUNCTION_GENERATOR_ADDRESS + + +void HGraphBuilder::VisitCallRuntime(CallRuntime* expr) { + Handle<String> name = expr->name(); + if (name->IsEqualTo(CStrVector("_Log"))) { + ast_context()->ReturnValue(graph()->GetConstantUndefined()); + return; + } + + Runtime::Function* function = expr->function(); + if (expr->is_jsruntime()) { + BAILOUT("call to a JavaScript runtime function"); + } + ASSERT(function != NULL); + + VisitArgumentList(expr->arguments()); + CHECK_BAILOUT; + + int argument_count = expr->arguments()->length(); + if (function->intrinsic_type == Runtime::INLINE) { + ASSERT(name->length() > 0); + ASSERT(name->Get(0) == '_'); + // Call to an inline function. + int lookup_index = static_cast<int>(function->function_id) - + static_cast<int>(Runtime::kFirstInlineFunction); + ASSERT(lookup_index >= 0); + ASSERT(static_cast<size_t>(lookup_index) < + ARRAY_SIZE(kInlineFunctionGenerators)); + InlineFunctionGenerator generator = kInlineFunctionGenerators[lookup_index]; + + // Call the inline code generator using the pointer-to-member. + (this->*generator)(argument_count, expr->id()); + } else { + ASSERT(function->intrinsic_type == Runtime::RUNTIME); + HCall* call = new HCallRuntime(name, expr->function(), argument_count); + call->set_position(RelocInfo::kNoPosition); + ProcessCall(call); + ast_context()->ReturnInstruction(call, expr->id()); + } +} + + +void HGraphBuilder::VisitUnaryOperation(UnaryOperation* expr) { + Token::Value op = expr->op(); + if (op == Token::VOID) { + VISIT_FOR_EFFECT(expr->expression()); + ast_context()->ReturnValue(graph()->GetConstantUndefined()); + } else if (op == Token::DELETE) { + Property* prop = expr->expression()->AsProperty(); + Variable* var = expr->expression()->AsVariableProxy()->AsVariable(); + if (prop == NULL && var == NULL) { + // Result of deleting non-property, non-variable reference is true. + // Evaluate the subexpression for side effects. + VISIT_FOR_EFFECT(expr->expression()); + ast_context()->ReturnValue(graph()->GetConstantTrue()); + } else if (var != NULL && + !var->is_global() && + var->AsSlot() != NULL && + var->AsSlot()->type() != Slot::LOOKUP) { + // Result of deleting non-global, non-dynamic variables is false. + // The subexpression does not have side effects. + ast_context()->ReturnValue(graph()->GetConstantFalse()); + } else if (prop != NULL) { + VISIT_FOR_VALUE(prop->obj()); + VISIT_FOR_VALUE(prop->key()); + HValue* key = Pop(); + HValue* obj = Pop(); + ast_context()->ReturnInstruction(new HDeleteProperty(obj, key), + expr->id()); + } else if (var->is_global()) { + BAILOUT("delete with global variable"); + } else { + BAILOUT("delete with non-global variable"); + } + } else if (op == Token::NOT) { + if (ast_context()->IsTest()) { + TestContext* context = TestContext::cast(ast_context()); + VisitForControl(expr->expression(), + context->if_false(), + context->if_true()); + } else { + HSubgraph* true_graph = CreateEmptySubgraph(); + HSubgraph* false_graph = CreateEmptySubgraph(); + VISIT_FOR_CONTROL(expr->expression(), + false_graph->entry_block(), + true_graph->entry_block()); + true_graph->entry_block()->SetJoinId(expr->expression()->id()); + true_graph->environment()->Push(graph_->GetConstantTrue()); + + false_graph->entry_block()->SetJoinId(expr->expression()->id()); + false_graph->environment()->Push(graph_->GetConstantFalse()); + + current_subgraph_->AppendJoin(true_graph, false_graph, expr); + ast_context()->ReturnValue(Pop()); + } + } else if (op == Token::BIT_NOT || op == Token::SUB) { + VISIT_FOR_VALUE(expr->expression()); + HValue* value = Pop(); + HInstruction* instr = NULL; + switch (op) { + case Token::BIT_NOT: + instr = new HBitNot(value); + break; + case Token::SUB: + instr = new HMul(graph_->GetConstantMinus1(), value); + break; + default: + UNREACHABLE(); + break; + } + ast_context()->ReturnInstruction(instr, expr->id()); + } else if (op == Token::TYPEOF) { + VISIT_FOR_VALUE(expr->expression()); + HValue* value = Pop(); + ast_context()->ReturnInstruction(new HTypeof(value), expr->id()); + } else { + BAILOUT("Value: unsupported unary operation"); + } +} + + +void HGraphBuilder::VisitIncrementOperation(IncrementOperation* expr) { + // IncrementOperation is never visited by the visitor. It only + // occurs as a subexpression of CountOperation. + UNREACHABLE(); +} + + +HInstruction* HGraphBuilder::BuildIncrement(HValue* value, bool increment) { + HConstant* delta = increment + ? graph_->GetConstant1() + : graph_->GetConstantMinus1(); + HInstruction* instr = new HAdd(value, delta); + AssumeRepresentation(instr, Representation::Integer32()); + return instr; +} + + +void HGraphBuilder::VisitCountOperation(CountOperation* expr) { + IncrementOperation* increment = expr->increment(); + Expression* target = increment->expression(); + VariableProxy* proxy = target->AsVariableProxy(); + Variable* var = proxy->AsVariable(); + Property* prop = target->AsProperty(); + ASSERT(var == NULL || prop == NULL); + bool inc = expr->op() == Token::INC; + + if (var != NULL) { + if (!var->is_global() && !var->IsStackAllocated()) { + BAILOUT("non-stack/non-global variable in count operation"); + } + + VISIT_FOR_VALUE(target); + + // Match the full code generator stack by simulating an extra stack + // element for postfix operations in a non-effect context. + bool has_extra = expr->is_postfix() && !ast_context()->IsEffect(); + HValue* before = has_extra ? Top() : Pop(); + HInstruction* after = BuildIncrement(before, inc); + AddInstruction(after); + Push(after); + + if (var->is_global()) { + HandleGlobalVariableAssignment(var, + after, + expr->position(), + expr->AssignmentId()); + } else { + ASSERT(var->IsStackAllocated()); + Bind(var, after); + } + Drop(has_extra ? 2 : 1); + ast_context()->ReturnValue(expr->is_postfix() ? before : after); + + } else if (prop != NULL) { + prop->RecordTypeFeedback(oracle()); + + if (prop->key()->IsPropertyName()) { + // Named property. + + // Match the full code generator stack by simulating an extra stack + // element for postfix operations in a non-effect context. + bool has_extra = expr->is_postfix() && !ast_context()->IsEffect(); + if (has_extra) Push(graph_->GetConstantUndefined()); + + VISIT_FOR_VALUE(prop->obj()); + HValue* obj = Top(); + + HInstruction* load = NULL; + if (prop->IsMonomorphic()) { + Handle<String> name = prop->key()->AsLiteral()->AsPropertyName(); + Handle<Map> map = prop->GetReceiverTypes()->first(); + load = BuildLoadNamed(obj, prop, map, name); + } else { + load = BuildLoadNamedGeneric(obj, prop); + } + PushAndAdd(load); + if (load->HasSideEffects()) AddSimulate(increment->id()); + + HValue* before = Pop(); + // There is no deoptimization to after the increment, so we don't need + // to simulate the expression stack after this instruction. + HInstruction* after = BuildIncrement(before, inc); + AddInstruction(after); + + HInstruction* store = BuildStoreNamed(obj, after, prop); + AddInstruction(store); + + // Overwrite the receiver in the bailout environment with the result + // of the operation, and the placeholder with the original value if + // necessary. + environment()->SetExpressionStackAt(0, after); + if (has_extra) environment()->SetExpressionStackAt(1, before); + if (store->HasSideEffects()) AddSimulate(expr->AssignmentId()); + Drop(has_extra ? 2 : 1); + + ast_context()->ReturnValue(expr->is_postfix() ? before : after); + + } else { + // Keyed property. + + // Match the full code generator stack by simulate an extra stack element + // for postfix operations in a non-effect context. + bool has_extra = expr->is_postfix() && !ast_context()->IsEffect(); + if (has_extra) Push(graph_->GetConstantUndefined()); + + VISIT_FOR_VALUE(prop->obj()); + VISIT_FOR_VALUE(prop->key()); + HValue* obj = environment()->ExpressionStackAt(1); + HValue* key = environment()->ExpressionStackAt(0); + + bool is_fast_elements = prop->IsMonomorphic() && + prop->GetMonomorphicReceiverType()->has_fast_elements(); + + HInstruction* load = is_fast_elements + ? BuildLoadKeyedFastElement(obj, key, prop) + : BuildLoadKeyedGeneric(obj, key); + PushAndAdd(load); + if (load->HasSideEffects()) AddSimulate(increment->id()); + + HValue* before = Pop(); + // There is no deoptimization to after the increment, so we don't need + // to simulate the expression stack after this instruction. + HInstruction* after = BuildIncrement(before, inc); + AddInstruction(after); + + HInstruction* store = is_fast_elements + ? BuildStoreKeyedFastElement(obj, key, after, prop) + : new HStoreKeyedGeneric(obj, key, after); + AddInstruction(store); + + // Drop the key from the bailout environment. Overwrite the receiver + // with the result of the operation, and the placeholder with the + // original value if necessary. + Drop(1); + environment()->SetExpressionStackAt(0, after); + if (has_extra) environment()->SetExpressionStackAt(1, before); + if (store->HasSideEffects()) AddSimulate(expr->AssignmentId()); + Drop(has_extra ? 2 : 1); + + ast_context()->ReturnValue(expr->is_postfix() ? before : after); + } + + } else { + BAILOUT("invalid lhs in count operation"); + } +} + + +HInstruction* HGraphBuilder::BuildBinaryOperation(BinaryOperation* expr, + HValue* left, + HValue* right) { + HInstruction* instr = NULL; + switch (expr->op()) { + case Token::ADD: + instr = new HAdd(left, right); + break; + case Token::SUB: + instr = new HSub(left, right); + break; + case Token::MUL: + instr = new HMul(left, right); + break; + case Token::MOD: + instr = new HMod(left, right); + break; + case Token::DIV: + instr = new HDiv(left, right); + break; + case Token::BIT_XOR: + instr = new HBitXor(left, right); + break; + case Token::BIT_AND: + instr = new HBitAnd(left, right); + break; + case Token::BIT_OR: + instr = new HBitOr(left, right); + break; + case Token::SAR: + instr = new HSar(left, right); + break; + case Token::SHR: + instr = new HShr(left, right); + break; + case Token::SHL: + instr = new HShl(left, right); + break; + default: + UNREACHABLE(); + } + TypeInfo info = oracle()->BinaryType(expr, TypeFeedbackOracle::RESULT); + // If we hit an uninitialized binary op stub we will get type info + // for a smi operation. If one of the operands is a constant string + // do not generate code assuming it is a smi operation. + if (info.IsSmi() && + ((left->IsConstant() && HConstant::cast(left)->HasStringValue()) || + (right->IsConstant() && HConstant::cast(right)->HasStringValue()))) { + return instr; + } + if (FLAG_trace_representation) { + PrintF("Info: %s/%s\n", info.ToString(), ToRepresentation(info).Mnemonic()); + } + AssumeRepresentation(instr, ToRepresentation(info)); + return instr; +} + + +// Check for the form (%_ClassOf(foo) === 'BarClass'). +static bool IsClassOfTest(CompareOperation* expr) { + if (expr->op() != Token::EQ_STRICT) return false; + CallRuntime* call = expr->left()->AsCallRuntime(); + if (call == NULL) return false; + Literal* literal = expr->right()->AsLiteral(); + if (literal == NULL) return false; + if (!literal->handle()->IsString()) return false; + if (!call->name()->IsEqualTo(CStrVector("_ClassOf"))) return false; + ASSERT(call->arguments()->length() == 1); + return true; +} + + +void HGraphBuilder::VisitBinaryOperation(BinaryOperation* expr) { + if (expr->op() == Token::COMMA) { + VISIT_FOR_EFFECT(expr->left()); + // Visit the right subexpression in the same AST context as the entire + // expression. + Visit(expr->right()); + + } else if (expr->op() == Token::AND || expr->op() == Token::OR) { + bool is_logical_and = (expr->op() == Token::AND); + if (ast_context()->IsTest()) { + TestContext* context = TestContext::cast(ast_context()); + // Translate left subexpression. + HBasicBlock* eval_right = graph()->CreateBasicBlock(); + if (is_logical_and) { + VISIT_FOR_CONTROL(expr->left(), eval_right, context->if_false()); + } else { + VISIT_FOR_CONTROL(expr->left(), context->if_true(), eval_right); + } + eval_right->SetJoinId(expr->RightId()); + + // Translate right subexpression by visiting it in the same AST + // context as the entire expression. + subgraph()->set_exit_block(eval_right); + Visit(expr->right()); + + } else { + VISIT_FOR_VALUE(expr->left()); + ASSERT(current_subgraph_->HasExit()); + + HValue* left = Top(); + HEnvironment* environment_copy = environment()->Copy(); + environment_copy->Pop(); + HSubgraph* right_subgraph; + right_subgraph = CreateBranchSubgraph(environment_copy); + ADD_TO_SUBGRAPH(right_subgraph, expr->right()); + current_subgraph_->AppendOptional(right_subgraph, is_logical_and, left); + current_subgraph_->exit_block()->SetJoinId(expr->id()); + ast_context()->ReturnValue(Pop()); + } + + } else { + VISIT_FOR_VALUE(expr->left()); + VISIT_FOR_VALUE(expr->right()); + + HValue* right = Pop(); + HValue* left = Pop(); + HInstruction* instr = BuildBinaryOperation(expr, left, right); + instr->set_position(expr->position()); + ast_context()->ReturnInstruction(instr, expr->id()); + } +} + + +void HGraphBuilder::AssumeRepresentation(HValue* value, Representation r) { + if (value->CheckFlag(HValue::kFlexibleRepresentation)) { + if (FLAG_trace_representation) { + PrintF("Assume representation for %s to be %s (%d)\n", + value->Mnemonic(), + r.Mnemonic(), + graph_->GetMaximumValueID()); + } + value->ChangeRepresentation(r); + // The representation of the value is dictated by type feedback. + value->ClearFlag(HValue::kFlexibleRepresentation); + } else if (FLAG_trace_representation) { + PrintF("No representation assumed\n"); + } +} + + +Representation HGraphBuilder::ToRepresentation(TypeInfo info) { + if (info.IsSmi()) return Representation::Integer32(); + if (info.IsInteger32()) return Representation::Integer32(); + if (info.IsDouble()) return Representation::Double(); + if (info.IsNumber()) return Representation::Double(); + return Representation::Tagged(); +} + + +void HGraphBuilder::VisitCompareOperation(CompareOperation* expr) { + if (IsClassOfTest(expr)) { + CallRuntime* call = expr->left()->AsCallRuntime(); + VISIT_FOR_VALUE(call->arguments()->at(0)); + HValue* value = Pop(); + Literal* literal = expr->right()->AsLiteral(); + Handle<String> rhs = Handle<String>::cast(literal->handle()); + HInstruction* instr = new HClassOfTest(value, rhs); + instr->set_position(expr->position()); + ast_context()->ReturnInstruction(instr, expr->id()); + return; + } + + // Check for the pattern: typeof <expression> == <string literal>. + UnaryOperation* left_unary = expr->left()->AsUnaryOperation(); + Literal* right_literal = expr->right()->AsLiteral(); + if ((expr->op() == Token::EQ || expr->op() == Token::EQ_STRICT) && + left_unary != NULL && left_unary->op() == Token::TYPEOF && + right_literal != NULL && right_literal->handle()->IsString()) { + VISIT_FOR_VALUE(left_unary->expression()); + HValue* left = Pop(); + HInstruction* instr = new HTypeofIs(left, + Handle<String>::cast(right_literal->handle())); + instr->set_position(expr->position()); + ast_context()->ReturnInstruction(instr, expr->id()); + return; + } + + VISIT_FOR_VALUE(expr->left()); + VISIT_FOR_VALUE(expr->right()); + + HValue* right = Pop(); + HValue* left = Pop(); + Token::Value op = expr->op(); + + TypeInfo info = oracle()->CompareType(expr, TypeFeedbackOracle::RESULT); + HInstruction* instr = NULL; + if (op == Token::INSTANCEOF) { + instr = new HInstanceOf(left, right); + } else if (op == Token::IN) { + BAILOUT("Unsupported comparison: in"); + } else if (info.IsNonPrimitive()) { + switch (op) { + case Token::EQ: + case Token::EQ_STRICT: { + AddInstruction(HCheckInstanceType::NewIsJSObjectOrJSFunction(left)); + AddInstruction(HCheckInstanceType::NewIsJSObjectOrJSFunction(right)); + instr = new HCompareJSObjectEq(left, right); + break; + } + default: + BAILOUT("Unsupported non-primitive compare"); + break; + } + } else { + HCompare* compare = new HCompare(left, right, op); + Representation r = ToRepresentation(info); + compare->SetInputRepresentation(r); + instr = compare; + } + instr->set_position(expr->position()); + ast_context()->ReturnInstruction(instr, expr->id()); +} + + +void HGraphBuilder::VisitCompareToNull(CompareToNull* expr) { + VISIT_FOR_VALUE(expr->expression()); + + HValue* value = Pop(); + HIsNull* compare = new HIsNull(value, expr->is_strict()); + ast_context()->ReturnInstruction(compare, expr->id()); +} + + +void HGraphBuilder::VisitThisFunction(ThisFunction* expr) { + BAILOUT("ThisFunction"); +} + + +void HGraphBuilder::VisitDeclaration(Declaration* decl) { + // We allow only declarations that do not require code generation. + // The following all require code generation: global variables and + // functions, variables with slot type LOOKUP, declarations with + // mode CONST, and functions. + Variable* var = decl->proxy()->var(); + Slot* slot = var->AsSlot(); + if (var->is_global() || + (slot != NULL && slot->type() == Slot::LOOKUP) || + decl->mode() == Variable::CONST || + decl->fun() != NULL) { + BAILOUT("unsupported declaration"); + } +} + + +// Generators for inline runtime functions. +// Support for types. +void HGraphBuilder::GenerateIsSmi(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HIsSmi* result = new HIsSmi(value); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateIsSpecObject(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HHasInstanceType* result = + new HHasInstanceType(value, FIRST_JS_OBJECT_TYPE, LAST_TYPE); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateIsFunction(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HHasInstanceType* result = new HHasInstanceType(value, JS_FUNCTION_TYPE); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateHasCachedArrayIndex(int argument_count, + int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HHasCachedArrayIndex* result = new HHasCachedArrayIndex(value); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateIsArray(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HHasInstanceType* result = new HHasInstanceType(value, JS_ARRAY_TYPE); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateIsRegExp(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HHasInstanceType* result = new HHasInstanceType(value, JS_REGEXP_TYPE); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateIsObject(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + + HValue* value = Pop(); + HIsObject* test = new HIsObject(value); + ast_context()->ReturnInstruction(test, ast_id); +} + + +void HGraphBuilder::GenerateIsNonNegativeSmi(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: IsNonNegativeSmi"); +} + + +void HGraphBuilder::GenerateIsUndetectableObject(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: IsUndetectableObject"); +} + + +void HGraphBuilder::GenerateIsStringWrapperSafeForDefaultValueOf( + int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: IsStringWrapperSafeForDefaultValueOf"); +} + + + // Support for construct call checks. +void HGraphBuilder::GenerateIsConstructCall(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: IsConstructCall"); +} + + +// Support for arguments.length and arguments[?]. +void HGraphBuilder::GenerateArgumentsLength(int argument_count, int ast_id) { + ASSERT(argument_count == 0); + HInstruction* elements = AddInstruction(new HArgumentsElements); + HArgumentsLength* result = new HArgumentsLength(elements); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateArguments(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* index = Pop(); + HInstruction* elements = AddInstruction(new HArgumentsElements); + HInstruction* length = AddInstruction(new HArgumentsLength(elements)); + HAccessArgumentsAt* result = new HAccessArgumentsAt(elements, length, index); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Support for accessing the class and value fields of an object. +void HGraphBuilder::GenerateClassOf(int argument_count, int ast_id) { + // The special form detected by IsClassOfTest is detected before we get here + // and does not cause a bailout. + BAILOUT("inlined runtime function: ClassOf"); +} + + +void HGraphBuilder::GenerateValueOf(int argument_count, int ast_id) { + ASSERT(argument_count == 1); + HValue* value = Pop(); + HValueOf* result = new HValueOf(value); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateSetValueOf(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: SetValueOf"); +} + + +// Fast support for charCodeAt(n). +void HGraphBuilder::GenerateStringCharCodeAt(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: StringCharCodeAt"); +} + + +// Fast support for string.charAt(n) and string[n]. +void HGraphBuilder::GenerateStringCharFromCode(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: StringCharFromCode"); +} + + +// Fast support for string.charAt(n) and string[n]. +void HGraphBuilder::GenerateStringCharAt(int argument_count, int ast_id) { + ASSERT_EQ(2, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::StringCharAt, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Fast support for object equality testing. +void HGraphBuilder::GenerateObjectEquals(int argument_count, int ast_id) { + ASSERT(argument_count == 2); + HValue* right = Pop(); + HValue* left = Pop(); + HCompareJSObjectEq* result = new HCompareJSObjectEq(left, right); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateLog(int argument_count, int ast_id) { + UNREACHABLE(); // We caught this in VisitCallRuntime. +} + + +// Fast support for Math.random(). +void HGraphBuilder::GenerateRandomHeapNumber(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: RandomHeapNumber"); +} + + +// Fast support for StringAdd. +void HGraphBuilder::GenerateStringAdd(int argument_count, int ast_id) { + ASSERT_EQ(2, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::StringAdd, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Fast support for SubString. +void HGraphBuilder::GenerateSubString(int argument_count, int ast_id) { + ASSERT_EQ(3, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::SubString, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Fast support for StringCompare. +void HGraphBuilder::GenerateStringCompare(int argument_count, int ast_id) { + ASSERT_EQ(2, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::StringCompare, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Support for direct calls from JavaScript to native RegExp code. +void HGraphBuilder::GenerateRegExpExec(int argument_count, int ast_id) { + ASSERT_EQ(4, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::RegExpExec, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Construct a RegExp exec result with two in-object properties. +void HGraphBuilder::GenerateRegExpConstructResult(int argument_count, + int ast_id) { + ASSERT_EQ(3, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = + new HCallStub(CodeStub::RegExpConstructResult, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Support for fast native caches. +void HGraphBuilder::GenerateGetFromCache(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: GetFromCache"); +} + + +// Fast support for number to string. +void HGraphBuilder::GenerateNumberToString(int argument_count, int ast_id) { + ASSERT_EQ(1, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = new HCallStub(CodeStub::NumberToString, argument_count); + ast_context()->ReturnInstruction(result, ast_id); +} + + +// Fast swapping of elements. Takes three expressions, the object and two +// indices. This should only be used if the indices are known to be +// non-negative and within bounds of the elements array at the call site. +void HGraphBuilder::GenerateSwapElements(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: SwapElements"); +} + + +// Fast call for custom callbacks. +void HGraphBuilder::GenerateCallFunction(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: CallFunction"); +} + + +// Fast call to math functions. +void HGraphBuilder::GenerateMathPow(int argument_count, int ast_id) { + ASSERT_EQ(2, argument_count); + HValue* right = Pop(); + HValue* left = Pop(); + HPower* result = new HPower(left, right); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateMathSin(int argument_count, int ast_id) { + ASSERT_EQ(1, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = + new HCallStub(CodeStub::TranscendentalCache, argument_count); + result->set_transcendental_type(TranscendentalCache::SIN); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateMathCos(int argument_count, int ast_id) { + ASSERT_EQ(1, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = + new HCallStub(CodeStub::TranscendentalCache, argument_count); + result->set_transcendental_type(TranscendentalCache::COS); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateMathLog(int argument_count, int ast_id) { + ASSERT_EQ(1, argument_count); + PushArgumentsForStubCall(argument_count); + HCallStub* result = + new HCallStub(CodeStub::TranscendentalCache, argument_count); + result->set_transcendental_type(TranscendentalCache::LOG); + ast_context()->ReturnInstruction(result, ast_id); +} + + +void HGraphBuilder::GenerateMathSqrt(int argument_count, int ast_id) { + BAILOUT("inlined runtime function: MathSqrt"); +} + + +// Check whether two RegExps are equivalent +void HGraphBuilder::GenerateIsRegExpEquivalent(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: IsRegExpEquivalent"); +} + + +void HGraphBuilder::GenerateGetCachedArrayIndex(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: GetCachedArrayIndex"); +} + + +void HGraphBuilder::GenerateFastAsciiArrayJoin(int argument_count, + int ast_id) { + BAILOUT("inlined runtime function: FastAsciiArrayJoin"); +} + + +#undef BAILOUT +#undef CHECK_BAILOUT +#undef VISIT_FOR_EFFECT +#undef VISIT_FOR_VALUE +#undef ADD_TO_SUBGRAPH + + +HEnvironment::HEnvironment(HEnvironment* outer, + Scope* scope, + Handle<JSFunction> closure) + : closure_(closure), + values_(0), + assigned_variables_(4), + parameter_count_(0), + local_count_(0), + outer_(outer), + pop_count_(0), + push_count_(0), + ast_id_(AstNode::kNoNumber) { + Initialize(scope->num_parameters() + 1, scope->num_stack_slots(), 0); +} + + +HEnvironment::HEnvironment(const HEnvironment* other) + : values_(0), + assigned_variables_(0), + parameter_count_(0), + local_count_(0), + outer_(NULL), + pop_count_(0), + push_count_(0), + ast_id_(other->ast_id()) { + Initialize(other); +} + + +void HEnvironment::Initialize(int parameter_count, + int local_count, + int stack_height) { + parameter_count_ = parameter_count; + local_count_ = local_count; + + // Avoid reallocating the temporaries' backing store on the first Push. + int total = parameter_count + local_count + stack_height; + values_.Initialize(total + 4); + for (int i = 0; i < total; ++i) values_.Add(NULL); +} + + +void HEnvironment::AddIncomingEdge(HBasicBlock* block, HEnvironment* other) { + ASSERT(!block->IsLoopHeader()); + ASSERT(values_.length() == other->values_.length()); + + int length = values_.length(); + for (int i = 0; i < length; ++i) { + HValue* value = values_[i]; + if (value != NULL && value->IsPhi() && value->block() == block) { + // There is already a phi for the i'th value. + HPhi* phi = HPhi::cast(value); + // Assert index is correct and that we haven't missed an incoming edge. + ASSERT(phi->merged_index() == i); + ASSERT(phi->OperandCount() == block->predecessors()->length()); + phi->AddInput(other->values_[i]); + } else if (values_[i] != other->values_[i]) { + // There is a fresh value on the incoming edge, a phi is needed. + ASSERT(values_[i] != NULL && other->values_[i] != NULL); + HPhi* phi = new HPhi(i); + HValue* old_value = values_[i]; + for (int j = 0; j < block->predecessors()->length(); j++) { + phi->AddInput(old_value); + } + phi->AddInput(other->values_[i]); + this->values_[i] = phi; + block->AddPhi(phi); + } + } +} + + +void HEnvironment::Initialize(const HEnvironment* other) { + closure_ = other->closure(); + values_.AddAll(other->values_); + assigned_variables_.AddAll(other->assigned_variables_); + parameter_count_ = other->parameter_count_; + local_count_ = other->local_count_; + if (other->outer_ != NULL) outer_ = other->outer_->Copy(); // Deep copy. + pop_count_ = other->pop_count_; + push_count_ = other->push_count_; + ast_id_ = other->ast_id_; +} + + +int HEnvironment::IndexFor(Variable* variable) const { + Slot* slot = variable->AsSlot(); + ASSERT(slot != NULL && slot->IsStackAllocated()); + if (slot->type() == Slot::PARAMETER) { + return slot->index() + 1; + } else { + return parameter_count_ + slot->index(); + } +} + + +HEnvironment* HEnvironment::Copy() const { + return new HEnvironment(this); +} + + +HEnvironment* HEnvironment::CopyWithoutHistory() const { + HEnvironment* result = Copy(); + result->ClearHistory(); + return result; +} + + +HEnvironment* HEnvironment::CopyAsLoopHeader(HBasicBlock* loop_header) const { + HEnvironment* new_env = Copy(); + for (int i = 0; i < values_.length(); ++i) { + HPhi* phi = new HPhi(i); + phi->AddInput(values_[i]); + new_env->values_[i] = phi; + loop_header->AddPhi(phi); + } + new_env->ClearHistory(); + return new_env; +} + + +HEnvironment* HEnvironment::CopyForInlining(Handle<JSFunction> target, + FunctionLiteral* function, + bool is_speculative, + HConstant* undefined) const { + // Outer environment is a copy of this one without the arguments. + int arity = function->scope()->num_parameters(); + HEnvironment* outer = Copy(); + outer->Drop(arity + 1); // Including receiver. + outer->ClearHistory(); + HEnvironment* inner = new HEnvironment(outer, function->scope(), target); + // Get the argument values from the original environment. + if (is_speculative) { + for (int i = 0; i <= arity; ++i) { // Include receiver. + HValue* push = ExpressionStackAt(arity - i); + inner->SetValueAt(i, push); + } + } else { + for (int i = 0; i <= arity; ++i) { // Include receiver. + inner->SetValueAt(i, ExpressionStackAt(arity - i)); + } + } + + // Initialize the stack-allocated locals to undefined. + int local_base = arity + 1; + int local_count = function->scope()->num_stack_slots(); + for (int i = 0; i < local_count; ++i) { + inner->SetValueAt(local_base + i, undefined); + } + + inner->set_ast_id(function->id()); + return inner; +} + + +void HEnvironment::PrintTo(StringStream* stream) { + for (int i = 0; i < total_count(); i++) { + if (i == 0) stream->Add("parameters\n"); + if (i == parameter_count()) stream->Add("locals\n"); + if (i == parameter_count() + local_count()) stream->Add("expressions"); + HValue* val = values_.at(i); + stream->Add("%d: ", i); + if (val != NULL) { + val->PrintNameTo(stream); + } else { + stream->Add("NULL"); + } + stream->Add("\n"); + } +} + + +void HEnvironment::PrintToStd() { + HeapStringAllocator string_allocator; + StringStream trace(&string_allocator); + PrintTo(&trace); + PrintF("%s", *trace.ToCString()); +} + + +void HTracer::TraceCompilation(FunctionLiteral* function) { + Tag tag(this, "compilation"); + Handle<String> name = function->debug_name(); + PrintStringProperty("name", *name->ToCString()); + PrintStringProperty("method", *name->ToCString()); + PrintLongProperty("date", static_cast<int64_t>(OS::TimeCurrentMillis())); +} + + +void HTracer::TraceLithium(const char* name, LChunk* chunk) { + Trace(name, chunk->graph(), chunk); +} + + +void HTracer::TraceHydrogen(const char* name, HGraph* graph) { + Trace(name, graph, NULL); +} + + +void HTracer::Trace(const char* name, HGraph* graph, LChunk* chunk) { + Tag tag(this, "cfg"); + PrintStringProperty("name", name); + const ZoneList<HBasicBlock*>* blocks = graph->blocks(); + for (int i = 0; i < blocks->length(); i++) { + HBasicBlock* current = blocks->at(i); + Tag block_tag(this, "block"); + PrintBlockProperty("name", current->block_id()); + PrintIntProperty("from_bci", -1); + PrintIntProperty("to_bci", -1); + + if (!current->predecessors()->is_empty()) { + PrintIndent(); + trace_.Add("predecessors"); + for (int j = 0; j < current->predecessors()->length(); ++j) { + trace_.Add(" \"B%d\"", current->predecessors()->at(j)->block_id()); + } + trace_.Add("\n"); + } else { + PrintEmptyProperty("predecessors"); + } + + if (current->end() == NULL || current->end()->FirstSuccessor() == NULL) { + PrintEmptyProperty("successors"); + } else if (current->end()->SecondSuccessor() == NULL) { + PrintBlockProperty("successors", + current->end()->FirstSuccessor()->block_id()); + } else { + PrintBlockProperty("successors", + current->end()->FirstSuccessor()->block_id(), + current->end()->SecondSuccessor()->block_id()); + } + + PrintEmptyProperty("xhandlers"); + PrintEmptyProperty("flags"); + + if (current->dominator() != NULL) { + PrintBlockProperty("dominator", current->dominator()->block_id()); + } + + if (chunk != NULL) { + int first_index = current->first_instruction_index(); + int last_index = current->last_instruction_index(); + PrintIntProperty( + "first_lir_id", + LifetimePosition::FromInstructionIndex(first_index).Value()); + PrintIntProperty( + "last_lir_id", + LifetimePosition::FromInstructionIndex(last_index).Value()); + } + + { + Tag states_tag(this, "states"); + Tag locals_tag(this, "locals"); + int total = current->phis()->length(); + trace_.Add("size %d\n", total); + trace_.Add("method \"None\""); + for (int j = 0; j < total; ++j) { + HPhi* phi = current->phis()->at(j); + trace_.Add("%d ", phi->merged_index()); + phi->PrintNameTo(&trace_); + trace_.Add(" "); + phi->PrintTo(&trace_); + trace_.Add("\n"); + } + } + + { + Tag HIR_tag(this, "HIR"); + HInstruction* instruction = current->first(); + while (instruction != NULL) { + int bci = 0; + int uses = instruction->uses()->length(); + trace_.Add("%d %d ", bci, uses); + instruction->PrintNameTo(&trace_); + trace_.Add(" "); + instruction->PrintTo(&trace_); + trace_.Add(" <|@\n"); + instruction = instruction->next(); + } + } + + + if (chunk != NULL) { + Tag LIR_tag(this, "LIR"); + int first_index = current->first_instruction_index(); + int last_index = current->last_instruction_index(); + if (first_index != -1 && last_index != -1) { + const ZoneList<LInstruction*>* instructions = chunk->instructions(); + for (int i = first_index; i <= last_index; ++i) { + LInstruction* linstr = instructions->at(i); + if (linstr != NULL) { + trace_.Add("%d ", + LifetimePosition::FromInstructionIndex(i).Value()); + linstr->PrintTo(&trace_); + trace_.Add(" <|@\n"); + } + } + } + } + } +} + + +void HTracer::TraceLiveRanges(const char* name, LAllocator* allocator) { + Tag tag(this, "intervals"); + PrintStringProperty("name", name); + + const ZoneList<LiveRange*>* fixed_d = allocator->fixed_double_live_ranges(); + for (int i = 0; i < fixed_d->length(); ++i) { + TraceLiveRange(fixed_d->at(i), "fixed"); + } + + const ZoneList<LiveRange*>* fixed = allocator->fixed_live_ranges(); + for (int i = 0; i < fixed->length(); ++i) { + TraceLiveRange(fixed->at(i), "fixed"); + } + + const ZoneList<LiveRange*>* live_ranges = allocator->live_ranges(); + for (int i = 0; i < live_ranges->length(); ++i) { + TraceLiveRange(live_ranges->at(i), "object"); + } +} + + +void HTracer::TraceLiveRange(LiveRange* range, const char* type) { + if (range != NULL && !range->IsEmpty()) { + trace_.Add("%d %s", range->id(), type); + if (range->HasRegisterAssigned()) { + LOperand* op = range->CreateAssignedOperand(); + int assigned_reg = op->index(); + if (op->IsDoubleRegister()) { + trace_.Add(" \"%s\"", + DoubleRegister::AllocationIndexToString(assigned_reg)); + } else { + ASSERT(op->IsRegister()); + trace_.Add(" \"%s\"", Register::AllocationIndexToString(assigned_reg)); + } + } else if (range->IsSpilled()) { + LOperand* op = range->TopLevel()->GetSpillOperand(); + if (op->IsDoubleStackSlot()) { + trace_.Add(" \"double_stack:%d\"", op->index()); + } else { + ASSERT(op->IsStackSlot()); + trace_.Add(" \"stack:%d\"", op->index()); + } + } + int parent_index = -1; + if (range->IsChild()) { + parent_index = range->parent()->id(); + } else { + parent_index = range->id(); + } + LOperand* op = range->FirstHint(); + int hint_index = -1; + if (op != NULL && op->IsUnallocated()) hint_index = op->VirtualRegister(); + trace_.Add(" %d %d", parent_index, hint_index); + UseInterval* cur_interval = range->first_interval(); + while (cur_interval != NULL) { + trace_.Add(" [%d, %d[", + cur_interval->start().Value(), + cur_interval->end().Value()); + cur_interval = cur_interval->next(); + } + + UsePosition* current_pos = range->first_pos(); + while (current_pos != NULL) { + if (current_pos->RegisterIsBeneficial()) { + trace_.Add(" %d M", current_pos->pos().Value()); + } + current_pos = current_pos->next(); + } + + trace_.Add(" \"\"\n"); + } +} + + +void HTracer::FlushToFile() { + AppendChars(filename_, *trace_.ToCString(), trace_.length(), false); + trace_.Reset(); +} + + +void HStatistics::Print() { + PrintF("Timing results:\n"); + int64_t sum = 0; + for (int i = 0; i < timing_.length(); ++i) { + sum += timing_[i]; + } + + for (int i = 0; i < names_.length(); ++i) { + PrintF("%30s", names_[i]); + double ms = static_cast<double>(timing_[i]) / 1000; + double percent = static_cast<double>(timing_[i]) * 100 / sum; + PrintF(" - %0.3f ms / %0.3f %% \n", ms, percent); + } + PrintF("%30s - %0.3f ms \n", "Sum", static_cast<double>(sum) / 1000); + PrintF("---------------------------------------------------------------\n"); + PrintF("%30s - %0.3f ms (%0.1f times slower than full code gen)\n", + "Total", + static_cast<double>(total_) / 1000, + static_cast<double>(total_) / full_code_gen_); +} + + +void HStatistics::SaveTiming(const char* name, int64_t ticks) { + if (name == HPhase::kFullCodeGen) { + full_code_gen_ += ticks; + } else if (name == HPhase::kTotal) { + total_ += ticks; + } else { + for (int i = 0; i < names_.length(); ++i) { + if (names_[i] == name) { + timing_[i] += ticks; + return; + } + } + names_.Add(name); + timing_.Add(ticks); + } +} + + +const char* const HPhase::kFullCodeGen = "Full code generator"; +const char* const HPhase::kTotal = "Total"; + + +void HPhase::Begin(const char* name, + HGraph* graph, + LChunk* chunk, + LAllocator* allocator) { + name_ = name; + graph_ = graph; + chunk_ = chunk; + allocator_ = allocator; + if (allocator != NULL && chunk_ == NULL) { + chunk_ = allocator->chunk(); + } + if (FLAG_time_hydrogen) start_ = OS::Ticks(); +} + + +void HPhase::End() const { + if (FLAG_time_hydrogen) { + int64_t end = OS::Ticks(); + HStatistics::Instance()->SaveTiming(name_, end - start_); + } + + if (FLAG_trace_hydrogen) { + if (graph_ != NULL) HTracer::Instance()->TraceHydrogen(name_, graph_); + if (chunk_ != NULL) HTracer::Instance()->TraceLithium(name_, chunk_); + if (allocator_ != NULL) { + HTracer::Instance()->TraceLiveRanges(name_, allocator_); + } + } + +#ifdef DEBUG + if (graph_ != NULL) graph_->Verify(); + if (chunk_ != NULL) chunk_->Verify(); + if (allocator_ != NULL) allocator_->Verify(); +#endif +} + +} } // namespace v8::internal diff --git a/src/hydrogen.h b/src/hydrogen.h new file mode 100644 index 00000000..ebabf3d2 --- /dev/null +++ b/src/hydrogen.h @@ -0,0 +1,1070 @@ +// Copyright 2010 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. + +#ifndef V8_HYDROGEN_H_ +#define V8_HYDROGEN_H_ + +#include "v8.h" + +#include "ast.h" +#include "compiler.h" +#include "data-flow.h" +#include "hydrogen-instructions.h" +#include "zone.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class HEnvironment; +class HGraph; +class HLoopInformation; +class HTracer; +class LAllocator; +class LChunk; +class LiveRange; + + +class HBasicBlock: public ZoneObject { + public: + explicit HBasicBlock(HGraph* graph); + virtual ~HBasicBlock() { } + + // Simple accessors. + int block_id() const { return block_id_; } + void set_block_id(int id) { block_id_ = id; } + HGraph* graph() const { return graph_; } + const ZoneList<HPhi*>* phis() const { return &phis_; } + HInstruction* first() const { return first_; } + HInstruction* GetLastInstruction(); + HControlInstruction* end() const { return end_; } + HLoopInformation* loop_information() const { return loop_information_; } + const ZoneList<HBasicBlock*>* predecessors() const { return &predecessors_; } + bool HasPredecessor() const { return predecessors_.length() > 0; } + const ZoneList<HBasicBlock*>* dominated_blocks() const { + return &dominated_blocks_; + } + const ZoneList<int>* deleted_phis() const { + return &deleted_phis_; + } + void RecordDeletedPhi(int merge_index) { + deleted_phis_.Add(merge_index); + } + HBasicBlock* dominator() const { return dominator_; } + HEnvironment* last_environment() const { return last_environment_; } + int argument_count() const { return argument_count_; } + void set_argument_count(int count) { argument_count_ = count; } + int first_instruction_index() const { return first_instruction_index_; } + void set_first_instruction_index(int index) { + first_instruction_index_ = index; + } + int last_instruction_index() const { return last_instruction_index_; } + void set_last_instruction_index(int index) { + last_instruction_index_ = index; + } + + void AttachLoopInformation(); + void DetachLoopInformation(); + bool IsLoopHeader() const { return loop_information() != NULL; } + bool IsStartBlock() const { return block_id() == 0; } + void PostProcessLoopHeader(IterationStatement* stmt); + + bool IsFinished() const { return end_ != NULL; } + void AddPhi(HPhi* phi); + void RemovePhi(HPhi* phi); + void AddInstruction(HInstruction* instr); + bool Dominates(HBasicBlock* other) const; + + void SetInitialEnvironment(HEnvironment* env); + void ClearEnvironment() { last_environment_ = NULL; } + bool HasEnvironment() const { return last_environment_ != NULL; } + void UpdateEnvironment(HEnvironment* env) { last_environment_ = env; } + HBasicBlock* parent_loop_header() const { + if (!HasParentLoopHeader()) return NULL; + return parent_loop_header_.get(); + } + + void set_parent_loop_header(HBasicBlock* block) { + parent_loop_header_.set(block); + } + + bool HasParentLoopHeader() const { return parent_loop_header_.is_set(); } + + void SetJoinId(int id); + + void Finish(HControlInstruction* last); + void Goto(HBasicBlock* block, bool include_stack_check = false); + + int PredecessorIndexOf(HBasicBlock* predecessor) const; + void AddSimulate(int id) { AddInstruction(CreateSimulate(id)); } + void AssignCommonDominator(HBasicBlock* other); + + // Add the inlined function exit sequence, adding an HLeaveInlined + // instruction and updating the bailout environment. + void AddLeaveInlined(HValue* return_value, HBasicBlock* target); + + // If a target block is tagged as an inline function return, all + // predecessors should contain the inlined exit sequence: + // + // LeaveInlined + // Simulate (caller's environment) + // Goto (target block) + bool IsInlineReturnTarget() const { return is_inline_return_target_; } + void MarkAsInlineReturnTarget() { is_inline_return_target_ = true; } + + Handle<Object> cond() { return cond_; } + void set_cond(Handle<Object> value) { cond_ = value; } + +#ifdef DEBUG + void Verify(); +#endif + + private: + void RegisterPredecessor(HBasicBlock* pred); + void AddDominatedBlock(HBasicBlock* block); + + HSimulate* CreateSimulate(int id); + + int block_id_; + HGraph* graph_; + ZoneList<HPhi*> phis_; + HInstruction* first_; + HInstruction* last_; // Last non-control instruction of the block. + HControlInstruction* end_; + HLoopInformation* loop_information_; + ZoneList<HBasicBlock*> predecessors_; + HBasicBlock* dominator_; + ZoneList<HBasicBlock*> dominated_blocks_; + HEnvironment* last_environment_; + // Outgoing parameter count at block exit, set during lithium translation. + int argument_count_; + // Instruction indices into the lithium code stream. + int first_instruction_index_; + int last_instruction_index_; + ZoneList<int> deleted_phis_; + SetOncePointer<HBasicBlock> parent_loop_header_; + bool is_inline_return_target_; + Handle<Object> cond_; +}; + + +class HLoopInformation: public ZoneObject { + public: + explicit HLoopInformation(HBasicBlock* loop_header) + : back_edges_(4), loop_header_(loop_header), blocks_(8) { + blocks_.Add(loop_header); + } + virtual ~HLoopInformation() {} + + const ZoneList<HBasicBlock*>* back_edges() const { return &back_edges_; } + const ZoneList<HBasicBlock*>* blocks() const { return &blocks_; } + HBasicBlock* loop_header() const { return loop_header_; } + HBasicBlock* GetLastBackEdge() const; + void RegisterBackEdge(HBasicBlock* block); + + private: + void AddBlock(HBasicBlock* block); + + ZoneList<HBasicBlock*> back_edges_; + HBasicBlock* loop_header_; + ZoneList<HBasicBlock*> blocks_; +}; + + +class HSubgraph: public ZoneObject { + public: + explicit HSubgraph(HGraph* graph) + : graph_(graph), + entry_block_(NULL), + exit_block_(NULL), + break_continue_info_(4) { + } + + HGraph* graph() const { return graph_; } + HEnvironment* environment() const { + ASSERT(HasExit()); + return exit_block_->last_environment(); + } + + bool HasExit() const { return exit_block_ != NULL; } + + void PreProcessOsrEntry(IterationStatement* statement); + + void AppendOptional(HSubgraph* graph, + bool on_true_branch, + HValue* boolean_value); + void AppendJoin(HSubgraph* then_graph, HSubgraph* else_graph, AstNode* node); + void AppendWhile(HSubgraph* condition, + HSubgraph* body, + IterationStatement* statement, + HSubgraph* continue_subgraph, + HSubgraph* exit); + void AppendDoWhile(HSubgraph* body, + IterationStatement* statement, + HSubgraph* go_back, + HSubgraph* exit); + void AppendEndless(HSubgraph* body, IterationStatement* statement); + void Append(HSubgraph* next, BreakableStatement* statement); + void ResolveContinue(IterationStatement* statement); + HBasicBlock* BundleBreak(BreakableStatement* statement); + HBasicBlock* BundleContinue(IterationStatement* statement); + HBasicBlock* BundleBreakContinue(BreakableStatement* statement, + bool is_continue, + int join_id); + HBasicBlock* JoinBlocks(HBasicBlock* a, HBasicBlock* b, int id); + + void FinishExit(HControlInstruction* instruction); + void FinishBreakContinue(BreakableStatement* target, bool is_continue); + void Initialize(HBasicBlock* block) { + ASSERT(entry_block_ == NULL); + entry_block_ = block; + exit_block_ = block; + } + HBasicBlock* entry_block() const { return entry_block_; } + HBasicBlock* exit_block() const { return exit_block_; } + void set_exit_block(HBasicBlock* block) { + exit_block_ = block; + } + + void ConnectExitTo(HBasicBlock* other, bool include_stack_check = false) { + if (HasExit()) { + exit_block()->Goto(other, include_stack_check); + } + } + + void AddBreakContinueInfo(HSubgraph* other) { + break_continue_info_.AddAll(other->break_continue_info_); + } + + protected: + class BreakContinueInfo: public ZoneObject { + public: + BreakContinueInfo(BreakableStatement* target, HBasicBlock* block, + bool is_continue) + : target_(target), block_(block), continue_(is_continue) {} + BreakableStatement* target() const { return target_; } + HBasicBlock* block() const { return block_; } + bool is_continue() const { return continue_; } + bool IsResolved() const { return block_ == NULL; } + void Resolve() { block_ = NULL; } + + private: + BreakableStatement* target_; + HBasicBlock* block_; + bool continue_; + }; + + const ZoneList<BreakContinueInfo*>* break_continue_info() const { + return &break_continue_info_; + } + + HGraph* graph_; // The graph this is a subgraph of. + HBasicBlock* entry_block_; + HBasicBlock* exit_block_; + + private: + ZoneList<BreakContinueInfo*> break_continue_info_; +}; + + +class HGraph: public HSubgraph { + public: + explicit HGraph(CompilationInfo* info); + + CompilationInfo* info() const { return info_; } + const ZoneList<HBasicBlock*>* blocks() const { return &blocks_; } + const ZoneList<HPhi*>* phi_list() const { return phi_list_; } + Handle<String> debug_name() const { return info_->function()->debug_name(); } + HEnvironment* start_environment() const { return start_environment_; } + + void InitializeInferredTypes(); + void InsertTypeConversions(); + void InsertRepresentationChanges(); + bool ProcessArgumentsObject(); + void EliminateRedundantPhis(); + void Canonicalize(); + void OrderBlocks(); + void AssignDominators(); + + // Returns false if there are phi-uses of the arguments-object + // which are not supported by the optimizing compiler. + bool CollectPhis(); + + Handle<Code> Compile(); + + void set_undefined_constant(HConstant* constant) { + undefined_constant_.set(constant); + } + HConstant* GetConstantUndefined() const { return undefined_constant_.get(); } + HConstant* GetConstant1(); + HConstant* GetConstantMinus1(); + HConstant* GetConstantTrue(); + HConstant* GetConstantFalse(); + + HBasicBlock* CreateBasicBlock(); + HArgumentsObject* GetArgumentsObject() const { + return arguments_object_.get(); + } + bool HasArgumentsObject() const { return arguments_object_.is_set(); } + + void SetArgumentsObject(HArgumentsObject* object) { + arguments_object_.set(object); + } + + // True iff. we are compiling for OSR and the statement is the entry. + bool HasOsrEntryAt(IterationStatement* statement); + + int GetMaximumValueID() const { return values_.length(); } + int GetNextBlockID() { return next_block_id_++; } + int GetNextValueID(HValue* value) { + values_.Add(value); + return values_.length() - 1; + } + HValue* LookupValue(int id) const { + if (id >= 0 && id < values_.length()) return values_[id]; + return NULL; + } + +#ifdef DEBUG + void Verify() const; +#endif + + private: + void Postorder(HBasicBlock* block, + BitVector* visited, + ZoneList<HBasicBlock*>* order, + HBasicBlock* loop_header); + void PostorderLoopBlocks(HLoopInformation* loop, + BitVector* visited, + ZoneList<HBasicBlock*>* order, + HBasicBlock* loop_header); + HConstant* GetConstant(SetOncePointer<HConstant>* pointer, + Object* value); + + void InsertTypeConversions(HInstruction* instr); + void PropagateMinusZeroChecks(HValue* value, BitVector* visited); + void InsertRepresentationChangeForUse(HValue* value, + HValue* use, + Representation to, + bool truncating); + void InsertRepresentationChanges(HValue* current); + void InferTypes(ZoneList<HValue*>* worklist); + void InitializeInferredTypes(int from_inclusive, int to_inclusive); + void CheckForBackEdge(HBasicBlock* block, HBasicBlock* successor); + + int next_block_id_; + CompilationInfo* info_; + HEnvironment* start_environment_; + ZoneList<HBasicBlock*> blocks_; + ZoneList<HValue*> values_; + ZoneList<HPhi*>* phi_list_; + SetOncePointer<HConstant> undefined_constant_; + SetOncePointer<HConstant> constant_1_; + SetOncePointer<HConstant> constant_minus1_; + SetOncePointer<HConstant> constant_true_; + SetOncePointer<HConstant> constant_false_; + SetOncePointer<HArgumentsObject> arguments_object_; + + friend class HSubgraph; + + DISALLOW_COPY_AND_ASSIGN(HGraph); +}; + + +class HEnvironment: public ZoneObject { + public: + HEnvironment(HEnvironment* outer, + Scope* scope, + Handle<JSFunction> closure); + + void Bind(Variable* variable, HValue* value) { + Bind(IndexFor(variable), value); + + if (FLAG_trace_environment) { + PrintF("Slot index=%d name=%s\n", + variable->AsSlot()->index(), + *variable->name()->ToCString()); + } + } + + void Bind(int index, HValue* value) { + ASSERT(value != NULL); + if (!assigned_variables_.Contains(index)) { + assigned_variables_.Add(index); + } + values_[index] = value; + } + + HValue* Lookup(Variable* variable) const { + return Lookup(IndexFor(variable)); + } + HValue* Lookup(int index) const { + HValue* result = values_[index]; + ASSERT(result != NULL); + return result; + } + + void Push(HValue* value) { + ASSERT(value != NULL); + ++push_count_; + values_.Add(value); + } + + HValue* Top() const { return ExpressionStackAt(0); } + + HValue* ExpressionStackAt(int index_from_top) const { + int index = values_.length() - index_from_top - 1; + ASSERT(IsExpressionStackIndex(index)); + return values_[index]; + } + + void SetExpressionStackAt(int index_from_top, HValue* value) { + int index = values_.length() - index_from_top - 1; + ASSERT(IsExpressionStackIndex(index)); + values_[index] = value; + } + + HValue* Pop() { + ASSERT(!IsExpressionStackEmpty()); + if (push_count_ > 0) { + --push_count_; + ASSERT(push_count_ >= 0); + } else { + ++pop_count_; + } + return values_.RemoveLast(); + } + + void Drop(int count) { + for (int i = 0; i < count; ++i) { + Pop(); + } + } + + Handle<JSFunction> closure() const { return closure_; } + + // ID of the original AST node to identify deoptimization points. + int ast_id() const { return ast_id_; } + void set_ast_id(int id) { ast_id_ = id; } + + const ZoneList<HValue*>* values() const { return &values_; } + const ZoneList<int>* assigned_variables() const { + return &assigned_variables_; + } + int parameter_count() const { return parameter_count_; } + int local_count() const { return local_count_; } + int push_count() const { return push_count_; } + int pop_count() const { return pop_count_; } + int total_count() const { return values_.length(); } + HEnvironment* outer() const { return outer_; } + HEnvironment* Copy() const; + HEnvironment* CopyWithoutHistory() const; + HEnvironment* CopyAsLoopHeader(HBasicBlock* block) const; + + // Create an "inlined version" of this environment, where the original + // environment is the outer environment but the top expression stack + // elements are moved to an inner environment as parameters. If + // is_speculative, the argument values are expected to be PushArgument + // instructions, otherwise they are the actual values. + HEnvironment* CopyForInlining(Handle<JSFunction> target, + FunctionLiteral* function, + bool is_speculative, + HConstant* undefined) const; + + void AddIncomingEdge(HBasicBlock* block, HEnvironment* other); + void ClearHistory() { + pop_count_ = 0; + push_count_ = 0; + assigned_variables_.Clear(); + } + void SetValueAt(int index, HValue* value) { + ASSERT(index < total_count()); + values_[index] = value; + } + + void PrintTo(StringStream* stream); + void PrintToStd(); + + private: + explicit HEnvironment(const HEnvironment* other); + + bool IsExpressionStackIndex(int index) const { + return index >= parameter_count_ + local_count_; + } + bool IsExpressionStackEmpty() const { + int length = values_.length(); + int first_expression = parameter_count() + local_count(); + ASSERT(length >= first_expression); + return length == first_expression; + } + void Initialize(int parameter_count, int local_count, int stack_height); + void Initialize(const HEnvironment* other); + int VariableToIndex(Variable* var); + int IndexFor(Variable* variable) const; + + Handle<JSFunction> closure_; + // Value array [parameters] [locals] [temporaries]. + ZoneList<HValue*> values_; + ZoneList<int> assigned_variables_; + int parameter_count_; + int local_count_; + HEnvironment* outer_; + int pop_count_; + int push_count_; + int ast_id_; +}; + + +class HGraphBuilder; + +class AstContext { + public: + bool IsEffect() const { return kind_ == Expression::kEffect; } + bool IsValue() const { return kind_ == Expression::kValue; } + bool IsTest() const { return kind_ == Expression::kTest; } + + // 'Fill' this context with a hydrogen value. The value is assumed to + // have already been inserted in the instruction stream (or not need to + // be, e.g., HPhi). Call this function in tail position in the Visit + // functions for expressions. + virtual void ReturnValue(HValue* value) = 0; + + // Add a hydrogen instruction to the instruction stream (recording an + // environment simulation if necessary) and then fill this context with + // the instruction as value. + virtual void ReturnInstruction(HInstruction* instr, int ast_id) = 0; + + protected: + AstContext(HGraphBuilder* owner, Expression::Context kind); + virtual ~AstContext(); + + HGraphBuilder* owner() const { return owner_; } + + // We want to be able to assert, in a context-specific way, that the stack + // height makes sense when the context is filled. +#ifdef DEBUG + int original_count_; +#endif + + private: + HGraphBuilder* owner_; + Expression::Context kind_; + AstContext* outer_; +}; + + +class EffectContext: public AstContext { + public: + explicit EffectContext(HGraphBuilder* owner) + : AstContext(owner, Expression::kEffect) { + } + virtual ~EffectContext(); + + virtual void ReturnValue(HValue* value); + virtual void ReturnInstruction(HInstruction* instr, int ast_id); +}; + + +class ValueContext: public AstContext { + public: + explicit ValueContext(HGraphBuilder* owner) + : AstContext(owner, Expression::kValue) { + } + virtual ~ValueContext(); + + virtual void ReturnValue(HValue* value); + virtual void ReturnInstruction(HInstruction* instr, int ast_id); +}; + + +class TestContext: public AstContext { + public: + TestContext(HGraphBuilder* owner, + HBasicBlock* if_true, + HBasicBlock* if_false) + : AstContext(owner, Expression::kTest), + if_true_(if_true), + if_false_(if_false) { + } + + virtual void ReturnValue(HValue* value); + virtual void ReturnInstruction(HInstruction* instr, int ast_id); + + static TestContext* cast(AstContext* context) { + ASSERT(context->IsTest()); + return reinterpret_cast<TestContext*>(context); + } + + HBasicBlock* if_true() const { return if_true_; } + HBasicBlock* if_false() const { return if_false_; } + + private: + // Build the shared core part of the translation unpacking a value into + // control flow. + void BuildBranch(HValue* value); + + HBasicBlock* if_true_; + HBasicBlock* if_false_; +}; + + +class HGraphBuilder: public AstVisitor { + public: + explicit HGraphBuilder(TypeFeedbackOracle* oracle) + : oracle_(oracle), + graph_(NULL), + current_subgraph_(NULL), + peeled_statement_(NULL), + ast_context_(NULL), + call_context_(NULL), + function_return_(NULL), + inlined_count_(0) { } + + HGraph* CreateGraph(CompilationInfo* info); + + // Simple accessors. + HGraph* graph() const { return graph_; } + HSubgraph* subgraph() const { return current_subgraph_; } + + HEnvironment* environment() const { return subgraph()->environment(); } + HBasicBlock* CurrentBlock() const { return subgraph()->exit_block(); } + + // Adding instructions. + HInstruction* AddInstruction(HInstruction* instr); + void AddSimulate(int id); + + // Bailout environment manipulation. + void Push(HValue* value) { environment()->Push(value); } + HValue* Pop() { return environment()->Pop(); } + + private: + // Type of a member function that generates inline code for a native function. + typedef void (HGraphBuilder::*InlineFunctionGenerator)(int argument_count, + int ast_id); + + // Forward declarations for inner scope classes. + class SubgraphScope; + + static const InlineFunctionGenerator kInlineFunctionGenerators[]; + + static const int kMaxCallPolymorphism = 4; + static const int kMaxLoadPolymorphism = 4; + static const int kMaxStorePolymorphism = 4; + + static const int kMaxInlinedNodes = 196; + static const int kMaxInlinedSize = 196; + static const int kMaxSourceSize = 600; + + // Simple accessors. + TypeFeedbackOracle* oracle() const { return oracle_; } + AstContext* ast_context() const { return ast_context_; } + void set_ast_context(AstContext* context) { ast_context_ = context; } + AstContext* call_context() const { return call_context_; } + HBasicBlock* function_return() const { return function_return_; } + + // Generators for inline runtime functions. +#define INLINE_FUNCTION_GENERATOR_DECLARATION(Name, argc, ressize) \ + void Generate##Name(int argument_count, int ast_id); + + INLINE_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_DECLARATION) + INLINE_RUNTIME_FUNCTION_LIST(INLINE_FUNCTION_GENERATOR_DECLARATION) +#undef INLINE_FUNCTION_GENERATOR_DECLARATION + + void Bailout(const char* reason); + + void AppendPeeledWhile(IterationStatement* stmt, + HSubgraph* cond_graph, + HSubgraph* body_graph, + HSubgraph* exit_graph); + + void AddToSubgraph(HSubgraph* graph, ZoneList<Statement*>* stmts); + void AddToSubgraph(HSubgraph* graph, Statement* stmt); + void AddToSubgraph(HSubgraph* graph, Expression* expr); + + HValue* Top() const { return environment()->Top(); } + void Drop(int n) { environment()->Drop(n); } + void Bind(Variable* var, HValue* value) { environment()->Bind(var, value); } + + void VisitForValue(Expression* expr); + void VisitForEffect(Expression* expr); + void VisitForControl(Expression* expr, + HBasicBlock* true_block, + HBasicBlock* false_block); + + // Visit an argument and wrap it in a PushArgument instruction. + HValue* VisitArgument(Expression* expr); + void VisitArgumentList(ZoneList<Expression*>* arguments); + + void AddPhi(HPhi* phi); + + void PushAndAdd(HInstruction* instr); + + void PushArgumentsForStubCall(int argument_count); + + // Remove the arguments from the bailout environment and emit instructions + // to push them as outgoing parameters. + void ProcessCall(HCall* call); + + void AssumeRepresentation(HValue* value, Representation r); + static Representation ToRepresentation(TypeInfo info); + + void SetupScope(Scope* scope); + virtual void VisitStatements(ZoneList<Statement*>* statements); + +#define DECLARE_VISIT(type) virtual void Visit##type(type* node); + AST_NODE_LIST(DECLARE_VISIT) +#undef DECLARE_VISIT + + bool ShouldPeel(HSubgraph* cond, HSubgraph* body); + + HBasicBlock* CreateBasicBlock(HEnvironment* env); + HSubgraph* CreateEmptySubgraph(); + HSubgraph* CreateGotoSubgraph(HEnvironment* env); + HSubgraph* CreateBranchSubgraph(HEnvironment* env); + HSubgraph* CreateLoopHeaderSubgraph(HEnvironment* env); + HSubgraph* CreateInlinedSubgraph(HEnvironment* outer, + Handle<JSFunction> target, + FunctionLiteral* function); + + // Helpers for flow graph construction. + void LookupGlobalPropertyCell(Variable* var, + LookupResult* lookup, + bool is_store); + + bool TryArgumentsAccess(Property* expr); + bool TryCallApply(Call* expr); + bool TryInline(Call* expr); + bool TryMathFunctionInline(Call* expr); + void TraceInline(Handle<JSFunction> target, bool result); + + void HandleGlobalVariableAssignment(Variable* var, + HValue* value, + int position, + int ast_id); + + void HandlePropertyAssignment(Assignment* expr); + void HandleCompoundAssignment(Assignment* expr); + void HandlePolymorphicLoadNamedField(Property* expr, + HValue* object, + ZoneMapList* types, + Handle<String> name); + void HandlePolymorphicStoreNamedField(Assignment* expr, + HValue* object, + HValue* value, + ZoneMapList* types, + Handle<String> name); + void HandlePolymorphicCallNamed(Call* expr, + HValue* receiver, + ZoneMapList* types, + Handle<String> name); + + HInstruction* BuildBinaryOperation(BinaryOperation* expr, + HValue* left, + HValue* right); + HInstruction* BuildIncrement(HValue* value, bool increment); + HLoadNamedField* BuildLoadNamedField(HValue* object, + Property* expr, + Handle<Map> type, + LookupResult* result, + bool smi_and_map_check); + HInstruction* BuildLoadNamedGeneric(HValue* object, Property* expr); + HInstruction* BuildLoadKeyedFastElement(HValue* object, + HValue* key, + Property* expr); + HInstruction* BuildLoadKeyedGeneric(HValue* object, + HValue* key); + + HInstruction* BuildLoadNamed(HValue* object, + Property* prop, + Handle<Map> map, + Handle<String> name); + HInstruction* BuildStoreNamed(HValue* object, + HValue* value, + Expression* expr); + HInstruction* BuildStoreNamedField(HValue* object, + Handle<String> name, + HValue* value, + Handle<Map> type, + LookupResult* lookup, + bool smi_and_map_check); + HInstruction* BuildStoreNamedGeneric(HValue* object, + Handle<String> name, + HValue* value); + HInstruction* BuildStoreKeyedGeneric(HValue* object, + HValue* key, + HValue* value); + + HInstruction* BuildStoreKeyedFastElement(HValue* object, + HValue* key, + HValue* val, + Expression* expr); + + HCompare* BuildSwitchCompare(HSubgraph* subgraph, + HValue* switch_value, + CaseClause* clause); + + void AddCheckConstantFunction(Call* expr, + HValue* receiver, + Handle<Map> receiver_map, + bool smi_and_map_check); + + + HBasicBlock* BuildTypeSwitch(ZoneMapList* maps, + ZoneList<HSubgraph*>* subgraphs, + HValue* receiver, + int join_id); + + TypeFeedbackOracle* oracle_; + HGraph* graph_; + HSubgraph* current_subgraph_; + IterationStatement* peeled_statement_; + // Expression context of the currently visited subexpression. NULL when + // visiting statements. + AstContext* ast_context_; + + // During function inlining, expression context of the call being + // inlined. NULL when not inlining. + AstContext* call_context_; + + // When inlining a call in an effect or value context, the return + // block. NULL otherwise. When inlining a call in a test context, there + // are a pair of target blocks in the call context. + HBasicBlock* function_return_; + + int inlined_count_; + + friend class AstContext; // Pushes and pops the AST context stack. + + DISALLOW_COPY_AND_ASSIGN(HGraphBuilder); +}; + + +class HValueMap: public ZoneObject { + public: + HValueMap() + : array_size_(0), + lists_size_(0), + count_(0), + present_flags_(0), + array_(NULL), + lists_(NULL), + free_list_head_(kNil) { + ResizeLists(kInitialSize); + Resize(kInitialSize); + } + + void Kill(int flags); + + void Add(HValue* value) { + present_flags_ |= value->flags(); + Insert(value); + } + + HValue* Lookup(HValue* value) const; + HValueMap* Copy() const { return new HValueMap(this); } + + private: + // A linked list of HValue* values. Stored in arrays. + struct HValueMapListElement { + HValue* value; + int next; // Index in the array of the next list element. + }; + static const int kNil = -1; // The end of a linked list + + // Must be a power of 2. + static const int kInitialSize = 16; + + explicit HValueMap(const HValueMap* other); + + void Resize(int new_size); + void ResizeLists(int new_size); + void Insert(HValue* value); + uint32_t Bound(uint32_t value) const { return value & (array_size_ - 1); } + + int array_size_; + int lists_size_; + int count_; // The number of values stored in the HValueMap. + int present_flags_; // All flags that are in any value in the HValueMap. + HValueMapListElement* array_; // Primary store - contains the first value + // with a given hash. Colliding elements are stored in linked lists. + HValueMapListElement* lists_; // The linked lists containing hash collisions. + int free_list_head_; // Unused elements in lists_ are on the free list. +}; + + +class HStatistics: public Malloced { + public: + void Print(); + void SaveTiming(const char* name, int64_t ticks); + static HStatistics* Instance() { + static SetOncePointer<HStatistics> instance; + if (!instance.is_set()) { + instance.set(new HStatistics()); + } + return instance.get(); + } + + private: + + HStatistics() : timing_(5), names_(5), total_(0), full_code_gen_(0) { } + + List<int64_t> timing_; + List<const char*> names_; + int64_t total_; + int64_t full_code_gen_; +}; + + +class HPhase BASE_EMBEDDED { + public: + static const char* const kFullCodeGen; + static const char* const kTotal; + + explicit HPhase(const char* name) { Begin(name, NULL, NULL, NULL); } + HPhase(const char* name, HGraph* graph) { + Begin(name, graph, NULL, NULL); + } + HPhase(const char* name, LChunk* chunk) { + Begin(name, NULL, chunk, NULL); + } + HPhase(const char* name, LAllocator* allocator) { + Begin(name, NULL, NULL, allocator); + } + + ~HPhase() { + End(); + } + + private: + void Begin(const char* name, + HGraph* graph, + LChunk* chunk, + LAllocator* allocator); + void End() const; + + int64_t start_; + const char* name_; + HGraph* graph_; + LChunk* chunk_; + LAllocator* allocator_; +}; + + +class HTracer: public Malloced { + public: + void TraceCompilation(FunctionLiteral* function); + void TraceHydrogen(const char* name, HGraph* graph); + void TraceLithium(const char* name, LChunk* chunk); + void TraceLiveRanges(const char* name, LAllocator* allocator); + + static HTracer* Instance() { + static SetOncePointer<HTracer> instance; + if (!instance.is_set()) { + instance.set(new HTracer("hydrogen.cfg")); + } + return instance.get(); + } + + private: + class Tag BASE_EMBEDDED { + public: + Tag(HTracer* tracer, const char* name) { + name_ = name; + tracer_ = tracer; + tracer->PrintIndent(); + tracer->trace_.Add("begin_%s\n", name); + tracer->indent_++; + } + + ~Tag() { + tracer_->indent_--; + tracer_->PrintIndent(); + tracer_->trace_.Add("end_%s\n", name_); + ASSERT(tracer_->indent_ >= 0); + tracer_->FlushToFile(); + } + + private: + HTracer* tracer_; + const char* name_; + }; + + explicit HTracer(const char* filename) + : filename_(filename), trace_(&string_allocator_), indent_(0) { + WriteChars(filename, "", 0, false); + } + + void TraceLiveRange(LiveRange* range, const char* type); + void Trace(const char* name, HGraph* graph, LChunk* chunk); + void FlushToFile(); + + void PrintEmptyProperty(const char* name) { + PrintIndent(); + trace_.Add("%s\n", name); + } + + void PrintStringProperty(const char* name, const char* value) { + PrintIndent(); + trace_.Add("%s \"%s\"\n", name, value); + } + + void PrintLongProperty(const char* name, int64_t value) { + PrintIndent(); + trace_.Add("%s %d000\n", name, static_cast<int>(value / 1000)); + } + + void PrintBlockProperty(const char* name, int block_id) { + PrintIndent(); + trace_.Add("%s \"B%d\"\n", name, block_id); + } + + void PrintBlockProperty(const char* name, int block_id1, int block_id2) { + PrintIndent(); + trace_.Add("%s \"B%d\" \"B%d\"\n", name, block_id1, block_id2); + } + + void PrintIntProperty(const char* name, int value) { + PrintIndent(); + trace_.Add("%s %d\n", name, value); + } + + void PrintIndent() { + for (int i = 0; i < indent_; i++) { + trace_.Add(" "); + } + } + + const char* filename_; + HeapStringAllocator string_allocator_; + StringStream trace_; + int indent_; +}; + + +} } // namespace v8::internal + +#endif // V8_HYDROGEN_H_ diff --git a/src/ia32/assembler-ia32-inl.h b/src/ia32/assembler-ia32-inl.h index ecbdfdcf..54cfb5c3 100644 --- a/src/ia32/assembler-ia32-inl.h +++ b/src/ia32/assembler-ia32-inl.h @@ -120,6 +120,30 @@ Address* RelocInfo::target_reference_address() { } +Handle<JSGlobalPropertyCell> RelocInfo::target_cell_handle() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + return Handle<JSGlobalPropertyCell>( + reinterpret_cast<JSGlobalPropertyCell**>(address)); +} + + +JSGlobalPropertyCell* RelocInfo::target_cell() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + Object* object = HeapObject::FromAddress( + address - JSGlobalPropertyCell::kValueOffset); + return reinterpret_cast<JSGlobalPropertyCell*>(object); +} + + +void RelocInfo::set_target_cell(JSGlobalPropertyCell* cell) { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = cell->address() + JSGlobalPropertyCell::kValueOffset; + Memory::Address_at(pc_) = address; +} + + Address RelocInfo::call_address() { ASSERT((IsJSReturn(rmode()) && IsPatchedReturnSequence()) || (IsDebugBreakSlot(rmode()) && IsPatchedDebugBreakSlotSequence())); @@ -167,6 +191,8 @@ void RelocInfo::Visit(ObjectVisitor* visitor) { visitor->VisitPointer(target_object_address()); } else if (RelocInfo::IsCodeTarget(mode)) { visitor->VisitCodeTarget(this); + } else if (mode == RelocInfo::GLOBAL_PROPERTY_CELL) { + visitor->VisitGlobalPropertyCell(this); } else if (mode == RelocInfo::EXTERNAL_REFERENCE) { visitor->VisitExternalReference(target_reference_address()); #ifdef ENABLE_DEBUGGER_SUPPORT @@ -190,6 +216,8 @@ void RelocInfo::Visit() { StaticVisitor::VisitPointer(target_object_address()); } else if (RelocInfo::IsCodeTarget(mode)) { StaticVisitor::VisitCodeTarget(this); + } else if (mode == RelocInfo::GLOBAL_PROPERTY_CELL) { + StaticVisitor::VisitGlobalPropertyCell(this); } else if (mode == RelocInfo::EXTERNAL_REFERENCE) { StaticVisitor::VisitExternalReference(target_reference_address()); #ifdef ENABLE_DEBUGGER_SUPPORT @@ -246,6 +274,12 @@ Immediate::Immediate(Smi* value) { } +Immediate::Immediate(Address addr) { + x_ = reinterpret_cast<int32_t>(addr); + rmode_ = RelocInfo::NONE; +} + + void Assembler::emit(uint32_t x) { *reinterpret_cast<uint32_t*>(pc_) = x; pc_ += sizeof(uint32_t); diff --git a/src/ia32/assembler-ia32.cc b/src/ia32/assembler-ia32.cc index 125f503b..c173a3dc 100644 --- a/src/ia32/assembler-ia32.cc +++ b/src/ia32/assembler-ia32.cc @@ -32,7 +32,7 @@ // The original source code covered by the above license above has been modified // significantly by Google Inc. -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 the V8 project authors. All rights reserved. #include "v8.h" @@ -56,10 +56,10 @@ uint64_t CpuFeatures::found_by_runtime_probing_ = 0; // The Probe method needs executable memory, so it uses Heap::CreateCode. // Allocation failure is silent and leads to safe default. -void CpuFeatures::Probe() { +void CpuFeatures::Probe(bool portable) { ASSERT(Heap::HasBeenSetup()); ASSERT(supported_ == 0); - if (Serializer::enabled()) { + if (portable && Serializer::enabled()) { supported_ |= OS::CpuFeaturesImpliedByPlatform(); return; // No features if we might serialize. } @@ -137,7 +137,7 @@ void CpuFeatures::Probe() { found_by_runtime_probing_ = supported_; uint64_t os_guarantees = OS::CpuFeaturesImpliedByPlatform(); supported_ |= os_guarantees; - found_by_runtime_probing_ &= ~os_guarantees; + found_by_runtime_probing_ &= portable ? ~os_guarantees : 0; } @@ -435,6 +435,13 @@ void Assembler::push(const Immediate& x) { } +void Assembler::push_imm32(int32_t imm32) { + EnsureSpace ensure_space(this); + EMIT(0x68); + emit(imm32); +} + + void Assembler::push(Register src) { EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -1542,7 +1549,9 @@ void Assembler::bind(NearLabel* L) { L->bind_to(pc_offset()); } + void Assembler::call(Label* L) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; if (L->is_bound()) { @@ -1561,6 +1570,7 @@ void Assembler::call(Label* L) { void Assembler::call(byte* entry, RelocInfo::Mode rmode) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; ASSERT(!RelocInfo::IsCodeTarget(rmode)); @@ -1570,6 +1580,7 @@ void Assembler::call(byte* entry, RelocInfo::Mode rmode) { void Assembler::call(const Operand& adr) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; EMIT(0xFF); @@ -1772,6 +1783,14 @@ void Assembler::fldz() { } +void Assembler::fldln2() { + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0xD9); + EMIT(0xED); +} + + void Assembler::fld_s(const Operand& adr) { EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -1902,6 +1921,14 @@ void Assembler::fsin() { } +void Assembler::fyl2x() { + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0xD9); + EMIT(0xF1); +} + + void Assembler::fadd(int i) { EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -2382,6 +2409,7 @@ void Assembler::movsd(XMMRegister dst, const Operand& src) { emit_sse_operand(dst, src); } + void Assembler::movsd(XMMRegister dst, XMMRegister src) { ASSERT(CpuFeatures::IsEnabled(SSE2)); EnsureSpace ensure_space(this); @@ -2404,6 +2432,28 @@ void Assembler::movd(XMMRegister dst, const Operand& src) { } +void Assembler::movd(const Operand& dst, XMMRegister src) { + ASSERT(CpuFeatures::IsEnabled(SSE2)); + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0x66); + EMIT(0x0F); + EMIT(0x7E); + emit_sse_operand(src, dst); +} + + +void Assembler::pand(XMMRegister dst, XMMRegister src) { + ASSERT(CpuFeatures::IsEnabled(SSE2)); + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0x66); + EMIT(0x0F); + EMIT(0xDB); + emit_sse_operand(dst, src); +} + + void Assembler::pxor(XMMRegister dst, XMMRegister src) { ASSERT(CpuFeatures::IsEnabled(SSE2)); EnsureSpace ensure_space(this); @@ -2427,7 +2477,7 @@ void Assembler::ptest(XMMRegister dst, XMMRegister src) { } -void Assembler::psllq(XMMRegister reg, int8_t imm8) { +void Assembler::psllq(XMMRegister reg, int8_t shift) { ASSERT(CpuFeatures::IsEnabled(SSE2)); EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -2435,7 +2485,32 @@ void Assembler::psllq(XMMRegister reg, int8_t imm8) { EMIT(0x0F); EMIT(0x73); emit_sse_operand(esi, reg); // esi == 6 - EMIT(imm8); + EMIT(shift); +} + + +void Assembler::pshufd(XMMRegister dst, XMMRegister src, int8_t shuffle) { + ASSERT(CpuFeatures::IsEnabled(SSE2)); + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0x66); + EMIT(0x0F); + EMIT(0x70); + emit_sse_operand(dst, src); + EMIT(shuffle); +} + + +void Assembler::pextrd(const Operand& dst, XMMRegister src, int8_t offset) { + ASSERT(CpuFeatures::IsEnabled(SSE4_1)); + EnsureSpace ensure_space(this); + last_pc_ = pc_; + EMIT(0x66); + EMIT(0x0F); + EMIT(0x3A); + EMIT(0x16); + emit_sse_operand(src, dst); + EMIT(offset); } @@ -2475,7 +2550,7 @@ void Assembler::RecordDebugBreakSlot() { void Assembler::RecordComment(const char* msg) { - if (FLAG_debug_code) { + if (FLAG_code_comments) { EnsureSpace ensure_space(this); RecordRelocInfo(RelocInfo::COMMENT, reinterpret_cast<intptr_t>(msg)); } @@ -2607,9 +2682,15 @@ void Assembler::emit_farith(int b1, int b2, int i) { } -void Assembler::dd(uint32_t data, RelocInfo::Mode reloc_info) { +void Assembler::db(uint8_t data) { + EnsureSpace ensure_space(this); + EMIT(data); +} + + +void Assembler::dd(uint32_t data) { EnsureSpace ensure_space(this); - emit(data, reloc_info); + emit(data); } diff --git a/src/ia32/assembler-ia32.h b/src/ia32/assembler-ia32.h index 79637a19..11acb561 100644 --- a/src/ia32/assembler-ia32.h +++ b/src/ia32/assembler-ia32.h @@ -30,7 +30,7 @@ // The original source code covered by the above license above has been // modified significantly by Google Inc. -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 the V8 project authors. All rights reserved. // A light-weight IA32 Assembler. @@ -64,7 +64,36 @@ namespace internal { // and best performance in optimized code. // struct Register { - bool is_valid() const { return 0 <= code_ && code_ < 8; } + static const int kNumAllocatableRegisters = 5; + static const int kNumRegisters = 8; + + static int ToAllocationIndex(Register reg) { + ASSERT(reg.code() < 4 || reg.code() == 7); + return (reg.code() == 7) ? 4 : reg.code(); + } + + static Register FromAllocationIndex(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + return (index == 4) ? from_code(7) : from_code(index); + } + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "eax", + "ecx", + "edx", + "ebx", + "edi" + }; + return names[index]; + } + + static Register from_code(int code) { + Register r = { code }; + return r; + } + bool is_valid() const { return 0 <= code_ && code_ < kNumRegisters; } bool is(Register reg) const { return code_ == reg.code_; } // eax, ebx, ecx and edx are byte registers, the rest are not. bool is_byte_register() const { return code_ <= 3; } @@ -93,7 +122,40 @@ const Register no_reg = { -1 }; struct XMMRegister { - bool is_valid() const { return 0 <= code_ && code_ < 8; } + static const int kNumAllocatableRegisters = 7; + static const int kNumRegisters = 8; + + static int ToAllocationIndex(XMMRegister reg) { + ASSERT(reg.code() != 0); + return reg.code() - 1; + } + + static XMMRegister FromAllocationIndex(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + return from_code(index + 1); + } + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "xmm1", + "xmm2", + "xmm3", + "xmm4", + "xmm5", + "xmm6", + "xmm7" + }; + return names[index]; + } + + static XMMRegister from_code(int code) { + XMMRegister r = { code }; + return r; + } + + bool is_valid() const { return 0 <= code_ && code_ < kNumRegisters; } + bool is(XMMRegister reg) const { return code_ == reg.code_; } int code() const { ASSERT(is_valid()); return code_; @@ -102,6 +164,7 @@ struct XMMRegister { int code_; }; + const XMMRegister xmm0 = { 0 }; const XMMRegister xmm1 = { 1 }; const XMMRegister xmm2 = { 2 }; @@ -111,6 +174,17 @@ const XMMRegister xmm5 = { 5 }; const XMMRegister xmm6 = { 6 }; const XMMRegister xmm7 = { 7 }; + +typedef XMMRegister DoubleRegister; + + +// Index of register used in pusha/popa. +// Order of pushed registers: EAX, ECX, EDX, EBX, ESP, EBP, ESI, and EDI +inline int EspIndexForPushAll(Register reg) { + return Register::kNumRegisters - 1 - reg.code(); +} + + enum Condition { // any value < 0 is considered no_condition no_condition = -1, @@ -202,6 +276,7 @@ class Immediate BASE_EMBEDDED { inline explicit Immediate(const ExternalReference& ext); inline explicit Immediate(Handle<Object> handle); inline explicit Immediate(Smi* value); + inline explicit Immediate(Address addr); static Immediate CodeRelativeOffset(Label* label) { return Immediate(label); @@ -281,6 +356,11 @@ class Operand BASE_EMBEDDED { RelocInfo::EXTERNAL_REFERENCE); } + static Operand Cell(Handle<JSGlobalPropertyCell> cell) { + return Operand(reinterpret_cast<int32_t>(cell.location()), + RelocInfo::GLOBAL_PROPERTY_CELL); + } + // Returns true if this Operand is a wrapper for the specified register. bool is_reg(Register reg) const; @@ -369,9 +449,12 @@ class Displacement BASE_EMBEDDED { // } class CpuFeatures : public AllStatic { public: - // Detect features of the target CPU. Set safe defaults if the serializer - // is enabled (snapshots must be portable). - static void Probe(); + // Detect features of the target CPU. If the portable flag is set, + // the method sets safe defaults if the serializer is enabled + // (snapshots must be portable). + static void Probe(bool portable); + static void Clear() { supported_ = 0; } + // Check whether a feature is supported by the target CPU. static bool IsSupported(CpuFeature f) { if (f == SSE2 && !FLAG_enable_sse2) return false; @@ -484,6 +567,20 @@ class Assembler : public Malloced { // The debug break slot must be able to contain a call instruction. static const int kDebugBreakSlotLength = kCallInstructionLength; + // One byte opcode for test eax,0xXXXXXXXX. + static const byte kTestEaxByte = 0xA9; + // One byte opcode for test al, 0xXX. + static const byte kTestAlByte = 0xA8; + // One byte opcode for nop. + static const byte kNopByte = 0x90; + + // One byte opcode for a short unconditional jump. + static const byte kJmpShortOpcode = 0xEB; + // One byte prefix for a short conditional jump. + static const byte kJccShortPrefix = 0x70; + static const byte kJncShortOpcode = kJccShortPrefix | not_carry; + static const byte kJcShortOpcode = kJccShortPrefix | carry; + // --------------------------------------------------------------------------- // Code generation // @@ -519,6 +616,7 @@ class Assembler : public Malloced { void popfd(); void push(const Immediate& x); + void push_imm32(int32_t imm32); void push(Register src); void push(const Operand& src); @@ -720,6 +818,7 @@ class Assembler : public Malloced { void fld1(); void fldz(); void fldpi(); + void fldln2(); void fld_s(const Operand& adr); void fld_d(const Operand& adr); @@ -744,6 +843,7 @@ class Assembler : public Malloced { void fchs(); void fcos(); void fsin(); + void fyl2x(); void fadd(int i); void fsub(int i); @@ -814,12 +914,16 @@ class Assembler : public Malloced { void movdbl(const Operand& dst, XMMRegister src); void movd(XMMRegister dst, const Operand& src); + void movd(const Operand& src, XMMRegister dst); void movsd(XMMRegister dst, XMMRegister src); + void pand(XMMRegister dst, XMMRegister src); void pxor(XMMRegister dst, XMMRegister src); void ptest(XMMRegister dst, XMMRegister src); - void psllq(XMMRegister reg, int8_t imm8); + void psllq(XMMRegister reg, int8_t shift); + void pshufd(XMMRegister dst, XMMRegister src, int8_t shuffle); + void pextrd(const Operand& dst, XMMRegister src, int8_t offset); // Parallel XMM operations. void movntdqa(XMMRegister src, const Operand& dst); @@ -843,12 +947,13 @@ class Assembler : public Malloced { void RecordDebugBreakSlot(); // Record a comment relocation entry that can be used by a disassembler. - // Use --debug_code to enable. + // Use --code-comments to enable. void RecordComment(const char* msg); - // Writes a single word of data in the code stream. - // Used for inline tables, e.g., jump-tables. - void dd(uint32_t data, RelocInfo::Mode reloc_info); + // Writes a single byte or word of data in the code stream. Used for + // inline tables, e.g., jump-tables. + void db(uint8_t data); + void dd(uint32_t data); int pc_offset() const { return pc_ - buffer_; } @@ -876,8 +981,8 @@ class Assembler : public Malloced { void emit_sse_operand(XMMRegister dst, XMMRegister src); void emit_sse_operand(Register dst, XMMRegister src); - private: byte* addr_at(int pos) { return buffer_ + pos; } + private: byte byte_at(int pos) { return buffer_[pos]; } void set_byte_at(int pos, byte value) { buffer_[pos] = value; } uint32_t long_at(int pos) { diff --git a/src/ia32/builtins-ia32.cc b/src/ia32/builtins-ia32.cc index 0ad3e6d4..918f346d 100644 --- a/src/ia32/builtins-ia32.cc +++ b/src/ia32/builtins-ia32.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -29,8 +29,9 @@ #if defined(V8_TARGET_ARCH_IA32) -#include "code-stubs.h" #include "codegen-inl.h" +#include "deoptimizer.h" +#include "full-codegen.h" namespace v8 { namespace internal { @@ -480,6 +481,85 @@ void Builtins::Generate_LazyCompile(MacroAssembler* masm) { } +void Builtins::Generate_LazyRecompile(MacroAssembler* masm) { + // Enter an internal frame. + __ EnterInternalFrame(); + + // Push a copy of the function onto the stack. + __ push(edi); + + __ push(edi); // Function is also the parameter to the runtime call. + __ CallRuntime(Runtime::kLazyRecompile, 1); + + // Restore function and tear down temporary frame. + __ pop(edi); + __ LeaveInternalFrame(); + + // Do a tail-call of the compiled function. + __ lea(ecx, FieldOperand(eax, Code::kHeaderSize)); + __ jmp(Operand(ecx)); +} + + +static void Generate_NotifyDeoptimizedHelper(MacroAssembler* masm, + Deoptimizer::BailoutType type) { + // Enter an internal frame. + __ EnterInternalFrame(); + + // Pass the function and deoptimization type to the runtime system. + __ push(Immediate(Smi::FromInt(static_cast<int>(type)))); + __ CallRuntime(Runtime::kNotifyDeoptimized, 1); + + // Tear down temporary frame. + __ LeaveInternalFrame(); + + // Get the full codegen state from the stack and untag it. + __ mov(ecx, Operand(esp, 1 * kPointerSize)); + __ SmiUntag(ecx); + + // Switch on the state. + NearLabel not_no_registers, not_tos_eax; + __ cmp(ecx, FullCodeGenerator::NO_REGISTERS); + __ j(not_equal, ¬_no_registers); + __ ret(1 * kPointerSize); // Remove state. + + __ bind(¬_no_registers); + __ mov(eax, Operand(esp, 2 * kPointerSize)); + __ cmp(ecx, FullCodeGenerator::TOS_REG); + __ j(not_equal, ¬_tos_eax); + __ ret(2 * kPointerSize); // Remove state, eax. + + __ bind(¬_tos_eax); + __ Abort("no cases left"); +} + + +void Builtins::Generate_NotifyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::EAGER); +} + + +void Builtins::Generate_NotifyLazyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::LAZY); +} + + +void Builtins::Generate_NotifyOSR(MacroAssembler* masm) { + // TODO(kasperl): Do we need to save/restore the XMM registers too? + + // For now, we are relying on the fact that Runtime::NotifyOSR + // doesn't do any garbage collection which allows us to save/restore + // the registers without worrying about which of them contain + // pointers. This seems a bit fragile. + __ pushad(); + __ EnterInternalFrame(); + __ CallRuntime(Runtime::kNotifyOSR, 0); + __ LeaveInternalFrame(); + __ popad(); + __ ret(0); +} + + void Builtins::Generate_FunctionCall(MacroAssembler* masm) { // 1. Make sure we have at least one argument. { Label done; @@ -1418,6 +1498,76 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { } +void Builtins::Generate_OnStackReplacement(MacroAssembler* masm) { + // We shouldn't be performing on-stack replacement in the first + // place if the CPU features we need for the optimized Crankshaft + // code aren't supported. + CpuFeatures::Probe(false); + if (!CpuFeatures::IsSupported(SSE2)) { + __ Abort("Unreachable code: Cannot optimize without SSE2 support."); + return; + } + + // Get the loop depth of the stack guard check. This is recorded in + // a test(eax, depth) instruction right after the call. + Label stack_check; + __ mov(ebx, Operand(esp, 0)); // return address + if (FLAG_debug_code) { + __ cmpb(Operand(ebx, 0), Assembler::kTestAlByte); + __ Assert(equal, "test eax instruction not found after loop stack check"); + } + __ movzx_b(ebx, Operand(ebx, 1)); // depth + + // Get the loop nesting level at which we allow OSR from the + // unoptimized code and check if we want to do OSR yet. If not we + // should perform a stack guard check so we can get interrupts while + // waiting for on-stack replacement. + __ mov(eax, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ mov(ecx, FieldOperand(eax, JSFunction::kSharedFunctionInfoOffset)); + __ mov(ecx, FieldOperand(ecx, SharedFunctionInfo::kCodeOffset)); + __ cmpb(ebx, FieldOperand(ecx, Code::kAllowOSRAtLoopNestingLevelOffset)); + __ j(greater, &stack_check); + + // Pass the function to optimize as the argument to the on-stack + // replacement runtime function. + __ EnterInternalFrame(); + __ push(eax); + __ CallRuntime(Runtime::kCompileForOnStackReplacement, 1); + __ LeaveInternalFrame(); + + // If the result was -1 it means that we couldn't optimize the + // function. Just return and continue in the unoptimized version. + NearLabel skip; + __ cmp(Operand(eax), Immediate(Smi::FromInt(-1))); + __ j(not_equal, &skip); + __ ret(0); + + // If we decide not to perform on-stack replacement we perform a + // stack guard check to enable interrupts. + __ bind(&stack_check); + NearLabel ok; + ExternalReference stack_limit = + ExternalReference::address_of_stack_limit(); + __ cmp(esp, Operand::StaticVariable(stack_limit)); + __ j(above_equal, &ok, taken); + StackCheckStub stub; + __ TailCallStub(&stub); + __ Abort("Unreachable code: returned from tail call."); + __ bind(&ok); + __ ret(0); + + __ bind(&skip); + // Untag the AST id and push it on the stack. + __ SmiUntag(eax); + __ push(eax); + + // Generate the code for doing the frame-to-frame translation using + // the deoptimizer infrastructure. + Deoptimizer::EntryGenerator generator(masm, Deoptimizer::OSR); + generator.Generate(); +} + + #undef __ } } // namespace v8::internal diff --git a/src/ia32/code-stubs-ia32.cc b/src/ia32/code-stubs-ia32.cc index 5975ad27..a371c963 100644 --- a/src/ia32/code-stubs-ia32.cc +++ b/src/ia32/code-stubs-ia32.cc @@ -64,6 +64,8 @@ void FastNewClosureStub::Generate(MacroAssembler* masm) { __ mov(FieldOperand(eax, JSFunction::kSharedFunctionInfoOffset), edx); __ mov(FieldOperand(eax, JSFunction::kContextOffset), esi); __ mov(FieldOperand(eax, JSFunction::kLiteralsOffset), ebx); + __ mov(FieldOperand(eax, JSFunction::kNextFunctionLinkOffset), + Immediate(Factory::undefined_value())); // Initialize the code pointer in the function to be the one // found in the shared function info object. @@ -446,6 +448,11 @@ class FloatingPointHelper : public AllStatic { Label* non_float, Register scratch); + // Checks that the two floating point numbers on top of the FPU stack + // have int32 values. + static void CheckFloatOperandsAreInt32(MacroAssembler* masm, + Label* non_int32); + // Takes the operands in edx and eax and loads them as integers in eax // and ecx. static void LoadAsIntegers(MacroAssembler* masm, @@ -460,8 +467,16 @@ class FloatingPointHelper : public AllStatic { bool use_sse3, Label* operand_conversion_failure); - // Test if operands are smis or heap numbers and load them - // into xmm0 and xmm1 if they are. Operands are in edx and eax. + // Must only be called after LoadUnknownsAsIntegers. Assumes that the + // operands are pushed on the stack, and that their conversions to int32 + // are in eax and ecx. Checks that the original numbers were in the int32 + // range. + static void CheckLoadedIntegersWereInt32(MacroAssembler* masm, + bool use_sse3, + Label* not_int32); + + // Assumes that operands are smis or heap numbers and loads them + // into xmm0 and xmm1. Operands are in edx and eax. // Leaves operands unchanged. static void LoadSSE2Operands(MacroAssembler* masm); @@ -474,6 +489,12 @@ class FloatingPointHelper : public AllStatic { // Similar to LoadSSE2Operands but assumes that both operands are smis. // Expects operands in edx, eax. static void LoadSSE2Smis(MacroAssembler* masm, Register scratch); + + // Checks that the two floating point numbers loaded into xmm0 and xmm1 + // have int32 values. + static void CheckSSE2OperandsAreInt32(MacroAssembler* masm, + Label* non_int32, + Register scratch); }; @@ -709,22 +730,27 @@ void GenericBinaryOpStub::GenerateSmiCode(MacroAssembler* masm, Label* slow) { case Token::SHL: { Comment perform_float(masm, "-- Perform float operation on smis"); __ bind(&use_fp_on_smis); - // Result we want is in left == edx, so we can put the allocated heap - // number in eax. - __ AllocateHeapNumber(eax, ecx, ebx, slow); - // Store the result in the HeapNumber and return. - if (CpuFeatures::IsSupported(SSE2)) { - CpuFeatures::Scope use_sse2(SSE2); - __ cvtsi2sd(xmm0, Operand(left)); - __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + if (runtime_operands_type_ != BinaryOpIC::UNINIT_OR_SMI) { + // Result we want is in left == edx, so we can put the allocated heap + // number in eax. + __ AllocateHeapNumber(eax, ecx, ebx, slow); + // Store the result in the HeapNumber and return. + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + __ cvtsi2sd(xmm0, Operand(left)); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + } else { + // It's OK to overwrite the right argument on the stack because we + // are about to return. + __ mov(Operand(esp, 1 * kPointerSize), left); + __ fild_s(Operand(esp, 1 * kPointerSize)); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + } + GenerateReturn(masm); } else { - // It's OK to overwrite the right argument on the stack because we - // are about to return. - __ mov(Operand(esp, 1 * kPointerSize), left); - __ fild_s(Operand(esp, 1 * kPointerSize)); - __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + ASSERT(runtime_operands_type_ == BinaryOpIC::UNINIT_OR_SMI); + __ jmp(slow); } - GenerateReturn(masm); break; } @@ -757,31 +783,36 @@ void GenericBinaryOpStub::GenerateSmiCode(MacroAssembler* masm, Label* slow) { default: UNREACHABLE(); break; } - __ AllocateHeapNumber(ecx, ebx, no_reg, slow); - if (CpuFeatures::IsSupported(SSE2)) { - CpuFeatures::Scope use_sse2(SSE2); - FloatingPointHelper::LoadSSE2Smis(masm, ebx); - switch (op_) { - case Token::ADD: __ addsd(xmm0, xmm1); break; - case Token::SUB: __ subsd(xmm0, xmm1); break; - case Token::MUL: __ mulsd(xmm0, xmm1); break; - case Token::DIV: __ divsd(xmm0, xmm1); break; - default: UNREACHABLE(); - } - __ movdbl(FieldOperand(ecx, HeapNumber::kValueOffset), xmm0); - } else { // SSE2 not available, use FPU. - FloatingPointHelper::LoadFloatSmis(masm, ebx); - switch (op_) { - case Token::ADD: __ faddp(1); break; - case Token::SUB: __ fsubp(1); break; - case Token::MUL: __ fmulp(1); break; - case Token::DIV: __ fdivp(1); break; - default: UNREACHABLE(); + if (runtime_operands_type_ != BinaryOpIC::UNINIT_OR_SMI) { + __ AllocateHeapNumber(ecx, ebx, no_reg, slow); + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Smis(masm, ebx); + switch (op_) { + case Token::ADD: __ addsd(xmm0, xmm1); break; + case Token::SUB: __ subsd(xmm0, xmm1); break; + case Token::MUL: __ mulsd(xmm0, xmm1); break; + case Token::DIV: __ divsd(xmm0, xmm1); break; + default: UNREACHABLE(); + } + __ movdbl(FieldOperand(ecx, HeapNumber::kValueOffset), xmm0); + } else { // SSE2 not available, use FPU. + FloatingPointHelper::LoadFloatSmis(masm, ebx); + switch (op_) { + case Token::ADD: __ faddp(1); break; + case Token::SUB: __ fsubp(1); break; + case Token::MUL: __ fmulp(1); break; + case Token::DIV: __ fdivp(1); break; + default: UNREACHABLE(); + } + __ fstp_d(FieldOperand(ecx, HeapNumber::kValueOffset)); } - __ fstp_d(FieldOperand(ecx, HeapNumber::kValueOffset)); + __ mov(eax, ecx); + GenerateReturn(masm); + } else { + ASSERT(runtime_operands_type_ == BinaryOpIC::UNINIT_OR_SMI); + __ jmp(slow); } - __ mov(eax, ecx); - GenerateReturn(masm); break; } @@ -821,6 +852,13 @@ void GenericBinaryOpStub::Generate(MacroAssembler* masm) { __ IncrementCounter(&Counters::generic_binary_stub_calls, 1); + if (runtime_operands_type_ == BinaryOpIC::UNINIT_OR_SMI) { + Label slow; + if (ShouldGenerateSmiCode()) GenerateSmiCode(masm, &slow); + __ bind(&slow); + GenerateTypeTransition(masm); + } + // Generate fast case smi code if requested. This flag is set when the fast // case smi code is not generated by the caller. Generating it here will speed // up common operations. @@ -1215,42 +1253,1284 @@ Handle<Code> GetBinaryOpStub(int key, BinaryOpIC::TypeInfo type_info) { } +Handle<Code> GetTypeRecordingBinaryOpStub(int key, + TRBinaryOpIC::TypeInfo type_info, + TRBinaryOpIC::TypeInfo result_type_info) { + TypeRecordingBinaryOpStub stub(key, type_info, result_type_info); + return stub.GetCode(); +} + + +void TypeRecordingBinaryOpStub::GenerateTypeTransition(MacroAssembler* masm) { + __ pop(ecx); // Save return address. + __ push(edx); + __ push(eax); + // Left and right arguments are now on top. + // Push this stub's key. Although the operation and the type info are + // encoded into the key, the encoding is opaque, so push them too. + __ push(Immediate(Smi::FromInt(MinorKey()))); + __ push(Immediate(Smi::FromInt(op_))); + __ push(Immediate(Smi::FromInt(operands_type_))); + + __ 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::kTypeRecordingBinaryOp_Patch)), + 5, + 1); +} + + +// Prepare for a type transition runtime call when the args are already on +// the stack, under the return address. +void TypeRecordingBinaryOpStub::GenerateTypeTransitionWithSavedArgs( + MacroAssembler* masm) { + __ pop(ecx); // Save return address. + // Left and right arguments are already on top of the stack. + // Push this stub's key. Although the operation and the type info are + // encoded into the key, the encoding is opaque, so push them too. + __ push(Immediate(Smi::FromInt(MinorKey()))); + __ push(Immediate(Smi::FromInt(op_))); + __ push(Immediate(Smi::FromInt(operands_type_))); + + __ 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::kTypeRecordingBinaryOp_Patch)), + 5, + 1); +} + + +void TypeRecordingBinaryOpStub::Generate(MacroAssembler* masm) { + switch (operands_type_) { + case TRBinaryOpIC::UNINITIALIZED: + GenerateTypeTransition(masm); + break; + case TRBinaryOpIC::SMI: + GenerateSmiStub(masm); + break; + case TRBinaryOpIC::INT32: + GenerateInt32Stub(masm); + break; + case TRBinaryOpIC::HEAP_NUMBER: + GenerateHeapNumberStub(masm); + break; + case TRBinaryOpIC::STRING: + GenerateStringStub(masm); + break; + case TRBinaryOpIC::GENERIC: + GenerateGeneric(masm); + break; + default: + UNREACHABLE(); + } +} + + +const char* TypeRecordingBinaryOpStub::GetName() { + if (name_ != NULL) return name_; + const int kMaxNameLength = 100; + name_ = Bootstrapper::AllocateAutoDeletedArray(kMaxNameLength); + if (name_ == NULL) return "OOM"; + const char* op_name = Token::Name(op_); + const char* overwrite_name; + switch (mode_) { + case NO_OVERWRITE: overwrite_name = "Alloc"; break; + case OVERWRITE_RIGHT: overwrite_name = "OverwriteRight"; break; + case OVERWRITE_LEFT: overwrite_name = "OverwriteLeft"; break; + default: overwrite_name = "UnknownOverwrite"; break; + } + + OS::SNPrintF(Vector<char>(name_, kMaxNameLength), + "TypeRecordingBinaryOpStub_%s_%s_%s", + op_name, + overwrite_name, + TRBinaryOpIC::GetName(operands_type_)); + return name_; +} + + +void TypeRecordingBinaryOpStub::GenerateSmiCode(MacroAssembler* masm, + Label* slow, + SmiCodeGenerateHeapNumberResults allow_heapnumber_results) { + // 1. Move arguments into edx, eax except for DIV and MOD, which need the + // dividend in eax and edx free for the division. Use eax, ebx for those. + Comment load_comment(masm, "-- Load arguments"); + Register left = edx; + Register right = eax; + if (op_ == Token::DIV || op_ == Token::MOD) { + left = eax; + right = ebx; + __ mov(ebx, eax); + __ mov(eax, edx); + } + + + // 2. Prepare the smi check of both operands by oring them together. + Comment smi_check_comment(masm, "-- Smi check arguments"); + Label not_smis; + Register combined = ecx; + ASSERT(!left.is(combined) && !right.is(combined)); + switch (op_) { + case Token::BIT_OR: + // Perform the operation into eax and smi check the result. Preserve + // eax in case the result is not a smi. + ASSERT(!left.is(ecx) && !right.is(ecx)); + __ mov(ecx, right); + __ or_(right, Operand(left)); // Bitwise or is commutative. + combined = right; + break; + + case Token::BIT_XOR: + case Token::BIT_AND: + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + case Token::MOD: + __ mov(combined, right); + __ or_(combined, Operand(left)); + break; + + case Token::SHL: + case Token::SAR: + case Token::SHR: + // Move the right operand into ecx for the shift operation, use eax + // for the smi check register. + ASSERT(!left.is(ecx) && !right.is(ecx)); + __ mov(ecx, right); + __ or_(right, Operand(left)); + combined = right; + break; + + default: + break; + } + + // 3. Perform the smi check of the operands. + STATIC_ASSERT(kSmiTag == 0); // Adjust zero check if not the case. + __ test(combined, Immediate(kSmiTagMask)); + __ j(not_zero, ¬_smis, not_taken); + + // 4. Operands are both smis, perform the operation leaving the result in + // eax and check the result if necessary. + Comment perform_smi(masm, "-- Perform smi operation"); + Label use_fp_on_smis; + switch (op_) { + case Token::BIT_OR: + // Nothing to do. + break; + + case Token::BIT_XOR: + ASSERT(right.is(eax)); + __ xor_(right, Operand(left)); // Bitwise xor is commutative. + break; + + case Token::BIT_AND: + ASSERT(right.is(eax)); + __ and_(right, Operand(left)); // Bitwise and is commutative. + break; + + case Token::SHL: + // Remove tags from operands (but keep sign). + __ SmiUntag(left); + __ SmiUntag(ecx); + // Perform the operation. + __ shl_cl(left); + // Check that the *signed* result fits in a smi. + __ cmp(left, 0xc0000000); + __ j(sign, &use_fp_on_smis, not_taken); + // Tag the result and store it in register eax. + __ SmiTag(left); + __ mov(eax, left); + break; + + case Token::SAR: + // Remove tags from operands (but keep sign). + __ SmiUntag(left); + __ SmiUntag(ecx); + // Perform the operation. + __ sar_cl(left); + // Tag the result and store it in register eax. + __ SmiTag(left); + __ mov(eax, left); + break; + + case Token::SHR: + // Remove tags from operands (but keep sign). + __ SmiUntag(left); + __ SmiUntag(ecx); + // Perform the operation. + __ shr_cl(left); + // Check that the *unsigned* result fits in a smi. + // Neither of the two high-order bits can be set: + // - 0x80000000: high bit would be lost when smi tagging. + // - 0x40000000: this number would convert to negative when + // Smi tagging these two cases can only happen with shifts + // by 0 or 1 when handed a valid smi. + __ test(left, Immediate(0xc0000000)); + __ j(not_zero, slow, not_taken); + // Tag the result and store it in register eax. + __ SmiTag(left); + __ mov(eax, left); + break; + + case Token::ADD: + ASSERT(right.is(eax)); + __ add(right, Operand(left)); // Addition is commutative. + __ j(overflow, &use_fp_on_smis, not_taken); + break; + + case Token::SUB: + __ sub(left, Operand(right)); + __ j(overflow, &use_fp_on_smis, not_taken); + __ mov(eax, left); + break; + + case Token::MUL: + // If the smi tag is 0 we can just leave the tag on one operand. + STATIC_ASSERT(kSmiTag == 0); // Adjust code below if not the case. + // We can't revert the multiplication if the result is not a smi + // so save the right operand. + __ mov(ebx, right); + // Remove tag from one of the operands (but keep sign). + __ SmiUntag(right); + // Do multiplication. + __ imul(right, Operand(left)); // Multiplication is commutative. + __ j(overflow, &use_fp_on_smis, not_taken); + // Check for negative zero result. Use combined = left | right. + __ NegativeZeroTest(right, combined, &use_fp_on_smis); + break; + + case Token::DIV: + // We can't revert the division if the result is not a smi so + // save the left operand. + __ mov(edi, left); + // Check for 0 divisor. + __ test(right, Operand(right)); + __ j(zero, &use_fp_on_smis, not_taken); + // Sign extend left into edx:eax. + ASSERT(left.is(eax)); + __ cdq(); + // Divide edx:eax by right. + __ idiv(right); + // Check for the corner case of dividing the most negative smi by + // -1. We cannot use the overflow flag, since it is not set by idiv + // instruction. + STATIC_ASSERT(kSmiTag == 0 && kSmiTagSize == 1); + __ cmp(eax, 0x40000000); + __ j(equal, &use_fp_on_smis); + // Check for negative zero result. Use combined = left | right. + __ NegativeZeroTest(eax, combined, &use_fp_on_smis); + // Check that the remainder is zero. + __ test(edx, Operand(edx)); + __ j(not_zero, &use_fp_on_smis); + // Tag the result and store it in register eax. + __ SmiTag(eax); + break; + + case Token::MOD: + // Check for 0 divisor. + __ test(right, Operand(right)); + __ j(zero, ¬_smis, not_taken); + + // Sign extend left into edx:eax. + ASSERT(left.is(eax)); + __ cdq(); + // Divide edx:eax by right. + __ idiv(right); + // Check for negative zero result. Use combined = left | right. + __ NegativeZeroTest(edx, combined, slow); + // Move remainder to register eax. + __ mov(eax, edx); + break; + + default: + UNREACHABLE(); + } + + // 5. Emit return of result in eax. Some operations have registers pushed. + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + __ ret(0); + break; + case Token::MOD: + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: + __ ret(2 * kPointerSize); + break; + default: + UNREACHABLE(); + } + + // 6. For some operations emit inline code to perform floating point + // operations on known smis (e.g., if the result of the operation + // overflowed the smi range). + if (allow_heapnumber_results == NO_HEAPNUMBER_RESULTS) { + __ bind(&use_fp_on_smis); + switch (op_) { + // Undo the effects of some operations, and some register moves. + case Token::SHL: + // The arguments are saved on the stack, and only used from there. + break; + case Token::ADD: + // Revert right = right + left. + __ sub(right, Operand(left)); + break; + case Token::SUB: + // Revert left = left - right. + __ add(left, Operand(right)); + break; + case Token::MUL: + // Right was clobbered but a copy is in ebx. + __ mov(right, ebx); + break; + case Token::DIV: + // Left was clobbered but a copy is in edi. Right is in ebx for + // division. They should be in eax, ebx for jump to not_smi. + __ mov(eax, edi); + break; + default: + // No other operators jump to use_fp_on_smis. + break; + } + __ jmp(¬_smis); + } else { + ASSERT(allow_heapnumber_results == ALLOW_HEAPNUMBER_RESULTS); + switch (op_) { + case Token::SHL: { + Comment perform_float(masm, "-- Perform float operation on smis"); + __ bind(&use_fp_on_smis); + // Result we want is in left == edx, so we can put the allocated heap + // number in eax. + __ AllocateHeapNumber(eax, ecx, ebx, slow); + // Store the result in the HeapNumber and return. + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + __ cvtsi2sd(xmm0, Operand(left)); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + } else { + // It's OK to overwrite the right argument on the stack because we + // are about to return. + __ mov(Operand(esp, 1 * kPointerSize), left); + __ fild_s(Operand(esp, 1 * kPointerSize)); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + } + __ ret(2 * kPointerSize); + break; + } + + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: { + Comment perform_float(masm, "-- Perform float operation on smis"); + __ bind(&use_fp_on_smis); + // Restore arguments to edx, eax. + switch (op_) { + case Token::ADD: + // Revert right = right + left. + __ sub(right, Operand(left)); + break; + case Token::SUB: + // Revert left = left - right. + __ add(left, Operand(right)); + break; + case Token::MUL: + // Right was clobbered but a copy is in ebx. + __ mov(right, ebx); + break; + case Token::DIV: + // Left was clobbered but a copy is in edi. Right is in ebx for + // division. + __ mov(edx, edi); + __ mov(eax, right); + break; + default: UNREACHABLE(); + break; + } + __ AllocateHeapNumber(ecx, ebx, no_reg, slow); + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Smis(masm, ebx); + switch (op_) { + case Token::ADD: __ addsd(xmm0, xmm1); break; + case Token::SUB: __ subsd(xmm0, xmm1); break; + case Token::MUL: __ mulsd(xmm0, xmm1); break; + case Token::DIV: __ divsd(xmm0, xmm1); break; + default: UNREACHABLE(); + } + __ movdbl(FieldOperand(ecx, HeapNumber::kValueOffset), xmm0); + } else { // SSE2 not available, use FPU. + FloatingPointHelper::LoadFloatSmis(masm, ebx); + switch (op_) { + case Token::ADD: __ faddp(1); break; + case Token::SUB: __ fsubp(1); break; + case Token::MUL: __ fmulp(1); break; + case Token::DIV: __ fdivp(1); break; + default: UNREACHABLE(); + } + __ fstp_d(FieldOperand(ecx, HeapNumber::kValueOffset)); + } + __ mov(eax, ecx); + __ ret(0); + break; + } + + default: + break; + } + } + + // 7. Non-smi operands, fall out to the non-smi code with the operands in + // edx and eax. + Comment done_comment(masm, "-- Enter non-smi code"); + __ bind(¬_smis); + switch (op_) { + case Token::BIT_OR: + case Token::SHL: + case Token::SAR: + case Token::SHR: + // Right operand is saved in ecx and eax was destroyed by the smi + // check. + __ mov(eax, ecx); + break; + + case Token::DIV: + case Token::MOD: + // Operands are in eax, ebx at this point. + __ mov(edx, eax); + __ mov(eax, ebx); + break; + + default: + break; + } +} + + +void TypeRecordingBinaryOpStub::GenerateSmiStub(MacroAssembler* masm) { + Label call_runtime; + + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + break; + case Token::MOD: + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: + GenerateRegisterArgsPush(masm); + break; + default: + UNREACHABLE(); + } + + if (result_type_ == TRBinaryOpIC::UNINITIALIZED || + result_type_ == TRBinaryOpIC::SMI) { + GenerateSmiCode(masm, &call_runtime, NO_HEAPNUMBER_RESULTS); + } else { + GenerateSmiCode(masm, &call_runtime, ALLOW_HEAPNUMBER_RESULTS); + } + __ bind(&call_runtime); + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + GenerateTypeTransition(masm); + break; + case Token::MOD: + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: + GenerateTypeTransitionWithSavedArgs(masm); + break; + default: + UNREACHABLE(); + } +} + + + +void TypeRecordingBinaryOpStub::GenerateStringStub(MacroAssembler* masm) { + Label call_runtime; + ASSERT(operands_type_ == TRBinaryOpIC::STRING); + ASSERT(op_ == Token::ADD); + // If one of the arguments is a string, call the string add stub. + // Otherwise, transition to the generic TRBinaryOpIC type. + + // Registers containing left and right operands respectively. + Register left = edx; + Register right = eax; + + // Test if left operand is a string. + NearLabel left_not_string; + __ test(left, Immediate(kSmiTagMask)); + __ j(zero, &left_not_string); + __ CmpObjectType(left, FIRST_NONSTRING_TYPE, ecx); + __ j(above_equal, &left_not_string); + + StringAddStub string_add_left_stub(NO_STRING_CHECK_LEFT_IN_STUB); + GenerateRegisterArgsPush(masm); + __ TailCallStub(&string_add_left_stub); + + // Left operand is not a string, test right. + __ bind(&left_not_string); + __ test(right, Immediate(kSmiTagMask)); + __ j(zero, &call_runtime); + __ CmpObjectType(right, FIRST_NONSTRING_TYPE, ecx); + __ j(above_equal, &call_runtime); + + StringAddStub string_add_right_stub(NO_STRING_CHECK_RIGHT_IN_STUB); + GenerateRegisterArgsPush(masm); + __ TailCallStub(&string_add_right_stub); + + // Neither argument is a string. + __ bind(&call_runtime); + GenerateTypeTransition(masm); +} + + +void TypeRecordingBinaryOpStub::GenerateInt32Stub(MacroAssembler* masm) { + Label call_runtime; + ASSERT(operands_type_ == TRBinaryOpIC::INT32); + + // Floating point case. + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: { + Label not_floats; + Label not_int32; + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Operands(masm, ¬_floats); + FloatingPointHelper::CheckSSE2OperandsAreInt32(masm, ¬_int32, ecx); + switch (op_) { + case Token::ADD: __ addsd(xmm0, xmm1); break; + case Token::SUB: __ subsd(xmm0, xmm1); break; + case Token::MUL: __ mulsd(xmm0, xmm1); break; + case Token::DIV: __ divsd(xmm0, xmm1); break; + default: UNREACHABLE(); + } + // Check result type if it is currently Int32. + if (result_type_ <= TRBinaryOpIC::INT32) { + __ cvttsd2si(ecx, Operand(xmm0)); + __ cvtsi2sd(xmm2, Operand(ecx)); + __ ucomisd(xmm0, xmm2); + __ j(not_zero, ¬_int32); + __ j(carry, ¬_int32); + } + GenerateHeapResultAllocation(masm, &call_runtime); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + __ ret(0); + } else { // SSE2 not available, use FPU. + FloatingPointHelper::CheckFloatOperands(masm, ¬_floats, ebx); + FloatingPointHelper::LoadFloatOperands( + masm, + ecx, + FloatingPointHelper::ARGS_IN_REGISTERS); + FloatingPointHelper::CheckFloatOperandsAreInt32(masm, ¬_int32); + switch (op_) { + case Token::ADD: __ faddp(1); break; + case Token::SUB: __ fsubp(1); break; + case Token::MUL: __ fmulp(1); break; + case Token::DIV: __ fdivp(1); break; + default: UNREACHABLE(); + } + Label after_alloc_failure; + GenerateHeapResultAllocation(masm, &after_alloc_failure); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + __ ret(0); + __ bind(&after_alloc_failure); + __ ffree(); + __ jmp(&call_runtime); + } + + __ bind(¬_floats); + __ bind(¬_int32); + GenerateTypeTransition(masm); + break; + } + + case Token::MOD: { + // For MOD we go directly to runtime in the non-smi case. + break; + } + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: { + GenerateRegisterArgsPush(masm); + Label not_floats; + Label not_int32; + Label non_smi_result; + /* { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Operands(masm, ¬_floats); + FloatingPointHelper::CheckSSE2OperandsAreInt32(masm, ¬_int32, ecx); + }*/ + FloatingPointHelper::LoadUnknownsAsIntegers(masm, + use_sse3_, + ¬_floats); + FloatingPointHelper::CheckLoadedIntegersWereInt32(masm, use_sse3_, + ¬_int32); + switch (op_) { + case Token::BIT_OR: __ or_(eax, Operand(ecx)); break; + case Token::BIT_AND: __ and_(eax, Operand(ecx)); break; + case Token::BIT_XOR: __ xor_(eax, Operand(ecx)); break; + case Token::SAR: __ sar_cl(eax); break; + case Token::SHL: __ shl_cl(eax); break; + case Token::SHR: __ shr_cl(eax); break; + default: UNREACHABLE(); + } + if (op_ == Token::SHR) { + // Check if result is non-negative and fits in a smi. + __ test(eax, Immediate(0xc0000000)); + __ j(not_zero, &call_runtime); + } else { + // Check if result fits in a smi. + __ cmp(eax, 0xc0000000); + __ j(negative, &non_smi_result); + } + // Tag smi result and return. + __ SmiTag(eax); + __ ret(2 * kPointerSize); // Drop two pushed arguments from the stack. + + // All ops except SHR return a signed int32 that we load in + // a HeapNumber. + if (op_ != Token::SHR) { + __ bind(&non_smi_result); + // Allocate a heap number if needed. + __ mov(ebx, Operand(eax)); // ebx: result + NearLabel skip_allocation; + switch (mode_) { + case OVERWRITE_LEFT: + case OVERWRITE_RIGHT: + // If the operand was an object, we skip the + // allocation of a heap number. + __ mov(eax, Operand(esp, mode_ == OVERWRITE_RIGHT ? + 1 * kPointerSize : 2 * kPointerSize)); + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &skip_allocation, not_taken); + // Fall through! + case NO_OVERWRITE: + __ AllocateHeapNumber(eax, ecx, edx, &call_runtime); + __ bind(&skip_allocation); + break; + default: UNREACHABLE(); + } + // Store the result in the HeapNumber and return. + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + __ cvtsi2sd(xmm0, Operand(ebx)); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + } else { + __ mov(Operand(esp, 1 * kPointerSize), ebx); + __ fild_s(Operand(esp, 1 * kPointerSize)); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + } + __ ret(2 * kPointerSize); // Drop two pushed arguments from the stack. + } + + __ bind(¬_floats); + __ bind(¬_int32); + GenerateTypeTransitionWithSavedArgs(masm); + break; + } + default: UNREACHABLE(); break; + } + + // If an allocation fails, or SHR or MOD hit a hard case, + // use the runtime system to get the correct result. + __ bind(&call_runtime); + + switch (op_) { + case Token::ADD: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::ADD, JUMP_FUNCTION); + break; + case Token::SUB: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::SUB, JUMP_FUNCTION); + break; + case Token::MUL: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::MUL, JUMP_FUNCTION); + break; + case Token::DIV: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::DIV, JUMP_FUNCTION); + break; + case Token::MOD: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::MOD, JUMP_FUNCTION); + break; + case Token::BIT_OR: + __ InvokeBuiltin(Builtins::BIT_OR, JUMP_FUNCTION); + break; + case Token::BIT_AND: + __ InvokeBuiltin(Builtins::BIT_AND, JUMP_FUNCTION); + break; + case Token::BIT_XOR: + __ InvokeBuiltin(Builtins::BIT_XOR, JUMP_FUNCTION); + break; + case Token::SAR: + __ InvokeBuiltin(Builtins::SAR, JUMP_FUNCTION); + break; + case Token::SHL: + __ InvokeBuiltin(Builtins::SHL, JUMP_FUNCTION); + break; + case Token::SHR: + __ InvokeBuiltin(Builtins::SHR, JUMP_FUNCTION); + break; + default: + UNREACHABLE(); + } +} + + +void TypeRecordingBinaryOpStub::GenerateHeapNumberStub(MacroAssembler* masm) { + Label call_runtime; + ASSERT(operands_type_ == TRBinaryOpIC::HEAP_NUMBER || + operands_type_ == TRBinaryOpIC::INT32); + + // Floating point case. + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: { + Label not_floats; + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Operands(masm, ¬_floats); + + switch (op_) { + case Token::ADD: __ addsd(xmm0, xmm1); break; + case Token::SUB: __ subsd(xmm0, xmm1); break; + case Token::MUL: __ mulsd(xmm0, xmm1); break; + case Token::DIV: __ divsd(xmm0, xmm1); break; + default: UNREACHABLE(); + } + GenerateHeapResultAllocation(masm, &call_runtime); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + __ ret(0); + } else { // SSE2 not available, use FPU. + FloatingPointHelper::CheckFloatOperands(masm, ¬_floats, ebx); + FloatingPointHelper::LoadFloatOperands( + masm, + ecx, + FloatingPointHelper::ARGS_IN_REGISTERS); + switch (op_) { + case Token::ADD: __ faddp(1); break; + case Token::SUB: __ fsubp(1); break; + case Token::MUL: __ fmulp(1); break; + case Token::DIV: __ fdivp(1); break; + default: UNREACHABLE(); + } + Label after_alloc_failure; + GenerateHeapResultAllocation(masm, &after_alloc_failure); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + __ ret(0); + __ bind(&after_alloc_failure); + __ ffree(); + __ jmp(&call_runtime); + } + + __ bind(¬_floats); + GenerateTypeTransition(masm); + break; + } + + case Token::MOD: { + // For MOD we go directly to runtime in the non-smi case. + break; + } + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: { + GenerateRegisterArgsPush(masm); + Label not_floats; + Label non_smi_result; + FloatingPointHelper::LoadUnknownsAsIntegers(masm, + use_sse3_, + ¬_floats); + switch (op_) { + case Token::BIT_OR: __ or_(eax, Operand(ecx)); break; + case Token::BIT_AND: __ and_(eax, Operand(ecx)); break; + case Token::BIT_XOR: __ xor_(eax, Operand(ecx)); break; + case Token::SAR: __ sar_cl(eax); break; + case Token::SHL: __ shl_cl(eax); break; + case Token::SHR: __ shr_cl(eax); break; + default: UNREACHABLE(); + } + if (op_ == Token::SHR) { + // Check if result is non-negative and fits in a smi. + __ test(eax, Immediate(0xc0000000)); + __ j(not_zero, &call_runtime); + } else { + // Check if result fits in a smi. + __ cmp(eax, 0xc0000000); + __ j(negative, &non_smi_result); + } + // Tag smi result and return. + __ SmiTag(eax); + __ ret(2 * kPointerSize); // Drop two pushed arguments from the stack. + + // All ops except SHR return a signed int32 that we load in + // a HeapNumber. + if (op_ != Token::SHR) { + __ bind(&non_smi_result); + // Allocate a heap number if needed. + __ mov(ebx, Operand(eax)); // ebx: result + NearLabel skip_allocation; + switch (mode_) { + case OVERWRITE_LEFT: + case OVERWRITE_RIGHT: + // If the operand was an object, we skip the + // allocation of a heap number. + __ mov(eax, Operand(esp, mode_ == OVERWRITE_RIGHT ? + 1 * kPointerSize : 2 * kPointerSize)); + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &skip_allocation, not_taken); + // Fall through! + case NO_OVERWRITE: + __ AllocateHeapNumber(eax, ecx, edx, &call_runtime); + __ bind(&skip_allocation); + break; + default: UNREACHABLE(); + } + // Store the result in the HeapNumber and return. + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + __ cvtsi2sd(xmm0, Operand(ebx)); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + } else { + __ mov(Operand(esp, 1 * kPointerSize), ebx); + __ fild_s(Operand(esp, 1 * kPointerSize)); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + } + __ ret(2 * kPointerSize); // Drop two pushed arguments from the stack. + } + + __ bind(¬_floats); + GenerateTypeTransitionWithSavedArgs(masm); + break; + } + default: UNREACHABLE(); break; + } + + // If an allocation fails, or SHR or MOD hit a hard case, + // use the runtime system to get the correct result. + __ bind(&call_runtime); + + switch (op_) { + case Token::ADD: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::ADD, JUMP_FUNCTION); + break; + case Token::SUB: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::SUB, JUMP_FUNCTION); + break; + case Token::MUL: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::MUL, JUMP_FUNCTION); + break; + case Token::DIV: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::DIV, JUMP_FUNCTION); + break; + case Token::MOD: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::MOD, JUMP_FUNCTION); + break; + case Token::BIT_OR: + __ InvokeBuiltin(Builtins::BIT_OR, JUMP_FUNCTION); + break; + case Token::BIT_AND: + __ InvokeBuiltin(Builtins::BIT_AND, JUMP_FUNCTION); + break; + case Token::BIT_XOR: + __ InvokeBuiltin(Builtins::BIT_XOR, JUMP_FUNCTION); + break; + case Token::SAR: + __ InvokeBuiltin(Builtins::SAR, JUMP_FUNCTION); + break; + case Token::SHL: + __ InvokeBuiltin(Builtins::SHL, JUMP_FUNCTION); + break; + case Token::SHR: + __ InvokeBuiltin(Builtins::SHR, JUMP_FUNCTION); + break; + default: + UNREACHABLE(); + } +} + + +void TypeRecordingBinaryOpStub::GenerateGeneric(MacroAssembler* masm) { + Label call_runtime; + + __ IncrementCounter(&Counters::generic_binary_stub_calls, 1); + + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + break; + case Token::MOD: + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: + GenerateRegisterArgsPush(masm); + break; + default: + UNREACHABLE(); + } + + GenerateSmiCode(masm, &call_runtime, ALLOW_HEAPNUMBER_RESULTS); + + // Floating point case. + switch (op_) { + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: { + Label not_floats; + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + FloatingPointHelper::LoadSSE2Operands(masm, ¬_floats); + + switch (op_) { + case Token::ADD: __ addsd(xmm0, xmm1); break; + case Token::SUB: __ subsd(xmm0, xmm1); break; + case Token::MUL: __ mulsd(xmm0, xmm1); break; + case Token::DIV: __ divsd(xmm0, xmm1); break; + default: UNREACHABLE(); + } + GenerateHeapResultAllocation(masm, &call_runtime); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + __ ret(0); + } else { // SSE2 not available, use FPU. + FloatingPointHelper::CheckFloatOperands(masm, ¬_floats, ebx); + FloatingPointHelper::LoadFloatOperands( + masm, + ecx, + FloatingPointHelper::ARGS_IN_REGISTERS); + switch (op_) { + case Token::ADD: __ faddp(1); break; + case Token::SUB: __ fsubp(1); break; + case Token::MUL: __ fmulp(1); break; + case Token::DIV: __ fdivp(1); break; + default: UNREACHABLE(); + } + Label after_alloc_failure; + GenerateHeapResultAllocation(masm, &after_alloc_failure); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + __ ret(0); + __ bind(&after_alloc_failure); + __ ffree(); + __ jmp(&call_runtime); + } + __ bind(¬_floats); + break; + } + case Token::MOD: { + // For MOD we go directly to runtime in the non-smi case. + break; + } + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SAR: + case Token::SHL: + case Token::SHR: { + Label non_smi_result; + FloatingPointHelper::LoadUnknownsAsIntegers(masm, + use_sse3_, + &call_runtime); + switch (op_) { + case Token::BIT_OR: __ or_(eax, Operand(ecx)); break; + case Token::BIT_AND: __ and_(eax, Operand(ecx)); break; + case Token::BIT_XOR: __ xor_(eax, Operand(ecx)); break; + case Token::SAR: __ sar_cl(eax); break; + case Token::SHL: __ shl_cl(eax); break; + case Token::SHR: __ shr_cl(eax); break; + default: UNREACHABLE(); + } + if (op_ == Token::SHR) { + // Check if result is non-negative and fits in a smi. + __ test(eax, Immediate(0xc0000000)); + __ j(not_zero, &call_runtime); + } else { + // Check if result fits in a smi. + __ cmp(eax, 0xc0000000); + __ j(negative, &non_smi_result); + } + // Tag smi result and return. + __ SmiTag(eax); + __ ret(2 * kPointerSize); // Drop the arguments from the stack. + + // All ops except SHR return a signed int32 that we load in + // a HeapNumber. + if (op_ != Token::SHR) { + __ bind(&non_smi_result); + // Allocate a heap number if needed. + __ mov(ebx, Operand(eax)); // ebx: result + NearLabel skip_allocation; + switch (mode_) { + case OVERWRITE_LEFT: + case OVERWRITE_RIGHT: + // If the operand was an object, we skip the + // allocation of a heap number. + __ mov(eax, Operand(esp, mode_ == OVERWRITE_RIGHT ? + 1 * kPointerSize : 2 * kPointerSize)); + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &skip_allocation, not_taken); + // Fall through! + case NO_OVERWRITE: + __ AllocateHeapNumber(eax, ecx, edx, &call_runtime); + __ bind(&skip_allocation); + break; + default: UNREACHABLE(); + } + // Store the result in the HeapNumber and return. + if (CpuFeatures::IsSupported(SSE2)) { + CpuFeatures::Scope use_sse2(SSE2); + __ cvtsi2sd(xmm0, Operand(ebx)); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm0); + } else { + __ mov(Operand(esp, 1 * kPointerSize), ebx); + __ fild_s(Operand(esp, 1 * kPointerSize)); + __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); + } + __ ret(2 * kPointerSize); + } + break; + } + default: UNREACHABLE(); break; + } + + // If all else fails, use the runtime system to get the correct + // result. + __ bind(&call_runtime); + switch (op_) { + case Token::ADD: { + GenerateRegisterArgsPush(masm); + // Test for string arguments before calling runtime. + // Registers containing left and right operands respectively. + Register lhs, rhs; + lhs = edx; + rhs = eax; + + // Test if left operand is a string. + NearLabel lhs_not_string; + __ test(lhs, Immediate(kSmiTagMask)); + __ j(zero, &lhs_not_string); + __ CmpObjectType(lhs, FIRST_NONSTRING_TYPE, ecx); + __ j(above_equal, &lhs_not_string); + + StringAddStub string_add_left_stub(NO_STRING_CHECK_LEFT_IN_STUB); + __ TailCallStub(&string_add_left_stub); + + NearLabel call_add_runtime; + // Left operand is not a string, test right. + __ bind(&lhs_not_string); + __ test(rhs, Immediate(kSmiTagMask)); + __ j(zero, &call_add_runtime); + __ CmpObjectType(rhs, FIRST_NONSTRING_TYPE, ecx); + __ j(above_equal, &call_add_runtime); + + StringAddStub string_add_right_stub(NO_STRING_CHECK_RIGHT_IN_STUB); + __ TailCallStub(&string_add_right_stub); + + // Neither argument is a string. + __ bind(&call_add_runtime); + __ InvokeBuiltin(Builtins::ADD, JUMP_FUNCTION); + break; + } + case Token::SUB: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::SUB, JUMP_FUNCTION); + break; + case Token::MUL: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::MUL, JUMP_FUNCTION); + break; + case Token::DIV: + GenerateRegisterArgsPush(masm); + __ InvokeBuiltin(Builtins::DIV, JUMP_FUNCTION); + break; + case Token::MOD: + __ InvokeBuiltin(Builtins::MOD, JUMP_FUNCTION); + break; + case Token::BIT_OR: + __ InvokeBuiltin(Builtins::BIT_OR, JUMP_FUNCTION); + break; + case Token::BIT_AND: + __ InvokeBuiltin(Builtins::BIT_AND, JUMP_FUNCTION); + break; + case Token::BIT_XOR: + __ InvokeBuiltin(Builtins::BIT_XOR, JUMP_FUNCTION); + break; + case Token::SAR: + __ InvokeBuiltin(Builtins::SAR, JUMP_FUNCTION); + break; + case Token::SHL: + __ InvokeBuiltin(Builtins::SHL, JUMP_FUNCTION); + break; + case Token::SHR: + __ InvokeBuiltin(Builtins::SHR, JUMP_FUNCTION); + break; + default: + UNREACHABLE(); + } +} + + +void TypeRecordingBinaryOpStub::GenerateHeapResultAllocation( + MacroAssembler* masm, + Label* alloc_failure) { + Label skip_allocation; + OverwriteMode mode = mode_; + switch (mode) { + case OVERWRITE_LEFT: { + // If the argument in edx is already an object, we skip the + // allocation of a heap number. + __ test(edx, Immediate(kSmiTagMask)); + __ j(not_zero, &skip_allocation, not_taken); + // Allocate a heap number for the result. Keep eax and edx intact + // for the possible runtime call. + __ AllocateHeapNumber(ebx, ecx, no_reg, alloc_failure); + // Now edx can be overwritten losing one of the arguments as we are + // now done and will not need it any more. + __ mov(edx, Operand(ebx)); + __ bind(&skip_allocation); + // Use object in edx as a result holder + __ mov(eax, Operand(edx)); + break; + } + case OVERWRITE_RIGHT: + // If the argument in eax is already an object, we skip the + // allocation of a heap number. + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &skip_allocation, not_taken); + // Fall through! + case NO_OVERWRITE: + // Allocate a heap number for the result. Keep eax and edx intact + // for the possible runtime call. + __ AllocateHeapNumber(ebx, ecx, no_reg, alloc_failure); + // Now eax can be overwritten losing one of the arguments as we are + // now done and will not need it any more. + __ mov(eax, ebx); + __ bind(&skip_allocation); + break; + default: UNREACHABLE(); + } +} + + +void TypeRecordingBinaryOpStub::GenerateRegisterArgsPush(MacroAssembler* masm) { + __ pop(ecx); + __ push(edx); + __ push(eax); + __ push(ecx); +} + + void TranscendentalCacheStub::Generate(MacroAssembler* masm) { - // Input on stack: - // esp[4]: argument (should be number). - // esp[0]: return address. - // Test that eax is a number. + // TAGGED case: + // Input: + // esp[4]: tagged number input argument (should be number). + // esp[0]: return address. + // Output: + // eax: tagged double result. + // UNTAGGED case: + // Input:: + // esp[0]: return address. + // xmm1: untagged double input argument + // Output: + // xmm1: untagged double result. + Label runtime_call; Label runtime_call_clear_stack; - NearLabel input_not_smi; - NearLabel loaded; - __ mov(eax, Operand(esp, kPointerSize)); - __ test(eax, Immediate(kSmiTagMask)); - __ j(not_zero, &input_not_smi); - // Input is a smi. Untag and load it onto the FPU stack. - // Then load the low and high words of the double into ebx, edx. - STATIC_ASSERT(kSmiTagSize == 1); - __ sar(eax, 1); - __ sub(Operand(esp), Immediate(2 * kPointerSize)); - __ mov(Operand(esp, 0), eax); - __ fild_s(Operand(esp, 0)); - __ fst_d(Operand(esp, 0)); - __ pop(edx); - __ pop(ebx); - __ jmp(&loaded); - __ bind(&input_not_smi); - // Check if input is a HeapNumber. - __ mov(ebx, FieldOperand(eax, HeapObject::kMapOffset)); - __ cmp(Operand(ebx), Immediate(Factory::heap_number_map())); - __ j(not_equal, &runtime_call); - // Input is a HeapNumber. Push it on the FPU stack and load its - // low and high words into ebx, edx. - __ fld_d(FieldOperand(eax, HeapNumber::kValueOffset)); - __ mov(edx, FieldOperand(eax, HeapNumber::kExponentOffset)); - __ mov(ebx, FieldOperand(eax, HeapNumber::kMantissaOffset)); + Label skip_cache; + const bool tagged = (argument_type_ == TAGGED); + if (tagged) { + // Test that eax is a number. + NearLabel input_not_smi; + NearLabel loaded; + __ mov(eax, Operand(esp, kPointerSize)); + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &input_not_smi); + // Input is a smi. Untag and load it onto the FPU stack. + // Then load the low and high words of the double into ebx, edx. + STATIC_ASSERT(kSmiTagSize == 1); + __ sar(eax, 1); + __ sub(Operand(esp), Immediate(2 * kPointerSize)); + __ mov(Operand(esp, 0), eax); + __ fild_s(Operand(esp, 0)); + __ fst_d(Operand(esp, 0)); + __ pop(edx); + __ pop(ebx); + __ jmp(&loaded); + __ bind(&input_not_smi); + // Check if input is a HeapNumber. + __ mov(ebx, FieldOperand(eax, HeapObject::kMapOffset)); + __ cmp(Operand(ebx), Immediate(Factory::heap_number_map())); + __ j(not_equal, &runtime_call); + // Input is a HeapNumber. Push it on the FPU stack and load its + // low and high words into ebx, edx. + __ fld_d(FieldOperand(eax, HeapNumber::kValueOffset)); + __ mov(edx, FieldOperand(eax, HeapNumber::kExponentOffset)); + __ mov(ebx, FieldOperand(eax, HeapNumber::kMantissaOffset)); + + __ bind(&loaded); + } else { // UNTAGGED. + if (CpuFeatures::IsSupported(SSE4_1)) { + CpuFeatures::Scope sse4_scope(SSE4_1); + __ pextrd(Operand(edx), xmm1, 0x1); // copy xmm1[63..32] to edx. + } else { + __ pshufd(xmm0, xmm1, 0x1); + __ movd(Operand(edx), xmm0); + } + __ movd(Operand(ebx), xmm1); + } - __ bind(&loaded); - // ST[0] == double value + // ST[0] or xmm1 == double value // ebx = low 32 bits of double value // edx = high 32 bits of double value // Compute hash (the shifts are arithmetic): @@ -1266,7 +2546,7 @@ void TranscendentalCacheStub::Generate(MacroAssembler* masm) { ASSERT(IsPowerOf2(TranscendentalCache::kCacheSize)); __ and_(Operand(ecx), Immediate(TranscendentalCache::kCacheSize - 1)); - // ST[0] == double value. + // ST[0] or xmm1 == double value. // ebx = low 32 bits of double value. // edx = high 32 bits of double value. // ecx = TranscendentalCache::hash(double value). @@ -1303,33 +2583,83 @@ void TranscendentalCacheStub::Generate(MacroAssembler* masm) { __ j(not_equal, &cache_miss); // Cache hit! __ mov(eax, Operand(ecx, 2 * kIntSize)); - __ fstp(0); - __ ret(kPointerSize); + if (tagged) { + __ fstp(0); + __ ret(kPointerSize); + } else { // UNTAGGED. + __ movdbl(xmm1, FieldOperand(eax, HeapNumber::kValueOffset)); + __ Ret(); + } __ bind(&cache_miss); // Update cache with new value. // We are short on registers, so use no_reg as scratch. // This gives slightly larger code. - __ AllocateHeapNumber(eax, edi, no_reg, &runtime_call_clear_stack); + if (tagged) { + __ AllocateHeapNumber(eax, edi, no_reg, &runtime_call_clear_stack); + } else { // UNTAGGED. + __ AllocateHeapNumber(eax, edi, no_reg, &skip_cache); + __ sub(Operand(esp), Immediate(kDoubleSize)); + __ movdbl(Operand(esp, 0), xmm1); + __ fld_d(Operand(esp, 0)); + __ add(Operand(esp), Immediate(kDoubleSize)); + } GenerateOperation(masm); __ mov(Operand(ecx, 0), ebx); __ mov(Operand(ecx, kIntSize), edx); __ mov(Operand(ecx, 2 * kIntSize), eax); __ fstp_d(FieldOperand(eax, HeapNumber::kValueOffset)); - __ ret(kPointerSize); + if (tagged) { + __ ret(kPointerSize); + } else { // UNTAGGED. + __ movdbl(xmm1, FieldOperand(eax, HeapNumber::kValueOffset)); + __ Ret(); + + // Skip cache and return answer directly, only in untagged case. + __ bind(&skip_cache); + __ sub(Operand(esp), Immediate(kDoubleSize)); + __ movdbl(Operand(esp, 0), xmm1); + __ fld_d(Operand(esp, 0)); + GenerateOperation(masm); + __ fstp_d(Operand(esp, 0)); + __ movdbl(xmm1, Operand(esp, 0)); + __ add(Operand(esp), Immediate(kDoubleSize)); + // We return the value in xmm1 without adding it to the cache, but + // we cause a scavenging GC so that future allocations will succeed. + __ EnterInternalFrame(); + // Allocate an unused object bigger than a HeapNumber. + __ push(Immediate(Smi::FromInt(2 * kDoubleSize))); + __ CallRuntimeSaveDoubles(Runtime::kAllocateInNewSpace); + __ LeaveInternalFrame(); + __ Ret(); + } - __ bind(&runtime_call_clear_stack); - __ fstp(0); - __ bind(&runtime_call); - __ TailCallExternalReference(ExternalReference(RuntimeFunction()), 1, 1); + // Call runtime, doing whatever allocation and cleanup is necessary. + if (tagged) { + __ bind(&runtime_call_clear_stack); + __ fstp(0); + __ bind(&runtime_call); + __ TailCallExternalReference(ExternalReference(RuntimeFunction()), 1, 1); + } else { // UNTAGGED. + __ bind(&runtime_call_clear_stack); + __ bind(&runtime_call); + __ AllocateHeapNumber(eax, edi, no_reg, &skip_cache); + __ movdbl(FieldOperand(eax, HeapNumber::kValueOffset), xmm1); + __ EnterInternalFrame(); + __ push(eax); + __ CallRuntime(RuntimeFunction(), 1); + __ LeaveInternalFrame(); + __ movdbl(xmm1, FieldOperand(eax, HeapNumber::kValueOffset)); + __ Ret(); + } } Runtime::FunctionId TranscendentalCacheStub::RuntimeFunction() { switch (type_) { - // Add more cases when necessary. case TranscendentalCache::SIN: return Runtime::kMath_sin; case TranscendentalCache::COS: return Runtime::kMath_cos; + case TranscendentalCache::LOG: return Runtime::kMath_log; default: UNIMPLEMENTED(); return Runtime::kAbort; @@ -1339,85 +2669,90 @@ Runtime::FunctionId TranscendentalCacheStub::RuntimeFunction() { void TranscendentalCacheStub::GenerateOperation(MacroAssembler* masm) { // Only free register is edi. - NearLabel done; - ASSERT(type_ == TranscendentalCache::SIN || - type_ == TranscendentalCache::COS); - // More transcendental types can be added later. - - // Both fsin and fcos require arguments in the range +/-2^63 and - // return NaN for infinities and NaN. They can share all code except - // the actual fsin/fcos operation. - NearLabel in_range; - // If argument is outside the range -2^63..2^63, fsin/cos doesn't - // work. We must reduce it to the appropriate range. - __ mov(edi, edx); - __ and_(Operand(edi), Immediate(0x7ff00000)); // Exponent only. - int supported_exponent_limit = - (63 + HeapNumber::kExponentBias) << HeapNumber::kExponentShift; - __ cmp(Operand(edi), Immediate(supported_exponent_limit)); - __ j(below, &in_range, taken); - // Check for infinity and NaN. Both return NaN for sin. - __ cmp(Operand(edi), Immediate(0x7ff00000)); - NearLabel non_nan_result; - __ j(not_equal, &non_nan_result, taken); - // Input is +/-Infinity or NaN. Result is NaN. - __ fstp(0); - // NaN is represented by 0x7ff8000000000000. - __ push(Immediate(0x7ff80000)); - __ push(Immediate(0)); - __ fld_d(Operand(esp, 0)); - __ add(Operand(esp), Immediate(2 * kPointerSize)); - __ jmp(&done); - - __ bind(&non_nan_result); + // Input value is on FP stack, and also in ebx/edx. + // Input value is possibly in xmm1. + // Address of result (a newly allocated HeapNumber) may be in eax. + if (type_ == TranscendentalCache::SIN || type_ == TranscendentalCache::COS) { + // Both fsin and fcos require arguments in the range +/-2^63 and + // return NaN for infinities and NaN. They can share all code except + // the actual fsin/fcos operation. + NearLabel in_range, done; + // If argument is outside the range -2^63..2^63, fsin/cos doesn't + // work. We must reduce it to the appropriate range. + __ mov(edi, edx); + __ and_(Operand(edi), Immediate(0x7ff00000)); // Exponent only. + int supported_exponent_limit = + (63 + HeapNumber::kExponentBias) << HeapNumber::kExponentShift; + __ cmp(Operand(edi), Immediate(supported_exponent_limit)); + __ j(below, &in_range, taken); + // Check for infinity and NaN. Both return NaN for sin. + __ cmp(Operand(edi), Immediate(0x7ff00000)); + NearLabel non_nan_result; + __ j(not_equal, &non_nan_result, taken); + // Input is +/-Infinity or NaN. Result is NaN. + __ fstp(0); + // NaN is represented by 0x7ff8000000000000. + __ push(Immediate(0x7ff80000)); + __ push(Immediate(0)); + __ fld_d(Operand(esp, 0)); + __ add(Operand(esp), Immediate(2 * kPointerSize)); + __ jmp(&done); - // Use fpmod to restrict argument to the range +/-2*PI. - __ mov(edi, eax); // Save eax before using fnstsw_ax. - __ fldpi(); - __ fadd(0); - __ fld(1); - // FPU Stack: input, 2*pi, input. - { - NearLabel no_exceptions; - __ fwait(); - __ fnstsw_ax(); - // Clear if Illegal Operand or Zero Division exceptions are set. - __ test(Operand(eax), Immediate(5)); - __ j(zero, &no_exceptions); - __ fnclex(); - __ bind(&no_exceptions); - } + __ bind(&non_nan_result); - // Compute st(0) % st(1) - { - NearLabel partial_remainder_loop; - __ bind(&partial_remainder_loop); - __ fprem1(); - __ fwait(); - __ fnstsw_ax(); - __ test(Operand(eax), Immediate(0x400 /* C2 */)); - // If C2 is set, computation only has partial result. Loop to - // continue computation. - __ j(not_zero, &partial_remainder_loop); - } - // FPU Stack: input, 2*pi, input % 2*pi - __ fstp(2); - __ fstp(0); - __ mov(eax, edi); // Restore eax (allocated HeapNumber pointer). + // Use fpmod to restrict argument to the range +/-2*PI. + __ mov(edi, eax); // Save eax before using fnstsw_ax. + __ fldpi(); + __ fadd(0); + __ fld(1); + // FPU Stack: input, 2*pi, input. + { + NearLabel no_exceptions; + __ fwait(); + __ fnstsw_ax(); + // Clear if Illegal Operand or Zero Division exceptions are set. + __ test(Operand(eax), Immediate(5)); + __ j(zero, &no_exceptions); + __ fnclex(); + __ bind(&no_exceptions); + } - // FPU Stack: input % 2*pi - __ bind(&in_range); - switch (type_) { - case TranscendentalCache::SIN: - __ fsin(); - break; - case TranscendentalCache::COS: - __ fcos(); - break; - default: - UNREACHABLE(); + // Compute st(0) % st(1) + { + NearLabel partial_remainder_loop; + __ bind(&partial_remainder_loop); + __ fprem1(); + __ fwait(); + __ fnstsw_ax(); + __ test(Operand(eax), Immediate(0x400 /* C2 */)); + // If C2 is set, computation only has partial result. Loop to + // continue computation. + __ j(not_zero, &partial_remainder_loop); + } + // FPU Stack: input, 2*pi, input % 2*pi + __ fstp(2); + __ fstp(0); + __ mov(eax, edi); // Restore eax (allocated HeapNumber pointer). + + // FPU Stack: input % 2*pi + __ bind(&in_range); + switch (type_) { + case TranscendentalCache::SIN: + __ fsin(); + break; + case TranscendentalCache::COS: + __ fcos(); + break; + default: + UNREACHABLE(); + } + __ bind(&done); + } else { + ASSERT(type_ == TranscendentalCache::LOG); + __ fldln2(); + __ fxch(); + __ fyl2x(); } - __ bind(&done); } @@ -1701,6 +3036,13 @@ void FloatingPointHelper::LoadAsIntegers(MacroAssembler* masm, } +void FloatingPointHelper::CheckLoadedIntegersWereInt32(MacroAssembler* masm, + bool use_sse3, + Label* not_int32) { + return; +} + + void FloatingPointHelper::LoadFloatOperand(MacroAssembler* masm, Register number) { NearLabel load_smi, done; @@ -1796,6 +3138,22 @@ void FloatingPointHelper::LoadSSE2Smis(MacroAssembler* masm, } +void FloatingPointHelper::CheckSSE2OperandsAreInt32(MacroAssembler* masm, + Label* non_int32, + Register scratch) { + __ cvttsd2si(scratch, Operand(xmm0)); + __ cvtsi2sd(xmm2, Operand(scratch)); + __ ucomisd(xmm0, xmm2); + __ j(not_zero, non_int32); + __ j(carry, non_int32); + __ cvttsd2si(scratch, Operand(xmm1)); + __ cvtsi2sd(xmm2, Operand(scratch)); + __ ucomisd(xmm1, xmm2); + __ j(not_zero, non_int32); + __ j(carry, non_int32); +} + + void FloatingPointHelper::LoadFloatOperands(MacroAssembler* masm, Register scratch, ArgLocation arg_location) { @@ -1879,6 +3237,12 @@ void FloatingPointHelper::CheckFloatOperands(MacroAssembler* masm, } +void FloatingPointHelper::CheckFloatOperandsAreInt32(MacroAssembler* masm, + Label* non_int32) { + return; +} + + void GenericUnaryOpStub::Generate(MacroAssembler* masm) { Label slow, done, undo; @@ -2013,6 +3377,160 @@ void GenericUnaryOpStub::Generate(MacroAssembler* masm) { } +void MathPowStub::Generate(MacroAssembler* masm) { + // Registers are used as follows: + // edx = base + // eax = exponent + // ecx = temporary, result + + CpuFeatures::Scope use_sse2(SSE2); + Label allocate_return, call_runtime; + + // Load input parameters. + __ mov(edx, Operand(esp, 2 * kPointerSize)); + __ mov(eax, Operand(esp, 1 * kPointerSize)); + + // Save 1 in xmm3 - we need this several times later on. + __ mov(ecx, Immediate(1)); + __ cvtsi2sd(xmm3, Operand(ecx)); + + Label exponent_nonsmi; + Label base_nonsmi; + // If the exponent is a heap number go to that specific case. + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &exponent_nonsmi); + __ test(edx, Immediate(kSmiTagMask)); + __ j(not_zero, &base_nonsmi); + + // Optimized version when both exponent and base is a smi. + Label powi; + __ SmiUntag(edx); + __ cvtsi2sd(xmm0, Operand(edx)); + __ jmp(&powi); + // exponent is smi and base is a heapnumber. + __ bind(&base_nonsmi); + __ cmp(FieldOperand(edx, HeapObject::kMapOffset), + Factory::heap_number_map()); + __ j(not_equal, &call_runtime); + + __ movdbl(xmm0, FieldOperand(edx, HeapNumber::kValueOffset)); + + // Optimized version of pow if exponent is a smi. + // xmm0 contains the base. + __ bind(&powi); + __ SmiUntag(eax); + + // Save exponent in base as we need to check if exponent is negative later. + // We know that base and exponent are in different registers. + __ mov(edx, eax); + + // Get absolute value of exponent. + NearLabel no_neg; + __ cmp(eax, 0); + __ j(greater_equal, &no_neg); + __ neg(eax); + __ bind(&no_neg); + + // Load xmm1 with 1. + __ movsd(xmm1, xmm3); + NearLabel while_true; + NearLabel no_multiply; + + __ bind(&while_true); + __ shr(eax, 1); + __ j(not_carry, &no_multiply); + __ mulsd(xmm1, xmm0); + __ bind(&no_multiply); + __ test(eax, Operand(eax)); + __ mulsd(xmm0, xmm0); + __ j(not_zero, &while_true); + + // base has the original value of the exponent - if the exponent is + // negative return 1/result. + __ test(edx, Operand(edx)); + __ j(positive, &allocate_return); + // Special case if xmm1 has reached infinity. + __ mov(ecx, Immediate(0x7FB00000)); + __ movd(xmm0, Operand(ecx)); + __ cvtss2sd(xmm0, xmm0); + __ ucomisd(xmm0, xmm1); + __ j(equal, &call_runtime); + __ divsd(xmm3, xmm1); + __ movsd(xmm1, xmm3); + __ jmp(&allocate_return); + + // exponent (or both) is a heapnumber - no matter what we should now work + // on doubles. + __ bind(&exponent_nonsmi); + __ cmp(FieldOperand(eax, HeapObject::kMapOffset), + Factory::heap_number_map()); + __ j(not_equal, &call_runtime); + __ movdbl(xmm1, FieldOperand(eax, HeapNumber::kValueOffset)); + // Test if exponent is nan. + __ ucomisd(xmm1, xmm1); + __ j(parity_even, &call_runtime); + + NearLabel base_not_smi; + NearLabel handle_special_cases; + __ test(edx, Immediate(kSmiTagMask)); + __ j(not_zero, &base_not_smi); + __ SmiUntag(edx); + __ cvtsi2sd(xmm0, Operand(edx)); + __ jmp(&handle_special_cases); + + __ bind(&base_not_smi); + __ cmp(FieldOperand(edx, HeapObject::kMapOffset), + Factory::heap_number_map()); + __ j(not_equal, &call_runtime); + __ mov(ecx, FieldOperand(edx, HeapNumber::kExponentOffset)); + __ and_(ecx, HeapNumber::kExponentMask); + __ cmp(Operand(ecx), Immediate(HeapNumber::kExponentMask)); + // base is NaN or +/-Infinity + __ j(greater_equal, &call_runtime); + __ movdbl(xmm0, FieldOperand(edx, HeapNumber::kValueOffset)); + + // base is in xmm0 and exponent is in xmm1. + __ bind(&handle_special_cases); + NearLabel not_minus_half; + // Test for -0.5. + // Load xmm2 with -0.5. + __ mov(ecx, Immediate(0xBF000000)); + __ movd(xmm2, Operand(ecx)); + __ cvtss2sd(xmm2, xmm2); + // xmm2 now has -0.5. + __ ucomisd(xmm2, xmm1); + __ j(not_equal, ¬_minus_half); + + // Calculates reciprocal of square root. + // Note that 1/sqrt(x) = sqrt(1/x)) + __ divsd(xmm3, xmm0); + __ movsd(xmm1, xmm3); + __ sqrtsd(xmm1, xmm1); + __ jmp(&allocate_return); + + // Test for 0.5. + __ bind(¬_minus_half); + // Load xmm2 with 0.5. + // Since xmm3 is 1 and xmm2 is -0.5 this is simply xmm2 + xmm3. + __ addsd(xmm2, xmm3); + // xmm2 now has 0.5. + __ ucomisd(xmm2, xmm1); + __ j(not_equal, &call_runtime); + // Calculates square root. + __ movsd(xmm1, xmm0); + __ sqrtsd(xmm1, xmm1); + + __ bind(&allocate_return); + __ AllocateHeapNumber(ecx, eax, edx, &call_runtime); + __ movdbl(FieldOperand(ecx, HeapNumber::kValueOffset), xmm1); + __ mov(eax, ecx); + __ ret(2); + + __ bind(&call_runtime); + __ TailCallRuntime(Runtime::kMath_pow_cfunction, 2, 1); +} + + void ArgumentsAccessStub::GenerateReadElement(MacroAssembler* masm) { // The key is in edx and the parameter count is in eax. @@ -2507,6 +4025,87 @@ void RegExpExecStub::Generate(MacroAssembler* masm) { } +void RegExpConstructResultStub::Generate(MacroAssembler* masm) { + const int kMaxInlineLength = 100; + Label slowcase; + NearLabel done; + __ mov(ebx, Operand(esp, kPointerSize * 3)); + __ test(ebx, Immediate(kSmiTagMask)); + __ j(not_zero, &slowcase); + __ cmp(Operand(ebx), Immediate(Smi::FromInt(kMaxInlineLength))); + __ j(above, &slowcase); + // Smi-tagging is equivalent to multiplying by 2. + STATIC_ASSERT(kSmiTag == 0); + STATIC_ASSERT(kSmiTagSize == 1); + // Allocate RegExpResult followed by FixedArray with size in ebx. + // JSArray: [Map][empty properties][Elements][Length-smi][index][input] + // Elements: [Map][Length][..elements..] + __ AllocateInNewSpace(JSRegExpResult::kSize + FixedArray::kHeaderSize, + times_half_pointer_size, + ebx, // In: Number of elements (times 2, being a smi) + eax, // Out: Start of allocation (tagged). + ecx, // Out: End of allocation. + edx, // Scratch register + &slowcase, + TAG_OBJECT); + // eax: Start of allocated area, object-tagged. + + // Set JSArray map to global.regexp_result_map(). + // Set empty properties FixedArray. + // Set elements to point to FixedArray allocated right after the JSArray. + // Interleave operations for better latency. + __ mov(edx, ContextOperand(esi, Context::GLOBAL_INDEX)); + __ mov(ecx, Immediate(Factory::empty_fixed_array())); + __ lea(ebx, Operand(eax, JSRegExpResult::kSize)); + __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalContextOffset)); + __ mov(FieldOperand(eax, JSObject::kElementsOffset), ebx); + __ mov(FieldOperand(eax, JSObject::kPropertiesOffset), ecx); + __ mov(edx, ContextOperand(edx, Context::REGEXP_RESULT_MAP_INDEX)); + __ mov(FieldOperand(eax, HeapObject::kMapOffset), edx); + + // Set input, index and length fields from arguments. + __ mov(ecx, Operand(esp, kPointerSize * 1)); + __ mov(FieldOperand(eax, JSRegExpResult::kInputOffset), ecx); + __ mov(ecx, Operand(esp, kPointerSize * 2)); + __ mov(FieldOperand(eax, JSRegExpResult::kIndexOffset), ecx); + __ mov(ecx, Operand(esp, kPointerSize * 3)); + __ mov(FieldOperand(eax, JSArray::kLengthOffset), ecx); + + // Fill out the elements FixedArray. + // eax: JSArray. + // ebx: FixedArray. + // ecx: Number of elements in array, as smi. + + // Set map. + __ mov(FieldOperand(ebx, HeapObject::kMapOffset), + Immediate(Factory::fixed_array_map())); + // Set length. + __ mov(FieldOperand(ebx, FixedArray::kLengthOffset), ecx); + // Fill contents of fixed-array with the-hole. + __ SmiUntag(ecx); + __ mov(edx, Immediate(Factory::the_hole_value())); + __ lea(ebx, FieldOperand(ebx, FixedArray::kHeaderSize)); + // Fill fixed array elements with hole. + // eax: JSArray. + // ecx: Number of elements to fill. + // ebx: Start of elements in FixedArray. + // edx: the hole. + Label loop; + __ test(ecx, Operand(ecx)); + __ bind(&loop); + __ j(less_equal, &done); // Jump if ecx is negative or zero. + __ sub(Operand(ecx), Immediate(1)); + __ mov(Operand(ebx, ecx, times_pointer_size, 0), edx); + __ jmp(&loop); + + __ bind(&done); + __ ret(3 * kPointerSize); + + __ bind(&slowcase); + __ TailCallRuntime(Runtime::kRegExpConstructResult, 3, 1); +} + + void NumberToStringStub::GenerateLookupNumberStringCache(MacroAssembler* masm, Register object, Register result, @@ -3125,7 +4724,7 @@ void CEntryStub::GenerateCore(MacroAssembler* masm, __ j(zero, &failure_returned, not_taken); // Exit the JavaScript to C++ exit frame. - __ LeaveExitFrame(); + __ LeaveExitFrame(save_doubles_); __ ret(0); // Handling of failure. @@ -3225,7 +4824,7 @@ void CEntryStub::Generate(MacroAssembler* masm) { // a garbage collection and retrying the builtin (twice). // Enter the exit frame that transitions from JavaScript to C++. - __ EnterExitFrame(); + __ EnterExitFrame(save_doubles_); // eax: result parameter for PerformGC, if any (setup below) // ebx: pointer to builtin function (C callee-saved) @@ -3375,76 +4974,125 @@ void JSEntryStub::GenerateBody(MacroAssembler* masm, bool is_construct) { void InstanceofStub::Generate(MacroAssembler* masm) { - // Get the object - go slow case if it's a smi. - Label slow; - __ mov(eax, Operand(esp, 2 * kPointerSize)); // 2 ~ return address, function - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &slow, not_taken); + // Fixed register usage throughout the stub. + Register object = eax; // Object (lhs). + Register map = ebx; // Map of the object. + Register function = edx; // Function (rhs). + Register prototype = edi; // Prototype of the function. + Register scratch = ecx; + + // Get the object and function - they are always both needed. + Label slow, not_js_object; + if (!args_in_registers()) { + __ mov(object, Operand(esp, 2 * kPointerSize)); + __ mov(function, Operand(esp, 1 * kPointerSize)); + } // Check that the left hand is a JS object. - __ IsObjectJSObjectType(eax, eax, edx, &slow); - - // Get the prototype of the function. - __ mov(edx, Operand(esp, 1 * kPointerSize)); // 1 ~ return address - // edx is function, eax is map. + __ test(object, Immediate(kSmiTagMask)); + __ j(zero, ¬_js_object, not_taken); + __ IsObjectJSObjectType(object, map, scratch, ¬_js_object); // Look up the function and the map in the instanceof cache. NearLabel miss; ExternalReference roots_address = ExternalReference::roots_address(); - __ mov(ecx, Immediate(Heap::kInstanceofCacheFunctionRootIndex)); - __ cmp(edx, Operand::StaticArray(ecx, times_pointer_size, roots_address)); + __ mov(scratch, Immediate(Heap::kInstanceofCacheFunctionRootIndex)); + __ cmp(function, + Operand::StaticArray(scratch, times_pointer_size, roots_address)); __ j(not_equal, &miss); - __ mov(ecx, Immediate(Heap::kInstanceofCacheMapRootIndex)); - __ cmp(eax, Operand::StaticArray(ecx, times_pointer_size, roots_address)); + __ mov(scratch, Immediate(Heap::kInstanceofCacheMapRootIndex)); + __ cmp(map, Operand::StaticArray(scratch, times_pointer_size, roots_address)); __ j(not_equal, &miss); - __ mov(ecx, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); - __ mov(eax, Operand::StaticArray(ecx, times_pointer_size, roots_address)); - __ ret(2 * kPointerSize); + __ mov(scratch, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); + __ mov(eax, Operand::StaticArray(scratch, times_pointer_size, roots_address)); + __ IncrementCounter(&Counters::instance_of_cache, 1); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); __ bind(&miss); - __ TryGetFunctionPrototype(edx, ebx, ecx, &slow); + // Get the prototype of the function. + __ TryGetFunctionPrototype(function, prototype, scratch, &slow); // Check that the function prototype is a JS object. - __ test(ebx, Immediate(kSmiTagMask)); + __ test(prototype, Immediate(kSmiTagMask)); __ j(zero, &slow, not_taken); - __ IsObjectJSObjectType(ebx, ecx, ecx, &slow); - - // Register mapping: - // eax is object map. - // edx is function. - // ebx is function prototype. - __ mov(ecx, Immediate(Heap::kInstanceofCacheMapRootIndex)); - __ mov(Operand::StaticArray(ecx, times_pointer_size, roots_address), eax); - __ mov(ecx, Immediate(Heap::kInstanceofCacheFunctionRootIndex)); - __ mov(Operand::StaticArray(ecx, times_pointer_size, roots_address), edx); - - __ mov(ecx, FieldOperand(eax, Map::kPrototypeOffset)); - - // Loop through the prototype chain looking for the function prototype. + __ IsObjectJSObjectType(prototype, scratch, scratch, &slow); + + // Update the golbal instanceof cache with the current map and function. The + // cached answer will be set when it is known. + __ mov(scratch, Immediate(Heap::kInstanceofCacheMapRootIndex)); + __ mov(Operand::StaticArray(scratch, times_pointer_size, roots_address), map); + __ mov(scratch, Immediate(Heap::kInstanceofCacheFunctionRootIndex)); + __ mov(Operand::StaticArray(scratch, times_pointer_size, roots_address), + function); + + // Loop through the prototype chain of the object looking for the function + // prototype. + __ mov(scratch, FieldOperand(map, Map::kPrototypeOffset)); NearLabel loop, is_instance, is_not_instance; __ bind(&loop); - __ cmp(ecx, Operand(ebx)); + __ cmp(scratch, Operand(prototype)); __ j(equal, &is_instance); - __ cmp(Operand(ecx), Immediate(Factory::null_value())); + __ cmp(Operand(scratch), Immediate(Factory::null_value())); __ j(equal, &is_not_instance); - __ mov(ecx, FieldOperand(ecx, HeapObject::kMapOffset)); - __ mov(ecx, FieldOperand(ecx, Map::kPrototypeOffset)); + __ mov(scratch, FieldOperand(scratch, HeapObject::kMapOffset)); + __ mov(scratch, FieldOperand(scratch, Map::kPrototypeOffset)); __ jmp(&loop); __ bind(&is_instance); + __ IncrementCounter(&Counters::instance_of_stub_true, 1); __ Set(eax, Immediate(0)); - __ mov(ecx, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); - __ mov(Operand::StaticArray(ecx, times_pointer_size, roots_address), eax); - __ ret(2 * kPointerSize); + __ mov(scratch, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); + __ mov(Operand::StaticArray(scratch, times_pointer_size, roots_address), eax); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); __ bind(&is_not_instance); + __ IncrementCounter(&Counters::instance_of_stub_false, 1); __ Set(eax, Immediate(Smi::FromInt(1))); - __ mov(ecx, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); - __ mov(Operand::StaticArray(ecx, times_pointer_size, roots_address), eax); - __ ret(2 * kPointerSize); + __ mov(scratch, Immediate(Heap::kInstanceofCacheAnswerRootIndex)); + __ mov(Operand::StaticArray(scratch, times_pointer_size, roots_address), eax); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); + + Label object_not_null, object_not_null_or_smi; + __ bind(¬_js_object); + // Before null, smi and string value checks, check that the rhs is a function + // as for a non-function rhs an exception needs to be thrown. + __ test(function, Immediate(kSmiTagMask)); + __ j(zero, &slow, not_taken); + __ CmpObjectType(function, JS_FUNCTION_TYPE, scratch); + __ j(not_equal, &slow, not_taken); + + // Null is not instance of anything. + __ cmp(object, Factory::null_value()); + __ j(not_equal, &object_not_null); + __ IncrementCounter(&Counters::instance_of_stub_false_null, 1); + __ Set(eax, Immediate(Smi::FromInt(1))); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); + + __ bind(&object_not_null); + // Smi values is not instance of anything. + __ test(object, Immediate(kSmiTagMask)); + __ j(not_zero, &object_not_null_or_smi, not_taken); + __ Set(eax, Immediate(Smi::FromInt(1))); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); + + __ bind(&object_not_null_or_smi); + // String values is not instance of anything. + Condition is_string = masm->IsObjectStringType(object, scratch, scratch); + __ j(NegateCondition(is_string), &slow); + __ IncrementCounter(&Counters::instance_of_stub_false_string, 1); + __ Set(eax, Immediate(Smi::FromInt(1))); + __ ret((args_in_registers() ? 0 : 2) * kPointerSize); // Slow-case: Go through the JavaScript implementation. __ bind(&slow); + if (args_in_registers()) { + // Push arguments below return address. + __ pop(scratch); + __ push(object); + __ push(function); + __ push(scratch); + } + __ IncrementCounter(&Counters::instance_of_slow, 1); __ InvokeBuiltin(Builtins::INSTANCE_OF, JUMP_FUNCTION); } @@ -4573,6 +6221,192 @@ void StringCompareStub::Generate(MacroAssembler* masm) { __ TailCallRuntime(Runtime::kStringCompare, 2, 1); } + +void StringCharAtStub::Generate(MacroAssembler* masm) { + // Expects two arguments (object, index) on the stack: + + // Stack frame on entry. + // esp[0]: return address + // esp[4]: index + // esp[8]: object + + Register object = ebx; + Register index = eax; + Register scratch1 = ecx; + Register scratch2 = edx; + Register result = eax; + + __ pop(scratch1); // Return address. + __ pop(index); + __ pop(object); + __ push(scratch1); + + Label need_conversion; + Label index_out_of_range; + Label done; + StringCharAtGenerator generator(object, + index, + scratch1, + scratch2, + result, + &need_conversion, + &need_conversion, + &index_out_of_range, + STRING_INDEX_IS_NUMBER); + generator.GenerateFast(masm); + __ jmp(&done); + + __ bind(&index_out_of_range); + // When the index is out of range, the spec requires us to return + // the empty string. + __ Set(result, Immediate(Factory::empty_string())); + __ jmp(&done); + + __ bind(&need_conversion); + // Move smi zero into the result register, which will trigger + // conversion. + __ Set(result, Immediate(Smi::FromInt(0))); + __ jmp(&done); + + StubRuntimeCallHelper call_helper; + generator.GenerateSlow(masm, call_helper); + + __ bind(&done); + __ ret(0); +} + +void ICCompareStub::GenerateSmis(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::SMIS); + NearLabel miss; + __ mov(ecx, Operand(edx)); + __ or_(ecx, Operand(eax)); + __ test(ecx, Immediate(kSmiTagMask)); + __ j(not_zero, &miss, not_taken); + + if (GetCondition() == equal) { + // For equality we do not care about the sign of the result. + __ sub(eax, Operand(edx)); + } else { + NearLabel done; + __ sub(edx, Operand(eax)); + __ j(no_overflow, &done); + // Correct sign of result in case of overflow. + __ not_(edx); + __ bind(&done); + __ mov(eax, edx); + } + __ ret(0); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateHeapNumbers(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::HEAP_NUMBERS); + + NearLabel generic_stub; + NearLabel unordered; + NearLabel miss; + __ mov(ecx, Operand(edx)); + __ and_(ecx, Operand(eax)); + __ test(ecx, Immediate(kSmiTagMask)); + __ j(zero, &generic_stub, not_taken); + + __ CmpObjectType(eax, HEAP_NUMBER_TYPE, ecx); + __ j(not_equal, &miss, not_taken); + __ CmpObjectType(edx, HEAP_NUMBER_TYPE, ecx); + __ j(not_equal, &miss, not_taken); + + // Inlining the double comparison and falling back to the general compare + // stub if NaN is involved or SS2 or CMOV is unsupported. + if (CpuFeatures::IsSupported(SSE2) && CpuFeatures::IsSupported(CMOV)) { + CpuFeatures::Scope scope1(SSE2); + CpuFeatures::Scope scope2(CMOV); + + // Load left and right operand + __ movdbl(xmm0, FieldOperand(edx, HeapNumber::kValueOffset)); + __ movdbl(xmm1, FieldOperand(eax, HeapNumber::kValueOffset)); + + // Compare operands + __ ucomisd(xmm0, xmm1); + + // Don't base result on EFLAGS when a NaN is involved. + __ j(parity_even, &unordered, not_taken); + + // Return a result of -1, 0, or 1, based on EFLAGS. + // Performing mov, because xor would destroy the flag register. + __ mov(eax, 0); // equal + __ mov(ecx, Immediate(Smi::FromInt(1))); + __ cmov(above, eax, Operand(ecx)); + __ mov(ecx, Immediate(Smi::FromInt(-1))); + __ cmov(below, eax, Operand(ecx)); + __ ret(0); + + __ bind(&unordered); + } + + CompareStub stub(GetCondition(), strict(), NO_COMPARE_FLAGS); + __ bind(&generic_stub); + __ jmp(stub.GetCode(), RelocInfo::CODE_TARGET); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateObjects(MacroAssembler* masm) { + ASSERT(state_ == CompareIC::OBJECTS); + NearLabel miss; + __ mov(ecx, Operand(edx)); + __ and_(ecx, Operand(eax)); + __ test(ecx, Immediate(kSmiTagMask)); + __ j(zero, &miss, not_taken); + + __ CmpObjectType(eax, JS_OBJECT_TYPE, ecx); + __ j(not_equal, &miss, not_taken); + __ CmpObjectType(edx, JS_OBJECT_TYPE, ecx); + __ j(not_equal, &miss, not_taken); + + ASSERT(GetCondition() == equal); + __ sub(eax, Operand(edx)); + __ ret(0); + + __ bind(&miss); + GenerateMiss(masm); +} + + +void ICCompareStub::GenerateMiss(MacroAssembler* masm) { + // Save the registers. + __ pop(ecx); + __ push(edx); + __ push(eax); + __ push(ecx); + + // Call the runtime system in a fresh internal frame. + ExternalReference miss = ExternalReference(IC_Utility(IC::kCompareIC_Miss)); + __ EnterInternalFrame(); + __ push(edx); + __ push(eax); + __ push(Immediate(Smi::FromInt(op_))); + __ CallExternalReference(miss, 3); + __ LeaveInternalFrame(); + + // Compute the entry point of the rewritten stub. + __ lea(edi, FieldOperand(eax, Code::kHeaderSize)); + + // Restore registers. + __ pop(ecx); + __ pop(eax); + __ pop(edx); + __ push(ecx); + + // Do a tail call to the rewritten stub. + __ jmp(Operand(edi)); +} + + #undef __ } } // namespace v8::internal diff --git a/src/ia32/code-stubs-ia32.h b/src/ia32/code-stubs-ia32.h index 351636fa..f66a8c7e 100644 --- a/src/ia32/code-stubs-ia32.h +++ b/src/ia32/code-stubs-ia32.h @@ -40,13 +40,21 @@ namespace internal { // TranscendentalCache runtime function. class TranscendentalCacheStub: public CodeStub { public: - explicit TranscendentalCacheStub(TranscendentalCache::Type type) - : type_(type) {} + enum ArgumentType { + TAGGED = 0, + UNTAGGED = 1 << TranscendentalCache::kTranscendentalTypeBits + }; + + explicit TranscendentalCacheStub(TranscendentalCache::Type type, + ArgumentType argument_type) + : type_(type), argument_type_(argument_type) {} void Generate(MacroAssembler* masm); private: TranscendentalCache::Type type_; + ArgumentType argument_type_; + Major MajorKey() { return TranscendentalCache; } - int MinorKey() { return type_; } + int MinorKey() { return type_ | argument_type_; } Runtime::FunctionId RuntimeFunction(); void GenerateOperation(MacroAssembler* masm); }; @@ -83,7 +91,7 @@ class GenericBinaryOpStub: public CodeStub { args_in_registers_(false), args_reversed_(false), static_operands_type_(operands_type), - runtime_operands_type_(BinaryOpIC::DEFAULT), + runtime_operands_type_(BinaryOpIC::UNINIT_OR_SMI), name_(NULL) { if (static_operands_type_.IsSmi()) { mode_ = NO_OVERWRITE; @@ -117,6 +125,11 @@ class GenericBinaryOpStub: public CodeStub { || op_ == Token::MUL || op_ == Token::DIV; } + void SetArgsInRegisters() { + ASSERT(ArgsInRegistersSupported()); + args_in_registers_ = true; + } + private: Token::Value op_; OverwriteMode mode_; @@ -157,7 +170,7 @@ class GenericBinaryOpStub: public CodeStub { class ArgsReversedBits: public BitField<bool, 11, 1> {}; class FlagBits: public BitField<GenericBinaryFlags, 12, 1> {}; class StaticTypeInfoBits: public BitField<int, 13, 3> {}; - class RuntimeTypeInfoBits: public BitField<BinaryOpIC::TypeInfo, 16, 2> {}; + class RuntimeTypeInfoBits: public BitField<BinaryOpIC::TypeInfo, 16, 3> {}; Major MajorKey() { return GenericBinaryOp; } int MinorKey() { @@ -185,7 +198,6 @@ class GenericBinaryOpStub: public CodeStub { return (op_ == Token::ADD) || (op_ == Token::MUL); } - void SetArgsInRegisters() { args_in_registers_ = true; } void SetArgsReversed() { args_reversed_ = true; } bool HasSmiCodeInStub() { return (flags_ & NO_SMI_CODE_IN_STUB) == 0; } bool HasArgsInRegisters() { return args_in_registers_; } @@ -207,6 +219,123 @@ class GenericBinaryOpStub: public CodeStub { return BinaryOpIC::ToState(runtime_operands_type_); } + virtual void FinishCode(Code* code) { + code->set_binary_op_type(runtime_operands_type_); + } + + friend class CodeGenerator; +}; + + +class TypeRecordingBinaryOpStub: public CodeStub { + public: + TypeRecordingBinaryOpStub(Token::Value op, OverwriteMode mode) + : op_(op), + mode_(mode), + operands_type_(TRBinaryOpIC::UNINITIALIZED), + result_type_(TRBinaryOpIC::UNINITIALIZED), + name_(NULL) { + use_sse3_ = CpuFeatures::IsSupported(SSE3); + ASSERT(OpBits::is_valid(Token::NUM_TOKENS)); + } + + TypeRecordingBinaryOpStub( + int key, + TRBinaryOpIC::TypeInfo operands_type, + TRBinaryOpIC::TypeInfo result_type = TRBinaryOpIC::UNINITIALIZED) + : op_(OpBits::decode(key)), + mode_(ModeBits::decode(key)), + use_sse3_(SSE3Bits::decode(key)), + operands_type_(operands_type), + result_type_(result_type), + name_(NULL) { } + + // Generate code to call the stub with the supplied arguments. This will add + // code at the call site to prepare arguments either in registers or on the + // stack together with the actual call. + void GenerateCall(MacroAssembler* masm, Register left, Register right); + void GenerateCall(MacroAssembler* masm, Register left, Smi* right); + void GenerateCall(MacroAssembler* masm, Smi* left, Register right); + + private: + enum SmiCodeGenerateHeapNumberResults { + ALLOW_HEAPNUMBER_RESULTS, + NO_HEAPNUMBER_RESULTS + }; + + Token::Value op_; + OverwriteMode mode_; + bool use_sse3_; + + // Operand type information determined at runtime. + TRBinaryOpIC::TypeInfo operands_type_; + TRBinaryOpIC::TypeInfo result_type_; + + char* name_; + + const char* GetName(); + +#ifdef DEBUG + void Print() { + PrintF("TypeRecordingBinaryOpStub %d (op %s), " + "(mode %d, runtime_type_info %s)\n", + MinorKey(), + Token::String(op_), + static_cast<int>(mode_), + TRBinaryOpIC::GetName(operands_type_)); + } +#endif + + // Minor key encoding in 16 bits RRRTTTSOOOOOOOMM. + class ModeBits: public BitField<OverwriteMode, 0, 2> {}; + class OpBits: public BitField<Token::Value, 2, 7> {}; + class SSE3Bits: public BitField<bool, 9, 1> {}; + class OperandTypeInfoBits: public BitField<TRBinaryOpIC::TypeInfo, 10, 3> {}; + class ResultTypeInfoBits: public BitField<TRBinaryOpIC::TypeInfo, 13, 3> {}; + + Major MajorKey() { return TypeRecordingBinaryOp; } + int MinorKey() { + return OpBits::encode(op_) + | ModeBits::encode(mode_) + | SSE3Bits::encode(use_sse3_) + | OperandTypeInfoBits::encode(operands_type_) + | ResultTypeInfoBits::encode(result_type_); + } + + void Generate(MacroAssembler* masm); + void GenerateGeneric(MacroAssembler* masm); + void GenerateSmiCode(MacroAssembler* masm, + Label* slow, + SmiCodeGenerateHeapNumberResults heapnumber_results); + void GenerateLoadArguments(MacroAssembler* masm); + void GenerateReturn(MacroAssembler* masm); + void GenerateUninitializedStub(MacroAssembler* masm); + void GenerateSmiStub(MacroAssembler* masm); + void GenerateInt32Stub(MacroAssembler* masm); + void GenerateHeapNumberStub(MacroAssembler* masm); + void GenerateStringStub(MacroAssembler* masm); + void GenerateGenericStub(MacroAssembler* masm); + + void GenerateHeapResultAllocation(MacroAssembler* masm, Label* alloc_failure); + void GenerateRegisterArgsPush(MacroAssembler* masm); + void GenerateTypeTransition(MacroAssembler* masm); + void GenerateTypeTransitionWithSavedArgs(MacroAssembler* masm); + + bool IsOperationCommutative() { + return (op_ == Token::ADD) || (op_ == Token::MUL); + } + + virtual int GetCodeKind() { return Code::TYPE_RECORDING_BINARY_OP_IC; } + + virtual InlineCacheState GetICState() { + return TRBinaryOpIC::ToState(operands_type_); + } + + virtual void FinishCode(Code* code) { + code->set_type_recording_binary_op_type(operands_type_); + code->set_type_recording_binary_op_result_type(result_type_); + } + friend class CodeGenerator; }; diff --git a/src/ia32/codegen-ia32.cc b/src/ia32/codegen-ia32.cc index 04ff200b..2f14e82e 100644 --- a/src/ia32/codegen-ia32.cc +++ b/src/ia32/codegen-ia32.cc @@ -104,12 +104,12 @@ void VirtualFrameRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { } -void ICRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { masm->EnterInternalFrame(); } -void ICRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { masm->LeaveInternalFrame(); } @@ -7398,6 +7398,7 @@ void CodeGenerator::GenerateRegExpExec(ZoneList<Expression*>* args) { Load(args->at(1)); Load(args->at(2)); Load(args->at(3)); + RegExpExecStub stub; Result result = frame_->CallStub(&stub, 4); frame_->Push(&result); @@ -7405,91 +7406,15 @@ void CodeGenerator::GenerateRegExpExec(ZoneList<Expression*>* args) { void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) { - // No stub. This code only occurs a few times in regexp.js. - const int kMaxInlineLength = 100; ASSERT_EQ(3, args->length()); + Load(args->at(0)); // Size of array, smi. Load(args->at(1)); // "index" property value. Load(args->at(2)); // "input" property value. - { - VirtualFrame::SpilledScope spilled_scope; - Label slowcase; - Label done; - __ mov(ebx, Operand(esp, kPointerSize * 2)); - __ test(ebx, Immediate(kSmiTagMask)); - __ j(not_zero, &slowcase); - __ cmp(Operand(ebx), Immediate(Smi::FromInt(kMaxInlineLength))); - __ j(above, &slowcase); - // Smi-tagging is equivalent to multiplying by 2. - STATIC_ASSERT(kSmiTag == 0); - STATIC_ASSERT(kSmiTagSize == 1); - // Allocate RegExpResult followed by FixedArray with size in ebx. - // JSArray: [Map][empty properties][Elements][Length-smi][index][input] - // Elements: [Map][Length][..elements..] - __ AllocateInNewSpace(JSRegExpResult::kSize + FixedArray::kHeaderSize, - times_half_pointer_size, - ebx, // In: Number of elements (times 2, being a smi) - eax, // Out: Start of allocation (tagged). - ecx, // Out: End of allocation. - edx, // Scratch register - &slowcase, - TAG_OBJECT); - // eax: Start of allocated area, object-tagged. - - // Set JSArray map to global.regexp_result_map(). - // Set empty properties FixedArray. - // Set elements to point to FixedArray allocated right after the JSArray. - // Interleave operations for better latency. - __ mov(edx, ContextOperand(esi, Context::GLOBAL_INDEX)); - __ mov(ecx, Immediate(Factory::empty_fixed_array())); - __ lea(ebx, Operand(eax, JSRegExpResult::kSize)); - __ mov(edx, FieldOperand(edx, GlobalObject::kGlobalContextOffset)); - __ mov(FieldOperand(eax, JSObject::kElementsOffset), ebx); - __ mov(FieldOperand(eax, JSObject::kPropertiesOffset), ecx); - __ mov(edx, ContextOperand(edx, Context::REGEXP_RESULT_MAP_INDEX)); - __ mov(FieldOperand(eax, HeapObject::kMapOffset), edx); - - // Set input, index and length fields from arguments. - __ pop(FieldOperand(eax, JSRegExpResult::kInputOffset)); - __ pop(FieldOperand(eax, JSRegExpResult::kIndexOffset)); - __ pop(ecx); - __ mov(FieldOperand(eax, JSArray::kLengthOffset), ecx); - - // Fill out the elements FixedArray. - // eax: JSArray. - // ebx: FixedArray. - // ecx: Number of elements in array, as smi. - - // Set map. - __ mov(FieldOperand(ebx, HeapObject::kMapOffset), - Immediate(Factory::fixed_array_map())); - // Set length. - __ mov(FieldOperand(ebx, FixedArray::kLengthOffset), ecx); - // Fill contents of fixed-array with the-hole. - __ SmiUntag(ecx); - __ mov(edx, Immediate(Factory::the_hole_value())); - __ lea(ebx, FieldOperand(ebx, FixedArray::kHeaderSize)); - // Fill fixed array elements with hole. - // eax: JSArray. - // ecx: Number of elements to fill. - // ebx: Start of elements in FixedArray. - // edx: the hole. - Label loop; - __ test(ecx, Operand(ecx)); - __ bind(&loop); - __ j(less_equal, &done); // Jump if ecx is negative or zero. - __ sub(Operand(ecx), Immediate(1)); - __ mov(Operand(ebx, ecx, times_pointer_size, 0), edx); - __ jmp(&loop); - - __ bind(&slowcase); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); - - __ bind(&done); - } - frame_->Forget(3); - frame_->Push(eax); + RegExpConstructResultStub stub; + Result result = frame_->CallStub(&stub, 3); + frame_->Push(&result); } @@ -7987,7 +7912,8 @@ void CodeGenerator::GenerateMathPow(ZoneList<Expression*>* args) { void CodeGenerator::GenerateMathSin(ZoneList<Expression*>* args) { ASSERT_EQ(args->length(), 1); Load(args->at(0)); - TranscendentalCacheStub stub(TranscendentalCache::SIN); + TranscendentalCacheStub stub(TranscendentalCache::SIN, + TranscendentalCacheStub::TAGGED); Result result = frame_->CallStub(&stub, 1); frame_->Push(&result); } @@ -7996,7 +7922,18 @@ void CodeGenerator::GenerateMathSin(ZoneList<Expression*>* args) { void CodeGenerator::GenerateMathCos(ZoneList<Expression*>* args) { ASSERT_EQ(args->length(), 1); Load(args->at(0)); - TranscendentalCacheStub stub(TranscendentalCache::COS); + TranscendentalCacheStub stub(TranscendentalCache::COS, + TranscendentalCacheStub::TAGGED); + Result result = frame_->CallStub(&stub, 1); + frame_->Push(&result); +} + + +void CodeGenerator::GenerateMathLog(ZoneList<Expression*>* args) { + ASSERT_EQ(args->length(), 1); + Load(args->at(0)); + TranscendentalCacheStub stub(TranscendentalCache::LOG, + TranscendentalCacheStub::TAGGED); Result result = frame_->CallStub(&stub, 1); frame_->Push(&result); } @@ -8266,7 +8203,6 @@ void CodeGenerator::VisitUnaryOperation(UnaryOperation* node) { switch (op) { case Token::SUB: { __ neg(value.reg()); - frame_->Push(&value); if (node->no_negative_zero()) { // -MIN_INT is MIN_INT with the overflow flag set. unsafe_bailout_->Branch(overflow); @@ -8279,18 +8215,17 @@ void CodeGenerator::VisitUnaryOperation(UnaryOperation* node) { } case Token::BIT_NOT: { __ not_(value.reg()); - frame_->Push(&value); break; } case Token::ADD: { // Unary plus has no effect on int32 values. - frame_->Push(&value); break; } default: UNREACHABLE(); break; } + frame_->Push(&value); } else { Load(node->expression()); bool can_overwrite = node->expression()->ResultOverwriteAllowed(); @@ -9208,7 +9143,7 @@ void CodeGenerator::VisitCompareOperation(CompareOperation* node) { case Token::INSTANCEOF: { if (!left_already_loaded) Load(left); Load(right); - InstanceofStub stub; + InstanceofStub stub(InstanceofStub::kNoFlags); Result answer = frame_->CallStub(&stub, 2); answer.ToRegister(); __ test(answer.reg(), Operand(answer.reg())); @@ -10082,14 +10017,15 @@ void Reference::SetValue(InitState init_state) { #define __ masm. + +static void MemCopyWrapper(void* dest, const void* src, size_t size) { + memcpy(dest, src, size); +} + + MemCopyFunction CreateMemCopyFunction() { - size_t actual_size; - byte* buffer = static_cast<byte*>(OS::Allocate(Assembler::kMinimalBufferSize, - &actual_size, - true)); - CHECK(buffer); - HandleScope handles; - MacroAssembler masm(buffer, static_cast<int>(actual_size)); + HandleScope scope; + MacroAssembler masm(NULL, 1 * KB); // Generated code is put into a fixed, unmovable, buffer, and not into // the V8 heap. We can't, and don't, refer to any relocatable addresses @@ -10183,6 +10119,7 @@ MemCopyFunction CreateMemCopyFunction() { __ movdqu(xmm0, Operand(src, count, times_1, -0x10)); __ movdqu(Operand(dst, count, times_1, -0x10), xmm0); + __ mov(eax, Operand(esp, stack_offset + kDestinationOffset)); __ pop(esi); __ pop(edi); __ ret(0); @@ -10229,6 +10166,7 @@ MemCopyFunction CreateMemCopyFunction() { __ movdqu(xmm0, Operand(src, count, times_1, -0x10)); __ movdqu(Operand(dst, count, times_1, -0x10), xmm0); + __ mov(eax, Operand(esp, stack_offset + kDestinationOffset)); __ pop(esi); __ pop(edi); __ ret(0); @@ -10272,6 +10210,7 @@ MemCopyFunction CreateMemCopyFunction() { __ mov(eax, Operand(src, count, times_1, -4)); __ mov(Operand(dst, count, times_1, -4), eax); + __ mov(eax, Operand(esp, stack_offset + kDestinationOffset)); __ pop(esi); __ pop(edi); __ ret(0); @@ -10279,8 +10218,15 @@ MemCopyFunction CreateMemCopyFunction() { CodeDesc desc; masm.GetCode(&desc); - // Call the function from C++. - return FUNCTION_CAST<MemCopyFunction>(buffer); + ASSERT(desc.reloc_size == 0); + + // Copy the generated code into an executable chunk and return a pointer + // to the first instruction in it as a C++ function pointer. + LargeObjectChunk* chunk = LargeObjectChunk::New(desc.instr_size, EXECUTABLE); + if (chunk == NULL) return &MemCopyWrapper; + memcpy(chunk->GetStartAddress(), desc.buffer, desc.instr_size); + CPU::FlushICache(chunk->GetStartAddress(), desc.instr_size); + return FUNCTION_CAST<MemCopyFunction>(chunk->GetStartAddress()); } #undef __ diff --git a/src/ia32/codegen-ia32.h b/src/ia32/codegen-ia32.h index d1a2036c..46b12cbb 100644 --- a/src/ia32/codegen-ia32.h +++ b/src/ia32/codegen-ia32.h @@ -43,9 +43,6 @@ class RegisterAllocator; class RegisterFile; class RuntimeCallHelper; -enum InitState { CONST_INIT, NOT_CONST_INIT }; -enum TypeofState { INSIDE_TYPEOF, NOT_INSIDE_TYPEOF }; - // ------------------------------------------------------------------------- // Reference support @@ -310,6 +307,9 @@ class CodeGenerator: public AstVisitor { Code::Flags flags, CompilationInfo* info); + // Print the code after compiling it. + static void PrintCode(Handle<Code> code, CompilationInfo* info); + #ifdef ENABLE_LOGGING_AND_PROFILING static bool ShouldGenerateLog(Expression* type); #endif @@ -398,8 +398,9 @@ class CodeGenerator: public AstVisitor { // Node visitors. void VisitStatements(ZoneList<Statement*>* statements); + virtual void VisitSlot(Slot* node); #define DEF_VISIT(type) \ - void Visit##type(type* node); + virtual void Visit##type(type* node); AST_NODE_LIST(DEF_VISIT) #undef DEF_VISIT @@ -705,8 +706,9 @@ class CodeGenerator: public AstVisitor { void GenerateMathSin(ZoneList<Expression*>* args); void GenerateMathCos(ZoneList<Expression*>* args); void GenerateMathSqrt(ZoneList<Expression*>* args); + void GenerateMathLog(ZoneList<Expression*>* args); - // Check whether two RegExps are equivalent + // Check whether two RegExps are equivalent. void GenerateIsRegExpEquivalent(ZoneList<Expression*>* args); void GenerateHasCachedArrayIndex(ZoneList<Expression*>* args); @@ -782,6 +784,7 @@ class CodeGenerator: public AstVisitor { friend class FastCodeGenerator; friend class FullCodeGenerator; friend class FullCodeGenSyntaxChecker; + friend class LCodeGen; friend class CodeGeneratorPatcher; // Used in test-log-stack-tracer.cc diff --git a/src/ia32/cpu-ia32.cc b/src/ia32/cpu-ia32.cc index b15140f0..d64257f3 100644 --- a/src/ia32/cpu-ia32.cc +++ b/src/ia32/cpu-ia32.cc @@ -42,7 +42,11 @@ namespace v8 { namespace internal { void CPU::Setup() { - CpuFeatures::Probe(); + CpuFeatures::Clear(); + CpuFeatures::Probe(true); + if (!CpuFeatures::IsSupported(SSE2) || Serializer::enabled()) { + V8::DisableCrankshaft(); + } } diff --git a/src/ia32/deoptimizer-ia32.cc b/src/ia32/deoptimizer-ia32.cc new file mode 100644 index 00000000..d95df3e7 --- /dev/null +++ b/src/ia32/deoptimizer-ia32.cc @@ -0,0 +1,615 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "codegen.h" +#include "deoptimizer.h" +#include "full-codegen.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + + +int Deoptimizer::table_entry_size_ = 10; + +void Deoptimizer::DeoptimizeFunction(JSFunction* function) { + AssertNoAllocation no_allocation; + + if (!function->IsOptimized()) return; + + // Get the optimized code. + Code* code = function->code(); + + // Invalidate the relocation information, as it will become invalid by the + // code patching below, and is not needed any more. + code->InvalidateRelocation(); + + // For each return after a safepoint insert a absolute call to the + // corresponding deoptimization entry. + unsigned last_pc_offset = 0; + SafepointTable table(function->code()); + for (unsigned i = 0; i < table.length(); i++) { + unsigned pc_offset = table.GetPcOffset(i); + int deoptimization_index = table.GetDeoptimizationIndex(i); + int gap_code_size = table.GetGapCodeSize(i); +#ifdef DEBUG + // Destroy the code which is not supposed to run again. + unsigned instructions = pc_offset - last_pc_offset; + CodePatcher destroyer(code->instruction_start() + last_pc_offset, + instructions); + for (unsigned i = 0; i < instructions; i++) { + destroyer.masm()->int3(); + } +#endif + last_pc_offset = pc_offset; + if (deoptimization_index != Safepoint::kNoDeoptimizationIndex) { + CodePatcher patcher( + code->instruction_start() + pc_offset + gap_code_size, + Assembler::kCallInstructionLength); + patcher.masm()->call(GetDeoptimizationEntry(deoptimization_index, LAZY), + RelocInfo::NONE); + last_pc_offset += gap_code_size + Assembler::kCallInstructionLength; + } + } +#ifdef DEBUG + // Destroy the code which is not supposed to run again. + unsigned instructions = code->safepoint_table_start() - last_pc_offset; + CodePatcher destroyer(code->instruction_start() + last_pc_offset, + instructions); + for (unsigned i = 0; i < instructions; i++) { + destroyer.masm()->int3(); + } +#endif + + // Add the deoptimizing code to the list. + DeoptimizingCodeListNode* node = new DeoptimizingCodeListNode(code); + node->set_next(deoptimizing_code_list_); + deoptimizing_code_list_ = node; + + // Set the code for the function to non-optimized version. + function->ReplaceCode(function->shared()->code()); + + if (FLAG_trace_deopt) { + PrintF("[forced deoptimization: "); + function->PrintName(); + PrintF(" / %x]\n", reinterpret_cast<uint32_t>(function)); + } +} + + +void Deoptimizer::PatchStackCheckCode(RelocInfo* rinfo, + Code* replacement_code) { + // The stack check code matches the pattern (on ia32, for example): + // + // cmp esp, <limit> + // jae ok + // call <stack guard> + // ok: ... + // + // We will patch the code to: + // + // cmp esp, <limit> ;; Not changed + // nop + // nop + // call <on-stack replacment> + // ok: + Address call_target_address = rinfo->pc(); + ASSERT(*(call_target_address - 3) == 0x73 && // jae + *(call_target_address - 2) == 0x05 && // offset + *(call_target_address - 1) == 0xe8); // call + *(call_target_address - 3) = 0x90; // nop + *(call_target_address - 2) = 0x90; // nop + rinfo->set_target_address(replacement_code->entry()); +} + + +void Deoptimizer::RevertStackCheckCode(RelocInfo* rinfo, Code* check_code) { + Address call_target_address = rinfo->pc(); + ASSERT(*(call_target_address - 3) == 0x90 && // nop + *(call_target_address - 2) == 0x90 && // nop + *(call_target_address - 1) == 0xe8); // call + *(call_target_address - 3) = 0x73; // jae + *(call_target_address - 2) = 0x05; // offset + rinfo->set_target_address(check_code->entry()); +} + + +static int LookupBailoutId(DeoptimizationInputData* data, unsigned ast_id) { + ByteArray* translations = data->TranslationByteArray(); + int length = data->DeoptCount(); + for (int i = 0; i < length; i++) { + if (static_cast<unsigned>(data->AstId(i)->value()) == ast_id) { + TranslationIterator it(translations, data->TranslationIndex(i)->value()); + int value = it.Next(); + ASSERT(Translation::BEGIN == static_cast<Translation::Opcode>(value)); + // Read the number of frames. + value = it.Next(); + if (value == 1) return i; + } + } + UNREACHABLE(); + return -1; +} + + +void Deoptimizer::DoComputeOsrOutputFrame() { + DeoptimizationInputData* data = DeoptimizationInputData::cast( + optimized_code_->deoptimization_data()); + unsigned ast_id = data->OsrAstId()->value(); + // TODO(kasperl): This should not be the bailout_id_. It should be + // the ast id. Confusing. + ASSERT(bailout_id_ == ast_id); + + int bailout_id = LookupBailoutId(data, ast_id); + unsigned translation_index = data->TranslationIndex(bailout_id)->value(); + ByteArray* translations = data->TranslationByteArray(); + + TranslationIterator iterator(translations, translation_index); + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator.Next()); + ASSERT(Translation::BEGIN == opcode); + USE(opcode); + int count = iterator.Next(); + ASSERT(count == 1); + USE(count); + + opcode = static_cast<Translation::Opcode>(iterator.Next()); + USE(opcode); + ASSERT(Translation::FRAME == opcode); + unsigned node_id = iterator.Next(); + USE(node_id); + ASSERT(node_id == ast_id); + JSFunction* function = JSFunction::cast(ComputeLiteral(iterator.Next())); + USE(function); + ASSERT(function == function_); + unsigned height = iterator.Next(); + unsigned height_in_bytes = height * kPointerSize; + USE(height_in_bytes); + + unsigned fixed_size = ComputeFixedSize(function_); + unsigned input_frame_size = input_->GetFrameSize(); + ASSERT(fixed_size + height_in_bytes == input_frame_size); + + unsigned stack_slot_size = optimized_code_->stack_slots() * kPointerSize; + unsigned outgoing_height = data->ArgumentsStackHeight(bailout_id)->value(); + unsigned outgoing_size = outgoing_height * kPointerSize; + unsigned output_frame_size = fixed_size + stack_slot_size + outgoing_size; + ASSERT(outgoing_size == 0); // OSR does not happen in the middle of a call. + + if (FLAG_trace_osr) { + PrintF("[on-stack replacement: begin 0x%08" V8PRIxPTR " ", + reinterpret_cast<intptr_t>(function_)); + function_->PrintName(); + PrintF(" => node=%u, frame=%d->%d]\n", + ast_id, + input_frame_size, + output_frame_size); + } + + // There's only one output frame in the OSR case. + output_count_ = 1; + output_ = new FrameDescription*[1]; + output_[0] = new(output_frame_size) FrameDescription( + output_frame_size, function_); + + // Clear the incoming parameters in the optimized frame to avoid + // confusing the garbage collector. + unsigned output_offset = output_frame_size - kPointerSize; + int parameter_count = function_->shared()->formal_parameter_count() + 1; + for (int i = 0; i < parameter_count; ++i) { + output_[0]->SetFrameSlot(output_offset, 0); + output_offset -= kPointerSize; + } + + // Translate the incoming parameters. This may overwrite some of the + // incoming argument slots we've just cleared. + int input_offset = input_frame_size - kPointerSize; + bool ok = true; + int limit = input_offset - (parameter_count * kPointerSize); + while (ok && input_offset > limit) { + ok = DoOsrTranslateCommand(&iterator, &input_offset); + } + + // There are no translation commands for the caller's pc and fp, the + // context, and the function. Set them up explicitly. + for (int i = 0; ok && i < 4; i++) { + uint32_t input_value = input_->GetFrameSlot(input_offset); + if (FLAG_trace_osr) { + PrintF(" [esp + %d] <- 0x%08x ; [esp + %d] (fixed part)\n", + output_offset, + input_value, + input_offset); + } + output_[0]->SetFrameSlot(output_offset, input_->GetFrameSlot(input_offset)); + input_offset -= kPointerSize; + output_offset -= kPointerSize; + } + + // Translate the rest of the frame. + while (ok && input_offset >= 0) { + ok = DoOsrTranslateCommand(&iterator, &input_offset); + } + + // If translation of any command failed, continue using the input frame. + if (!ok) { + delete output_[0]; + output_[0] = input_; + output_[0]->SetPc(reinterpret_cast<uint32_t>(from_)); + } else { + // Setup the frame pointer and the context pointer. + output_[0]->SetRegister(ebp.code(), input_->GetRegister(ebp.code())); + output_[0]->SetRegister(esi.code(), input_->GetRegister(esi.code())); + + unsigned pc_offset = data->OsrPcOffset()->value(); + uint32_t pc = reinterpret_cast<uint32_t>( + optimized_code_->entry() + pc_offset); + output_[0]->SetPc(pc); + } + Code* continuation = Builtins::builtin(Builtins::NotifyOSR); + output_[0]->SetContinuation( + reinterpret_cast<uint32_t>(continuation->entry())); + + if (FLAG_trace_osr) { + PrintF("[on-stack replacement translation %s: 0x%08" V8PRIxPTR " ", + ok ? "finished" : "aborted", + reinterpret_cast<intptr_t>(function)); + function->PrintName(); + PrintF(" => pc=0x%0x]\n", output_[0]->GetPc()); + } +} + + +void Deoptimizer::DoComputeFrame(TranslationIterator* iterator, + int frame_index) { + // Read the ast node id, function, and frame height for this output frame. + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator->Next()); + USE(opcode); + ASSERT(Translation::FRAME == opcode); + int node_id = iterator->Next(); + JSFunction* function = JSFunction::cast(ComputeLiteral(iterator->Next())); + unsigned height = iterator->Next(); + unsigned height_in_bytes = height * kPointerSize; + if (FLAG_trace_deopt) { + PrintF(" translating "); + function->PrintName(); + PrintF(" => node=%d, height=%d\n", node_id, height_in_bytes); + } + + // The 'fixed' part of the frame consists of the incoming parameters and + // the part described by JavaScriptFrameConstants. + unsigned fixed_frame_size = ComputeFixedSize(function); + unsigned input_frame_size = input_->GetFrameSize(); + unsigned output_frame_size = height_in_bytes + fixed_frame_size; + + // Allocate and store the output frame description. + FrameDescription* output_frame = + new(output_frame_size) FrameDescription(output_frame_size, function); + + bool is_bottommost = (0 == frame_index); + bool is_topmost = (output_count_ - 1 == frame_index); + ASSERT(frame_index >= 0 && frame_index < output_count_); + ASSERT(output_[frame_index] == NULL); + output_[frame_index] = output_frame; + + // The top address for the bottommost output frame can be computed from + // the input frame pointer and the output frame's height. For all + // subsequent output frames, it can be computed from the previous one's + // top address and the current frame's size. + uint32_t top_address; + if (is_bottommost) { + // 2 = context and function in the frame. + top_address = + input_->GetRegister(ebp.code()) - (2 * kPointerSize) - height_in_bytes; + } else { + top_address = output_[frame_index - 1]->GetTop() - output_frame_size; + } + output_frame->SetTop(top_address); + + // Compute the incoming parameter translation. + int parameter_count = function->shared()->formal_parameter_count() + 1; + unsigned output_offset = output_frame_size; + unsigned input_offset = input_frame_size; + for (int i = 0; i < parameter_count; ++i) { + output_offset -= kPointerSize; + DoTranslateCommand(iterator, frame_index, output_offset); + } + input_offset -= (parameter_count * kPointerSize); + + // There are no translation commands for the caller's pc and fp, the + // context, and the function. Synthesize their values and set them up + // explicitly. + // + // The caller's pc for the bottommost output frame is the same as in the + // input frame. For all subsequent output frames, it can be read from the + // previous one. This frame's pc can be computed from the non-optimized + // function code and AST id of the bailout. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + intptr_t value; + if (is_bottommost) { + value = input_->GetFrameSlot(input_offset); + } else { + value = output_[frame_index - 1]->GetPc(); + } + output_frame->SetFrameSlot(output_offset, value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; caller's pc\n", + top_address + output_offset, output_offset, value); + } + + // The caller's frame pointer for the bottommost output frame is the same + // as in the input frame. For all subsequent output frames, it can be + // read from the previous one. Also compute and set this frame's frame + // pointer. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + if (is_bottommost) { + value = input_->GetFrameSlot(input_offset); + } else { + value = output_[frame_index - 1]->GetFp(); + } + output_frame->SetFrameSlot(output_offset, value); + intptr_t fp_value = top_address + output_offset; + ASSERT(!is_bottommost || input_->GetRegister(ebp.code()) == fp_value); + output_frame->SetFp(fp_value); + if (is_topmost) output_frame->SetRegister(ebp.code(), fp_value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; caller's fp\n", + fp_value, output_offset, value); + } + + // The context can be gotten from the function so long as we don't + // optimize functions that need local contexts. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + value = reinterpret_cast<uint32_t>(function->context()); + // The context for the bottommost output frame should also agree with the + // input frame. + ASSERT(!is_bottommost || input_->GetFrameSlot(input_offset) == value); + output_frame->SetFrameSlot(output_offset, value); + if (is_topmost) output_frame->SetRegister(esi.code(), value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; context\n", + top_address + output_offset, output_offset, value); + } + + // The function was mentioned explicitly in the BEGIN_FRAME. + output_offset -= kPointerSize; + input_offset -= kPointerSize; + value = reinterpret_cast<uint32_t>(function); + // The function for the bottommost output frame should also agree with the + // input frame. + ASSERT(!is_bottommost || input_->GetFrameSlot(input_offset) == value); + output_frame->SetFrameSlot(output_offset, value); + if (FLAG_trace_deopt) { + PrintF(" 0x%08x: [top + %d] <- 0x%08x ; function\n", + top_address + output_offset, output_offset, value); + } + + // Translate the rest of the frame. + for (unsigned i = 0; i < height; ++i) { + output_offset -= kPointerSize; + DoTranslateCommand(iterator, frame_index, output_offset); + } + ASSERT(0 == output_offset); + + // Compute this frame's PC, state, and continuation. + Code* non_optimized_code = function->shared()->code(); + FixedArray* raw_data = non_optimized_code->deoptimization_data(); + DeoptimizationOutputData* data = DeoptimizationOutputData::cast(raw_data); + Address start = non_optimized_code->instruction_start(); + unsigned pc_and_state = GetOutputInfo(data, node_id, function->shared()); + unsigned pc_offset = FullCodeGenerator::PcField::decode(pc_and_state); + uint32_t pc_value = reinterpret_cast<uint32_t>(start + pc_offset); + output_frame->SetPc(pc_value); + + FullCodeGenerator::State state = + FullCodeGenerator::StateField::decode(pc_and_state); + output_frame->SetState(Smi::FromInt(state)); + + // Set the continuation for the topmost frame. + if (is_topmost) { + Code* continuation = (bailout_type_ == EAGER) + ? Builtins::builtin(Builtins::NotifyDeoptimized) + : Builtins::builtin(Builtins::NotifyLazyDeoptimized); + output_frame->SetContinuation( + reinterpret_cast<uint32_t>(continuation->entry())); + } + + if (output_count_ - 1 == frame_index) iterator->Done(); +} + + +#define __ masm()-> + +void Deoptimizer::EntryGenerator::Generate() { + GeneratePrologue(); + CpuFeatures::Scope scope(SSE2); + + // Save all general purpose registers before messing with them. + const int kNumberOfRegisters = Register::kNumRegisters; + + const int kDoubleRegsSize = kDoubleSize * + XMMRegister::kNumAllocatableRegisters; + __ sub(Operand(esp), Immediate(kDoubleRegsSize)); + for (int i = 0; i < XMMRegister::kNumAllocatableRegisters; ++i) { + XMMRegister xmm_reg = XMMRegister::FromAllocationIndex(i); + int offset = i * kDoubleSize; + __ movdbl(Operand(esp, offset), xmm_reg); + } + + __ pushad(); + + const int kSavedRegistersAreaSize = kNumberOfRegisters * kPointerSize + + kDoubleRegsSize; + + // Get the bailout id from the stack. + __ mov(ebx, Operand(esp, kSavedRegistersAreaSize)); + + // Get the address of the location in the code object if possible + // and compute the fp-to-sp delta in register edx. + if (type() == EAGER) { + __ Set(ecx, Immediate(0)); + __ lea(edx, Operand(esp, kSavedRegistersAreaSize + 1 * kPointerSize)); + } else { + __ mov(ecx, Operand(esp, kSavedRegistersAreaSize + 1 * kPointerSize)); + __ lea(edx, Operand(esp, kSavedRegistersAreaSize + 2 * kPointerSize)); + } + __ sub(edx, Operand(ebp)); + __ neg(edx); + + // Allocate a new deoptimizer object. + __ PrepareCallCFunction(5, eax); + __ mov(eax, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ mov(Operand(esp, 0 * kPointerSize), eax); // Function. + __ mov(Operand(esp, 1 * kPointerSize), Immediate(type())); // Bailout type. + __ mov(Operand(esp, 2 * kPointerSize), ebx); // Bailout id. + __ mov(Operand(esp, 3 * kPointerSize), ecx); // Code address or 0. + __ mov(Operand(esp, 4 * kPointerSize), edx); // Fp-to-sp delta. + __ CallCFunction(ExternalReference::new_deoptimizer_function(), 5); + + // Preserve deoptimizer object in register eax and get the input + // frame descriptor pointer. + __ mov(ebx, Operand(eax, Deoptimizer::input_offset())); + + // Fill in the input registers. + for (int i = 0; i < kNumberOfRegisters; i++) { + int offset = (i * kIntSize) + FrameDescription::registers_offset(); + __ mov(ecx, Operand(esp, (kNumberOfRegisters - 1 - i) * kPointerSize)); + __ mov(Operand(ebx, offset), ecx); + } + + // Fill in the double input registers. + int double_regs_offset = FrameDescription::double_registers_offset(); + for (int i = 0; i < XMMRegister::kNumAllocatableRegisters; ++i) { + int dst_offset = i * kDoubleSize + double_regs_offset; + int src_offset = i * kDoubleSize + kNumberOfRegisters * kPointerSize; + __ movdbl(xmm0, Operand(esp, src_offset)); + __ movdbl(Operand(ebx, dst_offset), xmm0); + } + + // Remove the bailout id and the general purpose registers from the stack. + if (type() == EAGER) { + __ add(Operand(esp), Immediate(kSavedRegistersAreaSize + kPointerSize)); + } else { + __ add(Operand(esp), Immediate(kSavedRegistersAreaSize + 2 * kPointerSize)); + } + + // Compute a pointer to the unwinding limit in register ecx; that is + // the first stack slot not part of the input frame. + __ mov(ecx, Operand(ebx, FrameDescription::frame_size_offset())); + __ add(ecx, Operand(esp)); + + // Unwind the stack down to - but not including - the unwinding + // limit and copy the contents of the activation frame to the input + // frame description. + __ lea(edx, Operand(ebx, FrameDescription::frame_content_offset())); + Label pop_loop; + __ bind(&pop_loop); + __ pop(Operand(edx, 0)); + __ add(Operand(edx), Immediate(sizeof(uint32_t))); + __ cmp(ecx, Operand(esp)); + __ j(not_equal, &pop_loop); + + // Compute the output frame in the deoptimizer. + __ push(eax); + __ PrepareCallCFunction(1, ebx); + __ mov(Operand(esp, 0 * kPointerSize), eax); + __ CallCFunction(ExternalReference::compute_output_frames_function(), 1); + __ pop(eax); + + // Replace the current frame with the output frames. + Label outer_push_loop, inner_push_loop; + // Outer loop state: eax = current FrameDescription**, edx = one past the + // last FrameDescription**. + __ mov(edx, Operand(eax, Deoptimizer::output_count_offset())); + __ mov(eax, Operand(eax, Deoptimizer::output_offset())); + __ lea(edx, Operand(eax, edx, times_4, 0)); + __ bind(&outer_push_loop); + // Inner loop state: ebx = current FrameDescription*, ecx = loop index. + __ mov(ebx, Operand(eax, 0)); + __ mov(ecx, Operand(ebx, FrameDescription::frame_size_offset())); + __ bind(&inner_push_loop); + __ sub(Operand(ecx), Immediate(sizeof(uint32_t))); + __ push(Operand(ebx, ecx, times_1, FrameDescription::frame_content_offset())); + __ test(ecx, Operand(ecx)); + __ j(not_zero, &inner_push_loop); + __ add(Operand(eax), Immediate(kPointerSize)); + __ cmp(eax, Operand(edx)); + __ j(below, &outer_push_loop); + + // In case of OSR, we have to restore the XMM registers. + if (type() == OSR) { + for (int i = 0; i < XMMRegister::kNumAllocatableRegisters; ++i) { + XMMRegister xmm_reg = XMMRegister::FromAllocationIndex(i); + int src_offset = i * kDoubleSize + double_regs_offset; + __ movdbl(xmm_reg, Operand(ebx, src_offset)); + } + } + + // Push state, pc, and continuation from the last output frame. + if (type() != OSR) { + __ push(Operand(ebx, FrameDescription::state_offset())); + } + __ push(Operand(ebx, FrameDescription::pc_offset())); + __ push(Operand(ebx, FrameDescription::continuation_offset())); + + + // Push the registers from the last output frame. + for (int i = 0; i < kNumberOfRegisters; i++) { + int offset = (i * kIntSize) + FrameDescription::registers_offset(); + __ push(Operand(ebx, offset)); + } + + // Restore the registers from the stack. + __ popad(); + + // Return to the continuation point. + __ ret(0); +} + + +void Deoptimizer::TableEntryGenerator::GeneratePrologue() { + // Create a sequence of deoptimization entries. + Label done; + for (int i = 0; i < count(); i++) { + int start = masm()->pc_offset(); + USE(start); + __ push_imm32(i); + __ jmp(&done); + ASSERT(masm()->pc_offset() - start == table_entry_size_); + } + __ bind(&done); +} + +#undef __ + + +} } // namespace v8::internal diff --git a/src/ia32/disasm-ia32.cc b/src/ia32/disasm-ia32.cc index 52c2b384..dfbcbb76 100644 --- a/src/ia32/disasm-ia32.cc +++ b/src/ia32/disasm-ia32.cc @@ -733,7 +733,9 @@ int DisassemblerIA32::RegisterFPUInstruction(int escape_opcode, case 0xE4: mnem = "ftst"; break; case 0xE8: mnem = "fld1"; break; case 0xEB: mnem = "fldpi"; break; + case 0xED: mnem = "fldln2"; break; case 0xEE: mnem = "fldz"; break; + case 0xF1: mnem = "fyl2x"; break; case 0xF5: mnem = "fprem1"; break; case 0xF7: mnem = "fincstp"; break; case 0xF8: mnem = "fprem"; break; @@ -1105,6 +1107,21 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer, } else { UnimplementedInstruction(); } + } else if (*data == 0x3A) { + data++; + if (*data == 0x16) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + int8_t imm8 = static_cast<int8_t>(data[1]); + AppendToBuffer("pextrd %s,%s,%d", + NameOfXMMRegister(regop), + NameOfXMMRegister(rm), + static_cast<int>(imm8)); + data += 2; + } else { + UnimplementedInstruction(); + } } else if (*data == 0x2E || *data == 0x2F) { const char* mnem = (*data == 0x2E) ? "ucomisd" : "comisd"; data++; @@ -1127,6 +1144,14 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer, NameOfCPURegister(regop), NameOfXMMRegister(rm)); data++; + } else if (*data == 0x54) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + AppendToBuffer("andpd %s,%s", + NameOfXMMRegister(regop), + NameOfXMMRegister(rm)); + data++; } else if (*data == 0x57) { data++; int mod, regop, rm; @@ -1147,6 +1172,25 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer, get_modrm(*data, &mod, ®op, &rm); AppendToBuffer("movdqa %s,", NameOfXMMRegister(regop)); data += PrintRightOperand(data); + } else if (*data == 0x70) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + int8_t imm8 = static_cast<int8_t>(data[1]); + AppendToBuffer("pshufd %s,%s,%d", + NameOfXMMRegister(regop), + NameOfXMMRegister(rm), + static_cast<int>(imm8)); + data += 2; + } else if (*data == 0x73) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + int8_t imm8 = static_cast<int8_t>(data[1]); + AppendToBuffer("psllq %s,%d", + NameOfXMMRegister(rm), + static_cast<int>(imm8)); + data += 2; } else if (*data == 0x7F) { AppendToBuffer("movdqa "); data++; @@ -1154,6 +1198,21 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer, get_modrm(*data, &mod, ®op, &rm); data += PrintRightOperand(data); AppendToBuffer(",%s", NameOfXMMRegister(regop)); + } else if (*data == 0x7E) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + AppendToBuffer("movd "); + data += PrintRightOperand(data); + AppendToBuffer(",%s", NameOfXMMRegister(regop)); + } else if (*data == 0xDB) { + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + AppendToBuffer("pand %s,%s", + NameOfXMMRegister(regop), + NameOfXMMRegister(rm)); + data++; } else if (*data == 0xE7) { AppendToBuffer("movntdq "); data++; @@ -1162,30 +1221,13 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer, data += PrintRightOperand(data); AppendToBuffer(",%s", NameOfXMMRegister(regop)); } else if (*data == 0xEF) { - data++; - int mod, regop, rm; - get_modrm(*data, &mod, ®op, &rm); - AppendToBuffer("pxor %s,%s", - NameOfXMMRegister(regop), - NameOfXMMRegister(rm)); - data++; - } else if (*data == 0x73) { - data++; - int mod, regop, rm; - get_modrm(*data, &mod, ®op, &rm); - int8_t imm8 = static_cast<int8_t>(data[1]); - AppendToBuffer("psllq %s,%d", - NameOfXMMRegister(rm), - static_cast<int>(imm8)); - data += 2; - } else if (*data == 0x54) { - data++; - int mod, regop, rm; - get_modrm(*data, &mod, ®op, &rm); - AppendToBuffer("andpd %s,%s", - NameOfXMMRegister(regop), - NameOfXMMRegister(rm)); - data++; + data++; + int mod, regop, rm; + get_modrm(*data, &mod, ®op, &rm); + AppendToBuffer("pxor %s,%s", + NameOfXMMRegister(regop), + NameOfXMMRegister(rm)); + data++; } else { UnimplementedInstruction(); } diff --git a/src/ia32/frames-ia32.h b/src/ia32/frames-ia32.h index c3fe6c74..80846949 100644 --- a/src/ia32/frames-ia32.h +++ b/src/ia32/frames-ia32.h @@ -49,6 +49,10 @@ static const int kNumJSCallerSaved = 5; typedef Object* JSCallerSavedBuffer[kNumJSCallerSaved]; + +// Number of registers for which space is reserved in safepoints. +static const int kNumSafepointRegisters = 8; + // ---------------------------------------------------- @@ -90,6 +94,7 @@ class ExitFrameConstants : public AllStatic { class StandardFrameConstants : public AllStatic { public: + static const int kFixedFrameSize = 4; static const int kExpressionsOffset = -3 * kPointerSize; static const int kMarkerOffset = -2 * kPointerSize; static const int kContextOffset = -1 * kPointerSize; diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 8d6942d5..13a11777 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -41,8 +41,61 @@ namespace v8 { namespace internal { + #define __ ACCESS_MASM(masm_) + +class JumpPatchSite BASE_EMBEDDED { + public: + explicit JumpPatchSite(MacroAssembler* masm) + : masm_(masm) { +#ifdef DEBUG + info_emitted_ = false; +#endif + } + + ~JumpPatchSite() { + ASSERT(patch_site_.is_bound() == info_emitted_); + } + + void EmitJumpIfNotSmi(Register reg, NearLabel* target) { + __ test(reg, Immediate(kSmiTagMask)); + EmitJump(not_carry, target); // Always taken before patched. + } + + void EmitJumpIfSmi(Register reg, NearLabel* target) { + __ test(reg, Immediate(kSmiTagMask)); + EmitJump(carry, target); // Never taken before patched. + } + + void EmitPatchInfo() { + int delta_to_patch_site = masm_->SizeOfCodeGeneratedSince(&patch_site_); + ASSERT(is_int8(delta_to_patch_site)); + __ test(eax, Immediate(delta_to_patch_site)); +#ifdef DEBUG + info_emitted_ = true; +#endif + } + + bool is_bound() const { return patch_site_.is_bound(); } + + private: + // jc will be patched with jz, jnc will become jnz. + void EmitJump(Condition cc, NearLabel* target) { + ASSERT(!patch_site_.is_bound() && !info_emitted_); + ASSERT(cc == carry || cc == not_carry); + __ bind(&patch_site_); + __ j(cc, target); + } + + MacroAssembler* masm_; + Label patch_site_; +#ifdef DEBUG + bool info_emitted_; +#endif +}; + + // Generate code for a JS function. On entry to the function the receiver // and arguments have been pushed on the stack left to right, with the // return address on top of them. The actual argument count matches the @@ -168,7 +221,12 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { } } + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + { Comment cmnt(masm_, "[ Stack check"); + PrepareForBailout(info->function(), NO_REGISTERS); NearLabel ok; ExternalReference stack_limit = ExternalReference::address_of_stack_limit(); @@ -179,10 +237,6 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { __ bind(&ok); } - if (FLAG_trace) { - __ CallRuntime(Runtime::kTraceEnter, 0); - } - { Comment cmnt(masm_, "[ Body"); ASSERT(loop_depth() == 0); VisitStatements(function()->body()); @@ -202,6 +256,27 @@ void FullCodeGenerator::ClearAccumulator() { } +void FullCodeGenerator::EmitStackCheck(IterationStatement* stmt) { + Comment cmnt(masm_, "[ Stack check"); + NearLabel ok; + ExternalReference stack_limit = ExternalReference::address_of_stack_limit(); + __ cmp(esp, Operand::StaticVariable(stack_limit)); + __ j(above_equal, &ok, taken); + StackCheckStub stub; + __ CallStub(&stub); + __ bind(&ok); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + PrepareForBailoutForId(stmt->OsrEntryId(), NO_REGISTERS); + RecordStackCheck(stmt->OsrEntryId()); + // Loop stack checks can be patched to perform on-stack + // replacement. In order to decide whether or not to perform OSR we + // embed the loop depth in a test instruction after the call so we + // can extract it from the OSR builtin. + ASSERT(loop_depth() > 0); + __ test(eax, Immediate(Min(loop_depth(), Code::kMaxLoopNestingMarker))); +} + + void FullCodeGenerator::EmitReturnSequence() { Comment cmnt(masm_, "[ Return sequence"); if (return_label_.is_bound()) { @@ -218,7 +293,7 @@ void FullCodeGenerator::EmitReturnSequence() { Label check_exit_codesize; masm_->bind(&check_exit_codesize); #endif - CodeGenerator::RecordPositions(masm_, function()->end_position() - 1); + SetSourcePosition(function()->end_position() - 1); __ RecordJSReturn(); // Do not use the leave instruction here because it is too short to // patch with the code required by the debugger. @@ -271,6 +346,7 @@ void FullCodeGenerator::StackValueContext::Plug(Slot* slot) const { void FullCodeGenerator::TestContext::Plug(Slot* slot) const { // For simplicity we always test the accumulator register. codegen()->Move(result_register(), slot); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } @@ -314,22 +390,26 @@ void FullCodeGenerator::StackValueContext::Plug(Handle<Object> lit) const { void FullCodeGenerator::TestContext::Plug(Handle<Object> lit) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); ASSERT(!lit->IsUndetectableObject()); // There are no undetectable literals. if (lit->IsUndefined() || lit->IsNull() || lit->IsFalse()) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else if (lit->IsTrue() || lit->IsJSObject()) { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } else if (lit->IsString()) { if (String::cast(*lit)->length() == 0) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } } else if (lit->IsSmi()) { if (Smi::cast(*lit)->value() == 0) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } } else { // For simplicity we always test the accumulator register. @@ -369,13 +449,14 @@ void FullCodeGenerator::TestContext::DropAndPlug(int count, // For simplicity we always test the accumulator register. __ Drop(count); __ Move(result_register(), reg); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } void FullCodeGenerator::EffectContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT_EQ(materialize_true, materialize_false); + ASSERT(materialize_true == materialize_false); __ bind(materialize_true); } @@ -408,8 +489,8 @@ void FullCodeGenerator::StackValueContext::Plug( void FullCodeGenerator::TestContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT(materialize_false == false_label_); ASSERT(materialize_true == true_label_); + ASSERT(materialize_false == false_label_); } @@ -432,6 +513,10 @@ void FullCodeGenerator::StackValueContext::Plug(bool flag) const { void FullCodeGenerator::TestContext::Plug(bool flag) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); if (flag) { if (true_label_ != fall_through_) __ jmp(true_label_); } else { @@ -523,6 +608,32 @@ void FullCodeGenerator::Move(Slot* dst, } +void FullCodeGenerator::PrepareForBailoutBeforeSplit(State state, + bool should_normalize, + Label* if_true, + Label* if_false) { + // Only prepare for bailouts before splits if we're in a test + // context. Otherwise, we let the Visit function deal with the + // preparation to avoid preparing with the same AST id twice. + if (!context()->IsTest() || !info_->IsOptimizable()) return; + + NearLabel skip; + if (should_normalize) __ jmp(&skip); + + ForwardBailoutStack* current = forward_bailout_stack_; + while (current != NULL) { + PrepareForBailout(current->expr(), state); + current = current->parent(); + } + + if (should_normalize) { + __ cmp(eax, Factory::true_value()); + Split(equal, if_true, if_false, NULL); + __ bind(&skip); + } +} + + void FullCodeGenerator::EmitDeclaration(Variable* variable, Variable::Mode mode, FunctionLiteral* function) { @@ -633,8 +744,10 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { Comment cmnt(masm_, "[ SwitchStatement"); Breakable nested_statement(this, stmt); SetStatementPosition(stmt); + // Keep the switch value on the stack until a case matches. VisitForStackValue(stmt->tag()); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); ZoneList<CaseClause*>* clauses = stmt->cases(); CaseClause* default_clause = NULL; // Can occur anywhere in the list. @@ -659,12 +772,13 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { // Perform the comparison as if via '==='. __ mov(edx, Operand(esp, 0)); // Switch value. bool inline_smi_code = ShouldInlineSmiCase(Token::EQ_STRICT); + JumpPatchSite patch_site(masm_); if (inline_smi_code) { NearLabel slow_case; __ mov(ecx, edx); __ or_(ecx, Operand(eax)); - __ test(ecx, Immediate(kSmiTagMask)); - __ j(not_zero, &slow_case, not_taken); + patch_site.EmitJumpIfNotSmi(ecx, &slow_case); + __ cmp(edx, Operand(eax)); __ j(not_equal, &next_test); __ Drop(1); // Switch value is no longer needed. @@ -672,11 +786,11 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { __ bind(&slow_case); } - CompareFlags flags = inline_smi_code - ? NO_SMI_COMPARE_IN_STUB - : NO_COMPARE_FLAGS; - CompareStub stub(equal, true, flags); - __ CallStub(&stub); + // Record position before stub call for type feedback. + SetSourcePosition(clause->position()); + Handle<Code> ic = CompareIC::GetUninitialized(Token::EQ_STRICT); + EmitCallIC(ic, &patch_site); + __ test(eax, Operand(eax)); __ j(not_equal, &next_test); __ Drop(1); // Switch value is no longer needed. @@ -702,6 +816,7 @@ void FullCodeGenerator::VisitSwitchStatement(SwitchStatement* stmt) { } __ bind(nested_statement.break_target()); + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); } @@ -853,27 +968,20 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { __ bind(&update_each); __ mov(result_register(), ebx); // Perform the assignment as if via '='. - EmitAssignment(stmt->each()); + { EffectContext context(this); + EmitAssignment(stmt->each(), stmt->AssignmentId()); + } // Generate code for the body of the loop. - Label stack_limit_hit; - NearLabel stack_check_done; Visit(stmt->body()); - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_done); - // Generate code for going to the next element by incrementing the // index (smi) stored on top of the stack. __ bind(loop_statement.continue_target()); __ add(Operand(esp, 0 * kPointerSize), Immediate(Smi::FromInt(1))); - __ jmp(&loop); - // Slow case for the stack limit check. - StackCheckStub stack_check_stub; - __ bind(&stack_limit_hit); - __ CallStub(&stack_check_stub); - __ jmp(&stack_check_done); + EmitStackCheck(stmt); + __ jmp(&loop); // Remove the pointers stored on the stack. __ bind(loop_statement.break_target()); @@ -888,8 +996,14 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { void FullCodeGenerator::EmitNewClosure(Handle<SharedFunctionInfo> info, bool pretenure) { // Use the fast case closure allocation code that allocates in new - // space for nested functions that don't need literals cloning. - if (scope()->is_function_scope() && + // space for nested functions that don't need literals cloning. If + // we're running with the --always-opt or the --prepare-always-opt + // flag, we need to use the runtime function so that the new function + // we are creating here gets a chance to have its code optimized and + // doesn't just get a copy of the existing unoptimized code. + if (!FLAG_always_opt && + !FLAG_prepare_always_opt && + scope()->is_function_scope() && info->num_literals() == 0 && !pretenure) { FastNewClosureStub stub; @@ -1235,12 +1349,15 @@ void FullCodeGenerator::VisitObjectLiteral(ObjectLiteral* expr) { // Fall through. case ObjectLiteral::Property::COMPUTED: if (key->handle()->IsSymbol()) { - VisitForAccumulatorValue(value); - __ mov(ecx, Immediate(key->handle())); - __ mov(edx, Operand(esp, 0)); if (property->emit_store()) { + VisitForAccumulatorValue(value); + __ mov(ecx, Immediate(key->handle())); + __ mov(edx, Operand(esp, 0)); Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(key->id(), NO_REGISTERS); + } else { + VisitForEffect(value); } break; } @@ -1288,6 +1405,7 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { __ push(Immediate(Smi::FromInt(expr->literal_index()))); __ push(Immediate(expr->constant_elements())); if (expr->constant_elements()->map() == Heap::fixed_cow_array_map()) { + ASSERT(expr->depth() == 1); FastCloneShallowArrayStub stub( FastCloneShallowArrayStub::COPY_ON_WRITE_ELEMENTS, length); __ CallStub(&stub); @@ -1329,6 +1447,8 @@ void FullCodeGenerator::VisitArrayLiteral(ArrayLiteral* expr) { // Update the write barrier for the array store. __ RecordWrite(ebx, offset, result_register(), ecx); + + PrepareForBailoutForId(expr->GetIdForElement(i), NO_REGISTERS); } if (result_saved) { @@ -1373,17 +1493,30 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { VisitForStackValue(property->obj()); } break; - case KEYED_PROPERTY: + case KEYED_PROPERTY: { if (expr->is_compound()) { - VisitForStackValue(property->obj()); - VisitForAccumulatorValue(property->key()); + if (property->is_arguments_access()) { + VariableProxy* obj_proxy = property->obj()->AsVariableProxy(); + __ push(EmitSlotSearch(obj_proxy->var()->AsSlot(), ecx)); + __ mov(eax, Immediate(property->key()->AsLiteral()->handle())); + } else { + VisitForStackValue(property->obj()); + VisitForAccumulatorValue(property->key()); + } __ mov(edx, Operand(esp, 0)); __ push(eax); } else { - VisitForStackValue(property->obj()); - VisitForStackValue(property->key()); + if (property->is_arguments_access()) { + VariableProxy* obj_proxy = property->obj()->AsVariableProxy(); + __ push(EmitSlotSearch(obj_proxy->var()->AsSlot(), ecx)); + __ push(Immediate(property->key()->AsLiteral()->handle())); + } else { + VisitForStackValue(property->obj()); + VisitForStackValue(property->key()); + } } break; + } } if (expr->is_compound()) { @@ -1401,6 +1534,12 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { } } + // For property compound assignments we need another deoptimization + // point after the property load. + if (property != NULL) { + PrepareForBailoutForId(expr->CompoundLoadId(), TOS_REG); + } + Token::Value op = expr->binary_op(); ConstantOperand constant = ShouldInlineSmiCase(op) ? GetConstantOperand(op, expr->target(), expr->value()) @@ -1426,6 +1565,9 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { } else { EmitBinaryOp(op, mode); } + + // Deoptimization point in case the binary operation may have side effects. + PrepareForBailout(expr->binary_operation(), TOS_REG); } else { VisitForAccumulatorValue(expr->value()); } @@ -1438,6 +1580,8 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { case VARIABLE: EmitVariableAssignment(expr->target()->AsVariableProxy()->var(), expr->op()); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(eax); break; case NAMED_PROPERTY: EmitNamedPropertyAssignment(expr); @@ -1469,26 +1613,25 @@ void FullCodeGenerator::EmitConstantSmiAdd(Expression* expr, OverwriteMode mode, bool left_is_constant_smi, Smi* value) { - NearLabel call_stub; - Label done; + NearLabel call_stub, done; __ add(Operand(eax), Immediate(value)); __ j(overflow, &call_stub); - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &done); + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(eax, &done); // Undo the optimistic add operation and call the shared stub. __ bind(&call_stub); __ sub(Operand(eax), Immediate(value)); Token::Value op = Token::ADD; - GenericBinaryOpStub stub(op, mode, NO_SMI_CODE_IN_STUB, TypeInfo::Unknown()); + TypeRecordingBinaryOpStub stub(op, mode); if (left_is_constant_smi) { - __ push(Immediate(value)); - __ push(eax); + __ mov(edx, Immediate(value)); } else { - __ push(eax); - __ push(Immediate(value)); + __ mov(edx, eax); + __ mov(eax, Immediate(value)); } - __ CallStub(&stub); + EmitCallIC(stub.GetCode(), &patch_site); + __ bind(&done); context()->Plug(eax); } @@ -1498,7 +1641,7 @@ void FullCodeGenerator::EmitConstantSmiSub(Expression* expr, OverwriteMode mode, bool left_is_constant_smi, Smi* value) { - Label call_stub, done; + NearLabel call_stub, done; if (left_is_constant_smi) { __ mov(ecx, eax); __ mov(eax, Immediate(value)); @@ -1507,24 +1650,22 @@ void FullCodeGenerator::EmitConstantSmiSub(Expression* expr, __ sub(Operand(eax), Immediate(value)); } __ j(overflow, &call_stub); - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &done); + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(eax, &done); __ bind(&call_stub); - if (left_is_constant_smi) { - __ push(Immediate(value)); - __ push(ecx); + if (left_is_constant_smi) { + __ mov(edx, Immediate(value)); + __ mov(eax, ecx); } else { - // Undo the optimistic sub operation. - __ add(Operand(eax), Immediate(value)); - - __ push(eax); - __ push(Immediate(value)); + __ add(Operand(eax), Immediate(value)); // Undo the subtraction. + __ mov(edx, eax); + __ mov(eax, Immediate(value)); } - Token::Value op = Token::SUB; - GenericBinaryOpStub stub(op, mode, NO_SMI_CODE_IN_STUB, TypeInfo::Unknown()); - __ CallStub(&stub); + TypeRecordingBinaryOpStub stub(op, mode); + EmitCallIC(stub.GetCode(), &patch_site); + __ bind(&done); context()->Plug(eax); } @@ -1534,19 +1675,21 @@ void FullCodeGenerator::EmitConstantSmiShiftOp(Expression* expr, Token::Value op, OverwriteMode mode, Smi* value) { - Label call_stub, smi_case, done; + NearLabel call_stub, smi_case, done; int shift_value = value->value() & 0x1f; - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &smi_case); + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(eax, &smi_case); + // Call stub. __ bind(&call_stub); - GenericBinaryOpStub stub(op, mode, NO_SMI_CODE_IN_STUB, TypeInfo::Unknown()); - __ push(eax); - __ push(Immediate(value)); - __ CallStub(&stub); + __ mov(edx, eax); + __ mov(eax, Immediate(value)); + TypeRecordingBinaryOpStub stub(op, mode); + EmitCallIC(stub.GetCode(), &patch_site); __ jmp(&done); + // Smi case. __ bind(&smi_case); switch (op) { case Token::SHL: @@ -1596,18 +1739,19 @@ void FullCodeGenerator::EmitConstantSmiBitOp(Expression* expr, Token::Value op, OverwriteMode mode, Smi* value) { - Label smi_case, done; - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &smi_case); + NearLabel smi_case, done; + + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(eax, &smi_case); - GenericBinaryOpStub stub(op, mode, NO_SMI_CODE_IN_STUB, TypeInfo::Unknown()); // The order of the arguments does not matter for bit-ops with a // constant operand. - __ push(Immediate(value)); - __ push(eax); - __ CallStub(&stub); + __ mov(edx, Immediate(value)); + TypeRecordingBinaryOpStub stub(op, mode); + EmitCallIC(stub.GetCode(), &patch_site); __ jmp(&done); + // Smi case. __ bind(&smi_case); switch (op) { case Token::BIT_OR: @@ -1675,24 +1819,20 @@ void FullCodeGenerator::EmitInlineSmiBinaryOp(Expression* expr, // Do combined smi check of the operands. Left operand is on the // stack. Right operand is in eax. - Label done, stub_call, smi_case; + NearLabel done, smi_case, stub_call; __ pop(edx); __ mov(ecx, eax); __ or_(eax, Operand(edx)); - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &smi_case); + JumpPatchSite patch_site(masm_); + patch_site.EmitJumpIfSmi(eax, &smi_case); __ bind(&stub_call); - GenericBinaryOpStub stub(op, mode, NO_SMI_CODE_IN_STUB, TypeInfo::Unknown()); - if (stub.ArgsInRegistersSupported()) { - stub.GenerateCall(masm_, edx, ecx); - } else { - __ push(edx); - __ push(ecx); - __ CallStub(&stub); - } + __ mov(eax, ecx); + TypeRecordingBinaryOpStub stub(op, mode); + EmitCallIC(stub.GetCode(), &patch_site); __ jmp(&done); + // Smi case. __ bind(&smi_case); __ mov(eax, edx); // Copy left operand in case of a stub call. @@ -1769,20 +1909,14 @@ void FullCodeGenerator::EmitInlineSmiBinaryOp(Expression* expr, void FullCodeGenerator::EmitBinaryOp(Token::Value op, OverwriteMode mode) { - TypeInfo type = TypeInfo::Unknown(); - GenericBinaryOpStub stub(op, mode, NO_GENERIC_BINARY_FLAGS, type); - if (stub.ArgsInRegistersSupported()) { - __ pop(edx); - stub.GenerateCall(masm_, edx, eax); - } else { - __ push(result_register()); - __ CallStub(&stub); - } + __ pop(edx); + TypeRecordingBinaryOpStub stub(op, mode); + EmitCallIC(stub.GetCode(), NULL); // NULL signals no inlined smi code. context()->Plug(eax); } -void FullCodeGenerator::EmitAssignment(Expression* expr) { +void FullCodeGenerator::EmitAssignment(Expression* expr, int bailout_ast_id) { // Invalid left-hand sides are rewritten to have a 'throw // ReferenceError' on the left-hand side. if (!expr->IsValidLeftHandSide()) { @@ -1830,6 +1964,8 @@ void FullCodeGenerator::EmitAssignment(Expression* expr) { break; } } + PrepareForBailoutForId(bailout_ast_id, TOS_REG); + context()->Plug(eax); } @@ -1902,8 +2038,6 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&done); } - - context()->Plug(eax); } @@ -1940,10 +2074,10 @@ void FullCodeGenerator::EmitNamedPropertyAssignment(Assignment* expr) { __ push(Operand(esp, kPointerSize)); // Receiver is under value. __ CallRuntime(Runtime::kToFastProperties, 1); __ pop(eax); - context()->DropAndPlug(1, eax); - } else { - context()->Plug(eax); + __ Drop(1); } + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(eax); } @@ -1981,6 +2115,7 @@ void FullCodeGenerator::EmitKeyedPropertyAssignment(Assignment* expr) { __ pop(eax); } + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); context()->Plug(eax); } @@ -1992,13 +2127,14 @@ void FullCodeGenerator::VisitProperty(Property* expr) { if (key->IsPropertyName()) { VisitForAccumulatorValue(expr->obj()); EmitNamedPropertyLoad(expr); + context()->Plug(eax); } else { VisitForStackValue(expr->obj()); VisitForAccumulatorValue(expr->key()); __ pop(edx); EmitKeyedPropertyLoad(expr); + context()->Plug(eax); } - context()->Plug(eax); } @@ -2008,17 +2144,18 @@ void FullCodeGenerator::EmitCallWithIC(Call* expr, // Code common for calls using the IC. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } __ Set(ecx, Immediate(name)); } // Record source position of the IC call. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeCallInitialize(arg_count, in_loop); EmitCallIC(ic, mode); + RecordJSReturnSite(expr); // Restore context register. __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); context()->Plug(eax); @@ -2040,17 +2177,18 @@ void FullCodeGenerator::EmitKeyedCallWithIC(Call* expr, // Load the arguments. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position of the IC call. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeKeyedCallInitialize(arg_count, in_loop); __ mov(ecx, Operand(esp, (arg_count + 1) * kPointerSize)); // Key. EmitCallIC(ic, mode); + RecordJSReturnSite(expr); // Restore context register. __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, eax); // Drop the key still on the stack. @@ -2061,16 +2199,17 @@ void FullCodeGenerator::EmitCallWithStub(Call* expr) { // Code common for calls using the call stub. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); + RecordJSReturnSite(expr); // Restore context register. __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, eax); @@ -2078,6 +2217,12 @@ void FullCodeGenerator::EmitCallWithStub(Call* expr) { void FullCodeGenerator::VisitCall(Call* expr) { +#ifdef DEBUG + // We want to verify that RecordJSReturnSite gets called on all paths + // through this function. Avoid early returns. + expr->return_is_recorded_ = false; +#endif + Comment cmnt(masm_, "[ Call"); Expression* fun = expr->expression(); Variable* var = fun->AsVariableProxy()->AsVariable(); @@ -2089,7 +2234,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { // arguments. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope pos_scope(masm()->positions_recorder()); + { PreservePositionScope pos_scope(masm()->positions_recorder()); VisitForStackValue(fun); // Reserved receiver slot. __ push(Immediate(Factory::undefined_value())); @@ -2119,10 +2264,11 @@ void FullCodeGenerator::VisitCall(Call* expr) { __ mov(Operand(esp, (arg_count + 1) * kPointerSize), eax); } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); + RecordJSReturnSite(expr); // Restore context register. __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); context()->DropAndPlug(1, eax); @@ -2135,7 +2281,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a lookup slot (dynamically introduced variable). Label slow, done; - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); // Generate code for loading from variables potentially shadowed // by eval-introduced variables. EmitDynamicLoadFromSlotFastCase(var->AsSlot(), @@ -2181,15 +2327,15 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a keyed property. // For a synthetic property use keyed load IC followed by function call, // for a regular property use keyed EmitCallIC. - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(prop->obj()); } if (prop->is_synthetic()) { - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForAccumulatorValue(prop->key()); } // Record source code position for IC call. - SetSourcePosition(prop->position(), FORCED_POSITION); + SetSourcePosition(prop->position()); __ pop(edx); // We do not need to keep the receiver. Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); @@ -2214,7 +2360,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { loop_depth() == 0) { lit->set_try_full_codegen(true); } - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(fun); } // Load global receiver object. @@ -2223,6 +2369,11 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Emit function call. EmitCallWithStub(expr); } + +#ifdef DEBUG + // RecordJSReturnSite should have been called. + ASSERT(expr->return_is_recorded_); +#endif } @@ -2270,6 +2421,7 @@ void FullCodeGenerator::EmitIsSmi(ZoneList<Expression*>* args) { context()->PrepareTest(&materialize_true, &materialize_false, &if_true, &if_false, &fall_through); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ test(eax, Immediate(kSmiTagMask)); Split(zero, if_true, if_false, fall_through); @@ -2289,6 +2441,7 @@ void FullCodeGenerator::EmitIsNonNegativeSmi(ZoneList<Expression*>* args) { context()->PrepareTest(&materialize_true, &materialize_false, &if_true, &if_false, &fall_through); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ test(eax, Immediate(kSmiTagMask | 0x80000000)); Split(zero, if_true, if_false, fall_through); @@ -2321,6 +2474,7 @@ void FullCodeGenerator::EmitIsObject(ZoneList<Expression*>* args) { __ cmp(ecx, FIRST_JS_OBJECT_TYPE); __ j(below, if_false); __ cmp(ecx, LAST_JS_OBJECT_TYPE); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(below_equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2342,6 +2496,7 @@ void FullCodeGenerator::EmitIsSpecObject(ZoneList<Expression*>* args) { __ test(eax, Immediate(kSmiTagMask)); __ j(equal, if_false); __ CmpObjectType(eax, FIRST_JS_OBJECT_TYPE, ebx); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(above_equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2365,6 +2520,7 @@ void FullCodeGenerator::EmitIsUndetectableObject(ZoneList<Expression*>* args) { __ mov(ebx, FieldOperand(eax, HeapObject::kMapOffset)); __ movzx_b(ebx, FieldOperand(ebx, Map::kBitFieldOffset)); __ test(ebx, Immediate(1 << Map::kIsUndetectable)); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(not_zero, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2384,9 +2540,9 @@ void FullCodeGenerator::EmitIsStringWrapperSafeForDefaultValueOf( context()->PrepareTest(&materialize_true, &materialize_false, &if_true, &if_false, &fall_through); - // Just indicate false, as %_IsStringWrapperSafeForDefaultValueOf() is only - // used in a few functions in runtime.js which should not normally be hit by - // this compiler. + // TODO(3110205): Implement this. + // Currently unimplemented. Emit false, a safe choice. + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ jmp(if_false); context()->Plug(if_true, if_false); } @@ -2407,6 +2563,7 @@ void FullCodeGenerator::EmitIsFunction(ZoneList<Expression*>* args) { __ test(eax, Immediate(kSmiTagMask)); __ j(zero, if_false); __ CmpObjectType(eax, JS_FUNCTION_TYPE, ebx); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2428,6 +2585,7 @@ void FullCodeGenerator::EmitIsArray(ZoneList<Expression*>* args) { __ test(eax, Immediate(kSmiTagMask)); __ j(equal, if_false); __ CmpObjectType(eax, JS_ARRAY_TYPE, ebx); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2449,6 +2607,7 @@ void FullCodeGenerator::EmitIsRegExp(ZoneList<Expression*>* args) { __ test(eax, Immediate(kSmiTagMask)); __ j(equal, if_false); __ CmpObjectType(eax, JS_REGEXP_TYPE, ebx); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2480,6 +2639,7 @@ void FullCodeGenerator::EmitIsConstructCall(ZoneList<Expression*>* args) { __ bind(&check_frame_marker); __ cmp(Operand(eax, StandardFrameConstants::kMarkerOffset), Immediate(Smi::FromInt(StackFrame::CONSTRUCT))); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2502,6 +2662,7 @@ void FullCodeGenerator::EmitObjectEquals(ZoneList<Expression*>* args) { __ pop(ebx); __ cmp(eax, Operand(ebx)); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(equal, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -2719,7 +2880,9 @@ void FullCodeGenerator::EmitMathPow(ZoneList<Expression*>* args) { ASSERT(args->length() == 2); VisitForStackValue(args->at(0)); VisitForStackValue(args->at(1)); - __ CallRuntime(Runtime::kMath_pow, 2); + + MathPowStub stub; + __ CallStub(&stub); context()->Plug(eax); } @@ -2904,7 +3067,8 @@ void FullCodeGenerator::EmitStringCompare(ZoneList<Expression*>* args) { void FullCodeGenerator::EmitMathSin(ZoneList<Expression*>* args) { // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::SIN); + TranscendentalCacheStub stub(TranscendentalCache::SIN, + TranscendentalCacheStub::TAGGED); ASSERT(args->length() == 1); VisitForStackValue(args->at(0)); __ CallStub(&stub); @@ -2914,7 +3078,19 @@ void FullCodeGenerator::EmitMathSin(ZoneList<Expression*>* args) { void FullCodeGenerator::EmitMathCos(ZoneList<Expression*>* args) { // Load the argument on the stack and call the stub. - TranscendentalCacheStub stub(TranscendentalCache::COS); + TranscendentalCacheStub stub(TranscendentalCache::COS, + TranscendentalCacheStub::TAGGED); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallStub(&stub); + context()->Plug(eax); +} + + +void FullCodeGenerator::EmitMathLog(ZoneList<Expression*>* args) { + // Load the argument on the stack and call the stub. + TranscendentalCacheStub stub(TranscendentalCache::LOG, + TranscendentalCacheStub::TAGGED); ASSERT(args->length() == 1); VisitForStackValue(args->at(0)); __ CallStub(&stub); @@ -2951,11 +3127,13 @@ void FullCodeGenerator::EmitCallFunction(ZoneList<Expression*>* args) { void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) { + // Load the arguments on the stack and call the stub. + RegExpConstructResultStub stub; ASSERT(args->length() == 3); VisitForStackValue(args->at(0)); VisitForStackValue(args->at(1)); VisitForStackValue(args->at(2)); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); + __ CallStub(&stub); context()->Plug(eax); } @@ -2965,7 +3143,71 @@ void FullCodeGenerator::EmitSwapElements(ZoneList<Expression*>* args) { VisitForStackValue(args->at(0)); VisitForStackValue(args->at(1)); VisitForStackValue(args->at(2)); + Label done; + Label slow_case; + Register object = eax; + Register index_1 = ebx; + Register index_2 = ecx; + Register elements = edi; + Register temp = edx; + __ mov(object, Operand(esp, 2 * kPointerSize)); + // Fetch the map and check if array is in fast case. + // Check that object doesn't require security checks and + // has no indexed interceptor. + __ CmpObjectType(object, FIRST_JS_OBJECT_TYPE, temp); + __ j(below, &slow_case); + __ test_b(FieldOperand(temp, Map::kBitFieldOffset), + KeyedLoadIC::kSlowCaseBitFieldMask); + __ j(not_zero, &slow_case); + + // Check the object's elements are in fast case and writable. + __ mov(elements, FieldOperand(object, JSObject::kElementsOffset)); + __ cmp(FieldOperand(elements, HeapObject::kMapOffset), + Immediate(Factory::fixed_array_map())); + __ j(not_equal, &slow_case); + + // Check that both indices are smis. + __ mov(index_1, Operand(esp, 1 * kPointerSize)); + __ mov(index_2, Operand(esp, 0)); + __ mov(temp, index_1); + __ or_(temp, Operand(index_2)); + __ test(temp, Immediate(kSmiTagMask)); + __ j(not_zero, &slow_case); + + // Check that both indices are valid. + __ mov(temp, FieldOperand(object, JSArray::kLengthOffset)); + __ cmp(temp, Operand(index_1)); + __ j(below_equal, &slow_case); + __ cmp(temp, Operand(index_2)); + __ j(below_equal, &slow_case); + + // Bring addresses into index1 and index2. + __ lea(index_1, CodeGenerator::FixedArrayElementOperand(elements, index_1)); + __ lea(index_2, CodeGenerator::FixedArrayElementOperand(elements, index_2)); + + // Swap elements. Use object and temp as scratch registers. + __ mov(object, Operand(index_1, 0)); + __ mov(temp, Operand(index_2, 0)); + __ mov(Operand(index_2, 0), object); + __ mov(Operand(index_1, 0), temp); + + Label new_space; + __ InNewSpace(elements, temp, equal, &new_space); + + __ mov(object, elements); + __ RecordWriteHelper(object, index_1, temp); + __ RecordWriteHelper(elements, index_2, temp); + + __ bind(&new_space); + // We are done. Drop elements from the stack, and return undefined. + __ add(Operand(esp), Immediate(3 * kPointerSize)); + __ mov(eax, Factory::undefined_value()); + __ jmp(&done); + + __ bind(&slow_case); __ CallRuntime(Runtime::kSwapElements, 3); + + __ bind(&done); context()->Plug(eax); } @@ -3073,6 +3315,7 @@ void FullCodeGenerator::EmitHasCachedArrayIndex(ZoneList<Expression*>* args) { __ test(FieldOperand(eax, String::kHashFieldOffset), Immediate(String::kContainsCachedArrayIndexMask)); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); Split(zero, if_true, if_false, fall_through); context()->Plug(if_true, if_false); @@ -3377,6 +3620,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { // Notice that the labels are swapped. context()->PrepareTest(&materialize_true, &materialize_false, &if_false, &if_true, &fall_through); + if (context()->IsTest()) ForwardBailoutToChild(expr); VisitForControl(expr->expression(), if_true, if_false, fall_through); context()->Plug(if_false, if_true); // Labels swapped. break; @@ -3493,14 +3737,24 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ push(eax); EmitNamedPropertyLoad(prop); } else { - VisitForStackValue(prop->obj()); - VisitForAccumulatorValue(prop->key()); + if (prop->is_arguments_access()) { + VariableProxy* obj_proxy = prop->obj()->AsVariableProxy(); + __ push(EmitSlotSearch(obj_proxy->var()->AsSlot(), ecx)); + __ mov(eax, Immediate(prop->key()->AsLiteral()->handle())); + } else { + VisitForStackValue(prop->obj()); + VisitForAccumulatorValue(prop->key()); + } __ mov(edx, Operand(esp, 0)); __ push(eax); EmitKeyedPropertyLoad(prop); } } + // We need a second deoptimization point after loading the value + // in case evaluating the property load my have a side effect. + PrepareForBailout(expr->increment(), TOS_REG); + // Call ToNumber only if operand is not a smi. NearLabel no_conversion; if (ShouldInlineSmiCase(expr->op())) { @@ -3532,8 +3786,9 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { } // Inline smi case if we are in a loop. - NearLabel stub_call; - Label done; + NearLabel stub_call, done; + JumpPatchSite patch_site(masm_); + if (ShouldInlineSmiCase(expr->op())) { if (expr->op() == Token::INC) { __ add(Operand(eax), Immediate(Smi::FromInt(1))); @@ -3543,8 +3798,8 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ j(overflow, &stub_call); // We could eliminate this smi check if we split the code at // the first smi check before calling ToNumber. - __ test(eax, Immediate(kSmiTagMask)); - __ j(zero, &done); + patch_site.EmitJumpIfSmi(eax, &done); + __ bind(&stub_call); // Call stub. Undo operation first. if (expr->op() == Token::INC) { @@ -3553,12 +3808,16 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ add(Operand(eax), Immediate(Smi::FromInt(1))); } } + + // Record position before stub call. + SetSourcePosition(expr->position()); + // Call stub for +1/-1. - GenericBinaryOpStub stub(expr->binary_op(), - NO_OVERWRITE, - NO_GENERIC_BINARY_FLAGS, - TypeInfo::Unknown()); - stub.GenerateCall(masm(), eax, Smi::FromInt(1)); + __ mov(edx, eax); + __ mov(eax, Immediate(Smi::FromInt(1))); + TypeRecordingBinaryOpStub stub(expr->binary_op(), + NO_OVERWRITE); + EmitCallIC(stub.GetCode(), &patch_site); __ bind(&done); // Store the value returned in eax. @@ -3569,6 +3828,8 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { { EffectContext context(this); EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context.Plug(eax); } // For all contexts except EffectContext We have the result on // top of the stack. @@ -3579,6 +3840,8 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { // Perform the assignment as if via '='. EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); + context()->Plug(eax); } break; case NAMED_PROPERTY: { @@ -3586,6 +3849,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ pop(edx); Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); if (expr->is_postfix()) { if (!context()->IsEffect()) { context()->PlugTOS(); @@ -3600,6 +3864,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ pop(edx); Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailoutForId(expr->AssignmentId(), TOS_REG); if (expr->is_postfix()) { // Result is on the stack if (!context()->IsEffect()) { @@ -3627,6 +3892,7 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) { // Use a regular load, not a contextual load, to avoid a reference // error. EmitCallIC(ic, RelocInfo::CODE_TARGET); + PrepareForBailout(expr, TOS_REG); context()->Plug(eax); } else if (proxy != NULL && proxy->var()->AsSlot() != NULL && @@ -3642,12 +3908,13 @@ void FullCodeGenerator::VisitForTypeofValue(Expression* expr) { __ push(esi); __ push(Immediate(proxy->name())); __ CallRuntime(Runtime::kLoadContextSlotNoReferenceError, 2); + PrepareForBailout(expr, TOS_REG); __ bind(&done); context()->Plug(eax); } else { // This expression cannot throw a reference error at the top level. - Visit(expr); + context()->HandleExpression(expr); } } @@ -3672,6 +3939,7 @@ bool FullCodeGenerator::TryLiteralCompare(Token::Value op, { AccumulatorValueContext context(this); VisitForTypeofValue(left_unary->expression()); } + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); if (check->Equals(Heap::number_symbol())) { __ test(eax, Immediate(kSmiTagMask)); @@ -3767,14 +4035,17 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { case Token::IN: VisitForStackValue(expr->right()); __ InvokeBuiltin(Builtins::IN, CALL_FUNCTION); + PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); __ cmp(eax, Factory::true_value()); Split(equal, if_true, if_false, fall_through); break; case Token::INSTANCEOF: { VisitForStackValue(expr->right()); - InstanceofStub stub; + __ IncrementCounter(&Counters::instance_of_full, 1); + InstanceofStub stub(InstanceofStub::kNoFlags); __ CallStub(&stub); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ test(eax, Operand(eax)); // The stub returns 0 for true. Split(zero, if_true, if_false, fall_through); @@ -3820,22 +4091,23 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { } bool inline_smi_code = ShouldInlineSmiCase(op); + JumpPatchSite patch_site(masm_); if (inline_smi_code) { NearLabel slow_case; __ mov(ecx, Operand(edx)); __ or_(ecx, Operand(eax)); - __ test(ecx, Immediate(kSmiTagMask)); - __ j(not_zero, &slow_case, not_taken); + patch_site.EmitJumpIfNotSmi(ecx, &slow_case); __ cmp(edx, Operand(eax)); Split(cc, if_true, if_false, NULL); __ bind(&slow_case); } - CompareFlags flags = inline_smi_code - ? NO_SMI_COMPARE_IN_STUB - : NO_COMPARE_FLAGS; - CompareStub stub(cc, strict, flags); - __ CallStub(&stub); + // Record position and call the compare IC. + SetSourcePosition(expr->position()); + Handle<Code> ic = CompareIC::GetUninitialized(op); + EmitCallIC(ic, &patch_site); + + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); __ test(eax, Operand(eax)); Split(cc, if_true, if_false, fall_through); } @@ -3856,6 +4128,8 @@ void FullCodeGenerator::VisitCompareToNull(CompareToNull* expr) { &if_true, &if_false, &fall_through); VisitForAccumulatorValue(expr->expression()); + PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false); + __ cmp(eax, Factory::null_value()); if (expr->is_strict()) { Split(equal, if_true, if_false, fall_through); @@ -3894,8 +4168,31 @@ Register FullCodeGenerator::context_register() { void FullCodeGenerator::EmitCallIC(Handle<Code> ic, RelocInfo::Mode mode) { ASSERT(mode == RelocInfo::CODE_TARGET || mode == RelocInfo::CODE_TARGET_CONTEXT); + switch (ic->kind()) { + case Code::LOAD_IC: + __ IncrementCounter(&Counters::named_load_full, 1); + break; + case Code::KEYED_LOAD_IC: + __ IncrementCounter(&Counters::keyed_load_full, 1); + break; + case Code::STORE_IC: + __ IncrementCounter(&Counters::named_store_full, 1); + break; + case Code::KEYED_STORE_IC: + __ IncrementCounter(&Counters::keyed_store_full, 1); + default: + break; + } + __ call(ic, mode); + // Crankshaft doesn't need patching of inlined loads and stores. + // When compiling the snapshot we need to produce code that works + // with and without Crankshaft. + if (V8::UseCrankshaft() && !Serializer::enabled()) { + return; + } + // If we're calling a (keyed) load or store stub, we have to mark // the call as containing no inlined code so we will not attempt to // patch it. @@ -3913,6 +4210,16 @@ void FullCodeGenerator::EmitCallIC(Handle<Code> ic, RelocInfo::Mode mode) { } +void FullCodeGenerator::EmitCallIC(Handle<Code> ic, JumpPatchSite* patch_site) { + __ call(ic, RelocInfo::CODE_TARGET); + if (patch_site != NULL && patch_site->is_bound()) { + patch_site->EmitPatchInfo(); + } else { + __ nop(); // Signals no inlined code. + } +} + + void FullCodeGenerator::StoreToFrameField(int frame_offset, Register value) { ASSERT_EQ(POINTER_SIZE_ALIGN(frame_offset), frame_offset); __ mov(Operand(ebp, frame_offset), value); diff --git a/src/ia32/ic-ia32.cc b/src/ia32/ic-ia32.cc index ddfbb91c..9c9304d5 100644 --- a/src/ia32/ic-ia32.cc +++ b/src/ia32/ic-ia32.cc @@ -108,9 +108,6 @@ static void GenerateStringDictionaryProbes(MacroAssembler* masm, Register name, Register r0, Register r1) { - // Assert that name contains a string. - if (FLAG_debug_code) __ AbortIfNotString(name); - // Compute the capacity mask. const int kCapacityOffset = StringDictionary::kHeaderSize + @@ -713,7 +710,7 @@ void KeyedLoadIC::GenerateString(MacroAssembler* masm) { char_at_generator.GenerateFast(masm); __ ret(0); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm, call_helper); __ bind(&miss); @@ -1552,14 +1549,7 @@ void KeyedCallIC::GenerateNormal(MacroAssembler* masm, int argc) { // -- esp[(argc + 1) * 4] : receiver // ----------------------------------- - // Check if the name is a string. - Label miss; - __ test(ecx, Immediate(kSmiTagMask)); - __ j(zero, &miss); - Condition cond = masm->IsObjectStringType(ecx, eax, eax); - __ j(NegateCondition(cond), &miss); GenerateCallNormal(masm, argc); - __ bind(&miss); GenerateMiss(masm, argc); } @@ -1639,16 +1629,15 @@ void LoadIC::GenerateMiss(MacroAssembler* masm) { } -// One byte opcode for test eax,0xXXXXXXXX. -static const byte kTestEaxByte = 0xA9; - bool LoadIC::PatchInlinedLoad(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // The address of the instruction following the call. Address test_instruction_address = address + Assembler::kCallTargetAddressOffset; // If the instruction following the call is not a test eax, nothing // was inlined. - if (*test_instruction_address != kTestEaxByte) return false; + if (*test_instruction_address != Assembler::kTestEaxByte) return false; Address delta_address = test_instruction_address + 1; // The delta to the start of the map check instruction. @@ -1692,6 +1681,8 @@ bool LoadIC::PatchInlinedContextualLoad(Address address, Object* map, Object* cell, bool is_dont_delete) { + if (V8::UseCrankshaft()) return false; + // The address of the instruction following the call. Address mov_instruction_address = address + Assembler::kCallTargetAddressOffset; @@ -1723,13 +1714,15 @@ bool LoadIC::PatchInlinedContextualLoad(Address address, bool StoreIC::PatchInlinedStore(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // The address of the instruction following the call. Address test_instruction_address = address + Assembler::kCallTargetAddressOffset; // If the instruction following the call is not a test eax, nothing // was inlined. - if (*test_instruction_address != kTestEaxByte) return false; + if (*test_instruction_address != Assembler::kTestEaxByte) return false; // Extract the encoded deltas from the test eax instruction. Address encoded_offsets_address = test_instruction_address + 1; @@ -1769,11 +1762,13 @@ bool StoreIC::PatchInlinedStore(Address address, Object* map, int offset) { static bool PatchInlinedMapCheck(Address address, Object* map) { + if (V8::UseCrankshaft()) return false; + Address test_instruction_address = address + Assembler::kCallTargetAddressOffset; // The keyed load has a fast inlined case if the IC call instruction // is immediately followed by a test instruction. - if (*test_instruction_address != kTestEaxByte) return false; + if (*test_instruction_address != Assembler::kTestEaxByte) return false; // Fetch the offset from the test instruction to the map cmp // instruction. This offset is stored in the last 4 bytes of the 5 @@ -1969,6 +1964,24 @@ void StoreIC::GenerateNormal(MacroAssembler* masm) { } +void StoreIC::GenerateGlobalProxy(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- eax : value + // -- ecx : name + // -- edx : receiver + // -- esp[0] : return address + // ----------------------------------- + __ pop(ebx); + __ push(edx); + __ push(ecx); + __ push(eax); + __ push(ebx); + + // Do tail-call to runtime routine. + __ TailCallRuntime(Runtime::kSetProperty, 3, 1); +} + + // Defined in ic.cc. Object* KeyedStoreIC_Miss(Arguments args); @@ -2010,9 +2023,107 @@ void KeyedStoreIC::GenerateMiss(MacroAssembler* masm) { __ TailCallExternalReference(ref, 3, 1); } + #undef __ +Condition CompareIC::ComputeCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return equal; + case Token::LT: + return less; + case Token::GT: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return less; + case Token::LTE: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return greater_equal; + case Token::GTE: + return greater_equal; + default: + UNREACHABLE(); + return no_condition; + } +} + + +static bool HasInlinedSmiCode(Address address) { + // The address of the instruction following the call. + Address test_instruction_address = + address + Assembler::kCallTargetAddressOffset; + + // If the instruction following the call is not a test al, nothing + // was inlined. + return *test_instruction_address == Assembler::kTestAlByte; +} + + +void CompareIC::UpdateCaches(Handle<Object> x, Handle<Object> y) { + HandleScope scope; + Handle<Code> rewritten; + State previous_state = GetState(); + + State state = TargetState(previous_state, HasInlinedSmiCode(address()), x, y); + if (state == GENERIC) { + CompareStub stub(GetCondition(), strict(), NO_COMPARE_FLAGS); + rewritten = stub.GetCode(); + } else { + ICCompareStub stub(op_, state); + rewritten = stub.GetCode(); + } + set_target(*rewritten); + +#ifdef DEBUG + if (FLAG_trace_ic) { + PrintF("[CompareIC (%s->%s)#%s]\n", + GetStateName(previous_state), + GetStateName(state), + Token::Name(op_)); + } +#endif + + // Activate inlined smi code. + if (previous_state == UNINITIALIZED) { + PatchInlinedSmiCode(address()); + } +} + + +void PatchInlinedSmiCode(Address address) { + // The address of the instruction following the call. + Address test_instruction_address = + address + Assembler::kCallTargetAddressOffset; + + // If the instruction following the call is not a test al, nothing + // was inlined. + if (*test_instruction_address != Assembler::kTestAlByte) { + ASSERT(*test_instruction_address == Assembler::kNopByte); + return; + } + + Address delta_address = test_instruction_address + 1; + // The delta to the start of the map check instruction and the + // condition code uses at the patched jump. + int8_t delta = *reinterpret_cast<int8_t*>(delta_address); + if (FLAG_trace_ic) { + PrintF("[ patching ic at %p, test=%p, delta=%d\n", + address, test_instruction_address, delta); + } + + // Patch with a short conditional jump. There must be a + // short jump-if-carry/not-carry at this position. + Address jmp_address = test_instruction_address - delta; + ASSERT(*jmp_address == Assembler::kJncShortOpcode || + *jmp_address == Assembler::kJcShortOpcode); + Condition cc = *jmp_address == Assembler::kJncShortOpcode + ? not_zero + : zero; + *jmp_address = static_cast<byte>(Assembler::kJccShortPrefix | cc); +} + + } } // namespace v8::internal #endif // V8_TARGET_ARCH_IA32 diff --git a/src/ia32/lithium-codegen-ia32.cc b/src/ia32/lithium-codegen-ia32.cc new file mode 100644 index 00000000..d64f528e --- /dev/null +++ b/src/ia32/lithium-codegen-ia32.cc @@ -0,0 +1,3276 @@ +// Copyright 2010 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. + +#include "ia32/lithium-codegen-ia32.h" +#include "code-stubs.h" +#include "stub-cache.h" + +namespace v8 { +namespace internal { + + +class SafepointGenerator : public PostCallGenerator { + public: + SafepointGenerator(LCodeGen* codegen, + LPointerMap* pointers, + int deoptimization_index) + : codegen_(codegen), + pointers_(pointers), + deoptimization_index_(deoptimization_index) { } + virtual ~SafepointGenerator() { } + + virtual void Generate() { + codegen_->RecordSafepoint(pointers_, deoptimization_index_); + } + + private: + LCodeGen* codegen_; + LPointerMap* pointers_; + int deoptimization_index_; +}; + + +#define __ masm()-> + +bool LCodeGen::GenerateCode() { + HPhase phase("Code generation", chunk()); + ASSERT(is_unused()); + status_ = GENERATING; + CpuFeatures::Scope scope(SSE2); + return GeneratePrologue() && + GenerateBody() && + GenerateDeferredCode() && + GenerateSafepointTable(); +} + + +void LCodeGen::FinishCode(Handle<Code> code) { + ASSERT(is_done()); + code->set_stack_slots(StackSlotCount()); + code->set_safepoint_table_start(safepoints_.GetCodeOffset()); + PopulateDeoptimizationData(code); +} + + +void LCodeGen::Abort(const char* format, ...) { + if (FLAG_trace_bailout) { + SmartPointer<char> debug_name = graph()->debug_name()->ToCString(); + PrintF("Aborting LCodeGen in @\"%s\": ", *debug_name); + va_list arguments; + va_start(arguments, format); + OS::VPrint(format, arguments); + va_end(arguments); + PrintF("\n"); + } + status_ = ABORTED; +} + + +void LCodeGen::Comment(const char* format, ...) { + if (!FLAG_code_comments) return; + char buffer[4 * KB]; + StringBuilder builder(buffer, ARRAY_SIZE(buffer)); + va_list arguments; + va_start(arguments, format); + builder.AddFormattedList(format, arguments); + va_end(arguments); + + // Copy the string before recording it in the assembler to avoid + // issues when the stack allocated buffer goes out of scope. + size_t length = builder.position(); + Vector<char> copy = Vector<char>::New(length + 1); + memcpy(copy.start(), builder.Finalize(), copy.length()); + masm()->RecordComment(copy.start()); +} + + +bool LCodeGen::GeneratePrologue() { + ASSERT(is_generating()); + +#ifdef DEBUG + if (strlen(FLAG_stop_at) > 0 && + info_->function()->name()->IsEqualTo(CStrVector(FLAG_stop_at))) { + __ int3(); + } +#endif + + __ push(ebp); // Caller's frame pointer. + __ mov(ebp, esp); + __ push(esi); // Callee's context. + __ push(edi); // Callee's JS function. + + // Reserve space for the stack slots needed by the code. + int slots = StackSlotCount(); + if (slots > 0) { + if (FLAG_debug_code) { + __ mov(Operand(eax), Immediate(slots)); + Label loop; + __ bind(&loop); + __ push(Immediate(kSlotsZapValue)); + __ dec(eax); + __ j(not_zero, &loop); + } else { + __ sub(Operand(esp), Immediate(slots * kPointerSize)); + } + } + + // Trace the call. + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + return !is_aborted(); +} + + +bool LCodeGen::GenerateBody() { + ASSERT(is_generating()); + bool emit_instructions = true; + for (current_instruction_ = 0; + !is_aborted() && current_instruction_ < instructions_->length(); + current_instruction_++) { + LInstruction* instr = instructions_->at(current_instruction_); + if (instr->IsLabel()) { + LLabel* label = LLabel::cast(instr); + emit_instructions = !label->HasReplacement(); + } + + if (emit_instructions) { + Comment(";;; @%d: %s.", current_instruction_, instr->Mnemonic()); + instr->CompileToNative(this); + } + } + return !is_aborted(); +} + + +LInstruction* LCodeGen::GetNextInstruction() { + if (current_instruction_ < instructions_->length() - 1) { + return instructions_->at(current_instruction_ + 1); + } else { + return NULL; + } +} + + +bool LCodeGen::GenerateDeferredCode() { + ASSERT(is_generating()); + for (int i = 0; !is_aborted() && i < deferred_.length(); i++) { + LDeferredCode* code = deferred_[i]; + __ bind(code->entry()); + code->Generate(); + __ jmp(code->exit()); + } + + // Deferred code is the last part of the instruction sequence. Mark + // the generated code as done unless we bailed out. + if (!is_aborted()) status_ = DONE; + return !is_aborted(); +} + + +bool LCodeGen::GenerateSafepointTable() { + ASSERT(is_done()); + safepoints_.Emit(masm(), StackSlotCount()); + return !is_aborted(); +} + + +Register LCodeGen::ToRegister(int index) const { + return Register::FromAllocationIndex(index); +} + + +XMMRegister LCodeGen::ToDoubleRegister(int index) const { + return XMMRegister::FromAllocationIndex(index); +} + + +Register LCodeGen::ToRegister(LOperand* op) const { + ASSERT(op->IsRegister()); + return ToRegister(op->index()); +} + + +XMMRegister LCodeGen::ToDoubleRegister(LOperand* op) const { + ASSERT(op->IsDoubleRegister()); + return ToDoubleRegister(op->index()); +} + + +int LCodeGen::ToInteger32(LConstantOperand* op) const { + Handle<Object> value = chunk_->LookupLiteral(op); + ASSERT(chunk_->LookupLiteralRepresentation(op).IsInteger32()); + ASSERT(static_cast<double>(static_cast<int32_t>(value->Number())) == + value->Number()); + return static_cast<int32_t>(value->Number()); +} + + +Immediate LCodeGen::ToImmediate(LOperand* op) { + LConstantOperand* const_op = LConstantOperand::cast(op); + Handle<Object> literal = chunk_->LookupLiteral(const_op); + Representation r = chunk_->LookupLiteralRepresentation(const_op); + if (r.IsInteger32()) { + ASSERT(literal->IsNumber()); + return Immediate(static_cast<int32_t>(literal->Number())); + } else if (r.IsDouble()) { + Abort("unsupported double immediate"); + } + ASSERT(r.IsTagged()); + return Immediate(literal); +} + + +Operand LCodeGen::ToOperand(LOperand* op) const { + if (op->IsRegister()) return Operand(ToRegister(op)); + if (op->IsDoubleRegister()) return Operand(ToDoubleRegister(op)); + ASSERT(op->IsStackSlot() || op->IsDoubleStackSlot()); + int index = op->index(); + if (index >= 0) { + // Local or spill slot. Skip the frame pointer, function, and + // context in the fixed part of the frame. + return Operand(ebp, -(index + 3) * kPointerSize); + } else { + // Incoming parameter. Skip the return address. + return Operand(ebp, -(index - 1) * kPointerSize); + } +} + + +void LCodeGen::AddToTranslation(Translation* translation, + LOperand* op, + bool is_tagged) { + if (op == NULL) { + // TODO(twuerthinger): Introduce marker operands to indicate that this value + // is not present and must be reconstructed from the deoptimizer. Currently + // this is only used for the arguments object. + translation->StoreArgumentsObject(); + } else if (op->IsStackSlot()) { + if (is_tagged) { + translation->StoreStackSlot(op->index()); + } else { + translation->StoreInt32StackSlot(op->index()); + } + } else if (op->IsDoubleStackSlot()) { + translation->StoreDoubleStackSlot(op->index()); + } else if (op->IsArgument()) { + ASSERT(is_tagged); + int src_index = StackSlotCount() + op->index(); + translation->StoreStackSlot(src_index); + } else if (op->IsRegister()) { + Register reg = ToRegister(op); + if (is_tagged) { + translation->StoreRegister(reg); + } else { + translation->StoreInt32Register(reg); + } + } else if (op->IsDoubleRegister()) { + XMMRegister reg = ToDoubleRegister(op); + translation->StoreDoubleRegister(reg); + } else if (op->IsConstantOperand()) { + Handle<Object> literal = chunk()->LookupLiteral(LConstantOperand::cast(op)); + int src_index = DefineDeoptimizationLiteral(literal); + translation->StoreLiteral(src_index); + } else { + UNREACHABLE(); + } +} + + +void LCodeGen::CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr) { + if (instr != NULL) { + LPointerMap* pointers = instr->pointer_map(); + RecordPosition(pointers->position()); + __ call(code, mode); + RegisterLazyDeoptimization(instr); + } else { + LPointerMap no_pointers(0); + RecordPosition(no_pointers.position()); + __ call(code, mode); + RecordSafepoint(&no_pointers, Safepoint::kNoDeoptimizationIndex); + } + + // Signal that we don't inline smi code before these stubs in the + // optimizing code generator. + if (code->kind() == Code::TYPE_RECORDING_BINARY_OP_IC || + code->kind() == Code::COMPARE_IC) { + __ nop(); + } +} + + +void LCodeGen::CallRuntime(Runtime::Function* function, + int num_arguments, + LInstruction* instr) { + ASSERT(instr != NULL); + LPointerMap* pointers = instr->pointer_map(); + ASSERT(pointers != NULL); + RecordPosition(pointers->position()); + + __ CallRuntime(function, num_arguments); + // Runtime calls to Throw are not supposed to ever return at the + // call site, so don't register lazy deoptimization for these. We do + // however have to record a safepoint since throwing exceptions can + // cause garbage collections. + // BUG(3243555): register a lazy deoptimization point at throw. We need + // it to be able to inline functions containing a throw statement. + if (!instr->IsThrow()) { + RegisterLazyDeoptimization(instr); + } else { + RecordSafepoint(instr->pointer_map(), Safepoint::kNoDeoptimizationIndex); + } +} + + +void LCodeGen::RegisterLazyDeoptimization(LInstruction* instr) { + // Create the environment to bailout to. If the call has side effects + // execution has to continue after the call otherwise execution can continue + // from a previous bailout point repeating the call. + LEnvironment* deoptimization_environment; + if (instr->HasDeoptimizationEnvironment()) { + deoptimization_environment = instr->deoptimization_environment(); + } else { + deoptimization_environment = instr->environment(); + } + + RegisterEnvironmentForDeoptimization(deoptimization_environment); + RecordSafepoint(instr->pointer_map(), + deoptimization_environment->deoptimization_index()); +} + + +void LCodeGen::RegisterEnvironmentForDeoptimization(LEnvironment* environment) { + if (!environment->HasBeenRegistered()) { + // Physical stack frame layout: + // -x ............. -4 0 ..................................... y + // [incoming arguments] [spill slots] [pushed outgoing arguments] + + // Layout of the environment: + // 0 ..................................................... size-1 + // [parameters] [locals] [expression stack including arguments] + + // Layout of the translation: + // 0 ........................................................ size - 1 + 4 + // [expression stack including arguments] [locals] [4 words] [parameters] + // |>------------ translation_size ------------<| + + int frame_count = 0; + for (LEnvironment* e = environment; e != NULL; e = e->outer()) { + ++frame_count; + } + Translation translation(&translations_, frame_count); + environment->WriteTranslation(this, &translation); + int deoptimization_index = deoptimizations_.length(); + environment->Register(deoptimization_index, translation.index()); + deoptimizations_.Add(environment); + } +} + + +void LCodeGen::DeoptimizeIf(Condition cc, LEnvironment* environment) { + RegisterEnvironmentForDeoptimization(environment); + ASSERT(environment->HasBeenRegistered()); + int id = environment->deoptimization_index(); + Address entry = Deoptimizer::GetDeoptimizationEntry(id, Deoptimizer::EAGER); + ASSERT(entry != NULL); + if (entry == NULL) { + Abort("bailout was not prepared"); + return; + } + + if (FLAG_deopt_every_n_times != 0) { + Handle<SharedFunctionInfo> shared(info_->shared_info()); + Label no_deopt; + __ pushfd(); + __ push(eax); + __ push(ebx); + __ mov(ebx, shared); + __ mov(eax, FieldOperand(ebx, SharedFunctionInfo::kDeoptCounterOffset)); + __ sub(Operand(eax), Immediate(Smi::FromInt(1))); + __ j(not_zero, &no_deopt); + if (FLAG_trap_on_deopt) __ int3(); + __ mov(eax, Immediate(Smi::FromInt(FLAG_deopt_every_n_times))); + __ mov(FieldOperand(ebx, SharedFunctionInfo::kDeoptCounterOffset), eax); + __ pop(ebx); + __ pop(eax); + __ popfd(); + __ jmp(entry, RelocInfo::RUNTIME_ENTRY); + + __ bind(&no_deopt); + __ mov(FieldOperand(ebx, SharedFunctionInfo::kDeoptCounterOffset), eax); + __ pop(ebx); + __ pop(eax); + __ popfd(); + } + + if (cc == no_condition) { + if (FLAG_trap_on_deopt) __ int3(); + __ jmp(entry, RelocInfo::RUNTIME_ENTRY); + } else { + if (FLAG_trap_on_deopt) { + NearLabel done; + __ j(NegateCondition(cc), &done); + __ int3(); + __ jmp(entry, RelocInfo::RUNTIME_ENTRY); + __ bind(&done); + } else { + __ j(cc, entry, RelocInfo::RUNTIME_ENTRY, not_taken); + } + } +} + + +void LCodeGen::PopulateDeoptimizationData(Handle<Code> code) { + int length = deoptimizations_.length(); + if (length == 0) return; + ASSERT(FLAG_deopt); + Handle<DeoptimizationInputData> data = + Factory::NewDeoptimizationInputData(length, TENURED); + + data->SetTranslationByteArray(*translations_.CreateByteArray()); + data->SetInlinedFunctionCount(Smi::FromInt(inlined_function_count_)); + + Handle<FixedArray> literals = + Factory::NewFixedArray(deoptimization_literals_.length(), TENURED); + for (int i = 0; i < deoptimization_literals_.length(); i++) { + literals->set(i, *deoptimization_literals_[i]); + } + data->SetLiteralArray(*literals); + + data->SetOsrAstId(Smi::FromInt(info_->osr_ast_id())); + data->SetOsrPcOffset(Smi::FromInt(osr_pc_offset_)); + + // Populate the deoptimization entries. + for (int i = 0; i < length; i++) { + LEnvironment* env = deoptimizations_[i]; + data->SetAstId(i, Smi::FromInt(env->ast_id())); + data->SetTranslationIndex(i, Smi::FromInt(env->translation_index())); + data->SetArgumentsStackHeight(i, + Smi::FromInt(env->arguments_stack_height())); + } + code->set_deoptimization_data(*data); +} + + +int LCodeGen::DefineDeoptimizationLiteral(Handle<Object> literal) { + int result = deoptimization_literals_.length(); + for (int i = 0; i < deoptimization_literals_.length(); ++i) { + if (deoptimization_literals_[i].is_identical_to(literal)) return i; + } + deoptimization_literals_.Add(literal); + return result; +} + + +void LCodeGen::PopulateDeoptimizationLiteralsWithInlinedFunctions() { + ASSERT(deoptimization_literals_.length() == 0); + + const ZoneList<Handle<JSFunction> >* inlined_closures = + chunk()->inlined_closures(); + + for (int i = 0, length = inlined_closures->length(); + i < length; + i++) { + DefineDeoptimizationLiteral(inlined_closures->at(i)); + } + + inlined_function_count_ = deoptimization_literals_.length(); +} + + +void LCodeGen::RecordSafepoint(LPointerMap* pointers, + int deoptimization_index) { + const ZoneList<LOperand*>* operands = pointers->operands(); + Safepoint safepoint = safepoints_.DefineSafepoint(masm(), + deoptimization_index); + for (int i = 0; i < operands->length(); i++) { + LOperand* pointer = operands->at(i); + if (pointer->IsStackSlot()) { + safepoint.DefinePointerSlot(pointer->index()); + } + } +} + + +void LCodeGen::RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + int deoptimization_index) { + const ZoneList<LOperand*>* operands = pointers->operands(); + Safepoint safepoint = + safepoints_.DefineSafepointWithRegisters( + masm(), arguments, deoptimization_index); + for (int i = 0; i < operands->length(); i++) { + LOperand* pointer = operands->at(i); + if (pointer->IsStackSlot()) { + safepoint.DefinePointerSlot(pointer->index()); + } else if (pointer->IsRegister()) { + safepoint.DefinePointerRegister(ToRegister(pointer)); + } + } + // Register esi always contains a pointer to the context. + safepoint.DefinePointerRegister(esi); +} + + +void LCodeGen::RecordPosition(int position) { + if (!FLAG_debug_info || position == RelocInfo::kNoPosition) return; + masm()->positions_recorder()->RecordPosition(position); +} + + +void LCodeGen::DoLabel(LLabel* label) { + if (label->is_loop_header()) { + Comment(";;; B%d - LOOP entry", label->block_id()); + } else { + Comment(";;; B%d", label->block_id()); + } + __ bind(label->label()); + current_block_ = label->block_id(); + LCodeGen::DoGap(label); +} + + +void LCodeGen::DoParallelMove(LParallelMove* move) { + // xmm0 must always be a scratch register. + XMMRegister xmm_scratch = xmm0; + LUnallocated marker_operand(LUnallocated::NONE); + + Register cpu_scratch = esi; + bool destroys_cpu_scratch = false; + + LGapResolver resolver(move->move_operands(), &marker_operand); + const ZoneList<LMoveOperands>* moves = resolver.ResolveInReverseOrder(); + for (int i = moves->length() - 1; i >= 0; --i) { + LMoveOperands move = moves->at(i); + LOperand* from = move.from(); + LOperand* to = move.to(); + ASSERT(!from->IsDoubleRegister() || + !ToDoubleRegister(from).is(xmm_scratch)); + ASSERT(!to->IsDoubleRegister() || !ToDoubleRegister(to).is(xmm_scratch)); + ASSERT(!from->IsRegister() || !ToRegister(from).is(cpu_scratch)); + ASSERT(!to->IsRegister() || !ToRegister(to).is(cpu_scratch)); + if (from->IsConstantOperand()) { + __ mov(ToOperand(to), ToImmediate(from)); + } else if (from == &marker_operand) { + if (to->IsRegister() || to->IsStackSlot()) { + __ mov(ToOperand(to), cpu_scratch); + ASSERT(destroys_cpu_scratch); + } else { + ASSERT(to->IsDoubleRegister() || to->IsDoubleStackSlot()); + __ movdbl(ToOperand(to), xmm_scratch); + } + } else if (to == &marker_operand) { + if (from->IsRegister() || from->IsStackSlot()) { + __ mov(cpu_scratch, ToOperand(from)); + destroys_cpu_scratch = true; + } else { + ASSERT(from->IsDoubleRegister() || from->IsDoubleStackSlot()); + __ movdbl(xmm_scratch, ToOperand(from)); + } + } else if (from->IsRegister()) { + __ mov(ToOperand(to), ToRegister(from)); + } else if (to->IsRegister()) { + __ mov(ToRegister(to), ToOperand(from)); + } else if (from->IsStackSlot()) { + ASSERT(to->IsStackSlot()); + __ push(eax); + __ mov(eax, ToOperand(from)); + __ mov(ToOperand(to), eax); + __ pop(eax); + } else if (from->IsDoubleRegister()) { + __ movdbl(ToOperand(to), ToDoubleRegister(from)); + } else if (to->IsDoubleRegister()) { + __ movdbl(ToDoubleRegister(to), ToOperand(from)); + } else { + ASSERT(to->IsDoubleStackSlot() && from->IsDoubleStackSlot()); + __ movdbl(xmm_scratch, ToOperand(from)); + __ movdbl(ToOperand(to), xmm_scratch); + } + } + + if (destroys_cpu_scratch) { + __ mov(cpu_scratch, Operand(ebp, -kPointerSize)); + } +} + + +void LCodeGen::DoGap(LGap* gap) { + for (int i = LGap::FIRST_INNER_POSITION; + i <= LGap::LAST_INNER_POSITION; + i++) { + LGap::InnerPosition inner_pos = static_cast<LGap::InnerPosition>(i); + LParallelMove* move = gap->GetParallelMove(inner_pos); + if (move != NULL) DoParallelMove(move); + } + + LInstruction* next = GetNextInstruction(); + if (next != NULL && next->IsLazyBailout()) { + int pc = masm()->pc_offset(); + safepoints_.SetPcAfterGap(pc); + } +} + + +void LCodeGen::DoParameter(LParameter* instr) { + // Nothing to do. +} + + +void LCodeGen::DoCallStub(LCallStub* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + switch (instr->hydrogen()->major_key()) { + case CodeStub::RegExpConstructResult: { + RegExpConstructResultStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::RegExpExec: { + RegExpExecStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::SubString: { + SubStringStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::StringCharAt: { + StringCharAtStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::MathPow: { + MathPowStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::NumberToString: { + NumberToStringStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::StringAdd: { + StringAddStub stub(NO_STRING_ADD_FLAGS); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::StringCompare: { + StringCompareStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + case CodeStub::TranscendentalCache: { + TranscendentalCacheStub stub(instr->transcendental_type(), + TranscendentalCacheStub::TAGGED); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + break; + } + default: + UNREACHABLE(); + } +} + + +void LCodeGen::DoUnknownOSRValue(LUnknownOSRValue* instr) { + // Nothing to do. +} + + +void LCodeGen::DoModI(LModI* instr) { + LOperand* right = instr->right(); + ASSERT(ToRegister(instr->result()).is(edx)); + ASSERT(ToRegister(instr->left()).is(eax)); + ASSERT(!ToRegister(instr->right()).is(eax)); + ASSERT(!ToRegister(instr->right()).is(edx)); + + Register right_reg = ToRegister(right); + + // Check for x % 0. + if (instr->hydrogen()->CheckFlag(HValue::kCanBeDivByZero)) { + __ test(right_reg, ToOperand(right)); + DeoptimizeIf(zero, instr->environment()); + } + + // Sign extend to edx. + __ cdq(); + + // Check for (0 % -x) that will produce negative zero. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + NearLabel positive_left; + NearLabel done; + __ test(eax, Operand(eax)); + __ j(not_sign, &positive_left); + __ idiv(right_reg); + + // Test the remainder for 0, because then the result would be -0. + __ test(edx, Operand(edx)); + __ j(not_zero, &done); + + DeoptimizeIf(no_condition, instr->environment()); + __ bind(&positive_left); + __ idiv(right_reg); + __ bind(&done); + } else { + __ idiv(right_reg); + } +} + + +void LCodeGen::DoDivI(LDivI* instr) { + LOperand* right = instr->right(); + ASSERT(ToRegister(instr->result()).is(eax)); + ASSERT(ToRegister(instr->left()).is(eax)); + ASSERT(!ToRegister(instr->right()).is(eax)); + ASSERT(!ToRegister(instr->right()).is(edx)); + + Register left_reg = eax; + + // Check for x / 0. + Register right_reg = ToRegister(right); + if (instr->hydrogen()->CheckFlag(HValue::kCanBeDivByZero)) { + __ test(right_reg, ToOperand(right)); + DeoptimizeIf(zero, instr->environment()); + } + + // Check for (0 / -x) that will produce negative zero. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + NearLabel left_not_zero; + __ test(left_reg, Operand(left_reg)); + __ j(not_zero, &left_not_zero); + __ test(right_reg, ToOperand(right)); + DeoptimizeIf(sign, instr->environment()); + __ bind(&left_not_zero); + } + + // Check for (-kMinInt / -1). + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + NearLabel left_not_min_int; + __ cmp(left_reg, kMinInt); + __ j(not_zero, &left_not_min_int); + __ cmp(right_reg, -1); + DeoptimizeIf(zero, instr->environment()); + __ bind(&left_not_min_int); + } + + // Sign extend to edx. + __ cdq(); + __ idiv(right_reg); + + // Deoptimize if remainder is not 0. + __ test(edx, Operand(edx)); + DeoptimizeIf(not_zero, instr->environment()); +} + + +void LCodeGen::DoMulI(LMulI* instr) { + Register left = ToRegister(instr->left()); + LOperand* right = instr->right(); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ mov(ToRegister(instr->temp()), left); + } + + if (right->IsConstantOperand()) { + __ imul(left, left, ToInteger32(LConstantOperand::cast(right))); + } else { + __ imul(left, ToOperand(right)); + } + + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + DeoptimizeIf(overflow, instr->environment()); + } + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + // Bail out if the result is supposed to be negative zero. + NearLabel done; + __ test(left, Operand(left)); + __ j(not_zero, &done); + if (right->IsConstantOperand()) { + if (ToInteger32(LConstantOperand::cast(right)) < 0) { + DeoptimizeIf(no_condition, instr->environment()); + } + } else { + // Test the non-zero operand for negative sign. + __ or_(ToRegister(instr->temp()), ToOperand(right)); + DeoptimizeIf(sign, instr->environment()); + } + __ bind(&done); + } +} + + +void LCodeGen::DoBitI(LBitI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + ASSERT(left->IsRegister()); + + if (right->IsConstantOperand()) { + int right_operand = ToInteger32(LConstantOperand::cast(right)); + switch (instr->op()) { + case Token::BIT_AND: + __ and_(ToRegister(left), right_operand); + break; + case Token::BIT_OR: + __ or_(ToRegister(left), right_operand); + break; + case Token::BIT_XOR: + __ xor_(ToRegister(left), right_operand); + break; + default: + UNREACHABLE(); + break; + } + } else { + switch (instr->op()) { + case Token::BIT_AND: + __ and_(ToRegister(left), ToOperand(right)); + break; + case Token::BIT_OR: + __ or_(ToRegister(left), ToOperand(right)); + break; + case Token::BIT_XOR: + __ xor_(ToRegister(left), ToOperand(right)); + break; + default: + UNREACHABLE(); + break; + } + } +} + + +void LCodeGen::DoShiftI(LShiftI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + ASSERT(left->IsRegister()); + if (right->IsRegister()) { + ASSERT(ToRegister(right).is(ecx)); + + switch (instr->op()) { + case Token::SAR: + __ sar_cl(ToRegister(left)); + break; + case Token::SHR: + __ shr_cl(ToRegister(left)); + if (instr->can_deopt()) { + __ test(ToRegister(left), Immediate(0x80000000)); + DeoptimizeIf(not_zero, instr->environment()); + } + break; + case Token::SHL: + __ shl_cl(ToRegister(left)); + break; + default: + UNREACHABLE(); + break; + } + } else { + int value = ToInteger32(LConstantOperand::cast(right)); + uint8_t shift_count = static_cast<uint8_t>(value & 0x1F); + switch (instr->op()) { + case Token::SAR: + if (shift_count != 0) { + __ sar(ToRegister(left), shift_count); + } + break; + case Token::SHR: + if (shift_count == 0 && instr->can_deopt()) { + __ test(ToRegister(left), Immediate(0x80000000)); + DeoptimizeIf(not_zero, instr->environment()); + } else { + __ shr(ToRegister(left), shift_count); + } + break; + case Token::SHL: + if (shift_count != 0) { + __ shl(ToRegister(left), shift_count); + } + break; + default: + UNREACHABLE(); + break; + } + } +} + + +void LCodeGen::DoSubI(LSubI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + + if (right->IsConstantOperand()) { + __ sub(ToOperand(left), ToImmediate(right)); + } else { + __ sub(ToRegister(left), ToOperand(right)); + } + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + DeoptimizeIf(overflow, instr->environment()); + } +} + + +void LCodeGen::DoConstantI(LConstantI* instr) { + ASSERT(instr->result()->IsRegister()); + __ mov(ToRegister(instr->result()), instr->value()); +} + + +void LCodeGen::DoConstantD(LConstantD* instr) { + ASSERT(instr->result()->IsDoubleRegister()); + XMMRegister res = ToDoubleRegister(instr->result()); + double v = instr->value(); + // Use xor to produce +0.0 in a fast and compact way, but avoid to + // do so if the constant is -0.0. + if (BitCast<uint64_t, double>(v) == 0) { + __ xorpd(res, res); + } else { + int32_t v_int32 = static_cast<int32_t>(v); + if (static_cast<double>(v_int32) == v) { + __ push_imm32(v_int32); + __ cvtsi2sd(res, Operand(esp, 0)); + __ add(Operand(esp), Immediate(kPointerSize)); + } else { + uint64_t int_val = BitCast<uint64_t, double>(v); + int32_t lower = static_cast<int32_t>(int_val); + int32_t upper = static_cast<int32_t>(int_val >> (kBitsPerInt)); + __ push_imm32(upper); + __ push_imm32(lower); + __ movdbl(res, Operand(esp, 0)); + __ add(Operand(esp), Immediate(2 * kPointerSize)); + } + } +} + + +void LCodeGen::DoConstantT(LConstantT* instr) { + ASSERT(instr->result()->IsRegister()); + __ mov(ToRegister(instr->result()), Immediate(instr->value())); +} + + +void LCodeGen::DoArrayLength(LArrayLength* instr) { + Register result = ToRegister(instr->result()); + + if (instr->hydrogen()->value()->IsLoadElements()) { + // We load the length directly from the elements array. + Register elements = ToRegister(instr->input()); + __ mov(result, FieldOperand(elements, FixedArray::kLengthOffset)); + } else { + // Check that the receiver really is an array. + Register array = ToRegister(instr->input()); + Register temporary = ToRegister(instr->temporary()); + __ CmpObjectType(array, JS_ARRAY_TYPE, temporary); + DeoptimizeIf(not_equal, instr->environment()); + + // Load length directly from the array. + __ mov(result, FieldOperand(array, JSArray::kLengthOffset)); + } +} + + +void LCodeGen::DoValueOf(LValueOf* instr) { + Register input = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + Register map = ToRegister(instr->temporary()); + ASSERT(input.is(result)); + NearLabel done; + // If the object is a smi return the object. + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, &done); + + // If the object is not a value type, return the object. + __ CmpObjectType(input, JS_VALUE_TYPE, map); + __ j(not_equal, &done); + __ mov(result, FieldOperand(input, JSValue::kValueOffset)); + + __ bind(&done); +} + + +void LCodeGen::DoBitNotI(LBitNotI* instr) { + LOperand* input = instr->input(); + ASSERT(input->Equals(instr->result())); + __ not_(ToRegister(input)); +} + + +void LCodeGen::DoThrow(LThrow* instr) { + __ push(ToOperand(instr->input())); + CallRuntime(Runtime::kThrow, 1, instr); + + if (FLAG_debug_code) { + Comment("Unreachable code."); + __ int3(); + } +} + + +void LCodeGen::DoAddI(LAddI* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + ASSERT(left->Equals(instr->result())); + + if (right->IsConstantOperand()) { + __ add(ToOperand(left), ToImmediate(right)); + } else { + __ add(ToRegister(left), ToOperand(right)); + } + + if (instr->hydrogen()->CheckFlag(HValue::kCanOverflow)) { + DeoptimizeIf(overflow, instr->environment()); + } +} + + +void LCodeGen::DoArithmeticD(LArithmeticD* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + // Modulo uses a fixed result register. + ASSERT(instr->op() == Token::MOD || left->Equals(instr->result())); + switch (instr->op()) { + case Token::ADD: + __ addsd(ToDoubleRegister(left), ToDoubleRegister(right)); + break; + case Token::SUB: + __ subsd(ToDoubleRegister(left), ToDoubleRegister(right)); + break; + case Token::MUL: + __ mulsd(ToDoubleRegister(left), ToDoubleRegister(right)); + break; + case Token::DIV: + __ divsd(ToDoubleRegister(left), ToDoubleRegister(right)); + break; + case Token::MOD: { + // Pass two doubles as arguments on the stack. + __ PrepareCallCFunction(4, eax); + __ movdbl(Operand(esp, 0 * kDoubleSize), ToDoubleRegister(left)); + __ movdbl(Operand(esp, 1 * kDoubleSize), ToDoubleRegister(right)); + __ CallCFunction(ExternalReference::double_fp_operation(Token::MOD), 4); + + // Return value is in st(0) on ia32. + // Store it into the (fixed) result register. + __ sub(Operand(esp), Immediate(kDoubleSize)); + __ fstp_d(Operand(esp, 0)); + __ movdbl(ToDoubleRegister(instr->result()), Operand(esp, 0)); + __ add(Operand(esp), Immediate(kDoubleSize)); + break; + } + default: + UNREACHABLE(); + break; + } +} + + +void LCodeGen::DoArithmeticT(LArithmeticT* instr) { + ASSERT(ToRegister(instr->left()).is(edx)); + ASSERT(ToRegister(instr->right()).is(eax)); + ASSERT(ToRegister(instr->result()).is(eax)); + + TypeRecordingBinaryOpStub stub(instr->op(), NO_OVERWRITE); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +int LCodeGen::GetNextEmittedBlock(int block) { + for (int i = block + 1; i < graph()->blocks()->length(); ++i) { + LLabel* label = chunk_->GetLabel(i); + if (!label->HasReplacement()) return i; + } + return -1; +} + + +void LCodeGen::EmitBranch(int left_block, int right_block, Condition cc) { + int next_block = GetNextEmittedBlock(current_block_); + right_block = chunk_->LookupDestination(right_block); + left_block = chunk_->LookupDestination(left_block); + + if (right_block == left_block) { + EmitGoto(left_block); + } else if (left_block == next_block) { + __ j(NegateCondition(cc), chunk_->GetAssemblyLabel(right_block)); + } else if (right_block == next_block) { + __ j(cc, chunk_->GetAssemblyLabel(left_block)); + } else { + __ j(cc, chunk_->GetAssemblyLabel(left_block)); + __ jmp(chunk_->GetAssemblyLabel(right_block)); + } +} + + +void LCodeGen::DoBranch(LBranch* instr) { + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Representation r = instr->hydrogen()->representation(); + if (r.IsInteger32()) { + Register reg = ToRegister(instr->input()); + __ test(reg, Operand(reg)); + EmitBranch(true_block, false_block, not_zero); + } else if (r.IsDouble()) { + XMMRegister reg = ToDoubleRegister(instr->input()); + __ xorpd(xmm0, xmm0); + __ ucomisd(reg, xmm0); + EmitBranch(true_block, false_block, not_equal); + } else { + ASSERT(r.IsTagged()); + Register reg = ToRegister(instr->input()); + if (instr->hydrogen()->type().IsBoolean()) { + __ cmp(reg, Factory::true_value()); + EmitBranch(true_block, false_block, equal); + } else { + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + __ cmp(reg, Factory::undefined_value()); + __ j(equal, false_label); + __ cmp(reg, Factory::true_value()); + __ j(equal, true_label); + __ cmp(reg, Factory::false_value()); + __ j(equal, false_label); + __ test(reg, Operand(reg)); + __ j(equal, false_label); + __ test(reg, Immediate(kSmiTagMask)); + __ j(zero, true_label); + + // Test for double values. Zero is false. + NearLabel call_stub; + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + Factory::heap_number_map()); + __ j(not_equal, &call_stub); + __ fldz(); + __ fld_d(FieldOperand(reg, HeapNumber::kValueOffset)); + __ FCmp(); + __ j(zero, false_label); + __ jmp(true_label); + + // The conversion stub doesn't cause garbage collections so it's + // safe to not record a safepoint after the call. + __ bind(&call_stub); + ToBooleanStub stub; + __ pushad(); + __ push(reg); + __ CallStub(&stub); + __ test(eax, Operand(eax)); + __ popad(); + EmitBranch(true_block, false_block, not_zero); + } + } +} + + +void LCodeGen::EmitGoto(int block, LDeferredCode* deferred_stack_check) { + block = chunk_->LookupDestination(block); + int next_block = GetNextEmittedBlock(current_block_); + if (block != next_block) { + // Perform stack overflow check if this goto needs it before jumping. + if (deferred_stack_check != NULL) { + ExternalReference stack_limit = + ExternalReference::address_of_stack_limit(); + __ cmp(esp, Operand::StaticVariable(stack_limit)); + __ j(above_equal, chunk_->GetAssemblyLabel(block)); + __ jmp(deferred_stack_check->entry()); + deferred_stack_check->SetExit(chunk_->GetAssemblyLabel(block)); + } else { + __ jmp(chunk_->GetAssemblyLabel(block)); + } + } +} + + +void LCodeGen::DoDeferredStackCheck(LGoto* instr) { + __ pushad(); + __ CallRuntimeSaveDoubles(Runtime::kStackGuard); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + __ popad(); +} + +void LCodeGen::DoGoto(LGoto* instr) { + class DeferredStackCheck: public LDeferredCode { + public: + DeferredStackCheck(LCodeGen* codegen, LGoto* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredStackCheck(instr_); } + private: + LGoto* instr_; + }; + + DeferredStackCheck* deferred = NULL; + if (instr->include_stack_check()) { + deferred = new DeferredStackCheck(this, instr); + } + EmitGoto(instr->block_id(), deferred); +} + + +Condition LCodeGen::TokenToCondition(Token::Value op, bool is_unsigned) { + Condition cond = no_condition; + switch (op) { + case Token::EQ: + case Token::EQ_STRICT: + cond = equal; + break; + case Token::LT: + cond = is_unsigned ? below : less; + break; + case Token::GT: + cond = is_unsigned ? above : greater; + break; + case Token::LTE: + cond = is_unsigned ? below_equal : less_equal; + break; + case Token::GTE: + cond = is_unsigned ? above_equal : greater_equal; + break; + case Token::IN: + case Token::INSTANCEOF: + default: + UNREACHABLE(); + } + return cond; +} + + +void LCodeGen::EmitCmpI(LOperand* left, LOperand* right) { + if (right->IsConstantOperand()) { + __ cmp(ToOperand(left), ToImmediate(right)); + } else { + __ cmp(ToRegister(left), ToOperand(right)); + } +} + + +void LCodeGen::DoCmpID(LCmpID* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + LOperand* result = instr->result(); + + NearLabel unordered; + if (instr->is_double()) { + // Don't base result on EFLAGS when a NaN is involved. Instead + // jump to the unordered case, which produces a false value. + __ ucomisd(ToDoubleRegister(left), ToDoubleRegister(right)); + __ j(parity_even, &unordered, not_taken); + } else { + EmitCmpI(left, right); + } + + NearLabel done; + Condition cc = TokenToCondition(instr->op(), instr->is_double()); + __ mov(ToRegister(result), Handle<Object>(Heap::true_value())); + __ j(cc, &done); + + __ bind(&unordered); + __ mov(ToRegister(result), Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoCmpIDAndBranch(LCmpIDAndBranch* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + + if (instr->is_double()) { + // Don't base result on EFLAGS when a NaN is involved. Instead + // jump to the false block. + __ ucomisd(ToDoubleRegister(left), ToDoubleRegister(right)); + __ j(parity_even, chunk_->GetAssemblyLabel(false_block)); + } else { + EmitCmpI(left, right); + } + + Condition cc = TokenToCondition(instr->op(), instr->is_double()); + EmitBranch(true_block, false_block, cc); +} + + +void LCodeGen::DoCmpJSObjectEq(LCmpJSObjectEq* instr) { + Register left = ToRegister(instr->left()); + Register right = ToRegister(instr->right()); + Register result = ToRegister(instr->result()); + + __ cmp(left, Operand(right)); + __ mov(result, Handle<Object>(Heap::true_value())); + NearLabel done; + __ j(equal, &done); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoCmpJSObjectEqAndBranch(LCmpJSObjectEqAndBranch* instr) { + Register left = ToRegister(instr->left()); + Register right = ToRegister(instr->right()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + + __ cmp(left, Operand(right)); + EmitBranch(true_block, false_block, equal); +} + + +void LCodeGen::DoIsNull(LIsNull* instr) { + Register reg = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + + // TODO(fsc): If the expression is known to be a smi, then it's + // definitely not null. Materialize false. + + __ cmp(reg, Factory::null_value()); + if (instr->is_strict()) { + __ mov(result, Handle<Object>(Heap::true_value())); + NearLabel done; + __ j(equal, &done); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); + } else { + NearLabel true_value, false_value, done; + __ j(equal, &true_value); + __ cmp(reg, Factory::undefined_value()); + __ j(equal, &true_value); + __ test(reg, Immediate(kSmiTagMask)); + __ j(zero, &false_value); + // Check for undetectable objects by looking in the bit field in + // the map. The object has already been smi checked. + Register scratch = result; + __ mov(scratch, FieldOperand(reg, HeapObject::kMapOffset)); + __ movzx_b(scratch, FieldOperand(scratch, Map::kBitFieldOffset)); + __ test(scratch, Immediate(1 << Map::kIsUndetectable)); + __ j(not_zero, &true_value); + __ bind(&false_value); + __ mov(result, Handle<Object>(Heap::false_value())); + __ jmp(&done); + __ bind(&true_value); + __ mov(result, Handle<Object>(Heap::true_value())); + __ bind(&done); + } +} + + +void LCodeGen::DoIsNullAndBranch(LIsNullAndBranch* instr) { + Register reg = ToRegister(instr->input()); + + // TODO(fsc): If the expression is known to be a smi, then it's + // definitely not null. Jump to the false block. + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + __ cmp(reg, Factory::null_value()); + if (instr->is_strict()) { + EmitBranch(true_block, false_block, equal); + } else { + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + __ j(equal, true_label); + __ cmp(reg, Factory::undefined_value()); + __ j(equal, true_label); + __ test(reg, Immediate(kSmiTagMask)); + __ j(zero, false_label); + // Check for undetectable objects by looking in the bit field in + // the map. The object has already been smi checked. + Register scratch = ToRegister(instr->temp()); + __ mov(scratch, FieldOperand(reg, HeapObject::kMapOffset)); + __ movzx_b(scratch, FieldOperand(scratch, Map::kBitFieldOffset)); + __ test(scratch, Immediate(1 << Map::kIsUndetectable)); + EmitBranch(true_block, false_block, not_zero); + } +} + + +Condition LCodeGen::EmitIsObject(Register input, + Register temp1, + Register temp2, + Label* is_not_object, + Label* is_object) { + ASSERT(!input.is(temp1)); + ASSERT(!input.is(temp2)); + ASSERT(!temp1.is(temp2)); + + __ test(input, Immediate(kSmiTagMask)); + __ j(equal, is_not_object); + + __ cmp(input, Factory::null_value()); + __ j(equal, is_object); + + __ mov(temp1, FieldOperand(input, HeapObject::kMapOffset)); + // Undetectable objects behave like undefined. + __ movzx_b(temp2, FieldOperand(temp1, Map::kBitFieldOffset)); + __ test(temp2, Immediate(1 << Map::kIsUndetectable)); + __ j(not_zero, is_not_object); + + __ movzx_b(temp2, FieldOperand(temp1, Map::kInstanceTypeOffset)); + __ cmp(temp2, FIRST_JS_OBJECT_TYPE); + __ j(below, is_not_object); + __ cmp(temp2, LAST_JS_OBJECT_TYPE); + return below_equal; +} + + +void LCodeGen::DoIsObject(LIsObject* instr) { + Register reg = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + Register temp = ToRegister(instr->temp()); + Label is_false, is_true, done; + + Condition true_cond = EmitIsObject(reg, result, temp, &is_false, &is_true); + __ j(true_cond, &is_true); + + __ bind(&is_false); + __ mov(result, Handle<Object>(Heap::false_value())); + __ jmp(&done); + + __ bind(&is_true); + __ mov(result, Handle<Object>(Heap::true_value())); + + __ bind(&done); +} + + +void LCodeGen::DoIsObjectAndBranch(LIsObjectAndBranch* instr) { + Register reg = ToRegister(instr->input()); + Register temp = ToRegister(instr->temp()); + Register temp2 = ToRegister(instr->temp2()); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition true_cond = EmitIsObject(reg, temp, temp2, false_label, true_label); + + EmitBranch(true_block, false_block, true_cond); +} + + +void LCodeGen::DoIsSmi(LIsSmi* instr) { + Operand input = ToOperand(instr->input()); + Register result = ToRegister(instr->result()); + + ASSERT(instr->hydrogen()->value()->representation().IsTagged()); + __ test(input, Immediate(kSmiTagMask)); + __ mov(result, Handle<Object>(Heap::true_value())); + NearLabel done; + __ j(zero, &done); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoIsSmiAndBranch(LIsSmiAndBranch* instr) { + Operand input = ToOperand(instr->input()); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + __ test(input, Immediate(kSmiTagMask)); + EmitBranch(true_block, false_block, zero); +} + + +InstanceType LHasInstanceType::TestType() { + InstanceType from = hydrogen()->from(); + InstanceType to = hydrogen()->to(); + if (from == FIRST_TYPE) return to; + ASSERT(from == to || to == LAST_TYPE); + return from; +} + + + +Condition LHasInstanceType::BranchCondition() { + InstanceType from = hydrogen()->from(); + InstanceType to = hydrogen()->to(); + if (from == to) return equal; + if (to == LAST_TYPE) return above_equal; + if (from == FIRST_TYPE) return below_equal; + UNREACHABLE(); + return equal; +} + + +void LCodeGen::DoHasInstanceType(LHasInstanceType* instr) { + Register input = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + + ASSERT(instr->hydrogen()->value()->representation().IsTagged()); + __ test(input, Immediate(kSmiTagMask)); + NearLabel done, is_false; + __ j(zero, &is_false); + __ CmpObjectType(input, instr->TestType(), result); + __ j(NegateCondition(instr->BranchCondition()), &is_false); + __ mov(result, Handle<Object>(Heap::true_value())); + __ jmp(&done); + __ bind(&is_false); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoHasInstanceTypeAndBranch(LHasInstanceTypeAndBranch* instr) { + Register input = ToRegister(instr->input()); + Register temp = ToRegister(instr->temp()); + + 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); + + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, false_label); + + __ CmpObjectType(input, instr->TestType(), temp); + EmitBranch(true_block, false_block, instr->BranchCondition()); +} + + +void LCodeGen::DoHasCachedArrayIndex(LHasCachedArrayIndex* instr) { + Register input = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + + ASSERT(instr->hydrogen()->value()->representation().IsTagged()); + __ mov(result, Handle<Object>(Heap::true_value())); + __ test(FieldOperand(input, String::kHashFieldOffset), + Immediate(String::kContainsCachedArrayIndexMask)); + NearLabel done; + __ j(not_zero, &done); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoHasCachedArrayIndexAndBranch( + LHasCachedArrayIndexAndBranch* instr) { + Register input = ToRegister(instr->input()); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + __ test(FieldOperand(input, String::kHashFieldOffset), + Immediate(String::kContainsCachedArrayIndexMask)); + EmitBranch(true_block, false_block, not_equal); +} + + +// Branches to a label or falls through with the answer in the z flag. Trashes +// the temp registers, but not the input. Only input and temp2 may alias. +void LCodeGen::EmitClassOfTest(Label* is_true, + Label* is_false, + Handle<String>class_name, + Register input, + Register temp, + Register temp2) { + ASSERT(!input.is(temp)); + ASSERT(!temp.is(temp2)); // But input and temp2 may be the same register. + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, is_false); + __ CmpObjectType(input, FIRST_JS_OBJECT_TYPE, temp); + __ j(below, is_false); + + // Map is now in temp. + // Functions have class 'Function'. + __ CmpInstanceType(temp, JS_FUNCTION_TYPE); + if (class_name->IsEqualTo(CStrVector("Function"))) { + __ j(equal, is_true); + } else { + __ j(equal, is_false); + } + + // Check if the constructor in the map is a function. + __ mov(temp, FieldOperand(temp, Map::kConstructorOffset)); + + // As long as JS_FUNCTION_TYPE is the last instance type and it is + // right after LAST_JS_OBJECT_TYPE, we can avoid checking for + // LAST_JS_OBJECT_TYPE. + ASSERT(LAST_TYPE == JS_FUNCTION_TYPE); + ASSERT(JS_FUNCTION_TYPE == LAST_JS_OBJECT_TYPE + 1); + + // Objects with a non-function constructor have class 'Object'. + __ CmpObjectType(temp, JS_FUNCTION_TYPE, temp2); + if (class_name->IsEqualTo(CStrVector("Object"))) { + __ j(not_equal, is_true); + } else { + __ j(not_equal, is_false); + } + + // temp now contains the constructor function. Grab the + // instance class name from there. + __ mov(temp, FieldOperand(temp, JSFunction::kSharedFunctionInfoOffset)); + __ mov(temp, FieldOperand(temp, + SharedFunctionInfo::kInstanceClassNameOffset)); + // The class name we are testing against is a symbol because it's a literal. + // The name in the constructor is a symbol because of the way the context is + // booted. This routine isn't expected to work for random API-created + // classes and it doesn't have to because you can't access it with natives + // syntax. Since both sides are symbols it is sufficient to use an identity + // comparison. + __ cmp(temp, class_name); + // End with the answer in the z flag. +} + + +void LCodeGen::DoClassOfTest(LClassOfTest* instr) { + Register input = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + ASSERT(input.is(result)); + Register temp = ToRegister(instr->temporary()); + Handle<String> class_name = instr->hydrogen()->class_name(); + NearLabel done; + Label is_true, is_false; + + EmitClassOfTest(&is_true, &is_false, class_name, input, temp, input); + + __ j(not_equal, &is_false); + + __ bind(&is_true); + __ mov(result, Handle<Object>(Heap::true_value())); + __ jmp(&done); + + __ bind(&is_false); + __ mov(result, Handle<Object>(Heap::false_value())); + __ bind(&done); +} + + +void LCodeGen::DoClassOfTestAndBranch(LClassOfTestAndBranch* instr) { + Register input = ToRegister(instr->input()); + Register temp = ToRegister(instr->temporary()); + Register temp2 = ToRegister(instr->temporary2()); + if (input.is(temp)) { + // Swap. + Register swapper = temp; + temp = temp2; + temp2 = swapper; + } + Handle<String> class_name = instr->hydrogen()->class_name(); + + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + EmitClassOfTest(true_label, false_label, class_name, input, temp, temp2); + + EmitBranch(true_block, false_block, equal); +} + + +void LCodeGen::DoCmpMapAndBranch(LCmpMapAndBranch* instr) { + Register reg = ToRegister(instr->input()); + int true_block = instr->true_block_id(); + int false_block = instr->false_block_id(); + + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), instr->map()); + EmitBranch(true_block, false_block, equal); +} + + +void LCodeGen::DoInstanceOf(LInstanceOf* instr) { + // Object and function are in fixed registers eax and edx. + InstanceofStub stub(InstanceofStub::kArgsInRegisters); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + + NearLabel true_value, done; + __ test(eax, Operand(eax)); + __ j(zero, &true_value); + __ mov(ToRegister(instr->result()), Factory::false_value()); + __ jmp(&done); + __ bind(&true_value); + __ mov(ToRegister(instr->result()), Factory::true_value()); + __ bind(&done); +} + + +void LCodeGen::DoInstanceOfAndBranch(LInstanceOfAndBranch* instr) { + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + + InstanceofStub stub(InstanceofStub::kArgsInRegisters); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + __ test(eax, Operand(eax)); + EmitBranch(true_block, false_block, zero); +} + + +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(); + + Handle<Code> ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + Condition condition = ComputeCompareCondition(op); + if (op == Token::GT || op == Token::LTE) { + condition = ReverseCondition(condition); + } + NearLabel true_value, done; + __ test(eax, Operand(eax)); + __ j(condition, &true_value); + __ mov(ToRegister(instr->result()), Factory::false_value()); + __ jmp(&done); + __ bind(&true_value); + __ mov(ToRegister(instr->result()), Factory::true_value()); + __ bind(&done); +} + + +void LCodeGen::DoCmpTAndBranch(LCmpTAndBranch* 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<Code> ic = CompareIC::GetUninitialized(op); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + + // The compare stub expects compare condition and the input operands + // reversed for GT and LTE. + Condition condition = ComputeCompareCondition(op); + if (op == Token::GT || op == Token::LTE) { + condition = ReverseCondition(condition); + } + __ test(eax, Operand(eax)); + EmitBranch(true_block, false_block, condition); +} + + +void LCodeGen::DoReturn(LReturn* instr) { + if (FLAG_trace) { + // Preserve the return value on the stack and rely on the runtime + // call to return the value in the same register. + __ push(eax); + __ CallRuntime(Runtime::kTraceExit, 1); + } + __ mov(esp, ebp); + __ pop(ebp); + __ ret((ParameterCount() + 1) * kPointerSize); +} + + +void LCodeGen::DoLoadGlobal(LLoadGlobal* instr) { + Register result = ToRegister(instr->result()); + __ mov(result, Operand::Cell(instr->hydrogen()->cell())); + if (instr->hydrogen()->check_hole_value()) { + __ cmp(result, Factory::the_hole_value()); + DeoptimizeIf(equal, instr->environment()); + } +} + + +void LCodeGen::DoStoreGlobal(LStoreGlobal* instr) { + Register value = ToRegister(instr->input()); + __ mov(Operand::Cell(instr->hydrogen()->cell()), value); +} + + +void LCodeGen::DoLoadNamedField(LLoadNamedField* instr) { + Register object = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + if (instr->hydrogen()->is_in_object()) { + __ mov(result, FieldOperand(object, instr->hydrogen()->offset())); + } else { + __ mov(result, FieldOperand(object, JSObject::kPropertiesOffset)); + __ mov(result, FieldOperand(result, instr->hydrogen()->offset())); + } +} + + +void LCodeGen::DoLoadNamedGeneric(LLoadNamedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(eax)); + ASSERT(ToRegister(instr->result()).is(eax)); + + __ mov(ecx, instr->name()); + Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoLoadElements(LLoadElements* instr) { + ASSERT(instr->result()->Equals(instr->input())); + Register reg = ToRegister(instr->input()); + __ mov(reg, FieldOperand(reg, JSObject::kElementsOffset)); + if (FLAG_debug_code) { + NearLabel done; + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + Immediate(Factory::fixed_array_map())); + __ j(equal, &done); + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + Immediate(Factory::fixed_cow_array_map())); + __ Check(equal, "Check for fast elements failed."); + __ bind(&done); + } +} + + +void LCodeGen::DoAccessArgumentsAt(LAccessArgumentsAt* instr) { + Register arguments = ToRegister(instr->arguments()); + Register length = ToRegister(instr->length()); + Operand index = ToOperand(instr->index()); + Register result = ToRegister(instr->result()); + + __ sub(length, index); + DeoptimizeIf(below_equal, instr->environment()); + + __ mov(result, Operand(arguments, length, times_4, kPointerSize)); +} + + +void LCodeGen::DoLoadKeyedFastElement(LLoadKeyedFastElement* instr) { + Register elements = ToRegister(instr->elements()); + Register key = ToRegister(instr->key()); + Register result; + if (instr->load_result() != NULL) { + result = ToRegister(instr->load_result()); + } else { + result = ToRegister(instr->result()); + ASSERT(result.is(elements)); + } + + // Load the result. + __ mov(result, FieldOperand(elements, key, times_4, FixedArray::kHeaderSize)); + + Representation r = instr->hydrogen()->representation(); + if (r.IsInteger32()) { + // Untag and check for smi. + __ SmiUntag(result); + DeoptimizeIf(carry, instr->environment()); + } else if (r.IsDouble()) { + EmitNumberUntagD(result, + ToDoubleRegister(instr->result()), + instr->environment()); + } else { + // Check for the hole value. + ASSERT(r.IsTagged()); + __ cmp(result, Factory::the_hole_value()); + DeoptimizeIf(equal, instr->environment()); + } +} + + +void LCodeGen::DoLoadKeyedGeneric(LLoadKeyedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(edx)); + ASSERT(ToRegister(instr->key()).is(eax)); + + Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoArgumentsElements(LArgumentsElements* instr) { + Register result = ToRegister(instr->result()); + + // Check for arguments adapter frame. + Label done, adapted; + __ mov(result, Operand(ebp, StandardFrameConstants::kCallerFPOffset)); + __ mov(result, Operand(result, StandardFrameConstants::kContextOffset)); + __ cmp(Operand(result), + Immediate(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR))); + __ j(equal, &adapted); + + // No arguments adaptor frame. + __ mov(result, Operand(ebp)); + __ jmp(&done); + + // Arguments adaptor frame present. + __ bind(&adapted); + __ mov(result, Operand(ebp, StandardFrameConstants::kCallerFPOffset)); + + // Done. Pointer to topmost argument is in result. + __ bind(&done); +} + + +void LCodeGen::DoArgumentsLength(LArgumentsLength* instr) { + Operand elem = ToOperand(instr->input()); + Register result = ToRegister(instr->result()); + + Label done; + + // No arguments adaptor frame. Number of arguments is fixed. + __ cmp(ebp, elem); + __ mov(result, Immediate(scope()->num_parameters())); + __ j(equal, &done); + + // Arguments adaptor frame present. Get argument length from there. + __ mov(result, Operand(ebp, StandardFrameConstants::kCallerFPOffset)); + __ mov(result, Operand(result, + ArgumentsAdaptorFrameConstants::kLengthOffset)); + __ SmiUntag(result); + + // Done. Argument length is in result register. + __ bind(&done); +} + + +void LCodeGen::DoApplyArguments(LApplyArguments* instr) { + Register receiver = ToRegister(instr->receiver()); + ASSERT(ToRegister(instr->function()).is(edi)); + ASSERT(ToRegister(instr->result()).is(eax)); + + // If the receiver is null or undefined, we have to pass the + // global object as a receiver. + NearLabel global_receiver, receiver_ok; + __ cmp(receiver, Factory::null_value()); + __ j(equal, &global_receiver); + __ cmp(receiver, Factory::undefined_value()); + __ j(not_equal, &receiver_ok); + __ bind(&global_receiver); + __ mov(receiver, GlobalObjectOperand()); + __ bind(&receiver_ok); + + Register length = ToRegister(instr->length()); + Register elements = ToRegister(instr->elements()); + + Label invoke; + + // Copy the arguments to this function possibly from the + // adaptor frame below it. + const uint32_t kArgumentsLimit = 1 * KB; + __ cmp(length, kArgumentsLimit); + DeoptimizeIf(above, instr->environment()); + + __ push(receiver); + __ mov(receiver, length); + + // Loop through the arguments pushing them onto the execution + // stack. + Label loop; + // length is a small non-negative integer, due to the test above. + __ test(length, Operand(length)); + __ j(zero, &invoke); + __ bind(&loop); + __ push(Operand(elements, length, times_pointer_size, 1 * kPointerSize)); + __ dec(length); + __ j(not_zero, &loop); + + // Invoke the function. + __ bind(&invoke); + ASSERT(receiver.is(eax)); + v8::internal::ParameterCount actual(eax); + SafepointGenerator safepoint_generator(this, + instr->pointer_map(), + Safepoint::kNoDeoptimizationIndex); + __ InvokeFunction(edi, actual, CALL_FUNCTION, &safepoint_generator); +} + + +void LCodeGen::DoPushArgument(LPushArgument* instr) { + LOperand* argument = instr->input(); + if (argument->IsConstantOperand()) { + __ push(ToImmediate(argument)); + } else { + __ push(ToOperand(argument)); + } +} + + +void LCodeGen::DoGlobalObject(LGlobalObject* instr) { + Register result = ToRegister(instr->result()); + __ mov(result, Operand(esi, Context::SlotOffset(Context::GLOBAL_INDEX))); +} + + +void LCodeGen::DoGlobalReceiver(LGlobalReceiver* instr) { + Register result = ToRegister(instr->result()); + __ mov(result, Operand(esi, Context::SlotOffset(Context::GLOBAL_INDEX))); + __ mov(result, FieldOperand(result, GlobalObject::kGlobalReceiverOffset)); +} + + +void LCodeGen::CallKnownFunction(Handle<JSFunction> function, + int arity, + LInstruction* instr) { + // Change context if needed. + bool change_context = + (graph()->info()->closure()->context() != function->context()) || + scope()->contains_with() || + (scope()->num_heap_slots() > 0); + if (change_context) { + __ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); + } + + // Set eax to arguments count if adaption is not needed. Assumes that eax + // is available to write to at this point. + if (!function->NeedsArgumentsAdaption()) { + __ mov(eax, arity); + } + + LPointerMap* pointers = instr->pointer_map(); + RecordPosition(pointers->position()); + + // Invoke function. + if (*function == *graph()->info()->closure()) { + __ CallSelf(); + } else { + __ call(FieldOperand(edi, JSFunction::kCodeEntryOffset)); + } + + // Setup deoptimization. + RegisterLazyDeoptimization(instr); + + // Restore context. + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallConstantFunction(LCallConstantFunction* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + __ mov(edi, instr->function()); + CallKnownFunction(instr->function(), instr->arity(), instr); +} + + +void LCodeGen::DoDeferredMathAbsTaggedHeapNumber(LUnaryMathOperation* instr) { + Register input_reg = ToRegister(instr->input()); + __ cmp(FieldOperand(input_reg, HeapObject::kMapOffset), + Factory::heap_number_map()); + DeoptimizeIf(not_equal, instr->environment()); + + Label done; + Register tmp = input_reg.is(eax) ? ecx : eax; + Register tmp2 = tmp.is(ecx) ? edx : input_reg.is(ecx) ? edx : ecx; + + // Preserve the value of all registers. + __ PushSafepointRegisters(); + + Label negative; + __ mov(tmp, FieldOperand(input_reg, HeapNumber::kExponentOffset)); + // Check the sign of the argument. If the argument is positive, + // just return it. + __ test(tmp, Immediate(HeapNumber::kSignMask)); + __ j(not_zero, &negative); + __ mov(tmp, input_reg); + __ jmp(&done); + + __ bind(&negative); + + Label allocated, slow; + __ AllocateHeapNumber(tmp, tmp2, no_reg, &slow); + __ jmp(&allocated); + + // Slow case: Call the runtime system to do the number allocation. + __ bind(&slow); + + __ CallRuntimeSaveDoubles(Runtime::kAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + // Set the pointer to the new heap number in tmp. + if (!tmp.is(eax)) __ mov(tmp, eax); + + // Restore input_reg after call to runtime. + __ mov(input_reg, Operand(esp, EspIndexForPushAll(input_reg) * kPointerSize)); + + __ bind(&allocated); + __ mov(tmp2, FieldOperand(input_reg, HeapNumber::kExponentOffset)); + __ and_(tmp2, ~HeapNumber::kSignMask); + __ mov(FieldOperand(tmp, HeapNumber::kExponentOffset), tmp2); + __ mov(tmp2, FieldOperand(input_reg, HeapNumber::kMantissaOffset)); + __ mov(FieldOperand(tmp, HeapNumber::kMantissaOffset), tmp2); + + __ bind(&done); + __ mov(Operand(esp, EspIndexForPushAll(input_reg) * kPointerSize), tmp); + + __ PopSafepointRegisters(); +} + + +void LCodeGen::DoMathAbs(LUnaryMathOperation* instr) { + // Class for deferred case. + class DeferredMathAbsTaggedHeapNumber: public LDeferredCode { + public: + DeferredMathAbsTaggedHeapNumber(LCodeGen* codegen, + LUnaryMathOperation* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { + codegen()->DoDeferredMathAbsTaggedHeapNumber(instr_); + } + private: + LUnaryMathOperation* instr_; + }; + + ASSERT(instr->input()->Equals(instr->result())); + Representation r = instr->hydrogen()->value()->representation(); + + if (r.IsDouble()) { + XMMRegister scratch = xmm0; + XMMRegister input_reg = ToDoubleRegister(instr->input()); + __ pxor(scratch, scratch); + __ subsd(scratch, input_reg); + __ pand(input_reg, scratch); + } else if (r.IsInteger32()) { + Register input_reg = ToRegister(instr->input()); + __ test(input_reg, Operand(input_reg)); + Label is_positive; + __ j(not_sign, &is_positive); + __ neg(input_reg); + __ test(input_reg, Operand(input_reg)); + DeoptimizeIf(negative, instr->environment()); + __ bind(&is_positive); + } else { // Tagged case. + DeferredMathAbsTaggedHeapNumber* deferred = + new DeferredMathAbsTaggedHeapNumber(this, instr); + Label not_smi; + Register input_reg = ToRegister(instr->input()); + // Smi check. + __ test(input_reg, Immediate(kSmiTagMask)); + __ j(not_zero, deferred->entry()); + __ test(input_reg, Operand(input_reg)); + Label is_positive; + __ j(not_sign, &is_positive); + __ neg(input_reg); + + __ test(input_reg, Operand(input_reg)); + DeoptimizeIf(negative, instr->environment()); + + __ bind(&is_positive); + __ bind(deferred->exit()); + } +} + + +void LCodeGen::DoMathFloor(LUnaryMathOperation* instr) { + XMMRegister xmm_scratch = xmm0; + Register output_reg = ToRegister(instr->result()); + XMMRegister input_reg = ToDoubleRegister(instr->input()); + __ xorpd(xmm_scratch, xmm_scratch); // Zero the register. + __ ucomisd(input_reg, xmm_scratch); + + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + DeoptimizeIf(below_equal, instr->environment()); + } else { + DeoptimizeIf(below, instr->environment()); + } + + // Use truncating instruction (OK because input is positive). + __ cvttsd2si(output_reg, Operand(input_reg)); + + // Overflow is signalled with minint. + __ cmp(output_reg, 0x80000000u); + DeoptimizeIf(equal, instr->environment()); +} + + +void LCodeGen::DoMathRound(LUnaryMathOperation* instr) { + XMMRegister xmm_scratch = xmm0; + Register output_reg = ToRegister(instr->result()); + XMMRegister input_reg = ToDoubleRegister(instr->input()); + + // xmm_scratch = 0.5 + ExternalReference one_half = ExternalReference::address_of_one_half(); + __ movdbl(xmm_scratch, Operand::StaticVariable(one_half)); + + // input = input + 0.5 + __ addsd(input_reg, xmm_scratch); + + // We need to return -0 for the input range [-0.5, 0[, otherwise + // compute Math.floor(value + 0.5). + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ ucomisd(input_reg, xmm_scratch); + DeoptimizeIf(below_equal, instr->environment()); + } else { + // If we don't need to bailout on -0, we check only bailout + // on negative inputs. + __ xorpd(xmm_scratch, xmm_scratch); // Zero the register. + __ ucomisd(input_reg, xmm_scratch); + DeoptimizeIf(below, instr->environment()); + } + + // Compute Math.floor(value + 0.5). + // Use truncating instruction (OK because input is positive). + __ cvttsd2si(output_reg, Operand(input_reg)); + + // Overflow is signalled with minint. + __ cmp(output_reg, 0x80000000u); + DeoptimizeIf(equal, instr->environment()); +} + + +void LCodeGen::DoMathSqrt(LUnaryMathOperation* instr) { + XMMRegister input_reg = ToDoubleRegister(instr->input()); + ASSERT(ToDoubleRegister(instr->result()).is(input_reg)); + __ sqrtsd(input_reg, input_reg); +} + + +void LCodeGen::DoMathPowHalf(LUnaryMathOperation* instr) { + XMMRegister xmm_scratch = xmm0; + XMMRegister input_reg = ToDoubleRegister(instr->input()); + ASSERT(ToDoubleRegister(instr->result()).is(input_reg)); + ExternalReference negative_infinity = + ExternalReference::address_of_negative_infinity(); + __ movdbl(xmm_scratch, Operand::StaticVariable(negative_infinity)); + __ ucomisd(xmm_scratch, input_reg); + DeoptimizeIf(equal, instr->environment()); + __ sqrtsd(input_reg, input_reg); +} + + +void LCodeGen::DoPower(LPower* instr) { + LOperand* left = instr->left(); + LOperand* right = instr->right(); + DoubleRegister result_reg = ToDoubleRegister(instr->result()); + Representation exponent_type = instr->hydrogen()->right()->representation(); + if (exponent_type.IsDouble()) { + // It is safe to use ebx directly since the instruction is marked + // as a call. + __ PrepareCallCFunction(4, ebx); + __ movdbl(Operand(esp, 0 * kDoubleSize), ToDoubleRegister(left)); + __ movdbl(Operand(esp, 1 * kDoubleSize), ToDoubleRegister(right)); + __ CallCFunction(ExternalReference::power_double_double_function(), 4); + } else if (exponent_type.IsInteger32()) { + // It is safe to use ebx directly since the instruction is marked + // as a call. + ASSERT(!ToRegister(right).is(ebx)); + __ PrepareCallCFunction(4, ebx); + __ movdbl(Operand(esp, 0 * kDoubleSize), ToDoubleRegister(left)); + __ mov(Operand(esp, 1 * kDoubleSize), ToRegister(right)); + __ CallCFunction(ExternalReference::power_double_int_function(), 4); + } else { + ASSERT(exponent_type.IsTagged()); + CpuFeatures::Scope scope(SSE2); + Register right_reg = ToRegister(right); + + Label non_smi, call; + __ test(right_reg, Immediate(kSmiTagMask)); + __ j(not_zero, &non_smi); + __ SmiUntag(right_reg); + __ cvtsi2sd(result_reg, Operand(right_reg)); + __ jmp(&call); + + __ bind(&non_smi); + // It is safe to use ebx directly since the instruction is marked + // as a call. + ASSERT(!right_reg.is(ebx)); + __ CmpObjectType(right_reg, HEAP_NUMBER_TYPE , ebx); + DeoptimizeIf(not_equal, instr->environment()); + __ movdbl(result_reg, FieldOperand(right_reg, HeapNumber::kValueOffset)); + + __ bind(&call); + __ PrepareCallCFunction(4, ebx); + __ movdbl(Operand(esp, 0 * kDoubleSize), ToDoubleRegister(left)); + __ movdbl(Operand(esp, 1 * kDoubleSize), result_reg); + __ CallCFunction(ExternalReference::power_double_double_function(), 4); + } + + // Return value is in st(0) on ia32. + // Store it into the (fixed) result register. + __ sub(Operand(esp), Immediate(kDoubleSize)); + __ fstp_d(Operand(esp, 0)); + __ movdbl(result_reg, Operand(esp, 0)); + __ add(Operand(esp), Immediate(kDoubleSize)); +} + + +void LCodeGen::DoMathLog(LUnaryMathOperation* instr) { + ASSERT(ToDoubleRegister(instr->result()).is(xmm1)); + TranscendentalCacheStub stub(TranscendentalCache::LOG, + TranscendentalCacheStub::UNTAGGED); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoMathCos(LUnaryMathOperation* instr) { + ASSERT(ToDoubleRegister(instr->result()).is(xmm1)); + TranscendentalCacheStub stub(TranscendentalCache::COS, + TranscendentalCacheStub::UNTAGGED); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoMathSin(LUnaryMathOperation* instr) { + ASSERT(ToDoubleRegister(instr->result()).is(xmm1)); + TranscendentalCacheStub stub(TranscendentalCache::SIN, + TranscendentalCacheStub::UNTAGGED); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoUnaryMathOperation(LUnaryMathOperation* instr) { + switch (instr->op()) { + case kMathAbs: + DoMathAbs(instr); + break; + case kMathFloor: + DoMathFloor(instr); + break; + case kMathRound: + DoMathRound(instr); + break; + case kMathSqrt: + DoMathSqrt(instr); + break; + case kMathPowHalf: + DoMathPowHalf(instr); + break; + case kMathCos: + DoMathCos(instr); + break; + case kMathSin: + DoMathSin(instr); + break; + case kMathLog: + DoMathLog(instr); + break; + + default: + UNREACHABLE(); + } +} + + +void LCodeGen::DoCallKeyed(LCallKeyed* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + + int arity = instr->arity(); + Handle<Code> ic = StubCache::ComputeKeyedCallInitialize(arity, NOT_IN_LOOP); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallNamed(LCallNamed* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + + int arity = instr->arity(); + Handle<Code> ic = StubCache::ComputeCallInitialize(arity, NOT_IN_LOOP); + __ mov(ecx, instr->name()); + CallCode(ic, RelocInfo::CODE_TARGET, instr); + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallFunction(LCallFunction* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + + int arity = instr->arity(); + CallFunctionStub stub(arity, NOT_IN_LOOP, RECEIVER_MIGHT_BE_VALUE); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + __ Drop(1); + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallGlobal(LCallGlobal* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + + int arity = instr->arity(); + Handle<Code> ic = StubCache::ComputeCallInitialize(arity, NOT_IN_LOOP); + __ mov(ecx, instr->name()); + CallCode(ic, RelocInfo::CODE_TARGET_CONTEXT, instr); + __ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); +} + + +void LCodeGen::DoCallKnownGlobal(LCallKnownGlobal* instr) { + ASSERT(ToRegister(instr->result()).is(eax)); + __ mov(edi, instr->target()); + CallKnownFunction(instr->target(), instr->arity(), instr); +} + + +void LCodeGen::DoCallNew(LCallNew* instr) { + ASSERT(ToRegister(instr->input()).is(edi)); + ASSERT(ToRegister(instr->result()).is(eax)); + + Handle<Code> builtin(Builtins::builtin(Builtins::JSConstructCall)); + __ Set(eax, Immediate(instr->arity())); + CallCode(builtin, RelocInfo::CONSTRUCT_CALL, instr); +} + + +void LCodeGen::DoCallRuntime(LCallRuntime* instr) { + CallRuntime(instr->function(), instr->arity(), instr); +} + + +void LCodeGen::DoStoreNamedField(LStoreNamedField* instr) { + Register object = ToRegister(instr->object()); + Register value = ToRegister(instr->value()); + int offset = instr->offset(); + + if (!instr->transition().is_null()) { + __ mov(FieldOperand(object, HeapObject::kMapOffset), instr->transition()); + } + + // Do the store. + if (instr->is_in_object()) { + __ mov(FieldOperand(object, offset), value); + if (instr->needs_write_barrier()) { + Register temp = ToRegister(instr->temp()); + // Update the write barrier for the object for in-object properties. + __ RecordWrite(object, offset, value, temp); + } + } else { + Register temp = ToRegister(instr->temp()); + __ mov(temp, FieldOperand(object, JSObject::kPropertiesOffset)); + __ mov(FieldOperand(temp, offset), value); + if (instr->needs_write_barrier()) { + // Update the write barrier for the properties array. + // object is used as a scratch register. + __ RecordWrite(temp, offset, value, object); + } + } +} + + +void LCodeGen::DoStoreNamedGeneric(LStoreNamedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(edx)); + ASSERT(ToRegister(instr->value()).is(eax)); + + __ mov(ecx, instr->name()); + Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoBoundsCheck(LBoundsCheck* instr) { + __ cmp(ToRegister(instr->index()), ToOperand(instr->length())); + DeoptimizeIf(above_equal, instr->environment()); +} + + +void LCodeGen::DoStoreKeyedFastElement(LStoreKeyedFastElement* instr) { + Register value = ToRegister(instr->value()); + Register elements = ToRegister(instr->object()); + Register key = instr->key()->IsRegister() ? ToRegister(instr->key()) : no_reg; + + // Do the store. + if (instr->key()->IsConstantOperand()) { + ASSERT(!instr->hydrogen()->NeedsWriteBarrier()); + LConstantOperand* const_operand = LConstantOperand::cast(instr->key()); + int offset = + ToInteger32(const_operand) * kPointerSize + FixedArray::kHeaderSize; + __ mov(FieldOperand(elements, offset), value); + } else { + __ mov(FieldOperand(elements, key, times_4, FixedArray::kHeaderSize), + value); + } + + // Update the write barrier unless we're certain that we're storing a smi. + if (instr->hydrogen()->NeedsWriteBarrier()) { + // Compute address of modified element and store it into key register. + __ lea(key, FieldOperand(elements, key, times_4, FixedArray::kHeaderSize)); + __ RecordWrite(elements, key, value); + } +} + + +void LCodeGen::DoStoreKeyedGeneric(LStoreKeyedGeneric* instr) { + ASSERT(ToRegister(instr->object()).is(edx)); + ASSERT(ToRegister(instr->key()).is(ecx)); + ASSERT(ToRegister(instr->value()).is(eax)); + + Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize)); + CallCode(ic, RelocInfo::CODE_TARGET, instr); +} + + +void LCodeGen::DoInteger32ToDouble(LInteger32ToDouble* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister() || input->IsStackSlot()); + LOperand* output = instr->result(); + ASSERT(output->IsDoubleRegister()); + __ cvtsi2sd(ToDoubleRegister(output), ToOperand(input)); +} + + +void LCodeGen::DoNumberTagI(LNumberTagI* instr) { + class DeferredNumberTagI: public LDeferredCode { + public: + DeferredNumberTagI(LCodeGen* codegen, LNumberTagI* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredNumberTagI(instr_); } + private: + LNumberTagI* instr_; + }; + + LOperand* input = instr->input(); + ASSERT(input->IsRegister() && input->Equals(instr->result())); + Register reg = ToRegister(input); + + DeferredNumberTagI* deferred = new DeferredNumberTagI(this, instr); + __ SmiTag(reg); + __ j(overflow, deferred->entry()); + __ bind(deferred->exit()); +} + + +void LCodeGen::DoDeferredNumberTagI(LNumberTagI* instr) { + Label slow; + Register reg = ToRegister(instr->input()); + Register tmp = reg.is(eax) ? ecx : eax; + + // Preserve the value of all registers. + __ PushSafepointRegisters(); + + // There was overflow, so bits 30 and 31 of the original integer + // disagree. Try to allocate a heap number in new space and store + // the value in there. If that fails, call the runtime system. + NearLabel done; + __ SmiUntag(reg); + __ xor_(reg, 0x80000000); + __ cvtsi2sd(xmm0, Operand(reg)); + if (FLAG_inline_new) { + __ AllocateHeapNumber(reg, tmp, no_reg, &slow); + __ jmp(&done); + } + + // Slow case: Call the runtime system to do the number allocation. + __ bind(&slow); + + // TODO(3095996): Put a valid pointer value in the stack slot where the result + // register is stored, as this register is in the pointer map, but contains an + // integer value. + __ mov(Operand(esp, EspIndexForPushAll(reg) * kPointerSize), Immediate(0)); + + __ CallRuntimeSaveDoubles(Runtime::kAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + if (!reg.is(eax)) __ mov(reg, eax); + + // Done. Put the value in xmm0 into the value of the allocated heap + // number. + __ bind(&done); + __ movdbl(FieldOperand(reg, HeapNumber::kValueOffset), xmm0); + __ mov(Operand(esp, EspIndexForPushAll(reg) * kPointerSize), reg); + __ PopSafepointRegisters(); +} + + +void LCodeGen::DoNumberTagD(LNumberTagD* instr) { + class DeferredNumberTagD: public LDeferredCode { + public: + DeferredNumberTagD(LCodeGen* codegen, LNumberTagD* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredNumberTagD(instr_); } + private: + LNumberTagD* instr_; + }; + + XMMRegister input_reg = ToDoubleRegister(instr->input()); + Register reg = ToRegister(instr->result()); + Register tmp = ToRegister(instr->temp()); + + DeferredNumberTagD* deferred = new DeferredNumberTagD(this, instr); + if (FLAG_inline_new) { + __ AllocateHeapNumber(reg, tmp, no_reg, deferred->entry()); + } else { + __ jmp(deferred->entry()); + } + __ bind(deferred->exit()); + __ movdbl(FieldOperand(reg, HeapNumber::kValueOffset), input_reg); +} + + +void LCodeGen::DoDeferredNumberTagD(LNumberTagD* instr) { + // TODO(3095996): Get rid of this. For now, we need to make the + // result register contain a valid pointer because it is already + // contained in the register pointer map. + Register reg = ToRegister(instr->result()); + __ Set(reg, Immediate(0)); + + __ PushSafepointRegisters(); + __ CallRuntimeSaveDoubles(Runtime::kAllocateHeapNumber); + RecordSafepointWithRegisters( + instr->pointer_map(), 0, Safepoint::kNoDeoptimizationIndex); + __ mov(Operand(esp, EspIndexForPushAll(reg) * kPointerSize), eax); + __ PopSafepointRegisters(); +} + + +void LCodeGen::DoSmiTag(LSmiTag* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister() && input->Equals(instr->result())); + ASSERT(!instr->hydrogen_value()->CheckFlag(HValue::kCanOverflow)); + __ SmiTag(ToRegister(input)); +} + + +void LCodeGen::DoSmiUntag(LSmiUntag* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister() && input->Equals(instr->result())); + if (instr->needs_check()) { + __ test(ToRegister(input), Immediate(kSmiTagMask)); + DeoptimizeIf(not_zero, instr->environment()); + } + __ SmiUntag(ToRegister(input)); +} + + +void LCodeGen::EmitNumberUntagD(Register input_reg, + XMMRegister result_reg, + LEnvironment* env) { + NearLabel load_smi, heap_number, done; + + // Smi check. + __ test(input_reg, Immediate(kSmiTagMask)); + __ j(zero, &load_smi, not_taken); + + // Heap number map check. + __ cmp(FieldOperand(input_reg, HeapObject::kMapOffset), + Factory::heap_number_map()); + __ j(equal, &heap_number); + + __ cmp(input_reg, Factory::undefined_value()); + DeoptimizeIf(not_equal, env); + + // Convert undefined to NaN. + __ push(input_reg); + __ mov(input_reg, Factory::nan_value()); + __ movdbl(result_reg, FieldOperand(input_reg, HeapNumber::kValueOffset)); + __ pop(input_reg); + __ jmp(&done); + + // Heap number to XMM conversion. + __ bind(&heap_number); + __ movdbl(result_reg, FieldOperand(input_reg, HeapNumber::kValueOffset)); + __ jmp(&done); + + // Smi to XMM conversion + __ bind(&load_smi); + __ SmiUntag(input_reg); // Untag smi before converting to float. + __ cvtsi2sd(result_reg, Operand(input_reg)); + __ SmiTag(input_reg); // Retag smi. + __ bind(&done); +} + + +class DeferredTaggedToI: public LDeferredCode { + public: + DeferredTaggedToI(LCodeGen* codegen, LTaggedToI* instr) + : LDeferredCode(codegen), instr_(instr) { } + virtual void Generate() { codegen()->DoDeferredTaggedToI(instr_); } + private: + LTaggedToI* instr_; +}; + + +void LCodeGen::DoDeferredTaggedToI(LTaggedToI* instr) { + NearLabel done, heap_number; + Register input_reg = ToRegister(instr->input()); + + // Heap number map check. + __ cmp(FieldOperand(input_reg, HeapObject::kMapOffset), + Factory::heap_number_map()); + + if (instr->truncating()) { + __ j(equal, &heap_number); + // Check for undefined. Undefined is converted to zero for truncating + // conversions. + __ cmp(input_reg, Factory::undefined_value()); + DeoptimizeIf(not_equal, instr->environment()); + __ mov(input_reg, 0); + __ jmp(&done); + + __ bind(&heap_number); + if (CpuFeatures::IsSupported(SSE3)) { + CpuFeatures::Scope scope(SSE3); + NearLabel convert; + // Use more powerful conversion when sse3 is available. + // Load x87 register with heap number. + __ fld_d(FieldOperand(input_reg, HeapNumber::kValueOffset)); + // Get exponent alone and check for too-big exponent. + __ mov(input_reg, FieldOperand(input_reg, HeapNumber::kExponentOffset)); + __ and_(input_reg, HeapNumber::kExponentMask); + const uint32_t kTooBigExponent = + (HeapNumber::kExponentBias + 63) << HeapNumber::kExponentShift; + __ cmp(Operand(input_reg), Immediate(kTooBigExponent)); + __ j(less, &convert); + // Pop FPU stack before deoptimizing. + __ ffree(0); + __ fincstp(); + DeoptimizeIf(no_condition, instr->environment()); + + // Reserve space for 64 bit answer. + __ bind(&convert); + __ sub(Operand(esp), Immediate(kDoubleSize)); + // Do conversion, which cannot fail because we checked the exponent. + __ fisttp_d(Operand(esp, 0)); + __ mov(input_reg, Operand(esp, 0)); // Low word of answer is the result. + __ add(Operand(esp), Immediate(kDoubleSize)); + } else { + NearLabel deopt; + XMMRegister xmm_temp = ToDoubleRegister(instr->temp()); + __ movdbl(xmm0, FieldOperand(input_reg, HeapNumber::kValueOffset)); + __ cvttsd2si(input_reg, Operand(xmm0)); + __ cmp(input_reg, 0x80000000u); + __ j(not_equal, &done); + // Check if the input was 0x8000000 (kMinInt). + // If no, then we got an overflow and we deoptimize. + ExternalReference min_int = ExternalReference::address_of_min_int(); + __ movdbl(xmm_temp, Operand::StaticVariable(min_int)); + __ ucomisd(xmm_temp, xmm0); + DeoptimizeIf(not_equal, instr->environment()); + DeoptimizeIf(parity_even, instr->environment()); // NaN. + } + } else { + // Deoptimize if we don't have a heap number. + DeoptimizeIf(not_equal, instr->environment()); + + XMMRegister xmm_temp = ToDoubleRegister(instr->temp()); + __ movdbl(xmm0, FieldOperand(input_reg, HeapNumber::kValueOffset)); + __ cvttsd2si(input_reg, Operand(xmm0)); + __ cvtsi2sd(xmm_temp, Operand(input_reg)); + __ ucomisd(xmm0, xmm_temp); + DeoptimizeIf(not_equal, instr->environment()); + DeoptimizeIf(parity_even, instr->environment()); // NaN. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + __ test(input_reg, Operand(input_reg)); + __ j(not_zero, &done); + __ movmskpd(input_reg, xmm0); + __ and_(input_reg, 1); + DeoptimizeIf(not_zero, instr->environment()); + } + } + __ bind(&done); +} + + +void LCodeGen::DoTaggedToI(LTaggedToI* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + ASSERT(input->Equals(instr->result())); + + Register input_reg = ToRegister(input); + + DeferredTaggedToI* deferred = new DeferredTaggedToI(this, instr); + + // Smi check. + __ test(input_reg, Immediate(kSmiTagMask)); + __ j(not_zero, deferred->entry()); + + // Smi to int32 conversion + __ SmiUntag(input_reg); // Untag smi. + + __ bind(deferred->exit()); +} + + +void LCodeGen::DoNumberUntagD(LNumberUntagD* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + LOperand* result = instr->result(); + ASSERT(result->IsDoubleRegister()); + + Register input_reg = ToRegister(input); + XMMRegister result_reg = ToDoubleRegister(result); + + EmitNumberUntagD(input_reg, result_reg, instr->environment()); +} + + +void LCodeGen::DoDoubleToI(LDoubleToI* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsDoubleRegister()); + LOperand* result = instr->result(); + ASSERT(result->IsRegister()); + + XMMRegister input_reg = ToDoubleRegister(input); + Register result_reg = ToRegister(result); + + if (instr->truncating()) { + // Performs a truncating conversion of a floating point number as used by + // the JS bitwise operations. + __ cvttsd2si(result_reg, Operand(input_reg)); + __ cmp(result_reg, 0x80000000u); + if (CpuFeatures::IsSupported(SSE3)) { + // This will deoptimize if the exponent of the input in out of range. + CpuFeatures::Scope scope(SSE3); + NearLabel convert, done; + __ j(not_equal, &done); + __ sub(Operand(esp), Immediate(kDoubleSize)); + __ movdbl(Operand(esp, 0), input_reg); + // Get exponent alone and check for too-big exponent. + __ mov(result_reg, Operand(esp, sizeof(int32_t))); + __ and_(result_reg, HeapNumber::kExponentMask); + const uint32_t kTooBigExponent = + (HeapNumber::kExponentBias + 63) << HeapNumber::kExponentShift; + __ cmp(Operand(result_reg), Immediate(kTooBigExponent)); + __ j(less, &convert); + __ add(Operand(esp), Immediate(kDoubleSize)); + DeoptimizeIf(no_condition, instr->environment()); + __ bind(&convert); + // Do conversion, which cannot fail because we checked the exponent. + __ fld_d(Operand(esp, 0)); + __ fisttp_d(Operand(esp, 0)); + __ mov(result_reg, Operand(esp, 0)); // Low word of answer is the result. + __ add(Operand(esp), Immediate(kDoubleSize)); + __ bind(&done); + } else { + // This will bail out if the input was not in the int32 range (or, + // unfortunately, if the input was 0x80000000). + DeoptimizeIf(equal, instr->environment()); + } + } else { + NearLabel done; + __ cvttsd2si(result_reg, Operand(input_reg)); + __ cvtsi2sd(xmm0, Operand(result_reg)); + __ ucomisd(xmm0, input_reg); + DeoptimizeIf(not_equal, instr->environment()); + DeoptimizeIf(parity_even, instr->environment()); // NaN. + if (instr->hydrogen()->CheckFlag(HValue::kBailoutOnMinusZero)) { + // The integer converted back is equal to the original. We + // only have to test if we got -0 as an input. + __ test(result_reg, Operand(result_reg)); + __ j(not_zero, &done); + __ movmskpd(result_reg, input_reg); + // Bit 0 contains the sign of the double in input_reg. + // If input was positive, we are ok and return 0, otherwise + // deoptimize. + __ and_(result_reg, 1); + DeoptimizeIf(not_zero, instr->environment()); + } + __ bind(&done); + } +} + + +void LCodeGen::DoCheckSmi(LCheckSmi* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + __ test(ToRegister(input), Immediate(kSmiTagMask)); + DeoptimizeIf(instr->condition(), instr->environment()); +} + + +void LCodeGen::DoCheckInstanceType(LCheckInstanceType* instr) { + Register input = ToRegister(instr->input()); + Register temp = ToRegister(instr->temp()); + InstanceType first = instr->hydrogen()->first(); + InstanceType last = instr->hydrogen()->last(); + + __ test(input, Immediate(kSmiTagMask)); + DeoptimizeIf(zero, instr->environment()); + + __ mov(temp, FieldOperand(input, HeapObject::kMapOffset)); + __ cmpb(FieldOperand(temp, Map::kInstanceTypeOffset), + static_cast<int8_t>(first)); + + // If there is only one type in the interval check for equality. + if (first == last) { + DeoptimizeIf(not_equal, instr->environment()); + } else { + DeoptimizeIf(below, instr->environment()); + // Omit check for the last type. + if (last != LAST_TYPE) { + __ cmpb(FieldOperand(temp, Map::kInstanceTypeOffset), + static_cast<int8_t>(last)); + DeoptimizeIf(above, instr->environment()); + } + } +} + + +void LCodeGen::DoCheckFunction(LCheckFunction* instr) { + ASSERT(instr->input()->IsRegister()); + Register reg = ToRegister(instr->input()); + __ cmp(reg, instr->hydrogen()->target()); + DeoptimizeIf(not_equal, instr->environment()); +} + + +void LCodeGen::DoCheckMap(LCheckMap* instr) { + LOperand* input = instr->input(); + ASSERT(input->IsRegister()); + Register reg = ToRegister(input); + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + instr->hydrogen()->map()); + DeoptimizeIf(not_equal, instr->environment()); +} + + +void LCodeGen::LoadPrototype(Register result, Handle<JSObject> prototype) { + if (Heap::InNewSpace(*prototype)) { + Handle<JSGlobalPropertyCell> cell = + Factory::NewJSGlobalPropertyCell(prototype); + __ mov(result, Operand::Cell(cell)); + } else { + __ mov(result, prototype); + } +} + + +void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) { + Register reg = ToRegister(instr->temp()); + + Handle<JSObject> holder = instr->holder(); + Handle<Map> receiver_map = instr->receiver_map(); + Handle<JSObject> current_prototype(JSObject::cast(receiver_map->prototype())); + + // Load prototype object. + LoadPrototype(reg, current_prototype); + + // Check prototype maps up to the holder. + while (!current_prototype.is_identical_to(holder)) { + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + Handle<Map>(current_prototype->map())); + DeoptimizeIf(not_equal, instr->environment()); + current_prototype = + Handle<JSObject>(JSObject::cast(current_prototype->GetPrototype())); + // Load next prototype object. + LoadPrototype(reg, current_prototype); + } + + // Check the holder map. + __ cmp(FieldOperand(reg, HeapObject::kMapOffset), + Handle<Map>(current_prototype->map())); + DeoptimizeIf(not_equal, instr->environment()); +} + + +void LCodeGen::DoArrayLiteral(LArrayLiteral* instr) { + // Setup the parameters to the stub/runtime call. + __ mov(eax, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ push(FieldOperand(eax, JSFunction::kLiteralsOffset)); + __ push(Immediate(Smi::FromInt(instr->hydrogen()->literal_index()))); + __ push(Immediate(instr->hydrogen()->constant_elements())); + + // Pick the right runtime function or stub to call. + int length = instr->hydrogen()->length(); + if (instr->hydrogen()->IsCopyOnWrite()) { + ASSERT(instr->hydrogen()->depth() == 1); + FastCloneShallowArrayStub::Mode mode = + FastCloneShallowArrayStub::COPY_ON_WRITE_ELEMENTS; + FastCloneShallowArrayStub stub(mode, length); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + } else if (instr->hydrogen()->depth() > 1) { + CallRuntime(Runtime::kCreateArrayLiteral, 3, instr); + } else if (length > FastCloneShallowArrayStub::kMaximumClonedLength) { + CallRuntime(Runtime::kCreateArrayLiteralShallow, 3, instr); + } else { + FastCloneShallowArrayStub::Mode mode = + FastCloneShallowArrayStub::CLONE_ELEMENTS; + FastCloneShallowArrayStub stub(mode, length); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + } +} + + +void LCodeGen::DoObjectLiteral(LObjectLiteral* instr) { + // Setup the parameters to the stub/runtime call. + __ mov(eax, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ push(FieldOperand(eax, JSFunction::kLiteralsOffset)); + __ push(Immediate(Smi::FromInt(instr->hydrogen()->literal_index()))); + __ push(Immediate(instr->hydrogen()->constant_properties())); + __ push(Immediate(Smi::FromInt(instr->hydrogen()->fast_elements() ? 1 : 0))); + + // Pick the right runtime function or stub to call. + if (instr->hydrogen()->depth() > 1) { + CallRuntime(Runtime::kCreateObjectLiteral, 4, instr); + } else { + CallRuntime(Runtime::kCreateObjectLiteralShallow, 4, instr); + } +} + + +void LCodeGen::DoRegExpLiteral(LRegExpLiteral* instr) { + NearLabel materialized; + // Registers will be used as follows: + // edi = JS function. + // ecx = literals array. + // ebx = regexp literal. + // eax = regexp literal clone. + __ mov(edi, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ mov(ecx, FieldOperand(edi, JSFunction::kLiteralsOffset)); + int literal_offset = FixedArray::kHeaderSize + + instr->hydrogen()->literal_index() * kPointerSize; + __ mov(ebx, FieldOperand(ecx, literal_offset)); + __ cmp(ebx, Factory::undefined_value()); + __ j(not_equal, &materialized); + + // Create regexp literal using runtime function + // Result will be in eax. + __ push(ecx); + __ push(Immediate(Smi::FromInt(instr->hydrogen()->literal_index()))); + __ push(Immediate(instr->hydrogen()->pattern())); + __ push(Immediate(instr->hydrogen()->flags())); + CallRuntime(Runtime::kMaterializeRegExpLiteral, 4, instr); + __ mov(ebx, eax); + + __ bind(&materialized); + int size = JSRegExp::kSize + JSRegExp::kInObjectFieldCount * kPointerSize; + Label allocated, runtime_allocate; + __ AllocateInNewSpace(size, eax, ecx, edx, &runtime_allocate, TAG_OBJECT); + __ jmp(&allocated); + + __ bind(&runtime_allocate); + __ push(ebx); + __ push(Immediate(Smi::FromInt(size))); + CallRuntime(Runtime::kAllocateInNewSpace, 1, instr); + __ pop(ebx); + + __ bind(&allocated); + // Copy the content into the newly allocated memory. + // (Unroll copy loop once for better throughput). + for (int i = 0; i < size - kPointerSize; i += 2 * kPointerSize) { + __ mov(edx, FieldOperand(ebx, i)); + __ mov(ecx, FieldOperand(ebx, i + kPointerSize)); + __ mov(FieldOperand(eax, i), edx); + __ mov(FieldOperand(eax, i + kPointerSize), ecx); + } + if ((size % (2 * kPointerSize)) != 0) { + __ mov(edx, FieldOperand(ebx, size - kPointerSize)); + __ mov(FieldOperand(eax, size - kPointerSize), edx); + } +} + + +void LCodeGen::DoFunctionLiteral(LFunctionLiteral* instr) { + // Use the fast case closure allocation code that allocates in new + // space for nested functions that don't need literals cloning. + Handle<SharedFunctionInfo> shared_info = instr->shared_info(); + bool pretenure = !instr->hydrogen()->pretenure(); + if (shared_info->num_literals() == 0 && !pretenure) { + FastNewClosureStub stub; + __ push(Immediate(shared_info)); + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + } else { + __ push(esi); + __ push(Immediate(shared_info)); + __ push(Immediate(pretenure + ? Factory::true_value() + : Factory::false_value())); + CallRuntime(Runtime::kNewClosure, 3, instr); + } +} + + +void LCodeGen::DoTypeof(LTypeof* instr) { + LOperand* input = instr->input(); + if (input->IsConstantOperand()) { + __ push(ToImmediate(input)); + } else { + __ push(ToOperand(input)); + } + CallRuntime(Runtime::kTypeof, 1, instr); +} + + +void LCodeGen::DoTypeofIs(LTypeofIs* instr) { + Register input = ToRegister(instr->input()); + Register result = ToRegister(instr->result()); + Label true_label; + Label false_label; + NearLabel done; + + Condition final_branch_condition = EmitTypeofIs(&true_label, + &false_label, + input, + instr->type_literal()); + __ j(final_branch_condition, &true_label); + __ bind(&false_label); + __ mov(result, Handle<Object>(Heap::false_value())); + __ jmp(&done); + + __ bind(&true_label); + __ mov(result, Handle<Object>(Heap::true_value())); + + __ bind(&done); +} + + +void LCodeGen::DoTypeofIsAndBranch(LTypeofIsAndBranch* instr) { + Register input = ToRegister(instr->input()); + int true_block = chunk_->LookupDestination(instr->true_block_id()); + int false_block = chunk_->LookupDestination(instr->false_block_id()); + Label* true_label = chunk_->GetAssemblyLabel(true_block); + Label* false_label = chunk_->GetAssemblyLabel(false_block); + + Condition final_branch_condition = EmitTypeofIs(true_label, + false_label, + input, + instr->type_literal()); + + EmitBranch(true_block, false_block, final_branch_condition); +} + + +Condition LCodeGen::EmitTypeofIs(Label* true_label, + Label* false_label, + Register input, + Handle<String> type_name) { + Condition final_branch_condition = no_condition; + if (type_name->Equals(Heap::number_symbol())) { + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, true_label); + __ cmp(FieldOperand(input, HeapObject::kMapOffset), + Factory::heap_number_map()); + final_branch_condition = equal; + + } else if (type_name->Equals(Heap::string_symbol())) { + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, false_label); + __ mov(input, FieldOperand(input, HeapObject::kMapOffset)); + __ test_b(FieldOperand(input, Map::kBitFieldOffset), + 1 << Map::kIsUndetectable); + __ j(not_zero, false_label); + __ CmpInstanceType(input, FIRST_NONSTRING_TYPE); + final_branch_condition = below; + + } else if (type_name->Equals(Heap::boolean_symbol())) { + __ cmp(input, Handle<Object>(Heap::true_value())); + __ j(equal, true_label); + __ cmp(input, Handle<Object>(Heap::false_value())); + final_branch_condition = equal; + + } else if (type_name->Equals(Heap::undefined_symbol())) { + __ cmp(input, Factory::undefined_value()); + __ j(equal, true_label); + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, false_label); + // Check for undetectable objects => true. + __ mov(input, FieldOperand(input, HeapObject::kMapOffset)); + __ test_b(FieldOperand(input, Map::kBitFieldOffset), + 1 << Map::kIsUndetectable); + final_branch_condition = not_zero; + + } else if (type_name->Equals(Heap::function_symbol())) { + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, false_label); + __ CmpObjectType(input, JS_FUNCTION_TYPE, input); + __ j(equal, true_label); + // Regular expressions => 'function' (they are callable). + __ CmpInstanceType(input, JS_REGEXP_TYPE); + final_branch_condition = equal; + + } else if (type_name->Equals(Heap::object_symbol())) { + __ test(input, Immediate(kSmiTagMask)); + __ j(zero, false_label); + __ cmp(input, Factory::null_value()); + __ j(equal, true_label); + // Regular expressions => 'function', not 'object'. + __ CmpObjectType(input, JS_REGEXP_TYPE, input); + __ j(equal, false_label); + // Check for undetectable objects => false. + __ test_b(FieldOperand(input, Map::kBitFieldOffset), + 1 << Map::kIsUndetectable); + __ j(not_zero, false_label); + // Check for JS objects => true. + __ CmpInstanceType(input, FIRST_JS_OBJECT_TYPE); + __ j(below, false_label); + __ CmpInstanceType(input, LAST_JS_OBJECT_TYPE); + final_branch_condition = below_equal; + + } else { + final_branch_condition = not_equal; + __ jmp(false_label); + // A dead branch instruction will be generated after this point. + } + + return final_branch_condition; +} + + +void LCodeGen::DoLazyBailout(LLazyBailout* instr) { + // No code for lazy bailout instruction. Used to capture environment after a + // call for populating the safepoint data with deoptimization data. +} + + +void LCodeGen::DoDeoptimize(LDeoptimize* instr) { + DeoptimizeIf(no_condition, instr->environment()); +} + + +void LCodeGen::DoDeleteProperty(LDeleteProperty* instr) { + LOperand* obj = instr->object(); + LOperand* key = instr->key(); + __ push(ToOperand(obj)); + if (key->IsConstantOperand()) { + __ push(ToImmediate(key)); + } else { + __ push(ToOperand(key)); + } + RecordPosition(instr->pointer_map()->position()); + SafepointGenerator safepoint_generator(this, + instr->pointer_map(), + Safepoint::kNoDeoptimizationIndex); + __ InvokeBuiltin(Builtins::DELETE, CALL_FUNCTION, &safepoint_generator); +} + + +void LCodeGen::DoStackCheck(LStackCheck* instr) { + // Perform stack overflow check. + NearLabel done; + ExternalReference stack_limit = ExternalReference::address_of_stack_limit(); + __ cmp(esp, Operand::StaticVariable(stack_limit)); + __ j(above_equal, &done); + + StackCheckStub stub; + CallCode(stub.GetCode(), RelocInfo::CODE_TARGET, instr); + __ bind(&done); +} + + +void LCodeGen::DoOsrEntry(LOsrEntry* instr) { + // This is a pseudo-instruction that ensures that the environment here is + // properly registered for deoptimization and records the assembler's PC + // offset. + LEnvironment* environment = instr->environment(); + environment->SetSpilledRegisters(instr->SpilledRegisterArray(), + instr->SpilledDoubleRegisterArray()); + + // If the environment were already registered, we would have no way of + // backpatching it with the spill slot operands. + ASSERT(!environment->HasBeenRegistered()); + RegisterEnvironmentForDeoptimization(environment); + ASSERT(osr_pc_offset_ == -1); + osr_pc_offset_ = masm()->pc_offset(); +} + + +#undef __ + +} } // namespace v8::internal diff --git a/src/ia32/lithium-codegen-ia32.h b/src/ia32/lithium-codegen-ia32.h new file mode 100644 index 00000000..6d8173a1 --- /dev/null +++ b/src/ia32/lithium-codegen-ia32.h @@ -0,0 +1,265 @@ +// Copyright 2010 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. + +#ifndef V8_IA32_LITHIUM_CODEGEN_IA32_H_ +#define V8_IA32_LITHIUM_CODEGEN_IA32_H_ + +#include "ia32/lithium-ia32.h" + +#include "checks.h" +#include "deoptimizer.h" +#include "safepoint-table.h" +#include "scopes.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LDeferredCode; +class SafepointGenerator; + + +class LCodeGen BASE_EMBEDDED { + public: + LCodeGen(LChunk* chunk, MacroAssembler* assembler, CompilationInfo* info) + : chunk_(chunk), + masm_(assembler), + info_(info), + current_block_(-1), + current_instruction_(-1), + instructions_(chunk->instructions()), + deoptimizations_(4), + deoptimization_literals_(8), + inlined_function_count_(0), + scope_(chunk->graph()->info()->scope()), + status_(UNUSED), + deferred_(8), + osr_pc_offset_(-1) { + PopulateDeoptimizationLiteralsWithInlinedFunctions(); + } + + // Try to generate code for the entire chunk, but it may fail if the + // chunk contains constructs we cannot handle. Returns true if the + // code generation attempt succeeded. + bool GenerateCode(); + + // Finish the code by setting stack height, safepoint, and bailout + // information on it. + void FinishCode(Handle<Code> code); + + // Deferred code support. + void DoDeferredNumberTagD(LNumberTagD* instr); + void DoDeferredNumberTagI(LNumberTagI* instr); + void DoDeferredTaggedToI(LTaggedToI* instr); + void DoDeferredMathAbsTaggedHeapNumber(LUnaryMathOperation* instr); + void DoDeferredStackCheck(LGoto* instr); + + // Parallel move support. + void DoParallelMove(LParallelMove* move); + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) void Do##type(L##type* node); + LITHIUM_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + private: + enum Status { + UNUSED, + GENERATING, + DONE, + ABORTED + }; + + bool is_unused() const { return status_ == UNUSED; } + bool is_generating() const { return status_ == GENERATING; } + bool is_done() const { return status_ == DONE; } + bool is_aborted() const { return status_ == ABORTED; } + + LChunk* chunk() const { return chunk_; } + Scope* scope() const { return scope_; } + HGraph* graph() const { return chunk_->graph(); } + MacroAssembler* masm() const { return masm_; } + + int GetNextEmittedBlock(int block); + LInstruction* GetNextInstruction(); + + void EmitClassOfTest(Label* if_true, + Label* if_false, + Handle<String> class_name, + Register input, + Register temporary, + Register temporary2); + + int StackSlotCount() const { return chunk()->spill_slot_count(); } + int ParameterCount() const { return scope()->num_parameters(); } + + void Abort(const char* format, ...); + void Comment(const char* format, ...); + + void AddDeferredCode(LDeferredCode* code) { deferred_.Add(code); } + + // Code generation passes. Returns true if code generation should + // continue. + bool GeneratePrologue(); + bool GenerateBody(); + bool GenerateDeferredCode(); + bool GenerateSafepointTable(); + + void CallCode(Handle<Code> code, + RelocInfo::Mode mode, + LInstruction* instr); + void CallRuntime(Runtime::Function* function, + int num_arguments, + LInstruction* instr); + void CallRuntime(Runtime::FunctionId id, + int num_arguments, + LInstruction* instr) { + Runtime::Function* function = Runtime::FunctionForId(id); + CallRuntime(function, num_arguments, instr); + } + + // Generate a direct call to a known function. Expects the function + // to be in edi. + void CallKnownFunction(Handle<JSFunction> function, + int arity, + LInstruction* instr); + + void LoadPrototype(Register result, Handle<JSObject> prototype); + + void RegisterLazyDeoptimization(LInstruction* instr); + void RegisterEnvironmentForDeoptimization(LEnvironment* environment); + void DeoptimizeIf(Condition cc, LEnvironment* environment); + + void AddToTranslation(Translation* translation, + LOperand* op, + bool is_tagged); + void PopulateDeoptimizationData(Handle<Code> code); + int DefineDeoptimizationLiteral(Handle<Object> literal); + + void PopulateDeoptimizationLiteralsWithInlinedFunctions(); + + Register ToRegister(int index) const; + XMMRegister ToDoubleRegister(int index) const; + Register ToRegister(LOperand* op) const; + XMMRegister ToDoubleRegister(LOperand* op) const; + int ToInteger32(LConstantOperand* op) const; + Operand ToOperand(LOperand* op) const; + Immediate ToImmediate(LOperand* op); + + // Specific math operations - used from DoUnaryMathOperation. + void DoMathAbs(LUnaryMathOperation* instr); + void DoMathFloor(LUnaryMathOperation* instr); + void DoMathRound(LUnaryMathOperation* instr); + void DoMathSqrt(LUnaryMathOperation* instr); + void DoMathPowHalf(LUnaryMathOperation* instr); + void DoMathLog(LUnaryMathOperation* instr); + void DoMathCos(LUnaryMathOperation* instr); + void DoMathSin(LUnaryMathOperation* instr); + + // Support for recording safepoint and position information. + void RecordSafepoint(LPointerMap* pointers, int deoptimization_index); + void RecordSafepointWithRegisters(LPointerMap* pointers, + int arguments, + int deoptimization_index); + void RecordPosition(int position); + + static Condition TokenToCondition(Token::Value op, bool is_unsigned); + void EmitGoto(int block, LDeferredCode* deferred_stack_check = NULL); + void EmitBranch(int left_block, int right_block, Condition cc); + void EmitCmpI(LOperand* left, LOperand* right); + void EmitNumberUntagD(Register input, XMMRegister result, LEnvironment* env); + + // Emits optimized code for typeof x == "y". Modifies input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitTypeofIs(Label* true_label, Label* false_label, + Register input, Handle<String> type_name); + + // Emits optimized code for %_IsObject(x). Preserves input register. + // Returns the condition on which a final split to + // true and false label should be made, to optimize fallthrough. + Condition EmitIsObject(Register input, + Register temp1, + Register temp2, + Label* is_not_object, + Label* is_object); + + LChunk* const chunk_; + MacroAssembler* const masm_; + CompilationInfo* const info_; + + int current_block_; + int current_instruction_; + const ZoneList<LInstruction*>* instructions_; + ZoneList<LEnvironment*> deoptimizations_; + ZoneList<Handle<Object> > deoptimization_literals_; + int inlined_function_count_; + Scope* const scope_; + Status status_; + TranslationBuffer translations_; + ZoneList<LDeferredCode*> deferred_; + int osr_pc_offset_; + + // Builder that keeps track of safepoints in the code. The table + // itself is emitted at the end of the generated code. + SafepointTableBuilder safepoints_; + + friend class LDeferredCode; + friend class LEnvironment; + friend class SafepointGenerator; + DISALLOW_COPY_AND_ASSIGN(LCodeGen); +}; + + +class LDeferredCode: public ZoneObject { + public: + explicit LDeferredCode(LCodeGen* codegen) + : codegen_(codegen), external_exit_(NULL) { + codegen->AddDeferredCode(this); + } + + virtual ~LDeferredCode() { } + virtual void Generate() = 0; + + void SetExit(Label *exit) { external_exit_ = exit; } + Label* entry() { return &entry_; } + Label* exit() { return external_exit_ != NULL ? external_exit_ : &exit_; } + + protected: + LCodeGen* codegen() const { return codegen_; } + MacroAssembler* masm() const { return codegen_->masm(); } + + private: + LCodeGen* codegen_; + Label entry_; + Label exit_; + Label* external_exit_; +}; + +} } // namespace v8::internal + +#endif // V8_IA32_LITHIUM_CODEGEN_IA32_H_ diff --git a/src/ia32/lithium-ia32.cc b/src/ia32/lithium-ia32.cc new file mode 100644 index 00000000..3b272d0b --- /dev/null +++ b/src/ia32/lithium-ia32.cc @@ -0,0 +1,2128 @@ +// Copyright 2010 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. + +#include "ia32/lithium-ia32.h" +#include "ia32/lithium-codegen-ia32.h" + +namespace v8 { +namespace internal { + +#define DEFINE_COMPILE(type) \ + void L##type::CompileToNative(LCodeGen* generator) { \ + generator->Do##type(this); \ + } +LITHIUM_CONCRETE_INSTRUCTION_LIST(DEFINE_COMPILE) +#undef DEFINE_COMPILE + +LOsrEntry::LOsrEntry() { + for (int i = 0; i < Register::kNumAllocatableRegisters; ++i) { + register_spills_[i] = NULL; + } + for (int i = 0; i < DoubleRegister::kNumAllocatableRegisters; ++i) { + double_register_spills_[i] = NULL; + } +} + + +void LOsrEntry::MarkSpilledRegister(int allocation_index, + LOperand* spill_operand) { + ASSERT(spill_operand->IsStackSlot()); + ASSERT(register_spills_[allocation_index] == NULL); + register_spills_[allocation_index] = spill_operand; +} + + +void LOsrEntry::MarkSpilledDoubleRegister(int allocation_index, + LOperand* spill_operand) { + ASSERT(spill_operand->IsDoubleStackSlot()); + ASSERT(double_register_spills_[allocation_index] == NULL); + double_register_spills_[allocation_index] = spill_operand; +} + + +void LInstruction::PrintTo(StringStream* stream) const { + stream->Add("%s ", this->Mnemonic()); + if (HasResult()) { + result()->PrintTo(stream); + stream->Add(" "); + } + PrintDataTo(stream); + + if (HasEnvironment()) { + stream->Add(" "); + environment()->PrintTo(stream); + } + + if (HasPointerMap()) { + stream->Add(" "); + pointer_map()->PrintTo(stream); + } +} + + +void LLabel::PrintDataTo(StringStream* stream) const { + LGap::PrintDataTo(stream); + LLabel* rep = replacement(); + if (rep != NULL) { + stream->Add(" Dead block replaced with B%d", rep->block_id()); + } +} + + +bool LParallelMove::IsRedundant() const { + for (int i = 0; i < move_operands_.length(); ++i) { + if (!move_operands_[i].IsRedundant()) return false; + } + return true; +} + + +void LParallelMove::PrintDataTo(StringStream* stream) const { + for (int i = move_operands_.length() - 1; i >= 0; --i) { + if (!move_operands_[i].IsEliminated()) { + LOperand* from = move_operands_[i].from(); + LOperand* to = move_operands_[i].to(); + if (from->Equals(to)) { + to->PrintTo(stream); + } else { + to->PrintTo(stream); + stream->Add(" = "); + from->PrintTo(stream); + } + stream->Add("; "); + } + } +} + + +bool LGap::IsRedundant() const { + for (int i = 0; i < 4; i++) { + if (parallel_moves_[i] != NULL && !parallel_moves_[i]->IsRedundant()) { + return false; + } + } + + return true; +} + + +void LGap::PrintDataTo(StringStream* stream) const { + for (int i = 0; i < 4; i++) { + stream->Add("("); + if (parallel_moves_[i] != NULL) { + parallel_moves_[i]->PrintDataTo(stream); + } + stream->Add(") "); + } +} + + +const char* LArithmeticD::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-d"; + case Token::SUB: return "sub-d"; + case Token::MUL: return "mul-d"; + case Token::DIV: return "div-d"; + case Token::MOD: return "mod-d"; + default: + UNREACHABLE(); + return NULL; + } +} + + +const char* LArithmeticT::Mnemonic() const { + switch (op()) { + case Token::ADD: return "add-t"; + case Token::SUB: return "sub-t"; + case Token::MUL: return "mul-t"; + case Token::MOD: return "mod-t"; + case Token::DIV: return "div-t"; + default: + UNREACHABLE(); + return NULL; + } +} + + + +void LBinaryOperation::PrintDataTo(StringStream* stream) const { + stream->Add("= "); + left()->PrintTo(stream); + stream->Add(" "); + right()->PrintTo(stream); +} + + +void LGoto::PrintDataTo(StringStream* stream) const { + stream->Add("B%d", block_id()); +} + + +void LBranch::PrintDataTo(StringStream* stream) const { + stream->Add("B%d | B%d on ", true_block_id(), false_block_id()); + input()->PrintTo(stream); +} + + +void LCmpIDAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if "); + left()->PrintTo(stream); + stream->Add(" %s ", Token::String(op())); + right()->PrintTo(stream); + stream->Add(" then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsNullAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if "); + input()->PrintTo(stream); + stream->Add(is_strict() ? " === null" : " == null"); + stream->Add(" then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsObjectAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if is_object("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LIsSmiAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if is_smi("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LHasInstanceTypeAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if has_instance_type("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LHasCachedArrayIndexAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if has_cached_array_index("); + input()->PrintTo(stream); + stream->Add(") then B%d else B%d", true_block_id(), false_block_id()); +} + + +void LClassOfTestAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if class_of_test("); + input()->PrintTo(stream); + stream->Add(", \"%o\") then B%d else B%d", + *hydrogen()->class_name(), + true_block_id(), + false_block_id()); +} + + +void LTypeofIs::PrintDataTo(StringStream* stream) const { + input()->PrintTo(stream); + stream->Add(" == \"%s\"", *hydrogen()->type_literal()->ToCString()); +} + + +void LTypeofIsAndBranch::PrintDataTo(StringStream* stream) const { + stream->Add("if typeof "); + input()->PrintTo(stream); + stream->Add(" == \"%s\" then B%d else B%d", + *hydrogen()->type_literal()->ToCString(), + true_block_id(), false_block_id()); +} + + +void LCallConstantFunction::PrintDataTo(StringStream* stream) const { + stream->Add("#%d / ", arity()); +} + + +void LUnaryMathOperation::PrintDataTo(StringStream* stream) const { + stream->Add("/%s ", hydrogen()->OpName()); + input()->PrintTo(stream); +} + + +void LCallKeyed::PrintDataTo(StringStream* stream) const { + stream->Add("[ecx] #%d / ", arity()); +} + + +void LCallNamed::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s #%d / ", *name_string, arity()); +} + + +void LCallGlobal::PrintDataTo(StringStream* stream) const { + SmartPointer<char> name_string = name()->ToCString(); + stream->Add("%s #%d / ", *name_string, arity()); +} + + +void LCallKnownGlobal::PrintDataTo(StringStream* stream) const { + stream->Add("#%d / ", arity()); +} + + +void LCallNew::PrintDataTo(StringStream* stream) const { + LUnaryOperation::PrintDataTo(stream); + stream->Add(" #%d / ", arity()); +} + + +void LClassOfTest::PrintDataTo(StringStream* stream) const { + stream->Add("= class_of_test("); + input()->PrintTo(stream); + stream->Add(", \"%o\")", *hydrogen()->class_name()); +} + + +void LUnaryOperation::PrintDataTo(StringStream* stream) const { + stream->Add("= "); + input()->PrintTo(stream); +} + + +void LAccessArgumentsAt::PrintDataTo(StringStream* stream) const { + arguments()->PrintTo(stream); + + stream->Add(" length "); + length()->PrintTo(stream); + + stream->Add(" index "); + index()->PrintTo(stream); +} + + +LChunk::LChunk(HGraph* graph) + : spill_slot_count_(0), + graph_(graph), + instructions_(32), + pointer_maps_(8), + inlined_closures_(1) { +} + + +void LChunk::Verify() const { + // TODO(twuerthinger): Implement verification for chunk. +} + + +int LChunk::GetNextSpillIndex(bool is_double) { + // Skip a slot if for a double-width slot. + if (is_double) spill_slot_count_++; + return spill_slot_count_++; +} + + +LOperand* LChunk::GetNextSpillSlot(bool is_double) { + int index = GetNextSpillIndex(is_double); + if (is_double) { + return LDoubleStackSlot::Create(index); + } else { + return LStackSlot::Create(index); + } +} + + +void LChunk::MarkEmptyBlocks() { + HPhase phase("Mark empty blocks", this); + for (int i = 0; i < graph()->blocks()->length(); ++i) { + HBasicBlock* block = graph()->blocks()->at(i); + int first = block->first_instruction_index(); + int last = block->last_instruction_index(); + LInstruction* first_instr = instructions()->at(first); + LInstruction* last_instr = instructions()->at(last); + + LLabel* label = LLabel::cast(first_instr); + if (last_instr->IsGoto()) { + LGoto* goto_instr = LGoto::cast(last_instr); + if (!goto_instr->include_stack_check() && + label->IsRedundant() && + !label->is_loop_header()) { + bool can_eliminate = true; + for (int i = first + 1; i < last && can_eliminate; ++i) { + LInstruction* cur = instructions()->at(i); + if (cur->IsGap()) { + LGap* gap = LGap::cast(cur); + if (!gap->IsRedundant()) { + can_eliminate = false; + } + } else { + can_eliminate = false; + } + } + + if (can_eliminate) { + label->set_replacement(GetLabel(goto_instr->block_id())); + } + } + } + } +} + + +void LStoreNamed::PrintDataTo(StringStream* stream) const { + object()->PrintTo(stream); + stream->Add("."); + stream->Add(*String::cast(*name())->ToCString()); + stream->Add(" <- "); + value()->PrintTo(stream); +} + + +void LStoreKeyed::PrintDataTo(StringStream* stream) const { + object()->PrintTo(stream); + stream->Add("["); + key()->PrintTo(stream); + stream->Add("] <- "); + value()->PrintTo(stream); +} + + +int LChunk::AddInstruction(LInstruction* instr, HBasicBlock* block) { + LGap* gap = new LGap(block); + int index = -1; + if (instr->IsControl()) { + instructions_.Add(gap); + index = instructions_.length(); + instructions_.Add(instr); + } else { + index = instructions_.length(); + instructions_.Add(instr); + instructions_.Add(gap); + } + if (instr->HasPointerMap()) { + pointer_maps_.Add(instr->pointer_map()); + instr->pointer_map()->set_lithium_position(index); + } + return index; +} + + +LConstantOperand* LChunk::DefineConstantOperand(HConstant* constant) { + return LConstantOperand::Create(constant->id()); +} + + +int LChunk::GetParameterStackSlot(int index) const { + // The receiver is at index 0, the first parameter at index 1, so we + // shift all parameter indexes down by the number of parameters, and + // make sure they end up negative so they are distinguishable from + // spill slots. + int result = index - graph()->info()->scope()->num_parameters() - 1; + ASSERT(result < 0); + return result; +} + +// A parameter relative to ebp in the arguments stub. +int LChunk::ParameterAt(int index) { + ASSERT(-1 <= index); // -1 is the receiver. + return (1 + graph()->info()->scope()->num_parameters() - index) * + kPointerSize; +} + + +LGap* LChunk::GetGapAt(int index) const { + return LGap::cast(instructions_[index]); +} + + +bool LChunk::IsGapAt(int index) const { + return instructions_[index]->IsGap(); +} + + +int LChunk::NearestGapPos(int index) const { + while (!IsGapAt(index)) index--; + return index; +} + + +void LChunk::AddGapMove(int index, LOperand* from, LOperand* to) { + GetGapAt(index)->GetOrCreateParallelMove(LGap::START)->AddMove(from, to); +} + + +class LGapNode: public ZoneObject { + public: + explicit LGapNode(LOperand* operand) + : operand_(operand), resolved_(false), visited_id_(-1) { } + + LOperand* operand() const { return operand_; } + bool IsResolved() const { return !IsAssigned() || resolved_; } + void MarkResolved() { + ASSERT(!IsResolved()); + resolved_ = true; + } + int visited_id() const { return visited_id_; } + void set_visited_id(int id) { + ASSERT(id > visited_id_); + visited_id_ = id; + } + + bool IsAssigned() const { return assigned_from_.is_set(); } + LGapNode* assigned_from() const { return assigned_from_.get(); } + void set_assigned_from(LGapNode* n) { assigned_from_.set(n); } + + private: + LOperand* operand_; + SetOncePointer<LGapNode> assigned_from_; + bool resolved_; + int visited_id_; +}; + + +LGapResolver::LGapResolver(const ZoneList<LMoveOperands>* moves, + LOperand* marker_operand) + : nodes_(4), + identified_cycles_(4), + result_(4), + marker_operand_(marker_operand), + next_visited_id_(0) { + for (int i = 0; i < moves->length(); ++i) { + LMoveOperands move = moves->at(i); + if (!move.IsRedundant()) RegisterMove(move); + } +} + + +const ZoneList<LMoveOperands>* LGapResolver::ResolveInReverseOrder() { + for (int i = 0; i < identified_cycles_.length(); ++i) { + ResolveCycle(identified_cycles_[i]); + } + + int unresolved_nodes; + do { + unresolved_nodes = 0; + for (int j = 0; j < nodes_.length(); j++) { + LGapNode* node = nodes_[j]; + if (!node->IsResolved() && node->assigned_from()->IsResolved()) { + AddResultMove(node->assigned_from(), node); + node->MarkResolved(); + } + if (!node->IsResolved()) ++unresolved_nodes; + } + } while (unresolved_nodes > 0); + return &result_; +} + + +void LGapResolver::AddResultMove(LGapNode* from, LGapNode* to) { + AddResultMove(from->operand(), to->operand()); +} + + +void LGapResolver::AddResultMove(LOperand* from, LOperand* to) { + result_.Add(LMoveOperands(from, to)); +} + + +void LGapResolver::ResolveCycle(LGapNode* start) { + ZoneList<LOperand*> circle_operands(8); + circle_operands.Add(marker_operand_); + LGapNode* cur = start; + do { + cur->MarkResolved(); + circle_operands.Add(cur->operand()); + cur = cur->assigned_from(); + } while (cur != start); + circle_operands.Add(marker_operand_); + + for (int i = circle_operands.length() - 1; i > 0; --i) { + LOperand* from = circle_operands[i]; + LOperand* to = circle_operands[i - 1]; + AddResultMove(from, to); + } +} + + +bool LGapResolver::CanReach(LGapNode* a, LGapNode* b, int visited_id) { + ASSERT(a != b); + LGapNode* cur = a; + while (cur != b && cur->visited_id() != visited_id && cur->IsAssigned()) { + cur->set_visited_id(visited_id); + cur = cur->assigned_from(); + } + + return cur == b; +} + + +bool LGapResolver::CanReach(LGapNode* a, LGapNode* b) { + ASSERT(a != b); + return CanReach(a, b, next_visited_id_++); +} + + +void LGapResolver::RegisterMove(LMoveOperands move) { + if (move.from()->IsConstantOperand()) { + // Constant moves should be last in the machine code. Therefore add them + // first to the result set. + AddResultMove(move.from(), move.to()); + } else { + LGapNode* from = LookupNode(move.from()); + LGapNode* to = LookupNode(move.to()); + if (to->IsAssigned() && to->assigned_from() == from) { + move.Eliminate(); + return; + } + ASSERT(!to->IsAssigned()); + if (CanReach(from, to)) { + // This introduces a circle. Save. + identified_cycles_.Add(from); + } + to->set_assigned_from(from); + } +} + + +LGapNode* LGapResolver::LookupNode(LOperand* operand) { + for (int i = 0; i < nodes_.length(); ++i) { + if (nodes_[i]->operand()->Equals(operand)) return nodes_[i]; + } + + // No node found => create a new one. + LGapNode* result = new LGapNode(operand); + nodes_.Add(result); + return result; +} + + +Handle<Object> LChunk::LookupLiteral(LConstantOperand* operand) const { + return HConstant::cast(graph_->LookupValue(operand->index()))->handle(); +} + + +Representation LChunk::LookupLiteralRepresentation( + LConstantOperand* operand) const { + return graph_->LookupValue(operand->index())->representation(); +} + + +LChunk* LChunkBuilder::Build() { + ASSERT(is_unused()); + chunk_ = new LChunk(graph()); + HPhase phase("Building chunk", chunk_); + status_ = BUILDING; + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int i = 0; i < blocks->length(); i++) { + HBasicBlock* next = NULL; + if (i < blocks->length() - 1) next = blocks->at(i + 1); + DoBasicBlock(blocks->at(i), next); + if (is_aborted()) return NULL; + } + status_ = DONE; + return chunk_; +} + + +void LChunkBuilder::Abort(const char* format, ...) { + if (FLAG_trace_bailout) { + SmartPointer<char> debug_name = graph()->debug_name()->ToCString(); + PrintF("Aborting LChunk building in @\"%s\": ", *debug_name); + va_list arguments; + va_start(arguments, format); + OS::VPrint(format, arguments); + va_end(arguments); + PrintF("\n"); + } + status_ = ABORTED; +} + + +LRegister* LChunkBuilder::ToOperand(Register reg) { + return LRegister::Create(Register::ToAllocationIndex(reg)); +} + + +LUnallocated* LChunkBuilder::ToUnallocated(Register reg) { + return new LUnallocated(LUnallocated::FIXED_REGISTER, + Register::ToAllocationIndex(reg)); +} + + +LUnallocated* LChunkBuilder::ToUnallocated(XMMRegister reg) { + return new LUnallocated(LUnallocated::FIXED_DOUBLE_REGISTER, + XMMRegister::ToAllocationIndex(reg)); +} + + +LOperand* LChunkBuilder::UseFixed(HValue* value, Register fixed_register) { + return Use(value, ToUnallocated(fixed_register)); +} + + +LOperand* LChunkBuilder::UseFixedDouble(HValue* value, XMMRegister reg) { + return Use(value, ToUnallocated(reg)); +} + + +LOperand* LChunkBuilder::UseRegister(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LOperand* LChunkBuilder::UseRegisterAtStart(HValue* value) { + return Use(value, + new LUnallocated(LUnallocated::MUST_HAVE_REGISTER, + LUnallocated::USED_AT_START)); +} + + +LOperand* LChunkBuilder::UseTempRegister(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::WRITABLE_REGISTER)); +} + + +LOperand* LChunkBuilder::Use(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::NONE)); +} + + +LOperand* LChunkBuilder::UseAtStart(HValue* value) { + return Use(value, new LUnallocated(LUnallocated::NONE, + LUnallocated::USED_AT_START)); +} + + +LOperand* LChunkBuilder::UseOrConstant(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : Use(value); +} + + +LOperand* LChunkBuilder::UseOrConstantAtStart(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseAtStart(value); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstant(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseRegister(value); +} + + +LOperand* LChunkBuilder::UseRegisterOrConstantAtStart(HValue* value) { + return value->IsConstant() + ? chunk_->DefineConstantOperand(HConstant::cast(value)) + : UseRegisterAtStart(value); +} + + +LOperand* LChunkBuilder::Use(HValue* value, LUnallocated* operand) { + if (value->EmitAtUses()) { + HInstruction* instr = HInstruction::cast(value); + VisitInstruction(instr); + } + allocator_->RecordUse(value, operand); + return operand; +} + + +LInstruction* LChunkBuilder::Define(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::NONE)); +} + + +LInstruction* LChunkBuilder::DefineAsRegister(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::MUST_HAVE_REGISTER)); +} + + +LInstruction* LChunkBuilder::DefineAsSpilled(LInstruction* instr, int index) { + return Define(instr, new LUnallocated(LUnallocated::FIXED_SLOT, index)); +} + + +LInstruction* LChunkBuilder::DefineSameAsAny(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::SAME_AS_ANY_INPUT)); +} + + +LInstruction* LChunkBuilder::DefineSameAsFirst(LInstruction* instr) { + return Define(instr, new LUnallocated(LUnallocated::SAME_AS_FIRST_INPUT)); +} + + +LInstruction* LChunkBuilder::DefineFixed(LInstruction* instr, Register reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::DefineFixedDouble(LInstruction* instr, + XMMRegister reg) { + return Define(instr, ToUnallocated(reg)); +} + + +LInstruction* LChunkBuilder::AssignEnvironment(LInstruction* instr) { + HEnvironment* hydrogen_env = current_block_->last_environment(); + instr->set_environment(CreateEnvironment(hydrogen_env)); + return instr; +} + + +LInstruction* LChunkBuilder::SetInstructionPendingDeoptimizationEnvironment( + LInstruction* instr, int ast_id) { + ASSERT(instructions_pending_deoptimization_environment_ == NULL); + ASSERT(pending_deoptimization_ast_id_ == AstNode::kNoNumber); + instructions_pending_deoptimization_environment_ = instr; + pending_deoptimization_ast_id_ = ast_id; + return instr; +} + + +void LChunkBuilder::ClearInstructionPendingDeoptimizationEnvironment() { + instructions_pending_deoptimization_environment_ = NULL; + pending_deoptimization_ast_id_ = AstNode::kNoNumber; +} + + +LInstruction* LChunkBuilder::MarkAsCall(LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize) { + allocator_->MarkAsCall(); + instr = AssignPointerMap(instr); + + if (hinstr->HasSideEffects()) { + ASSERT(hinstr->next()->IsSimulate()); + HSimulate* sim = HSimulate::cast(hinstr->next()); + instr = SetInstructionPendingDeoptimizationEnvironment( + instr, sim->ast_id()); + } + + // If instruction does not have side-effects lazy deoptimization + // after the call will try to deoptimize to the point before the call. + // Thus we still need to attach environment to this call even if + // call sequence can not deoptimize eagerly. + bool needs_environment = + (can_deoptimize == CAN_DEOPTIMIZE_EAGERLY) || !hinstr->HasSideEffects(); + if (needs_environment && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + } + + return instr; +} + + +LInstruction* LChunkBuilder::AssignPointerMap(LInstruction* instr) { + ASSERT(!instr->HasPointerMap()); + instr->set_pointer_map(new LPointerMap(position_)); + return instr; +} + + +LInstruction* LChunkBuilder::Define(LInstruction* instr, LUnallocated* result) { + allocator_->RecordDefinition(current_instruction_, result); + instr->set_result(result); + return instr; +} + + +LOperand* LChunkBuilder::Temp() { + LUnallocated* operand = new LUnallocated(LUnallocated::NONE); + allocator_->RecordTemporary(operand); + return operand; +} + + +LUnallocated* LChunkBuilder::TempRegister() { + LUnallocated* operand = new LUnallocated(LUnallocated::MUST_HAVE_REGISTER); + allocator_->RecordTemporary(operand); + return operand; +} + + +LOperand* LChunkBuilder::FixedTemp(Register reg) { + LUnallocated* operand = ToUnallocated(reg); + allocator_->RecordTemporary(operand); + return operand; +} + + +LOperand* LChunkBuilder::FixedTemp(XMMRegister reg) { + LUnallocated* operand = ToUnallocated(reg); + allocator_->RecordTemporary(operand); + return operand; +} + + +LInstruction* LChunkBuilder::DoBlockEntry(HBlockEntry* instr) { + return new LLabel(instr->block()); +} + + +LInstruction* LChunkBuilder::DoDeoptimize(HDeoptimize* instr) { + return AssignEnvironment(new LDeoptimize); +} + + +LInstruction* LChunkBuilder::DoBit(Token::Value op, + HBitwiseBinaryOperation* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + return DefineSameAsFirst(new LBitI(op, left, right)); +} + + +LInstruction* LChunkBuilder::DoShift(Token::Value op, + HBitwiseBinaryOperation* instr) { + ASSERT(instr->representation().IsInteger32()); + ASSERT(instr->OperandAt(0)->representation().IsInteger32()); + ASSERT(instr->OperandAt(1)->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->OperandAt(0)); + + HValue* right_value = instr->OperandAt(1); + LOperand* right = NULL; + int constant_value = 0; + if (right_value->IsConstant()) { + HConstant* constant = HConstant::cast(right_value); + right = chunk_->DefineConstantOperand(constant); + constant_value = constant->Integer32Value() & 0x1f; + } else { + right = UseFixed(right_value, ecx); + } + + // Shift operations can only deoptimize if we do a logical shift + // by 0 and the result cannot be truncated to int32. + bool can_deopt = (op == Token::SHR && constant_value == 0); + if (can_deopt) { + bool can_truncate = true; + for (int i = 0; i < instr->uses()->length(); i++) { + if (!instr->uses()->at(i)->CheckFlag(HValue::kTruncatingToInt32)) { + can_truncate = false; + break; + } + } + can_deopt = !can_truncate; + } + + LInstruction* result = + DefineSameAsFirst(new LShiftI(op, left, right, can_deopt)); + if (can_deopt) AssignEnvironment(result); + return result; +} + + +LInstruction* LChunkBuilder::DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr) { + ASSERT(instr->representation().IsDouble()); + ASSERT(instr->left()->representation().IsDouble()); + ASSERT(instr->right()->representation().IsDouble()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + LArithmeticD* result = new LArithmeticD(op, left, right); + return DefineSameAsFirst(result); +} + + +LInstruction* LChunkBuilder::DoArithmeticT(Token::Value op, + HArithmeticBinaryOperation* instr) { + ASSERT(op == Token::ADD || + op == Token::DIV || + op == Token::MOD || + op == Token::MUL || + op == Token::SUB); + HValue* left = instr->left(); + HValue* right = instr->right(); + ASSERT(left->representation().IsTagged()); + ASSERT(right->representation().IsTagged()); + LOperand* left_operand = UseFixed(left, edx); + LOperand* right_operand = UseFixed(right, eax); + LInstruction* result = new LArithmeticT(op, left_operand, right_operand); + return MarkAsCall(DefineFixed(result, eax), instr); +} + +void LChunkBuilder::DoBasicBlock(HBasicBlock* block, HBasicBlock* next_block) { + ASSERT(is_building()); + current_block_ = block; + next_block_ = next_block; + if (block->IsStartBlock()) { + block->UpdateEnvironment(graph_->start_environment()); + argument_count_ = 0; + } else if (block->predecessors()->length() == 1) { + // We have a single predecessor => copy environment and outgoing + // argument count from the predecessor. + ASSERT(block->phis()->length() == 0); + HBasicBlock* pred = block->predecessors()->at(0); + HEnvironment* last_environment = pred->last_environment(); + ASSERT(last_environment != NULL); + // Only copy the environment, if it is later used again. + if (pred->end()->SecondSuccessor() == NULL) { + ASSERT(pred->end()->FirstSuccessor() == block); + } else { + if (pred->end()->FirstSuccessor()->block_id() > block->block_id() || + pred->end()->SecondSuccessor()->block_id() > block->block_id()) { + last_environment = last_environment->Copy(); + } + } + block->UpdateEnvironment(last_environment); + ASSERT(pred->argument_count() >= 0); + argument_count_ = pred->argument_count(); + } else { + // We are at a state join => process phis. + HBasicBlock* pred = block->predecessors()->at(0); + // No need to copy the environment, it cannot be used later. + HEnvironment* last_environment = pred->last_environment(); + for (int i = 0; i < block->phis()->length(); ++i) { + HPhi* phi = block->phis()->at(i); + last_environment->SetValueAt(phi->merged_index(), phi); + } + for (int i = 0; i < block->deleted_phis()->length(); ++i) { + last_environment->SetValueAt(block->deleted_phis()->at(i), + graph_->GetConstantUndefined()); + } + block->UpdateEnvironment(last_environment); + // Pick up the outgoing argument count of one of the predecessors. + argument_count_ = pred->argument_count(); + } + HInstruction* current = block->first(); + int start = chunk_->instructions()->length(); + while (current != NULL && !is_aborted()) { + if (FLAG_trace_environment) { + PrintF("Process instruction %d\n", current->id()); + } + // Code for constants in registers is generated lazily. + if (!current->EmitAtUses()) { + VisitInstruction(current); + } + current = current->next(); + } + int end = chunk_->instructions()->length() - 1; + if (end >= start) { + block->set_first_instruction_index(start); + block->set_last_instruction_index(end); + } + block->set_argument_count(argument_count_); + next_block_ = NULL; + current_block_ = NULL; +} + + +void LChunkBuilder::VisitInstruction(HInstruction* current) { + HInstruction* old_current = current_instruction_; + current_instruction_ = current; + allocator_->BeginInstruction(); + if (current->has_position()) position_ = current->position(); + LInstruction* instr = current->CompileToLithium(this); + + if (instr != NULL) { + if (FLAG_stress_pointer_maps && !instr->HasPointerMap()) { + instr = AssignPointerMap(instr); + } + if (FLAG_stress_environments && !instr->HasEnvironment()) { + instr = AssignEnvironment(instr); + } + if (current->IsBranch()) { + instr->set_hydrogen_value(HBranch::cast(current)->value()); + } else { + instr->set_hydrogen_value(current); + } + + int index = chunk_->AddInstruction(instr, current_block_); + allocator_->SummarizeInstruction(index); + } else { + // This instruction should be omitted. + allocator_->OmitInstruction(); + } + current_instruction_ = old_current; +} + + +void LEnvironment::WriteTranslation(LCodeGen* cgen, + Translation* translation) const { + if (this == NULL) return; + + // The translation includes one command per value in the environment. + int translation_size = values()->length(); + // The output frame height does not include the parameters. + int height = translation_size - parameter_count(); + + outer()->WriteTranslation(cgen, translation); + int closure_id = cgen->DefineDeoptimizationLiteral(closure()); + translation->BeginFrame(ast_id(), closure_id, height); + for (int i = 0; i < translation_size; ++i) { + LOperand* value = values()->at(i); + // spilled_registers_ and spilled_double_registers_ are either + // both NULL or both set. + if (spilled_registers_ != NULL && value != NULL) { + if (value->IsRegister() && + spilled_registers_[value->index()] != NULL) { + translation->MarkDuplicate(); + cgen->AddToTranslation(translation, + spilled_registers_[value->index()], + HasTaggedValueAt(i)); + } else if (value->IsDoubleRegister() && + spilled_double_registers_[value->index()] != NULL) { + translation->MarkDuplicate(); + cgen->AddToTranslation(translation, + spilled_double_registers_[value->index()], + false); + } + } + + cgen->AddToTranslation(translation, value, HasTaggedValueAt(i)); + } +} + + +void LEnvironment::PrintTo(StringStream* stream) const { + stream->Add("[id=%d|", ast_id()); + stream->Add("[parameters=%d|", parameter_count()); + stream->Add("[arguments_stack_height=%d|", arguments_stack_height()); + for (int i = 0; i < values_.length(); ++i) { + if (i != 0) stream->Add(";"); + if (values_[i] == NULL) { + stream->Add("[hole]"); + } else { + values_[i]->PrintTo(stream); + } + } + stream->Add("]"); +} + + +LEnvironment* LChunkBuilder::CreateEnvironment(HEnvironment* hydrogen_env) { + if (hydrogen_env == NULL) return NULL; + + LEnvironment* outer = CreateEnvironment(hydrogen_env->outer()); + int ast_id = hydrogen_env->ast_id(); + ASSERT(ast_id != AstNode::kNoNumber); + int value_count = hydrogen_env->values()->length(); + LEnvironment* result = new LEnvironment(hydrogen_env->closure(), + ast_id, + hydrogen_env->parameter_count(), + argument_count_, + value_count, + outer); + int argument_index = 0; + for (int i = 0; i < value_count; ++i) { + HValue* value = hydrogen_env->values()->at(i); + LOperand* op = NULL; + if (value->IsArgumentsObject()) { + op = NULL; + } else if (value->IsPushArgument()) { + op = new LArgument(argument_index++); + } else { + op = UseOrConstant(value); + if (op->IsUnallocated()) { + LUnallocated* unalloc = LUnallocated::cast(op); + unalloc->set_policy(LUnallocated::ANY); + } + } + result->AddValue(op, value->representation()); + } + + return result; +} + + +LInstruction* LChunkBuilder::DoGoto(HGoto* instr) { + LInstruction* result = new LGoto(instr->FirstSuccessor()->block_id(), + instr->include_stack_check()); + if (instr->include_stack_check()) result = AssignPointerMap(result); + return result; +} + + +LInstruction* LChunkBuilder::DoBranch(HBranch* instr) { + HValue* v = instr->value(); + HBasicBlock* first = instr->FirstSuccessor(); + HBasicBlock* second = instr->SecondSuccessor(); + ASSERT(first != NULL && second != NULL); + int first_id = first->block_id(); + int second_id = second->block_id(); + + if (v->EmitAtUses()) { + if (v->IsClassOfTest()) { + HClassOfTest* compare = HClassOfTest::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LClassOfTestAndBranch(UseTempRegister(compare->value()), + TempRegister(), + TempRegister(), + first_id, + second_id); + } else if (v->IsCompare()) { + HCompare* compare = HCompare::cast(v); + Token::Value op = compare->token(); + HValue* left = compare->left(); + HValue* right = compare->right(); + if (left->representation().IsInteger32()) { + ASSERT(right->representation().IsInteger32()); + return new LCmpIDAndBranch(op, + UseRegisterAtStart(left), + UseOrConstantAtStart(right), + first_id, + second_id, + false); + } else if (left->representation().IsDouble()) { + ASSERT(right->representation().IsDouble()); + return new LCmpIDAndBranch(op, + UseRegisterAtStart(left), + UseRegisterAtStart(right), + first_id, + second_id, + true); + } else { + ASSERT(left->representation().IsTagged()); + ASSERT(right->representation().IsTagged()); + bool reversed = op == Token::GT || op == Token::LTE; + LOperand* left_operand = UseFixed(left, reversed ? eax : edx); + LOperand* right_operand = UseFixed(right, reversed ? edx : eax); + LInstruction* result = new LCmpTAndBranch(left_operand, + right_operand, + first_id, + second_id); + return MarkAsCall(result, instr); + } + } else if (v->IsIsSmi()) { + HIsSmi* compare = HIsSmi::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LIsSmiAndBranch(Use(compare->value()), + first_id, + second_id); + } else if (v->IsHasInstanceType()) { + HHasInstanceType* compare = HHasInstanceType::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LHasInstanceTypeAndBranch(UseRegisterAtStart(compare->value()), + TempRegister(), + first_id, + second_id); + } else if (v->IsHasCachedArrayIndex()) { + HHasCachedArrayIndex* compare = HHasCachedArrayIndex::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + return new LHasCachedArrayIndexAndBranch( + UseRegisterAtStart(compare->value()), first_id, second_id); + } else if (v->IsIsNull()) { + HIsNull* compare = HIsNull::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + // We only need a temp register for non-strict compare. + LOperand* temp = compare->is_strict() ? NULL : TempRegister(); + return new LIsNullAndBranch(UseRegisterAtStart(compare->value()), + compare->is_strict(), + temp, + first_id, + second_id); + } else if (v->IsIsObject()) { + HIsObject* compare = HIsObject::cast(v); + ASSERT(compare->value()->representation().IsTagged()); + + LOperand* temp1 = TempRegister(); + LOperand* temp2 = TempRegister(); + return new LIsObjectAndBranch(UseRegisterAtStart(compare->value()), + temp1, + temp2, + first_id, + second_id); + } else if (v->IsCompareJSObjectEq()) { + HCompareJSObjectEq* compare = HCompareJSObjectEq::cast(v); + return new LCmpJSObjectEqAndBranch(UseRegisterAtStart(compare->left()), + UseRegisterAtStart(compare->right()), + first_id, + second_id); + } else if (v->IsInstanceOf()) { + HInstanceOf* instance_of = HInstanceOf::cast(v); + LInstruction* result = + new LInstanceOfAndBranch(UseFixed(instance_of->left(), eax), + UseFixed(instance_of->right(), edx), + first_id, + second_id); + return MarkAsCall(result, instr); + } else if (v->IsTypeofIs()) { + HTypeofIs* typeof_is = HTypeofIs::cast(v); + return new LTypeofIsAndBranch(UseTempRegister(typeof_is->value()), + first_id, + second_id); + } else { + if (v->IsConstant()) { + if (HConstant::cast(v)->handle()->IsTrue()) { + return new LGoto(first_id); + } else if (HConstant::cast(v)->handle()->IsFalse()) { + return new LGoto(second_id); + } + } + Abort("Undefined compare before branch"); + return NULL; + } + } + return new LBranch(UseRegisterAtStart(v), first_id, second_id); +} + + +LInstruction* LChunkBuilder::DoCompareMapAndBranch( + HCompareMapAndBranch* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + HBasicBlock* first = instr->FirstSuccessor(); + HBasicBlock* second = instr->SecondSuccessor(); + return new LCmpMapAndBranch(value, + instr->map(), + first->block_id(), + second->block_id()); +} + + +LInstruction* LChunkBuilder::DoArgumentsLength(HArgumentsLength* length) { + return DefineAsRegister(new LArgumentsLength(Use(length->value()))); +} + + +LInstruction* LChunkBuilder::DoArgumentsElements(HArgumentsElements* elems) { + return DefineAsRegister(new LArgumentsElements); +} + + +LInstruction* LChunkBuilder::DoInstanceOf(HInstanceOf* instr) { + LInstruction* result = + new LInstanceOf(UseFixed(instr->left(), eax), + UseFixed(instr->right(), edx)); + return MarkAsCall(DefineFixed(result, eax), instr); +} + + +LInstruction* LChunkBuilder::DoApplyArguments(HApplyArguments* instr) { + LOperand* function = UseFixed(instr->function(), edi); + LOperand* receiver = UseFixed(instr->receiver(), eax); + LOperand* length = UseRegisterAtStart(instr->length()); + LOperand* elements = UseRegisterAtStart(instr->elements()); + LInstruction* result = new LApplyArguments(function, + receiver, + length, + elements); + return MarkAsCall(DefineFixed(result, eax), instr, CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoPushArgument(HPushArgument* instr) { + ++argument_count_; + LOperand* argument = UseOrConstant(instr->argument()); + return new LPushArgument(argument); +} + + +LInstruction* LChunkBuilder::DoGlobalObject(HGlobalObject* instr) { + return DefineAsRegister(new LGlobalObject); +} + + +LInstruction* LChunkBuilder::DoGlobalReceiver(HGlobalReceiver* instr) { + return DefineAsRegister(new LGlobalReceiver); +} + + +LInstruction* LChunkBuilder::DoCallConstantFunction( + HCallConstantFunction* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallConstantFunction, eax), instr); +} + + +LInstruction* LChunkBuilder::DoUnaryMathOperation(HUnaryMathOperation* instr) { + BuiltinFunctionId op = instr->op(); + if (op == kMathLog || op == kMathSin || op == kMathCos) { + LOperand* input = UseFixedDouble(instr->value(), xmm1); + LInstruction* result = new LUnaryMathOperation(input); + return MarkAsCall(DefineFixedDouble(result, xmm1), instr); + } else { + LOperand* input = UseRegisterAtStart(instr->value()); + LInstruction* result = new LUnaryMathOperation(input); + switch (op) { + case kMathAbs: + return AssignEnvironment(AssignPointerMap(DefineSameAsFirst(result))); + case kMathFloor: + return AssignEnvironment(DefineAsRegister(result)); + case kMathRound: + return AssignEnvironment(DefineAsRegister(result)); + case kMathSqrt: + return DefineSameAsFirst(result); + case kMathPowHalf: + return AssignEnvironment(DefineSameAsFirst(result)); + default: + UNREACHABLE(); + return NULL; + } + } +} + + +LInstruction* LChunkBuilder::DoCallKeyed(HCallKeyed* instr) { + ASSERT(instr->key()->representation().IsTagged()); + argument_count_ -= instr->argument_count(); + UseFixed(instr->key(), ecx); + return MarkAsCall(DefineFixed(new LCallKeyed, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallNamed(HCallNamed* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallNamed, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallGlobal(HCallGlobal* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallGlobal, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallKnownGlobal(HCallKnownGlobal* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallKnownGlobal, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallNew(HCallNew* instr) { + LOperand* constructor = UseFixed(instr->constructor(), edi); + argument_count_ -= instr->argument_count(); + LInstruction* result = new LCallNew(constructor); + return MarkAsCall(DefineFixed(result, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallFunction(HCallFunction* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallFunction, eax), instr); +} + + +LInstruction* LChunkBuilder::DoCallRuntime(HCallRuntime* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallRuntime, eax), instr); +} + + +LInstruction* LChunkBuilder::DoShr(HShr* instr) { + return DoShift(Token::SHR, instr); +} + + +LInstruction* LChunkBuilder::DoSar(HSar* instr) { + return DoShift(Token::SAR, instr); +} + + +LInstruction* LChunkBuilder::DoShl(HShl* instr) { + return DoShift(Token::SHL, instr); +} + + +LInstruction* LChunkBuilder::DoBitAnd(HBitAnd* instr) { + return DoBit(Token::BIT_AND, instr); +} + + +LInstruction* LChunkBuilder::DoBitNot(HBitNot* instr) { + ASSERT(instr->value()->representation().IsInteger32()); + ASSERT(instr->representation().IsInteger32()); + return DefineSameAsFirst(new LBitNotI(UseRegisterAtStart(instr->value()))); +} + + +LInstruction* LChunkBuilder::DoBitOr(HBitOr* instr) { + return DoBit(Token::BIT_OR, instr); +} + + +LInstruction* LChunkBuilder::DoBitXor(HBitXor* instr) { + return DoBit(Token::BIT_XOR, instr); +} + + +LInstruction* LChunkBuilder::DoDiv(HDiv* instr) { + if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::DIV, instr); + } else if (instr->representation().IsInteger32()) { + // The temporary operand is necessary to ensure that right is not allocated + // into edx. + FixedTemp(edx); + LOperand* value = UseFixed(instr->left(), eax); + LOperand* divisor = UseRegister(instr->right()); + return AssignEnvironment(DefineFixed(new LDivI(value, divisor), eax)); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::DIV, instr); + } +} + + +LInstruction* LChunkBuilder::DoMod(HMod* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + // The temporary operand is necessary to ensure that right is not allocated + // into edx. + FixedTemp(edx); + LOperand* value = UseFixed(instr->left(), eax); + LOperand* divisor = UseRegister(instr->right()); + LInstruction* result = DefineFixed(new LModI(value, divisor), edx); + if (instr->CheckFlag(HValue::kBailoutOnMinusZero) || + instr->CheckFlag(HValue::kCanBeDivByZero)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsTagged()) { + return DoArithmeticT(Token::MOD, instr); + } else { + ASSERT(instr->representation().IsDouble()); + // We call a C function for double modulo. It can't trigger a GC. + // We need to use fixed result register for the call. + // TODO(fschneider): Allow any register as input registers. + LOperand* left = UseFixedDouble(instr->left(), xmm1); + LOperand* right = UseFixedDouble(instr->right(), xmm2); + LArithmeticD* result = new LArithmeticD(Token::MOD, left, right); + return MarkAsCall(DefineFixedDouble(result, xmm1), instr); + } +} + + +LInstruction* LChunkBuilder::DoMul(HMul* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstant(instr->MostConstantOperand()); + LOperand* temp = NULL; + if (instr->CheckFlag(HValue::kBailoutOnMinusZero)) { + temp = TempRegister(); + } + LMulI* mul = new LMulI(left, right, temp); + return AssignEnvironment(DefineSameAsFirst(mul)); + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::MUL, instr); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::MUL, instr); + } +} + + +LInstruction* LChunkBuilder::DoSub(HSub* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + LSubI* sub = new LSubI(left, right); + LInstruction* result = DefineSameAsFirst(sub); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::SUB, instr); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::SUB, instr); + } +} + + +LInstruction* LChunkBuilder::DoAdd(HAdd* instr) { + if (instr->representation().IsInteger32()) { + ASSERT(instr->left()->representation().IsInteger32()); + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->LeastConstantOperand()); + LOperand* right = UseOrConstantAtStart(instr->MostConstantOperand()); + LAddI* add = new LAddI(left, right); + LInstruction* result = DefineSameAsFirst(add); + if (instr->CheckFlag(HValue::kCanOverflow)) { + result = AssignEnvironment(result); + } + return result; + } else if (instr->representation().IsDouble()) { + return DoArithmeticD(Token::ADD, instr); + } else { + ASSERT(instr->representation().IsTagged()); + return DoArithmeticT(Token::ADD, instr); + } +} + + +LInstruction* LChunkBuilder::DoPower(HPower* instr) { + ASSERT(instr->representation().IsDouble()); + // We call a C function for double power. It can't trigger a GC. + // We need to use fixed result register for the call. + Representation exponent_type = instr->right()->representation(); + ASSERT(instr->left()->representation().IsDouble()); + LOperand* left = UseFixedDouble(instr->left(), xmm1); + LOperand* right = exponent_type.IsDouble() ? + UseFixedDouble(instr->right(), xmm2) : + UseFixed(instr->right(), eax); + LPower* result = new LPower(left, right); + return MarkAsCall(DefineFixedDouble(result, xmm3), instr, + CAN_DEOPTIMIZE_EAGERLY); +} + + +LInstruction* LChunkBuilder::DoCompare(HCompare* instr) { + Token::Value op = instr->token(); + if (instr->left()->representation().IsInteger32()) { + ASSERT(instr->right()->representation().IsInteger32()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseOrConstantAtStart(instr->right()); + return DefineAsRegister(new LCmpID(op, left, right, false)); + } else if (instr->left()->representation().IsDouble()) { + ASSERT(instr->right()->representation().IsDouble()); + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + return DefineAsRegister(new LCmpID(op, left, right, true)); + } else { + bool reversed = (op == Token::GT || op == Token::LTE); + LOperand* left = UseFixed(instr->left(), reversed ? eax : edx); + LOperand* right = UseFixed(instr->right(), reversed ? edx : eax); + LInstruction* result = new LCmpT(left, right); + return MarkAsCall(DefineFixed(result, eax), instr); + } +} + + +LInstruction* LChunkBuilder::DoCompareJSObjectEq( + HCompareJSObjectEq* instr) { + LOperand* left = UseRegisterAtStart(instr->left()); + LOperand* right = UseRegisterAtStart(instr->right()); + LInstruction* result = new LCmpJSObjectEq(left, right); + return DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoIsNull(HIsNull* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + + return DefineAsRegister(new LIsNull(value, + instr->is_strict())); +} + + +LInstruction* LChunkBuilder::DoIsObject(HIsObject* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegister(instr->value()); + + return DefineAsRegister(new LIsObject(value, TempRegister())); +} + + +LInstruction* LChunkBuilder::DoIsSmi(HIsSmi* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseAtStart(instr->value()); + + return DefineAsRegister(new LIsSmi(value)); +} + + +LInstruction* LChunkBuilder::DoHasInstanceType(HHasInstanceType* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegisterAtStart(instr->value()); + + return DefineAsRegister(new LHasInstanceType(value)); +} + + +LInstruction* LChunkBuilder::DoHasCachedArrayIndex( + HHasCachedArrayIndex* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseRegister(instr->value()); + + return DefineAsRegister(new LHasCachedArrayIndex(value)); +} + + +LInstruction* LChunkBuilder::DoClassOfTest(HClassOfTest* instr) { + ASSERT(instr->value()->representation().IsTagged()); + LOperand* value = UseTempRegister(instr->value()); + + return DefineSameAsFirst(new LClassOfTest(value, TempRegister())); +} + + +LInstruction* LChunkBuilder::DoArrayLength(HArrayLength* instr) { + LOperand* array = NULL; + LOperand* temporary = NULL; + + if (instr->value()->IsLoadElements()) { + array = UseRegisterAtStart(instr->value()); + } else { + array = UseRegister(instr->value()); + temporary = TempRegister(); + } + + LInstruction* result = new LArrayLength(array, temporary); + return AssignEnvironment(DefineAsRegister(result)); +} + + +LInstruction* LChunkBuilder::DoValueOf(HValueOf* instr) { + LOperand* object = UseRegister(instr->value()); + LInstruction* result = new LValueOf(object, TempRegister()); + return AssignEnvironment(DefineSameAsFirst(result)); +} + + +LInstruction* LChunkBuilder::DoBoundsCheck(HBoundsCheck* instr) { + return AssignEnvironment(new LBoundsCheck(UseRegisterAtStart(instr->index()), + Use(instr->length()))); +} + + +LInstruction* LChunkBuilder::DoThrow(HThrow* instr) { + LOperand* value = UseFixed(instr->value(), eax); + return MarkAsCall(new LThrow(value), instr); +} + + +LInstruction* LChunkBuilder::DoChange(HChange* instr) { + Representation from = instr->from(); + Representation to = instr->to(); + if (from.IsTagged()) { + if (to.IsDouble()) { + LOperand* value = UseRegister(instr->value()); + LInstruction* res = new LNumberUntagD(value); + return AssignEnvironment(DefineAsRegister(res)); + } else { + ASSERT(to.IsInteger32()); + LOperand* value = UseRegister(instr->value()); + bool needs_check = !instr->value()->type().IsSmi(); + if (needs_check) { + LOperand* xmm_temp = + (instr->CanTruncateToInt32() && CpuFeatures::IsSupported(SSE3)) + ? NULL + : FixedTemp(xmm1); + LInstruction* res = new LTaggedToI(value, xmm_temp); + return AssignEnvironment(DefineSameAsFirst(res)); + } else { + return DefineSameAsFirst(new LSmiUntag(value, needs_check)); + } + } + } else if (from.IsDouble()) { + if (to.IsTagged()) { + LOperand* value = UseRegister(instr->value()); + LOperand* temp = TempRegister(); + + // Make sure that temp and result_temp are different registers. + LUnallocated* result_temp = TempRegister(); + LInstruction* result = new LNumberTagD(value, temp); + return AssignPointerMap(Define(result, result_temp)); + } else { + ASSERT(to.IsInteger32()); + LOperand* value = UseRegister(instr->value()); + return AssignEnvironment(DefineAsRegister(new LDoubleToI(value))); + } + } else if (from.IsInteger32()) { + if (to.IsTagged()) { + HValue* val = instr->value(); + LOperand* value = UseRegister(val); + if (val->HasRange() && val->range()->IsInSmiRange()) { + return DefineSameAsFirst(new LSmiTag(value)); + } else { + LInstruction* result = new LNumberTagI(value); + return AssignEnvironment(AssignPointerMap(DefineSameAsFirst(result))); + } + } else { + ASSERT(to.IsDouble()); + return DefineAsRegister(new LInteger32ToDouble(Use(instr->value()))); + } + } + UNREACHABLE(); + return NULL; +} + + +LInstruction* LChunkBuilder::DoCheckNonSmi(HCheckNonSmi* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckSmi(value, zero)); +} + + +LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LOperand* temp = TempRegister(); + LInstruction* result = new LCheckInstanceType(value, temp); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoCheckPrototypeMaps(HCheckPrototypeMaps* instr) { + LOperand* temp = TempRegister(); + LInstruction* result = + new LCheckPrototypeMaps(temp, + instr->holder(), + instr->receiver_map()); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoCheckSmi(HCheckSmi* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckSmi(value, not_zero)); +} + + +LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + return AssignEnvironment(new LCheckFunction(value)); +} + + +LInstruction* LChunkBuilder::DoCheckMap(HCheckMap* instr) { + LOperand* value = UseRegisterAtStart(instr->value()); + LInstruction* result = new LCheckMap(value); + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoReturn(HReturn* instr) { + return new LReturn(UseFixed(instr->value(), eax)); +} + + +LInstruction* LChunkBuilder::DoConstant(HConstant* instr) { + Representation r = instr->representation(); + if (r.IsInteger32()) { + int32_t value = instr->Integer32Value(); + return DefineAsRegister(new LConstantI(value)); + } else if (r.IsDouble()) { + double value = instr->DoubleValue(); + return DefineAsRegister(new LConstantD(value)); + } else if (r.IsTagged()) { + return DefineAsRegister(new LConstantT(instr->handle())); + } else { + Abort("unsupported constant of type double"); + return NULL; + } +} + + +LInstruction* LChunkBuilder::DoLoadGlobal(HLoadGlobal* instr) { + LInstruction* result = new LLoadGlobal; + return instr->check_hole_value() + ? AssignEnvironment(DefineAsRegister(result)) + : DefineAsRegister(result); +} + + +LInstruction* LChunkBuilder::DoStoreGlobal(HStoreGlobal* instr) { + return new LStoreGlobal(UseRegisterAtStart(instr->value())); +} + + +LInstruction* LChunkBuilder::DoLoadNamedField(HLoadNamedField* instr) { + return DefineAsRegister( + new LLoadNamedField(UseRegisterAtStart(instr->object()))); +} + + +LInstruction* LChunkBuilder::DoLoadNamedGeneric(HLoadNamedGeneric* instr) { + LOperand* object = UseFixed(instr->object(), eax); + LInstruction* result = DefineFixed(new LLoadNamedGeneric(object), eax); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoLoadElements(HLoadElements* instr) { + LOperand* input = UseRegisterAtStart(instr->value()); + return DefineSameAsFirst(new LLoadElements(input)); +} + + +LInstruction* LChunkBuilder::DoLoadKeyedFastElement( + HLoadKeyedFastElement* instr) { + Representation r = instr->representation(); + LOperand* obj = UseRegisterAtStart(instr->object()); + ASSERT(instr->key()->representation().IsInteger32()); + LOperand* key = UseRegisterAtStart(instr->key()); + LOperand* load_result = NULL; + // Double needs an extra temp, because the result is converted from heap + // number to a double register. + if (r.IsDouble()) load_result = TempRegister(); + LInstruction* result = new LLoadKeyedFastElement(obj, + key, + load_result); + if (r.IsDouble()) { + result = DefineAsRegister(result); + } else { + result = DefineSameAsFirst(result); + } + return AssignEnvironment(result); +} + + +LInstruction* LChunkBuilder::DoLoadKeyedGeneric(HLoadKeyedGeneric* instr) { + LOperand* object = UseFixed(instr->object(), edx); + LOperand* key = UseFixed(instr->key(), eax); + + LInstruction* result = + DefineFixed(new LLoadKeyedGeneric(object, key), eax); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoStoreKeyedFastElement( + HStoreKeyedFastElement* instr) { + bool needs_write_barrier = instr->NeedsWriteBarrier(); + ASSERT(instr->value()->representation().IsTagged()); + ASSERT(instr->object()->representation().IsTagged()); + ASSERT(instr->key()->representation().IsInteger32()); + + LOperand* obj = UseTempRegister(instr->object()); + LOperand* val = needs_write_barrier + ? UseTempRegister(instr->value()) + : UseRegisterAtStart(instr->value()); + LOperand* key = needs_write_barrier + ? UseTempRegister(instr->key()) + : UseRegisterOrConstantAtStart(instr->key()); + + return AssignEnvironment(new LStoreKeyedFastElement(obj, key, val)); +} + + +LInstruction* LChunkBuilder::DoStoreKeyedGeneric(HStoreKeyedGeneric* instr) { + LOperand* obj = UseFixed(instr->object(), edx); + LOperand* key = UseFixed(instr->key(), ecx); + LOperand* val = UseFixed(instr->value(), eax); + + ASSERT(instr->object()->representation().IsTagged()); + ASSERT(instr->key()->representation().IsTagged()); + ASSERT(instr->value()->representation().IsTagged()); + + return MarkAsCall(new LStoreKeyedGeneric(obj, key, val), instr); +} + + +LInstruction* LChunkBuilder::DoStoreNamedField(HStoreNamedField* instr) { + bool needs_write_barrier = !instr->value()->type().IsSmi(); + + LOperand* obj = needs_write_barrier + ? UseTempRegister(instr->object()) + : UseRegisterAtStart(instr->object()); + + LOperand* val = needs_write_barrier + ? UseTempRegister(instr->value()) + : UseRegister(instr->value()); + + // We only need a scratch register if we have a write barrier or we + // have a store into the properties array (not in-object-property). + LOperand* temp = (!instr->is_in_object() || needs_write_barrier) + ? TempRegister() : NULL; + + return new LStoreNamedField(obj, + instr->name(), + val, + instr->is_in_object(), + instr->offset(), + temp, + needs_write_barrier, + instr->transition()); +} + + +LInstruction* LChunkBuilder::DoStoreNamedGeneric(HStoreNamedGeneric* instr) { + LOperand* obj = UseFixed(instr->object(), edx); + LOperand* val = UseFixed(instr->value(), eax); + + LInstruction* result = new LStoreNamedGeneric(obj, instr->name(), val); + return MarkAsCall(result, instr); +} + + +LInstruction* LChunkBuilder::DoArrayLiteral(HArrayLiteral* instr) { + return MarkAsCall(DefineFixed(new LArrayLiteral, eax), instr); +} + + +LInstruction* LChunkBuilder::DoObjectLiteral(HObjectLiteral* instr) { + return MarkAsCall(DefineFixed(new LObjectLiteral, eax), instr); +} + + +LInstruction* LChunkBuilder::DoRegExpLiteral(HRegExpLiteral* instr) { + return MarkAsCall(DefineFixed(new LRegExpLiteral, eax), instr); +} + + +LInstruction* LChunkBuilder::DoFunctionLiteral(HFunctionLiteral* instr) { + return MarkAsCall(DefineFixed(new LFunctionLiteral, eax), instr); +} + + +LInstruction* LChunkBuilder::DoDeleteProperty(HDeleteProperty* instr) { + LInstruction* result = new LDeleteProperty(Use(instr->object()), + UseOrConstant(instr->key())); + return MarkAsCall(DefineFixed(result, eax), instr); +} + + +LInstruction* LChunkBuilder::DoOsrEntry(HOsrEntry* instr) { + allocator_->MarkAsOsrEntry(); + current_block_->last_environment()->set_ast_id(instr->ast_id()); + return AssignEnvironment(new LOsrEntry); +} + + +LInstruction* LChunkBuilder::DoParameter(HParameter* instr) { + int spill_index = chunk()->GetParameterStackSlot(instr->index()); + return DefineAsSpilled(new LParameter, spill_index); +} + + +LInstruction* LChunkBuilder::DoUnknownOSRValue(HUnknownOSRValue* instr) { + int spill_index = chunk()->GetNextSpillIndex(false); // Not double-width. + return DefineAsSpilled(new LUnknownOSRValue, spill_index); +} + + +LInstruction* LChunkBuilder::DoCallStub(HCallStub* instr) { + argument_count_ -= instr->argument_count(); + return MarkAsCall(DefineFixed(new LCallStub, eax), instr); +} + + +LInstruction* LChunkBuilder::DoArgumentsObject(HArgumentsObject* instr) { + // There are no real uses of the arguments object (we bail out in all other + // cases). + return NULL; +} + + +LInstruction* LChunkBuilder::DoAccessArgumentsAt(HAccessArgumentsAt* instr) { + LOperand* arguments = UseRegister(instr->arguments()); + LOperand* length = UseTempRegister(instr->length()); + LOperand* index = Use(instr->index()); + LInstruction* result = new LAccessArgumentsAt(arguments, length, index); + return DefineAsRegister(AssignEnvironment(result)); +} + + +LInstruction* LChunkBuilder::DoTypeof(HTypeof* instr) { + LInstruction* result = new LTypeof(Use(instr->value())); + return MarkAsCall(DefineFixed(result, eax), instr); +} + + +LInstruction* LChunkBuilder::DoTypeofIs(HTypeofIs* instr) { + return DefineSameAsFirst(new LTypeofIs(UseRegister(instr->value()))); +} + +LInstruction* LChunkBuilder::DoSimulate(HSimulate* instr) { + HEnvironment* env = current_block_->last_environment(); + ASSERT(env != NULL); + + env->set_ast_id(instr->ast_id()); + + env->Drop(instr->pop_count()); + for (int i = 0; i < instr->values()->length(); ++i) { + HValue* value = instr->values()->at(i); + if (instr->HasAssignedIndexAt(i)) { + env->Bind(instr->GetAssignedIndexAt(i), value); + } else { + env->Push(value); + } + } + + if (FLAG_trace_environment) { + PrintF("Reconstructed environment ast_id=%d, instr_id=%d\n", + instr->ast_id(), + instr->id()); + env->PrintToStd(); + } + ASSERT(env->values()->length() == instr->environment_height()); + + // 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()) { + LInstruction* result = new LLazyBailout; + result = AssignEnvironment(result); + instructions_pending_deoptimization_environment_-> + set_deoptimization_environment(result->environment()); + ClearInstructionPendingDeoptimizationEnvironment(); + return result; + } + + return NULL; +} + + +LInstruction* LChunkBuilder::DoStackCheck(HStackCheck* instr) { + return MarkAsCall(new LStackCheck, instr); +} + + +LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) { + HEnvironment* outer = current_block_->last_environment(); + HConstant* undefined = graph()->GetConstantUndefined(); + HEnvironment* inner = outer->CopyForInlining(instr->closure(), + instr->function(), + false, + undefined); + current_block_->UpdateEnvironment(inner); + chunk_->AddInlinedClosure(instr->closure()); + return NULL; +} + + +LInstruction* LChunkBuilder::DoLeaveInlined(HLeaveInlined* instr) { + HEnvironment* outer = current_block_->last_environment()->outer(); + current_block_->UpdateEnvironment(outer); + return NULL; +} + + +void LPointerMap::RecordPointer(LOperand* op) { + // Do not record arguments as pointers. + if (op->IsStackSlot() && op->index() < 0) return; + ASSERT(!op->IsDoubleRegister() && !op->IsDoubleStackSlot()); + pointer_operands_.Add(op); +} + + +void LPointerMap::PrintTo(StringStream* stream) const { + stream->Add("{"); + for (int i = 0; i < pointer_operands_.length(); ++i) { + if (i != 0) stream->Add(";"); + pointer_operands_[i]->PrintTo(stream); + } + stream->Add("} @%d", position()); +} + +} } // namespace v8::internal diff --git a/src/ia32/lithium-ia32.h b/src/ia32/lithium-ia32.h new file mode 100644 index 00000000..3f48e50e --- /dev/null +++ b/src/ia32/lithium-ia32.h @@ -0,0 +1,2128 @@ +// Copyright 2010 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. + +#ifndef V8_IA32_LITHIUM_IA32_H_ +#define V8_IA32_LITHIUM_IA32_H_ + +#include "hydrogen.h" +#include "lithium-allocator.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LCodeGen; +class LEnvironment; +class Translation; +class LGapNode; + + +// Type hierarchy: +// +// LInstruction +// LAccessArgumentsAt +// LArgumentsElements +// LArgumentsLength +// LBinaryOperation +// LAddI +// LApplyArguments +// LArithmeticD +// LArithmeticT +// LBitI +// LBoundsCheck +// LCmpID +// LCmpIDAndBranch +// LCmpJSObjectEq +// LCmpJSObjectEqAndBranch +// LCmpT +// LDivI +// LInstanceOf +// LInstanceOfAndBranch +// LLoadKeyedFastElement +// LLoadKeyedGeneric +// LModI +// LMulI +// LPower +// LShiftI +// LSubI +// LCallConstantFunction +// LCallFunction +// LCallGlobal +// LCallKeyed +// LCallKnownGlobal +// LCallNamed +// LCallRuntime +// LCallStub +// LConstant +// LConstantD +// LConstantI +// LConstantT +// LDeoptimize +// LFunctionLiteral +// LGlobalObject +// LGlobalReceiver +// LLabel +// LLayzBailout +// LLoadGlobal +// LMaterializedLiteral +// LArrayLiteral +// LObjectLiteral +// LRegExpLiteral +// LOsrEntry +// LParameter +// LRegExpConstructResult +// LStackCheck +// LStoreKeyed +// LStoreKeyedFastElement +// LStoreKeyedGeneric +// LStoreNamed +// LStoreNamedField +// LStoreNamedGeneric +// LUnaryOperation +// LArrayLength +// LBitNotI +// LBranch +// LCallNew +// LCheckFunction +// LCheckInstanceType +// LCheckMap +// LCheckPrototypeMaps +// LCheckSmi +// LClassOfTest +// LClassOfTestAndBranch +// LDeleteProperty +// LDoubleToI +// LHasCachedArrayIndex +// LHasCachedArrayIndexAndBranch +// LHasInstanceType +// LHasInstanceTypeAndBranch +// LInteger32ToDouble +// LIsNull +// LIsNullAndBranch +// LIsObject +// LIsObjectAndBranch +// LIsSmi +// LIsSmiAndBranch +// LLoadNamedField +// LLoadNamedGeneric +// LNumberTagD +// LNumberTagI +// LPushArgument +// LReturn +// LSmiTag +// LStoreGlobal +// LTaggedToI +// LThrow +// LTypeof +// LTypeofIs +// LTypeofIsAndBranch +// LUnaryMathOperation +// LValueOf +// LUnknownOSRValue + +#define LITHIUM_ALL_INSTRUCTION_LIST(V) \ + V(BinaryOperation) \ + V(Constant) \ + V(Call) \ + V(MaterializedLiteral) \ + V(StoreKeyed) \ + V(StoreNamed) \ + V(UnaryOperation) \ + LITHIUM_CONCRETE_INSTRUCTION_LIST(V) + + +#define LITHIUM_CONCRETE_INSTRUCTION_LIST(V) \ + V(AccessArgumentsAt) \ + V(AddI) \ + V(ApplyArguments) \ + V(ArgumentsElements) \ + V(ArgumentsLength) \ + V(ArithmeticD) \ + V(ArithmeticT) \ + V(ArrayLength) \ + V(ArrayLiteral) \ + V(BitI) \ + V(BitNotI) \ + V(BoundsCheck) \ + V(Branch) \ + V(CallConstantFunction) \ + V(CallFunction) \ + V(CallGlobal) \ + V(CallKeyed) \ + V(CallKnownGlobal) \ + V(CallNamed) \ + V(CallNew) \ + V(CallRuntime) \ + V(CallStub) \ + V(CheckFunction) \ + V(CheckInstanceType) \ + V(CheckMap) \ + V(CheckPrototypeMaps) \ + V(CheckSmi) \ + V(CmpID) \ + V(CmpIDAndBranch) \ + V(CmpJSObjectEq) \ + V(CmpJSObjectEqAndBranch) \ + V(CmpMapAndBranch) \ + V(CmpT) \ + V(CmpTAndBranch) \ + V(ConstantD) \ + V(ConstantI) \ + V(ConstantT) \ + V(DeleteProperty) \ + V(Deoptimize) \ + V(DivI) \ + V(DoubleToI) \ + V(FunctionLiteral) \ + V(Gap) \ + V(GlobalObject) \ + V(GlobalReceiver) \ + V(Goto) \ + V(InstanceOf) \ + V(InstanceOfAndBranch) \ + V(Integer32ToDouble) \ + V(IsNull) \ + V(IsNullAndBranch) \ + V(IsObject) \ + V(IsObjectAndBranch) \ + V(IsSmi) \ + V(IsSmiAndBranch) \ + V(HasInstanceType) \ + V(HasInstanceTypeAndBranch) \ + V(HasCachedArrayIndex) \ + V(HasCachedArrayIndexAndBranch) \ + V(ClassOfTest) \ + V(ClassOfTestAndBranch) \ + V(Label) \ + V(LazyBailout) \ + V(LoadElements) \ + V(LoadGlobal) \ + V(LoadKeyedFastElement) \ + V(LoadKeyedGeneric) \ + V(LoadNamedField) \ + V(LoadNamedGeneric) \ + V(ModI) \ + V(MulI) \ + V(NumberTagD) \ + V(NumberTagI) \ + V(NumberUntagD) \ + V(ObjectLiteral) \ + V(OsrEntry) \ + V(Parameter) \ + V(Power) \ + V(PushArgument) \ + V(RegExpLiteral) \ + V(Return) \ + V(ShiftI) \ + V(SmiTag) \ + V(SmiUntag) \ + V(StackCheck) \ + V(StoreGlobal) \ + V(StoreKeyedFastElement) \ + V(StoreKeyedGeneric) \ + V(StoreNamedField) \ + V(StoreNamedGeneric) \ + V(SubI) \ + V(TaggedToI) \ + V(Throw) \ + V(Typeof) \ + V(TypeofIs) \ + V(TypeofIsAndBranch) \ + V(UnaryMathOperation) \ + V(UnknownOSRValue) \ + V(ValueOf) + + +#define DECLARE_INSTRUCTION(type) \ + virtual bool Is##type() const { return true; } \ + static L##type* cast(LInstruction* instr) { \ + ASSERT(instr->Is##type()); \ + return reinterpret_cast<L##type*>(instr); \ + } + + +#define DECLARE_CONCRETE_INSTRUCTION(type, mnemonic) \ + virtual void CompileToNative(LCodeGen* generator); \ + virtual const char* Mnemonic() const { return mnemonic; } \ + DECLARE_INSTRUCTION(type) + + +#define DECLARE_HYDROGEN_ACCESSOR(type) \ + H##type* hydrogen() const { \ + return H##type::cast(hydrogen_value()); \ + } + + +class LInstruction: public ZoneObject { + public: + LInstruction() + : hydrogen_value_(NULL) { } + virtual ~LInstruction() { } + + virtual void CompileToNative(LCodeGen* generator) = 0; + virtual const char* Mnemonic() const = 0; + virtual void PrintTo(StringStream* stream) const; + virtual void PrintDataTo(StringStream* stream) const { } + + // Declare virtual type testers. +#define DECLARE_DO(type) virtual bool Is##type() const { return false; } + LITHIUM_ALL_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + virtual bool IsControl() const { return false; } + + void set_environment(LEnvironment* env) { environment_.set(env); } + LEnvironment* environment() const { return environment_.get(); } + bool HasEnvironment() const { return environment_.is_set(); } + + void set_pointer_map(LPointerMap* p) { pointer_map_.set(p); } + LPointerMap* pointer_map() const { return pointer_map_.get(); } + bool HasPointerMap() const { return pointer_map_.is_set(); } + + void set_result(LOperand* operand) { result_.set(operand); } + LOperand* result() const { return result_.get(); } + bool HasResult() const { return result_.is_set(); } + + void set_hydrogen_value(HValue* value) { hydrogen_value_ = value; } + HValue* hydrogen_value() const { return hydrogen_value_; } + + void set_deoptimization_environment(LEnvironment* env) { + deoptimization_environment_.set(env); + } + LEnvironment* deoptimization_environment() const { + return deoptimization_environment_.get(); + } + bool HasDeoptimizationEnvironment() const { + return deoptimization_environment_.is_set(); + } + + private: + SetOncePointer<LEnvironment> environment_; + SetOncePointer<LPointerMap> pointer_map_; + SetOncePointer<LOperand> result_; + HValue* hydrogen_value_; + SetOncePointer<LEnvironment> deoptimization_environment_; +}; + + +class LGapResolver BASE_EMBEDDED { + public: + LGapResolver(const ZoneList<LMoveOperands>* moves, LOperand* marker_operand); + const ZoneList<LMoveOperands>* ResolveInReverseOrder(); + + private: + LGapNode* LookupNode(LOperand* operand); + bool CanReach(LGapNode* a, LGapNode* b, int visited_id); + bool CanReach(LGapNode* a, LGapNode* b); + void RegisterMove(LMoveOperands move); + void AddResultMove(LOperand* from, LOperand* to); + void AddResultMove(LGapNode* from, LGapNode* to); + void ResolveCycle(LGapNode* start); + + ZoneList<LGapNode*> nodes_; + ZoneList<LGapNode*> identified_cycles_; + ZoneList<LMoveOperands> result_; + LOperand* marker_operand_; + int next_visited_id_; + int bailout_after_ast_id_; +}; + + +class LParallelMove : public ZoneObject { + public: + LParallelMove() : move_operands_(4) { } + + void AddMove(LOperand* from, LOperand* to) { + move_operands_.Add(LMoveOperands(from, to)); + } + + bool IsRedundant() const; + + const ZoneList<LMoveOperands>* move_operands() const { + return &move_operands_; + } + + void PrintDataTo(StringStream* stream) const; + + private: + ZoneList<LMoveOperands> move_operands_; +}; + + +class LGap: public LInstruction { + public: + explicit LGap(HBasicBlock* block) + : block_(block) { + parallel_moves_[BEFORE] = NULL; + parallel_moves_[START] = NULL; + parallel_moves_[END] = NULL; + parallel_moves_[AFTER] = NULL; + } + + DECLARE_CONCRETE_INSTRUCTION(Gap, "gap") + virtual void PrintDataTo(StringStream* stream) const; + + bool IsRedundant() const; + + HBasicBlock* block() const { return block_; } + + enum InnerPosition { + BEFORE, + START, + END, + AFTER, + FIRST_INNER_POSITION = BEFORE, + LAST_INNER_POSITION = AFTER + }; + + LParallelMove* GetOrCreateParallelMove(InnerPosition pos) { + if (parallel_moves_[pos] == NULL) parallel_moves_[pos] = new LParallelMove; + return parallel_moves_[pos]; + } + + LParallelMove* GetParallelMove(InnerPosition pos) { + return parallel_moves_[pos]; + } + + private: + LParallelMove* parallel_moves_[LAST_INNER_POSITION + 1]; + HBasicBlock* block_; +}; + + +class LGoto: public LInstruction { + public: + LGoto(int block_id, bool include_stack_check = false) + : block_id_(block_id), include_stack_check_(include_stack_check) { } + + DECLARE_CONCRETE_INSTRUCTION(Goto, "goto") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int block_id() const { return block_id_; } + bool include_stack_check() const { return include_stack_check_; } + + private: + int block_id_; + bool include_stack_check_; +}; + + +class LLazyBailout: public LInstruction { + public: + LLazyBailout() : gap_instructions_size_(0) { } + + DECLARE_CONCRETE_INSTRUCTION(LazyBailout, "lazy-bailout") + + void set_gap_instructions_size(int gap_instructions_size) { + gap_instructions_size_ = gap_instructions_size; + } + int gap_instructions_size() { return gap_instructions_size_; } + + private: + int gap_instructions_size_; +}; + + +class LDeoptimize: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(Deoptimize, "deoptimize") +}; + + +class LLabel: public LGap { + public: + explicit LLabel(HBasicBlock* block) + : LGap(block), replacement_(NULL) { } + + DECLARE_CONCRETE_INSTRUCTION(Label, "label") + + virtual void PrintDataTo(StringStream* stream) const; + + int block_id() const { return block()->block_id(); } + bool is_loop_header() const { return block()->IsLoopHeader(); } + Label* label() { return &label_; } + LLabel* replacement() const { return replacement_; } + void set_replacement(LLabel* label) { replacement_ = label; } + bool HasReplacement() const { return replacement_ != NULL; } + + private: + Label label_; + LLabel* replacement_; +}; + + +class LParameter: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(Parameter, "parameter") +}; + + +class LCallStub: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallStub, "call-stub") + DECLARE_HYDROGEN_ACCESSOR(CallStub) + + TranscendentalCache::Type transcendental_type() { + return hydrogen()->transcendental_type(); + } +}; + + +class LUnknownOSRValue: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(UnknownOSRValue, "unknown-osr-value") +}; + + +class LUnaryOperation: public LInstruction { + public: + explicit LUnaryOperation(LOperand* input) : input_(input) { } + + DECLARE_INSTRUCTION(UnaryOperation) + + LOperand* input() const { return input_; } + + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* input_; +}; + + +class LBinaryOperation: public LInstruction { + public: + LBinaryOperation(LOperand* left, LOperand* right) + : left_(left), right_(right) { } + + DECLARE_INSTRUCTION(BinaryOperation) + + LOperand* left() const { return left_; } + LOperand* right() const { return right_; } + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* left_; + LOperand* right_; +}; + + +class LApplyArguments: public LBinaryOperation { + public: + LApplyArguments(LOperand* function, + LOperand* receiver, + LOperand* length, + LOperand* elements) + : LBinaryOperation(function, receiver), + length_(length), + elements_(elements) { } + + DECLARE_CONCRETE_INSTRUCTION(ApplyArguments, "apply-arguments") + + LOperand* function() const { return left(); } + LOperand* receiver() const { return right(); } + LOperand* length() const { return length_; } + LOperand* elements() const { return elements_; } + + private: + LOperand* length_; + LOperand* elements_; +}; + + +class LAccessArgumentsAt: public LInstruction { + public: + LAccessArgumentsAt(LOperand* arguments, LOperand* length, LOperand* index) + : arguments_(arguments), length_(length), index_(index) { } + + DECLARE_CONCRETE_INSTRUCTION(AccessArgumentsAt, "access-arguments-at") + + LOperand* arguments() const { return arguments_; } + LOperand* length() const { return length_; } + LOperand* index() const { return index_; } + + virtual void PrintDataTo(StringStream* stream) const; + + private: + LOperand* arguments_; + LOperand* length_; + LOperand* index_; +}; + + +class LArgumentsLength: public LUnaryOperation { + public: + explicit LArgumentsLength(LOperand* elements) : LUnaryOperation(elements) {} + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsLength, "arguments-length") +}; + + +class LArgumentsElements: public LInstruction { + public: + LArgumentsElements() { } + + DECLARE_CONCRETE_INSTRUCTION(ArgumentsElements, "arguments-elements") +}; + + +class LModI: public LBinaryOperation { + public: + LModI(LOperand* left, LOperand* right) : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(ModI, "mod-i") + DECLARE_HYDROGEN_ACCESSOR(Mod) +}; + + +class LDivI: public LBinaryOperation { + public: + LDivI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(DivI, "div-i") + DECLARE_HYDROGEN_ACCESSOR(Div) +}; + + +class LMulI: public LBinaryOperation { + public: + LMulI(LOperand* left, LOperand* right, LOperand* temp) + : LBinaryOperation(left, right), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(MulI, "mul-i") + DECLARE_HYDROGEN_ACCESSOR(Mul) + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LCmpID: public LBinaryOperation { + public: + LCmpID(Token::Value op, LOperand* left, LOperand* right, bool is_double) + : LBinaryOperation(left, right), op_(op), is_double_(is_double) { } + + Token::Value op() const { return op_; } + bool is_double() const { return is_double_; } + + DECLARE_CONCRETE_INSTRUCTION(CmpID, "cmp-id") + + private: + Token::Value op_; + bool is_double_; +}; + + +class LCmpIDAndBranch: public LCmpID { + public: + LCmpIDAndBranch(Token::Value op, + LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id, + bool is_double) + : LCmpID(op, left, right, is_double), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpIDAndBranch, "cmp-id-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LUnaryMathOperation: public LUnaryOperation { + public: + explicit LUnaryMathOperation(LOperand* value) + : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(UnaryMathOperation, "unary-math-operation") + DECLARE_HYDROGEN_ACCESSOR(UnaryMathOperation) + + virtual void PrintDataTo(StringStream* stream) const; + BuiltinFunctionId op() const { return hydrogen()->op(); } +}; + + +class LCmpJSObjectEq: public LBinaryOperation { + public: + LCmpJSObjectEq(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) {} + + DECLARE_CONCRETE_INSTRUCTION(CmpJSObjectEq, "cmp-jsobject-eq") +}; + + +class LCmpJSObjectEqAndBranch: public LCmpJSObjectEq { + public: + LCmpJSObjectEqAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LCmpJSObjectEq(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpJSObjectEqAndBranch, + "cmp-jsobject-eq-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LIsNull: public LUnaryOperation { + public: + LIsNull(LOperand* value, bool is_strict) + : LUnaryOperation(value), is_strict_(is_strict) {} + + DECLARE_CONCRETE_INSTRUCTION(IsNull, "is-null") + + bool is_strict() const { return is_strict_; } + + private: + bool is_strict_; +}; + + +class LIsNullAndBranch: public LIsNull { + public: + LIsNullAndBranch(LOperand* value, + bool is_strict, + LOperand* temp, + int true_block_id, + int false_block_id) + : LIsNull(value, is_strict), + temp_(temp), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsNullAndBranch, "is-null-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; + int true_block_id_; + int false_block_id_; +}; + + +class LIsObject: public LUnaryOperation { + public: + LIsObject(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temp_(temp) {} + + DECLARE_CONCRETE_INSTRUCTION(IsObject, "is-object") + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LIsObjectAndBranch: public LIsObject { + public: + LIsObjectAndBranch(LOperand* value, + LOperand* temp, + LOperand* temp2, + int true_block_id, + int false_block_id) + : LIsObject(value, temp), + temp2_(temp2), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsObjectAndBranch, "is-object-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp2() const { return temp2_; } + + private: + LOperand* temp2_; + int true_block_id_; + int false_block_id_; +}; + + +class LIsSmi: public LUnaryOperation { + public: + explicit LIsSmi(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(IsSmi, "is-smi") + DECLARE_HYDROGEN_ACCESSOR(IsSmi) +}; + + +class LIsSmiAndBranch: public LIsSmi { + public: + LIsSmiAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LIsSmi(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(IsSmiAndBranch, "is-smi-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LHasInstanceType: public LUnaryOperation { + public: + explicit LHasInstanceType(LOperand* value) + : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceType, "has-instance-type") + DECLARE_HYDROGEN_ACCESSOR(HasInstanceType) + + InstanceType TestType(); // The type to test against when generating code. + Condition BranchCondition(); // The branch condition for 'true'. +}; + + +class LHasInstanceTypeAndBranch: public LHasInstanceType { + public: + LHasInstanceTypeAndBranch(LOperand* value, + LOperand* temporary, + int true_block_id, + int false_block_id) + : LHasInstanceType(value), + temp_(temporary), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(HasInstanceTypeAndBranch, + "has-instance-type-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + LOperand* temp() { return temp_; } + + private: + LOperand* temp_; + int true_block_id_; + int false_block_id_; +}; + + +class LHasCachedArrayIndex: public LUnaryOperation { + public: + explicit LHasCachedArrayIndex(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndex, "has-cached-array-index") + DECLARE_HYDROGEN_ACCESSOR(HasCachedArrayIndex) +}; + + +class LHasCachedArrayIndexAndBranch: public LHasCachedArrayIndex { + public: + LHasCachedArrayIndexAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LHasCachedArrayIndex(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(HasCachedArrayIndexAndBranch, + "has-cached-array-index-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LClassOfTest: public LUnaryOperation { + public: + LClassOfTest(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temporary_(temp) {} + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTest, "class-of-test") + DECLARE_HYDROGEN_ACCESSOR(ClassOfTest) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* temporary() { return temporary_; } + + private: + LOperand *temporary_; +}; + + +class LClassOfTestAndBranch: public LClassOfTest { + public: + LClassOfTestAndBranch(LOperand* value, + LOperand* temporary, + LOperand* temporary2, + int true_block_id, + int false_block_id) + : LClassOfTest(value, temporary), + temporary2_(temporary2), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(ClassOfTestAndBranch, + "class-of-test-and-branch") + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + LOperand* temporary2() { return temporary2_; } + + private: + LOperand* temporary2_; + int true_block_id_; + int false_block_id_; +}; + + +class LCmpT: public LBinaryOperation { + public: + LCmpT(LOperand* left, LOperand* right) : LBinaryOperation(left, right) {} + + DECLARE_CONCRETE_INSTRUCTION(CmpT, "cmp-t") + DECLARE_HYDROGEN_ACCESSOR(Compare) + + Token::Value op() const { return hydrogen()->token(); } +}; + + +class LCmpTAndBranch: public LCmpT { + public: + LCmpTAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LCmpT(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpTAndBranch, "cmp-t-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LInstanceOf: public LBinaryOperation { + public: + LInstanceOf(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOf, "instance-of") +}; + + +class LInstanceOfAndBranch: public LInstanceOf { + public: + LInstanceOfAndBranch(LOperand* left, + LOperand* right, + int true_block_id, + int false_block_id) + : LInstanceOf(left, right), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(InstanceOfAndBranch, "instance-of-and-branch") + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LBoundsCheck: public LBinaryOperation { + public: + LBoundsCheck(LOperand* index, LOperand* length) + : LBinaryOperation(index, length) { } + + LOperand* index() const { return left(); } + LOperand* length() const { return right(); } + + DECLARE_CONCRETE_INSTRUCTION(BoundsCheck, "bounds-check") +}; + + +class LBitI: public LBinaryOperation { + public: + LBitI(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + Token::Value op() const { return op_; } + + DECLARE_CONCRETE_INSTRUCTION(BitI, "bit-i") + + private: + Token::Value op_; +}; + + +class LShiftI: public LBinaryOperation { + public: + LShiftI(Token::Value op, LOperand* left, LOperand* right, bool can_deopt) + : LBinaryOperation(left, right), op_(op), can_deopt_(can_deopt) { } + + Token::Value op() const { return op_; } + + bool can_deopt() const { return can_deopt_; } + + DECLARE_CONCRETE_INSTRUCTION(ShiftI, "shift-i") + + private: + Token::Value op_; + bool can_deopt_; +}; + + +class LSubI: public LBinaryOperation { + public: + LSubI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(SubI, "sub-i") + DECLARE_HYDROGEN_ACCESSOR(Sub) +}; + + +class LConstant: public LInstruction { + DECLARE_INSTRUCTION(Constant) +}; + + +class LConstantI: public LConstant { + public: + explicit LConstantI(int32_t value) : value_(value) { } + int32_t value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantI, "constant-i") + + private: + int32_t value_; +}; + + +class LConstantD: public LConstant { + public: + explicit LConstantD(double value) : value_(value) { } + double value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantD, "constant-d") + + private: + double value_; +}; + + +class LConstantT: public LConstant { + public: + explicit LConstantT(Handle<Object> value) : value_(value) { } + Handle<Object> value() const { return value_; } + + DECLARE_CONCRETE_INSTRUCTION(ConstantT, "constant-t") + + private: + Handle<Object> value_; +}; + + +class LBranch: public LUnaryOperation { + public: + LBranch(LOperand* input, int true_block_id, int false_block_id) + : LUnaryOperation(input), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(Branch, "branch") + DECLARE_HYDROGEN_ACCESSOR(Value) + + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LCmpMapAndBranch: public LUnaryOperation { + public: + LCmpMapAndBranch(LOperand* value, + Handle<Map> map, + int true_block_id, + int false_block_id) + : LUnaryOperation(value), + map_(map), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(CmpMapAndBranch, "cmp-map-and-branch") + + virtual bool IsControl() const { return true; } + + Handle<Map> map() const { return map_; } + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + Handle<Map> map_; + int true_block_id_; + int false_block_id_; +}; + + +class LArrayLength: public LUnaryOperation { + public: + LArrayLength(LOperand* input, LOperand* temporary) + : LUnaryOperation(input), temporary_(temporary) { } + + LOperand* temporary() const { return temporary_; } + + DECLARE_CONCRETE_INSTRUCTION(ArrayLength, "array-length") + DECLARE_HYDROGEN_ACCESSOR(ArrayLength) + + private: + LOperand* temporary_; +}; + + +class LValueOf: public LUnaryOperation { + public: + LValueOf(LOperand* input, LOperand* temporary) + : LUnaryOperation(input), temporary_(temporary) { } + + LOperand* temporary() const { return temporary_; } + + DECLARE_CONCRETE_INSTRUCTION(ValueOf, "value-of") + DECLARE_HYDROGEN_ACCESSOR(ValueOf) + + private: + LOperand* temporary_; +}; + + +class LThrow: public LUnaryOperation { + public: + explicit LThrow(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(Throw, "throw") +}; + + +class LBitNotI: public LUnaryOperation { + public: + explicit LBitNotI(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(BitNotI, "bit-not-i") +}; + + +class LAddI: public LBinaryOperation { + public: + LAddI(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(AddI, "add-i") + DECLARE_HYDROGEN_ACCESSOR(Add) +}; + + +class LPower: public LBinaryOperation { + public: + LPower(LOperand* left, LOperand* right) + : LBinaryOperation(left, right) { } + + DECLARE_CONCRETE_INSTRUCTION(Power, "power") + DECLARE_HYDROGEN_ACCESSOR(Power) +}; + + +class LArithmeticD: public LBinaryOperation { + public: + LArithmeticD(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + Token::Value op() const { return op_; } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const; + + private: + Token::Value op_; +}; + + +class LArithmeticT: public LBinaryOperation { + public: + LArithmeticT(Token::Value op, LOperand* left, LOperand* right) + : LBinaryOperation(left, right), op_(op) { } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const; + + Token::Value op() const { return op_; } + + private: + Token::Value op_; +}; + + +class LReturn: public LUnaryOperation { + public: + explicit LReturn(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(Return, "return") +}; + + +class LLoadNamedField: public LUnaryOperation { + public: + explicit LLoadNamedField(LOperand* object) : LUnaryOperation(object) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedField, "load-named-field") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedField) +}; + + +class LLoadNamedGeneric: public LUnaryOperation { + public: + explicit LLoadNamedGeneric(LOperand* object) : LUnaryOperation(object) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadNamedGeneric, "load-named-generic") + DECLARE_HYDROGEN_ACCESSOR(LoadNamedGeneric) + + LOperand* object() const { return input(); } + Handle<Object> name() const { return hydrogen()->name(); } +}; + + +class LLoadElements: public LUnaryOperation { + public: + explicit LLoadElements(LOperand* obj) : LUnaryOperation(obj) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadElements, "load-elements") +}; + + +class LLoadKeyedFastElement: public LBinaryOperation { + public: + LLoadKeyedFastElement(LOperand* elements, + LOperand* key, + LOperand* load_result) + : LBinaryOperation(elements, key), + load_result_(load_result) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedFastElement, "load-keyed-fast-element") + DECLARE_HYDROGEN_ACCESSOR(LoadKeyedFastElement) + + LOperand* elements() const { return left(); } + LOperand* key() const { return right(); } + LOperand* load_result() const { return load_result_; } + + private: + LOperand* load_result_; +}; + + +class LLoadKeyedGeneric: public LBinaryOperation { + public: + LLoadKeyedGeneric(LOperand* obj, LOperand* key) + : LBinaryOperation(obj, key) { } + + DECLARE_CONCRETE_INSTRUCTION(LoadKeyedGeneric, "load-keyed-generic") + + LOperand* object() const { return left(); } + LOperand* key() const { return right(); } +}; + + +class LLoadGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(LoadGlobal, "load-global") + DECLARE_HYDROGEN_ACCESSOR(LoadGlobal) +}; + + +class LStoreGlobal: public LUnaryOperation { + public: + explicit LStoreGlobal(LOperand* value) : LUnaryOperation(value) {} + + DECLARE_CONCRETE_INSTRUCTION(StoreGlobal, "store-global") + DECLARE_HYDROGEN_ACCESSOR(StoreGlobal) +}; + + +class LPushArgument: public LUnaryOperation { + public: + explicit LPushArgument(LOperand* argument) : LUnaryOperation(argument) {} + + DECLARE_CONCRETE_INSTRUCTION(PushArgument, "push-argument") +}; + + +class LGlobalObject: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(GlobalObject, "global-object") +}; + + +class LGlobalReceiver: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(GlobalReceiver, "global-receiver") +}; + + +class LCallConstantFunction: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction, "call-constant-function") + DECLARE_HYDROGEN_ACCESSOR(CallConstantFunction) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<JSFunction> function() const { return hydrogen()->function(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallKeyed: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallKeyed, "call-keyed") + DECLARE_HYDROGEN_ACCESSOR(CallKeyed) + + virtual void PrintDataTo(StringStream* stream) const; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNamed: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallNamed, "call-named") + DECLARE_HYDROGEN_ACCESSOR(CallNamed) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const { return hydrogen()->name(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallFunction: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallFunction, "call-function") + DECLARE_HYDROGEN_ACCESSOR(CallFunction) + + int arity() const { return hydrogen()->argument_count() - 2; } +}; + + +class LCallGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallGlobal, "call-global") + DECLARE_HYDROGEN_ACCESSOR(CallGlobal) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<String> name() const {return hydrogen()->name(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallKnownGlobal: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal, "call-known-global") + DECLARE_HYDROGEN_ACCESSOR(CallKnownGlobal) + + virtual void PrintDataTo(StringStream* stream) const; + + Handle<JSFunction> target() const { return hydrogen()->target(); } + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallNew: public LUnaryOperation { + public: + explicit LCallNew(LOperand* constructor) : LUnaryOperation(constructor) { } + + DECLARE_CONCRETE_INSTRUCTION(CallNew, "call-new") + DECLARE_HYDROGEN_ACCESSOR(CallNew) + + virtual void PrintDataTo(StringStream* stream) const; + + int arity() const { return hydrogen()->argument_count() - 1; } +}; + + +class LCallRuntime: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(CallRuntime, "call-runtime") + DECLARE_HYDROGEN_ACCESSOR(CallRuntime) + + Runtime::Function* function() const { return hydrogen()->function(); } + int arity() const { return hydrogen()->argument_count(); } +}; + + +class LInteger32ToDouble: public LUnaryOperation { + public: + explicit LInteger32ToDouble(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(Integer32ToDouble, "int32-to-double") +}; + + +class LNumberTagI: public LUnaryOperation { + public: + explicit LNumberTagI(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagI, "number-tag-i") +}; + + +class LNumberTagD: public LUnaryOperation { + public: + explicit LNumberTagD(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberTagD, "number-tag-d") + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +// Sometimes truncating conversion from a tagged value to an int32. +class LDoubleToI: public LUnaryOperation { + public: + explicit LDoubleToI(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(DoubleToI, "double-to-i") + DECLARE_HYDROGEN_ACCESSOR(Change) + + bool truncating() { return hydrogen()->CanTruncateToInt32(); } +}; + + +// Truncating conversion from a tagged value to an int32. +class LTaggedToI: public LUnaryOperation { + public: + LTaggedToI(LOperand* value, LOperand* temp) + : LUnaryOperation(value), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(TaggedToI, "tagged-to-i") + DECLARE_HYDROGEN_ACCESSOR(Change) + + bool truncating() { return hydrogen()->CanTruncateToInt32(); } + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LSmiTag: public LUnaryOperation { + public: + explicit LSmiTag(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(SmiTag, "smi-tag") +}; + + +class LNumberUntagD: public LUnaryOperation { + public: + explicit LNumberUntagD(LOperand* value) : LUnaryOperation(value) { } + + DECLARE_CONCRETE_INSTRUCTION(NumberUntagD, "double-untag") +}; + + +class LSmiUntag: public LUnaryOperation { + public: + LSmiUntag(LOperand* use, bool needs_check) + : LUnaryOperation(use), needs_check_(needs_check) { } + + DECLARE_CONCRETE_INSTRUCTION(SmiUntag, "smi-untag") + + bool needs_check() const { return needs_check_; } + + private: + bool needs_check_; +}; + + +class LStoreNamed: public LInstruction { + public: + LStoreNamed(LOperand* obj, Handle<Object> name, LOperand* val) + : object_(obj), name_(name), value_(val) { } + + DECLARE_INSTRUCTION(StoreNamed) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* object() const { return object_; } + Handle<Object> name() const { return name_; } + LOperand* value() const { return value_; } + + private: + LOperand* object_; + Handle<Object> name_; + LOperand* value_; +}; + + +class LStoreNamedField: public LStoreNamed { + public: + LStoreNamedField(LOperand* obj, + Handle<Object> name, + LOperand* val, + bool in_object, + int offset, + LOperand* temp, + bool needs_write_barrier, + Handle<Map> transition) + : LStoreNamed(obj, name, val), + is_in_object_(in_object), + offset_(offset), + temp_(temp), + needs_write_barrier_(needs_write_barrier), + transition_(transition) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedField, "store-named-field") + + bool is_in_object() { return is_in_object_; } + int offset() { return offset_; } + LOperand* temp() { return temp_; } + bool needs_write_barrier() { return needs_write_barrier_; } + Handle<Map> transition() const { return transition_; } + void set_transition(Handle<Map> map) { transition_ = map; } + + private: + bool is_in_object_; + int offset_; + LOperand* temp_; + bool needs_write_barrier_; + Handle<Map> transition_; +}; + + +class LStoreNamedGeneric: public LStoreNamed { + public: + LStoreNamedGeneric(LOperand* obj, + Handle<Object> name, + LOperand* val) + : LStoreNamed(obj, name, val) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreNamedGeneric, "store-named-generic") +}; + + +class LStoreKeyed: public LInstruction { + public: + LStoreKeyed(LOperand* obj, LOperand* key, LOperand* val) + : object_(obj), key_(key), value_(val) { } + + DECLARE_INSTRUCTION(StoreKeyed) + + virtual void PrintDataTo(StringStream* stream) const; + + LOperand* object() const { return object_; } + LOperand* key() const { return key_; } + LOperand* value() const { return value_; } + + private: + LOperand* object_; + LOperand* key_; + LOperand* value_; +}; + + +class LStoreKeyedFastElement: public LStoreKeyed { + public: + LStoreKeyedFastElement(LOperand* obj, LOperand* key, LOperand* val) + : LStoreKeyed(obj, key, val) {} + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFastElement, + "store-keyed-fast-element") + DECLARE_HYDROGEN_ACCESSOR(StoreKeyedFastElement) +}; + + +class LStoreKeyedGeneric: public LStoreKeyed { + public: + LStoreKeyedGeneric(LOperand* obj, LOperand* key, LOperand* val) + : LStoreKeyed(obj, key, val) { } + + DECLARE_CONCRETE_INSTRUCTION(StoreKeyedGeneric, "store-keyed-generic") +}; + + +class LCheckFunction: public LUnaryOperation { + public: + explicit LCheckFunction(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckFunction, "check-function") + DECLARE_HYDROGEN_ACCESSOR(CheckFunction) +}; + + +class LCheckInstanceType: public LUnaryOperation { + public: + LCheckInstanceType(LOperand* use, LOperand* temp) + : LUnaryOperation(use), temp_(temp) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckInstanceType, "check-instance-type") + DECLARE_HYDROGEN_ACCESSOR(CheckInstanceType) + + LOperand* temp() const { return temp_; } + + private: + LOperand* temp_; +}; + + +class LCheckMap: public LUnaryOperation { + public: + explicit LCheckMap(LOperand* use) : LUnaryOperation(use) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckMap, "check-map") + DECLARE_HYDROGEN_ACCESSOR(CheckMap) +}; + + +class LCheckPrototypeMaps: public LInstruction { + public: + LCheckPrototypeMaps(LOperand* temp, + Handle<JSObject> holder, + Handle<Map> receiver_map) + : temp_(temp), + holder_(holder), + receiver_map_(receiver_map) { } + + DECLARE_CONCRETE_INSTRUCTION(CheckPrototypeMaps, "check-prototype-maps") + + LOperand* temp() const { return temp_; } + Handle<JSObject> holder() const { return holder_; } + Handle<Map> receiver_map() const { return receiver_map_; } + + private: + LOperand* temp_; + Handle<JSObject> holder_; + Handle<Map> receiver_map_; +}; + + +class LCheckSmi: public LUnaryOperation { + public: + LCheckSmi(LOperand* use, Condition condition) + : LUnaryOperation(use), condition_(condition) { } + + Condition condition() const { return condition_; } + + virtual void CompileToNative(LCodeGen* generator); + virtual const char* Mnemonic() const { + return (condition_ == zero) ? "check-non-smi" : "check-smi"; + } + + private: + Condition condition_; +}; + + +class LMaterializedLiteral: public LInstruction { + public: + DECLARE_INSTRUCTION(MaterializedLiteral) +}; + + +class LArrayLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(ArrayLiteral, "array-literal") + DECLARE_HYDROGEN_ACCESSOR(ArrayLiteral) +}; + + +class LObjectLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(ObjectLiteral, "object-literal") + DECLARE_HYDROGEN_ACCESSOR(ObjectLiteral) +}; + + +class LRegExpLiteral: public LMaterializedLiteral { + public: + DECLARE_CONCRETE_INSTRUCTION(RegExpLiteral, "regexp-literal") + DECLARE_HYDROGEN_ACCESSOR(RegExpLiteral) +}; + + +class LFunctionLiteral: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(FunctionLiteral, "function-literal") + DECLARE_HYDROGEN_ACCESSOR(FunctionLiteral) + + Handle<SharedFunctionInfo> shared_info() { return hydrogen()->shared_info(); } +}; + + +class LTypeof: public LUnaryOperation { + public: + explicit LTypeof(LOperand* input) : LUnaryOperation(input) { } + + DECLARE_CONCRETE_INSTRUCTION(Typeof, "typeof") +}; + + +class LTypeofIs: public LUnaryOperation { + public: + explicit LTypeofIs(LOperand* input) : LUnaryOperation(input) { } + virtual void PrintDataTo(StringStream* stream) const; + + DECLARE_CONCRETE_INSTRUCTION(TypeofIs, "typeof-is") + DECLARE_HYDROGEN_ACCESSOR(TypeofIs) + + Handle<String> type_literal() { return hydrogen()->type_literal(); } +}; + + +class LTypeofIsAndBranch: public LTypeofIs { + public: + LTypeofIsAndBranch(LOperand* value, + int true_block_id, + int false_block_id) + : LTypeofIs(value), + true_block_id_(true_block_id), + false_block_id_(false_block_id) { } + + DECLARE_CONCRETE_INSTRUCTION(TypeofIsAndBranch, "typeof-is-and-branch") + + virtual void PrintDataTo(StringStream* stream) const; + virtual bool IsControl() const { return true; } + + int true_block_id() const { return true_block_id_; } + int false_block_id() const { return false_block_id_; } + + private: + int true_block_id_; + int false_block_id_; +}; + + +class LDeleteProperty: public LBinaryOperation { + public: + LDeleteProperty(LOperand* obj, LOperand* key) : LBinaryOperation(obj, key) {} + + DECLARE_CONCRETE_INSTRUCTION(DeleteProperty, "delete-property") + + LOperand* object() const { return left(); } + LOperand* key() const { return right(); } +}; + + +class LOsrEntry: public LInstruction { + public: + LOsrEntry(); + + DECLARE_CONCRETE_INSTRUCTION(OsrEntry, "osr-entry") + + LOperand** SpilledRegisterArray() { return register_spills_; } + LOperand** SpilledDoubleRegisterArray() { return double_register_spills_; } + + void MarkSpilledRegister(int allocation_index, LOperand* spill_operand); + void MarkSpilledDoubleRegister(int allocation_index, + LOperand* spill_operand); + + private: + // Arrays of spill slot operands for registers with an assigned spill + // slot, i.e., that must also be restored to the spill slot on OSR entry. + // NULL if the register has no assigned spill slot. Indexed by allocation + // index. + LOperand* register_spills_[Register::kNumAllocatableRegisters]; + LOperand* double_register_spills_[DoubleRegister::kNumAllocatableRegisters]; +}; + + +class LStackCheck: public LInstruction { + public: + DECLARE_CONCRETE_INSTRUCTION(StackCheck, "stack-check") +}; + + +class LPointerMap: public ZoneObject { + public: + explicit LPointerMap(int position) + : pointer_operands_(8), position_(position), lithium_position_(-1) { } + + const ZoneList<LOperand*>* operands() const { return &pointer_operands_; } + int position() const { return position_; } + int lithium_position() const { return lithium_position_; } + + void set_lithium_position(int pos) { + ASSERT(lithium_position_ == -1); + lithium_position_ = pos; + } + + void RecordPointer(LOperand* op); + void PrintTo(StringStream* stream) const; + + private: + ZoneList<LOperand*> pointer_operands_; + int position_; + int lithium_position_; +}; + + +class LEnvironment: public ZoneObject { + public: + LEnvironment(Handle<JSFunction> closure, + int ast_id, + int parameter_count, + int argument_count, + int value_count, + LEnvironment* outer) + : closure_(closure), + arguments_stack_height_(argument_count), + deoptimization_index_(Safepoint::kNoDeoptimizationIndex), + translation_index_(-1), + ast_id_(ast_id), + parameter_count_(parameter_count), + values_(value_count), + representations_(value_count), + spilled_registers_(NULL), + spilled_double_registers_(NULL), + outer_(outer) { + } + + Handle<JSFunction> closure() const { return closure_; } + int arguments_stack_height() const { return arguments_stack_height_; } + int deoptimization_index() const { return deoptimization_index_; } + int translation_index() const { return translation_index_; } + int ast_id() const { return ast_id_; } + int parameter_count() const { return parameter_count_; } + const ZoneList<LOperand*>* values() const { return &values_; } + LEnvironment* outer() const { return outer_; } + + void AddValue(LOperand* operand, Representation representation) { + values_.Add(operand); + representations_.Add(representation); + } + + bool HasTaggedValueAt(int index) const { + return representations_[index].IsTagged(); + } + + void Register(int deoptimization_index, int translation_index) { + ASSERT(!HasBeenRegistered()); + deoptimization_index_ = deoptimization_index; + translation_index_ = translation_index; + } + bool HasBeenRegistered() const { + return deoptimization_index_ != Safepoint::kNoDeoptimizationIndex; + } + + void SetSpilledRegisters(LOperand** registers, + LOperand** double_registers) { + spilled_registers_ = registers; + spilled_double_registers_ = double_registers; + } + + // Emit frame translation commands for this environment. + void WriteTranslation(LCodeGen* cgen, Translation* translation) const; + + void PrintTo(StringStream* stream) const; + + private: + Handle<JSFunction> closure_; + int arguments_stack_height_; + int deoptimization_index_; + int translation_index_; + int ast_id_; + int parameter_count_; + ZoneList<LOperand*> values_; + ZoneList<Representation> representations_; + + // Allocation index indexed arrays of spill slot operands for registers + // that are also in spill slots at an OSR entry. NULL for environments + // that do not correspond to an OSR entry. + LOperand** spilled_registers_; + LOperand** spilled_double_registers_; + + LEnvironment* outer_; +}; + +class LChunkBuilder; +class LChunk: public ZoneObject { + public: + explicit LChunk(HGraph* graph); + + int AddInstruction(LInstruction* instruction, HBasicBlock* block); + LConstantOperand* DefineConstantOperand(HConstant* constant); + Handle<Object> LookupLiteral(LConstantOperand* operand) const; + Representation LookupLiteralRepresentation(LConstantOperand* operand) const; + + int GetNextSpillIndex(bool is_double); + LOperand* GetNextSpillSlot(bool is_double); + + int ParameterAt(int index); + int GetParameterStackSlot(int index) const; + int spill_slot_count() const { return spill_slot_count_; } + HGraph* graph() const { return graph_; } + const ZoneList<LInstruction*>* instructions() const { return &instructions_; } + void AddGapMove(int index, LOperand* from, LOperand* to); + LGap* GetGapAt(int index) const; + bool IsGapAt(int index) const; + int NearestGapPos(int index) const; + void MarkEmptyBlocks(); + const ZoneList<LPointerMap*>* pointer_maps() const { return &pointer_maps_; } + LLabel* GetLabel(int block_id) const { + HBasicBlock* block = graph_->blocks()->at(block_id); + int first_instruction = block->first_instruction_index(); + return LLabel::cast(instructions_[first_instruction]); + } + int LookupDestination(int block_id) const { + LLabel* cur = GetLabel(block_id); + while (cur->replacement() != NULL) { + cur = cur->replacement(); + } + return cur->block_id(); + } + Label* GetAssemblyLabel(int block_id) const { + LLabel* label = GetLabel(block_id); + ASSERT(!label->HasReplacement()); + return label->label(); + } + + const ZoneList<Handle<JSFunction> >* inlined_closures() const { + return &inlined_closures_; + } + + void AddInlinedClosure(Handle<JSFunction> closure) { + inlined_closures_.Add(closure); + } + + void Verify() const; + + private: + int spill_slot_count_; + HGraph* const graph_; + ZoneList<LInstruction*> instructions_; + ZoneList<LPointerMap*> pointer_maps_; + ZoneList<Handle<JSFunction> > inlined_closures_; +}; + + +class LChunkBuilder BASE_EMBEDDED { + public: + LChunkBuilder(HGraph* graph, LAllocator* allocator) + : chunk_(NULL), + graph_(graph), + status_(UNUSED), + current_instruction_(NULL), + current_block_(NULL), + next_block_(NULL), + argument_count_(0), + allocator_(allocator), + position_(RelocInfo::kNoPosition), + instructions_pending_deoptimization_environment_(NULL), + pending_deoptimization_ast_id_(AstNode::kNoNumber) { } + + // Build the sequence for the graph. + LChunk* Build(); + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) LInstruction* Do##type(H##type* node); + HYDROGEN_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + private: + enum Status { + UNUSED, + BUILDING, + DONE, + ABORTED + }; + + LChunk* chunk() const { return chunk_; } + HGraph* graph() const { return graph_; } + + bool is_unused() const { return status_ == UNUSED; } + bool is_building() const { return status_ == BUILDING; } + bool is_done() const { return status_ == DONE; } + bool is_aborted() const { return status_ == ABORTED; } + + void Abort(const char* format, ...); + + // Methods for getting operands for Use / Define / Temp. + LRegister* ToOperand(Register reg); + LUnallocated* ToUnallocated(Register reg); + LUnallocated* ToUnallocated(XMMRegister reg); + + // Methods for setting up define-use relationships. + LOperand* Use(HValue* value, LUnallocated* operand); + LOperand* UseFixed(HValue* value, Register fixed_register); + LOperand* UseFixedDouble(HValue* value, XMMRegister fixed_register); + + // A value that is guaranteed to be allocated to a register. + // Operand created by UseRegister is guaranteed to be live until the end of + // instruction. This means that register allocator will not reuse it's + // register for any other operand inside instruction. + // Operand created by UseRegisterAtStart is guaranteed to be live only at + // instruction start. Register allocator is free to assign the same register + // to some other operand used inside instruction (i.e. temporary or + // output). + LOperand* UseRegister(HValue* value); + LOperand* UseRegisterAtStart(HValue* value); + + // A value in a register that may be trashed. + LOperand* UseTempRegister(HValue* value); + LOperand* Use(HValue* value); + LOperand* UseAtStart(HValue* value); + LOperand* UseOrConstant(HValue* value); + LOperand* UseOrConstantAtStart(HValue* value); + LOperand* UseRegisterOrConstant(HValue* value); + LOperand* UseRegisterOrConstantAtStart(HValue* value); + + // Methods for setting up define-use relationships. + // Return the same instruction that they are passed. + LInstruction* Define(LInstruction* instr, LUnallocated* result); + LInstruction* Define(LInstruction* instr); + LInstruction* DefineAsRegister(LInstruction* instr); + LInstruction* DefineAsSpilled(LInstruction* instr, int index); + LInstruction* DefineSameAsAny(LInstruction* instr); + LInstruction* DefineSameAsFirst(LInstruction* instr); + LInstruction* DefineFixed(LInstruction* instr, Register reg); + LInstruction* DefineFixedDouble(LInstruction* instr, XMMRegister reg); + LInstruction* AssignEnvironment(LInstruction* instr); + LInstruction* AssignPointerMap(LInstruction* instr); + + enum CanDeoptimize { CAN_DEOPTIMIZE_EAGERLY, CANNOT_DEOPTIMIZE_EAGERLY }; + + // By default we assume that instruction sequences generated for calls + // cannot deoptimize eagerly and we do not attach environment to this + // instruction. + LInstruction* MarkAsCall( + LInstruction* instr, + HInstruction* hinstr, + CanDeoptimize can_deoptimize = CANNOT_DEOPTIMIZE_EAGERLY); + + LInstruction* SetInstructionPendingDeoptimizationEnvironment( + LInstruction* instr, int ast_id); + void ClearInstructionPendingDeoptimizationEnvironment(); + + LEnvironment* CreateEnvironment(HEnvironment* hydrogen_env); + + // Temporary operand that may be a memory location. + LOperand* Temp(); + // Temporary operand that must be in a register. + LUnallocated* TempRegister(); + LOperand* FixedTemp(Register reg); + LOperand* FixedTemp(XMMRegister reg); + + void VisitInstruction(HInstruction* current); + + void DoBasicBlock(HBasicBlock* block, HBasicBlock* next_block); + LInstruction* DoBit(Token::Value op, HBitwiseBinaryOperation* instr); + LInstruction* DoShift(Token::Value op, HBitwiseBinaryOperation* instr); + LInstruction* DoArithmeticD(Token::Value op, + HArithmeticBinaryOperation* instr); + LInstruction* DoArithmeticT(Token::Value op, + HArithmeticBinaryOperation* instr); + + LChunk* chunk_; + HGraph* const graph_; + Status status_; + HInstruction* current_instruction_; + HBasicBlock* current_block_; + HBasicBlock* next_block_; + int argument_count_; + LAllocator* allocator_; + int position_; + LInstruction* instructions_pending_deoptimization_environment_; + int pending_deoptimization_ast_id_; + + DISALLOW_COPY_AND_ASSIGN(LChunkBuilder); +}; + +#undef DECLARE_HYDROGEN_ACCESSOR +#undef DECLARE_INSTRUCTION +#undef DECLARE_CONCRETE_INSTRUCTION + +} } // namespace v8::internal + +#endif // V8_IA32_LITHIUM_IA32_H_ diff --git a/src/ia32/macro-assembler-ia32.cc b/src/ia32/macro-assembler-ia32.cc index cbf93dd6..7c339065 100644 --- a/src/ia32/macro-assembler-ia32.cc +++ b/src/ia32/macro-assembler-ia32.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -74,30 +74,6 @@ void MacroAssembler::RecordWriteHelper(Register object, } -void MacroAssembler::InNewSpace(Register object, - Register scratch, - Condition cc, - Label* branch) { - ASSERT(cc == equal || cc == not_equal); - if (Serializer::enabled()) { - // Can't do arithmetic on external references if it might get serialized. - mov(scratch, Operand(object)); - // The mask isn't really an address. We load it as an external reference in - // case the size of the new space is different between the snapshot maker - // and the running system. - and_(Operand(scratch), Immediate(ExternalReference::new_space_mask())); - cmp(Operand(scratch), Immediate(ExternalReference::new_space_start())); - j(cc, branch); - } else { - int32_t new_space_start = reinterpret_cast<int32_t>( - ExternalReference::new_space_start().address()); - lea(scratch, Operand(object, -new_space_start)); - and_(scratch, Heap::NewSpaceMask()); - j(cc, branch); - } -} - - void MacroAssembler::RecordWrite(Register object, int offset, Register value, @@ -109,7 +85,7 @@ void MacroAssembler::RecordWrite(Register object, // First, check if a write barrier is even needed. The tests below // catch stores of Smis and stores into young gen. - Label done; + NearLabel done; // Skip barrier if writing a smi. ASSERT_EQ(0, kSmiTag); @@ -183,13 +159,6 @@ void MacroAssembler::RecordWrite(Register object, } -void MacroAssembler::StackLimitCheck(Label* on_stack_overflow) { - cmp(esp, - Operand::StaticVariable(ExternalReference::address_of_stack_limit())); - j(below, on_stack_overflow); -} - - #ifdef ENABLE_DEBUGGER_SUPPORT void MacroAssembler::DebugBreak() { Set(eax, Immediate(0)); @@ -364,9 +333,20 @@ void MacroAssembler::EnterExitFramePrologue() { } -void MacroAssembler::EnterExitFrameEpilogue(int argc) { - // Reserve space for arguments. - sub(Operand(esp), Immediate(argc * kPointerSize)); +void MacroAssembler::EnterExitFrameEpilogue(int argc, bool save_doubles) { + // Optionally save all XMM registers. + if (save_doubles) { + CpuFeatures::Scope scope(SSE2); + int space = XMMRegister::kNumRegisters * kDoubleSize + argc * kPointerSize; + sub(Operand(esp), Immediate(space)); + int offset = -2 * kPointerSize; + for (int i = 0; i < XMMRegister::kNumRegisters; i++) { + XMMRegister reg = XMMRegister::from_code(i); + movdbl(Operand(ebp, offset - ((i + 1) * kDoubleSize)), reg); + } + } else { + sub(Operand(esp), Immediate(argc * kPointerSize)); + } // Get the required frame alignment for the OS. static const int kFrameAlignment = OS::ActivationFrameAlignment(); @@ -380,7 +360,7 @@ void MacroAssembler::EnterExitFrameEpilogue(int argc) { } -void MacroAssembler::EnterExitFrame() { +void MacroAssembler::EnterExitFrame(bool save_doubles) { EnterExitFramePrologue(); // Setup argc and argv in callee-saved registers. @@ -388,17 +368,27 @@ void MacroAssembler::EnterExitFrame() { mov(edi, Operand(eax)); lea(esi, Operand(ebp, eax, times_4, offset)); - EnterExitFrameEpilogue(2); + EnterExitFrameEpilogue(2, save_doubles); } void MacroAssembler::EnterApiExitFrame(int argc) { EnterExitFramePrologue(); - EnterExitFrameEpilogue(argc); + EnterExitFrameEpilogue(argc, false); } -void MacroAssembler::LeaveExitFrame() { +void MacroAssembler::LeaveExitFrame(bool save_doubles) { + // Optionally restore all XMM registers. + if (save_doubles) { + CpuFeatures::Scope scope(SSE2); + int offset = -2 * kPointerSize; + for (int i = 0; i < XMMRegister::kNumRegisters; i++) { + XMMRegister reg = XMMRegister::from_code(i); + movdbl(reg, Operand(ebp, offset - ((i + 1) * kDoubleSize))); + } + } + // Get the return address from the stack and restore the frame pointer. mov(ecx, Operand(ebp, 1 * kPointerSize)); mov(ebp, Operand(ebp, 0 * kPointerSize)); @@ -1098,6 +1088,16 @@ void MacroAssembler::CallRuntime(Runtime::FunctionId id, int num_arguments) { } +void MacroAssembler::CallRuntimeSaveDoubles(Runtime::FunctionId id) { + Runtime::Function* function = Runtime::FunctionForId(id); + Set(eax, Immediate(function->nargs)); + mov(ebx, Immediate(ExternalReference(function))); + CEntryStub ces(1); + ces.SaveDoubles(); + CallStub(&ces); +} + + MaybeObject* MacroAssembler::TryCallRuntime(Runtime::FunctionId id, int num_arguments) { return TryCallRuntime(Runtime::FunctionForId(id), num_arguments); @@ -1192,25 +1192,29 @@ MaybeObject* MacroAssembler::TryTailCallRuntime(Runtime::FunctionId fid, } -// If true, a Handle<T> passed by value is passed and returned by -// using the location_ field directly. If false, it is passed and -// returned as a pointer to a handle. -#ifdef USING_BSD_ABI -static const bool kPassHandlesDirectly = true; +// If true, a Handle<T> returned by value from a function with cdecl calling +// convention will be returned directly as a value of location_ field in a +// register eax. +// If false, it is returned as a pointer to a preallocated by caller memory +// region. Pointer to this region should be passed to a function as an +// implicit first argument. +#if defined(USING_BSD_ABI) || defined(__MINGW32__) +static const bool kReturnHandlesDirectly = true; #else -static const bool kPassHandlesDirectly = false; +static const bool kReturnHandlesDirectly = false; #endif Operand ApiParameterOperand(int index) { - return Operand(esp, (index + (kPassHandlesDirectly ? 0 : 1)) * kPointerSize); + return Operand( + esp, (index + (kReturnHandlesDirectly ? 0 : 1)) * kPointerSize); } void MacroAssembler::PrepareCallApiFunction(int argc, Register scratch) { - if (kPassHandlesDirectly) { + if (kReturnHandlesDirectly) { EnterApiExitFrame(argc); - // When handles as passed directly we don't have to allocate extra + // When handles are returned directly we don't have to allocate extra // space for and pass an out parameter. } else { // We allocate two additional slots: return value and pointer to it. @@ -1255,7 +1259,7 @@ MaybeObject* MacroAssembler::TryCallApiFunctionAndReturn(ApiFunction* function, // Call the api function! call(function->address(), RelocInfo::RUNTIME_ENTRY); - if (!kPassHandlesDirectly) { + if (!kReturnHandlesDirectly) { // The returned value is a pointer to the handle holding the result. // Dereference this to get to the location. mov(eax, Operand(eax, 0)); @@ -1336,7 +1340,8 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected, Handle<Code> code_constant, const Operand& code_operand, Label* done, - InvokeFlag flag) { + InvokeFlag flag, + PostCallGenerator* post_call_generator) { bool definitely_matches = false; Label invoke; if (expected.is_immediate()) { @@ -1387,6 +1392,7 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected, if (flag == CALL_FUNCTION) { call(adaptor, RelocInfo::CODE_TARGET); + if (post_call_generator != NULL) post_call_generator->Generate(); jmp(done); } else { jmp(adaptor, RelocInfo::CODE_TARGET); @@ -1399,11 +1405,14 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected, void MacroAssembler::InvokeCode(const Operand& code, const ParameterCount& expected, const ParameterCount& actual, - InvokeFlag flag) { + InvokeFlag flag, + PostCallGenerator* post_call_generator) { Label done; - InvokePrologue(expected, actual, Handle<Code>::null(), code, &done, flag); + InvokePrologue(expected, actual, Handle<Code>::null(), code, + &done, flag, post_call_generator); if (flag == CALL_FUNCTION) { call(code); + if (post_call_generator != NULL) post_call_generator->Generate(); } else { ASSERT(flag == JUMP_FUNCTION); jmp(code); @@ -1416,12 +1425,15 @@ void MacroAssembler::InvokeCode(Handle<Code> code, const ParameterCount& expected, const ParameterCount& actual, RelocInfo::Mode rmode, - InvokeFlag flag) { + InvokeFlag flag, + PostCallGenerator* post_call_generator) { Label done; Operand dummy(eax); - InvokePrologue(expected, actual, code, dummy, &done, flag); + InvokePrologue(expected, actual, code, dummy, &done, + flag, post_call_generator); if (flag == CALL_FUNCTION) { call(code, rmode); + if (post_call_generator != NULL) post_call_generator->Generate(); } else { ASSERT(flag == JUMP_FUNCTION); jmp(code, rmode); @@ -1432,7 +1444,8 @@ void MacroAssembler::InvokeCode(Handle<Code> code, void MacroAssembler::InvokeFunction(Register fun, const ParameterCount& actual, - InvokeFlag flag) { + InvokeFlag flag, + PostCallGenerator* post_call_generator) { ASSERT(fun.is(edi)); mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset)); mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); @@ -1441,25 +1454,37 @@ void MacroAssembler::InvokeFunction(Register fun, ParameterCount expected(ebx); InvokeCode(FieldOperand(edi, JSFunction::kCodeEntryOffset), - expected, actual, flag); + expected, actual, flag, post_call_generator); } void MacroAssembler::InvokeFunction(JSFunction* function, const ParameterCount& actual, - InvokeFlag flag) { + InvokeFlag flag, + PostCallGenerator* post_call_generator) { ASSERT(function->is_compiled()); // Get the function and setup the context. mov(edi, Immediate(Handle<JSFunction>(function))); mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); - // Invoke the cached code. - Handle<Code> code(function->code()); + ParameterCount expected(function->shared()->formal_parameter_count()); - InvokeCode(code, expected, actual, RelocInfo::CODE_TARGET, flag); + if (V8::UseCrankshaft()) { + // TODO(kasperl): For now, we always call indirectly through the + // code field in the function to allow recompilation to take effect + // without changing any of the call sites. + InvokeCode(FieldOperand(edi, JSFunction::kCodeEntryOffset), + expected, actual, flag, post_call_generator); + } else { + Handle<Code> code(function->code()); + InvokeCode(code, expected, actual, RelocInfo::CODE_TARGET, + flag, post_call_generator); + } } -void MacroAssembler::InvokeBuiltin(Builtins::JavaScript id, InvokeFlag flag) { +void MacroAssembler::InvokeBuiltin(Builtins::JavaScript id, + InvokeFlag flag, + PostCallGenerator* post_call_generator) { // Calls are not allowed in some stubs. ASSERT(flag == JUMP_FUNCTION || allow_stub_calls()); @@ -1469,7 +1494,7 @@ void MacroAssembler::InvokeBuiltin(Builtins::JavaScript id, InvokeFlag flag) { ParameterCount expected(0); GetBuiltinFunction(edi, id); InvokeCode(FieldOperand(edi, JSFunction::kCodeEntryOffset), - expected, expected, flag); + expected, expected, flag, post_call_generator); } void MacroAssembler::GetBuiltinFunction(Register target, @@ -1534,6 +1559,15 @@ void MacroAssembler::LoadGlobalFunctionInitialMap(Register function, } +int MacroAssembler::SafepointRegisterStackIndex(int reg_code) { + // The registers are pushed starting with the lowest encoding, + // which means that lowest encodings are furthest away from + // the stack pointer. + ASSERT(reg_code >= 0 && reg_code < kNumSafepointRegisters); + return kNumSafepointRegisters - reg_code - 1; +} + + void MacroAssembler::Ret() { ret(0); } diff --git a/src/ia32/macro-assembler-ia32.h b/src/ia32/macro-assembler-ia32.h index d208dbe3..6f5fa872 100644 --- a/src/ia32/macro-assembler-ia32.h +++ b/src/ia32/macro-assembler-ia32.h @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -51,6 +51,7 @@ typedef Operand MemOperand; // Forward declaration. class JumpTarget; +class PostCallGenerator; // MacroAssembler implements a collection of frequently used macros. class MacroAssembler: public Assembler { @@ -69,10 +70,11 @@ class MacroAssembler: public Assembler { // Check if object is in new space. // scratch can be object itself, but it will be clobbered. + template <typename LabelType> void InNewSpace(Register object, Register scratch, Condition cc, // equal for new space, not_equal otherwise. - Label* branch); + LabelType* branch); // For page containing |object| mark region covering [object+offset] // dirty. |object| is the object being stored into, |value| is the @@ -103,12 +105,6 @@ class MacroAssembler: public Assembler { #endif // --------------------------------------------------------------------------- - // Stack limit support - - // Do simple test for stack overflow. This doesn't handle an overflow. - void StackLimitCheck(Label* on_stack_limit_hit); - - // --------------------------------------------------------------------------- // Activation frames void EnterInternalFrame() { EnterFrame(StackFrame::INTERNAL); } @@ -117,18 +113,18 @@ class MacroAssembler: public Assembler { void EnterConstructFrame() { EnterFrame(StackFrame::CONSTRUCT); } void LeaveConstructFrame() { LeaveFrame(StackFrame::CONSTRUCT); } - // Enter specific kind of exit frame; either in normal or debug mode. - // Expects the number of arguments in register eax and - // sets up the number of arguments in register edi and the pointer - // to the first argument in register esi. - void EnterExitFrame(); + // Enter specific kind of exit frame. Expects the number of + // arguments in register eax and sets up the number of arguments in + // register edi and the pointer to the first argument in register + // esi. + void EnterExitFrame(bool save_doubles); void EnterApiExitFrame(int argc); // Leave the current exit frame. Expects the return value in // register eax:edx (untouched) and the pointer to the first // argument in register esi. - void LeaveExitFrame(); + void LeaveExitFrame(bool save_doubles); // Leave the current exit frame. Expects the return value in // register eax (untouched). @@ -144,6 +140,11 @@ class MacroAssembler: public Assembler { // function and map can be the same. void LoadGlobalFunctionInitialMap(Register function, Register map); + // Push and pop the registers that can hold pointers. + void PushSafepointRegisters() { pushad(); } + void PopSafepointRegisters() { popad(); } + static int SafepointRegisterStackIndex(int reg_code); + // --------------------------------------------------------------------------- // JavaScript invokes @@ -151,27 +152,33 @@ class MacroAssembler: public Assembler { void InvokeCode(const Operand& code, const ParameterCount& expected, const ParameterCount& actual, - InvokeFlag flag); + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); void InvokeCode(Handle<Code> code, const ParameterCount& expected, const ParameterCount& actual, RelocInfo::Mode rmode, - InvokeFlag flag); + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); // Invoke the JavaScript function in the given register. Changes the // current context to the context in the function before invoking. void InvokeFunction(Register function, const ParameterCount& actual, - InvokeFlag flag); + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); void InvokeFunction(JSFunction* function, const ParameterCount& actual, - InvokeFlag flag); + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); // Invoke specified builtin JavaScript function. Adds an entry to // the unresolved list if the name does not resolve. - void InvokeBuiltin(Builtins::JavaScript id, InvokeFlag flag); + void InvokeBuiltin(Builtins::JavaScript id, + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); // Store the function for the given builtin in the target register. void GetBuiltinFunction(Register target, Builtins::JavaScript id); @@ -457,6 +464,7 @@ class MacroAssembler: public Assembler { // Call a runtime routine. void CallRuntime(Runtime::Function* f, int num_arguments); + void CallRuntimeSaveDoubles(Runtime::FunctionId id); // Call a runtime function, returning the CodeStub object called. // Try to generate the stub code if necessary. Do not perform a GC @@ -546,6 +554,12 @@ class MacroAssembler: public Assembler { void Call(Label* target) { call(target); } + // Emit call to the code we are currently generating. + void CallSelf() { + Handle<Code> self(reinterpret_cast<Code**>(CodeObject().location())); + call(self, RelocInfo::CODE_TARGET); + } + // Move if the registers are not identical. void Move(Register target, Register source); @@ -618,14 +632,15 @@ class MacroAssembler: public Assembler { Handle<Code> code_constant, const Operand& code_operand, Label* done, - InvokeFlag flag); + InvokeFlag flag, + PostCallGenerator* post_call_generator = NULL); // Activation support. void EnterFrame(StackFrame::Type type); void LeaveFrame(StackFrame::Type type); void EnterExitFramePrologue(); - void EnterExitFrameEpilogue(int argc); + void EnterExitFrameEpilogue(int argc, bool save_doubles); void LeaveExitFrameEpilogue(); @@ -644,6 +659,31 @@ class MacroAssembler: public Assembler { }; +template <typename LabelType> +void MacroAssembler::InNewSpace(Register object, + Register scratch, + Condition cc, + LabelType* branch) { + ASSERT(cc == equal || cc == not_equal); + if (Serializer::enabled()) { + // Can't do arithmetic on external references if it might get serialized. + mov(scratch, Operand(object)); + // The mask isn't really an address. We load it as an external reference in + // case the size of the new space is different between the snapshot maker + // and the running system. + and_(Operand(scratch), Immediate(ExternalReference::new_space_mask())); + cmp(Operand(scratch), Immediate(ExternalReference::new_space_start())); + j(cc, branch); + } else { + int32_t new_space_start = reinterpret_cast<int32_t>( + ExternalReference::new_space_start().address()); + lea(scratch, Operand(object, -new_space_start)); + and_(scratch, Heap::NewSpaceMask()); + j(cc, branch); + } +} + + // The code patcher is used to patch (typically) small parts of code e.g. for // debugging and other types of instrumentation. When using the code patcher // the exact number of bytes specified must be emitted. Is not legal to emit @@ -664,6 +704,17 @@ class CodePatcher { }; +// Helper class for generating code or data associated with the code +// right after a call instruction. As an example this can be used to +// generate safepoint data after calls for crankshaft. +class PostCallGenerator { + public: + PostCallGenerator() { } + virtual ~PostCallGenerator() { } + virtual void Generate() = 0; +}; + + // ----------------------------------------------------------------------------- // Static helper functions. diff --git a/src/ia32/stub-cache-ia32.cc b/src/ia32/stub-cache-ia32.cc index adcb5219..99888b08 100644 --- a/src/ia32/stub-cache-ia32.cc +++ b/src/ia32/stub-cache-ia32.cc @@ -855,9 +855,14 @@ MUST_USE_RESULT static MaybeObject* GenerateCheckPropertyCell( } JSGlobalPropertyCell* cell = JSGlobalPropertyCell::cast(probe); ASSERT(cell->value()->IsTheHole()); - __ mov(scratch, Immediate(Handle<Object>(cell))); - __ cmp(FieldOperand(scratch, JSGlobalPropertyCell::kValueOffset), - Immediate(Factory::the_hole_value())); + if (Serializer::enabled()) { + __ mov(scratch, Immediate(Handle<Object>(cell))); + __ cmp(FieldOperand(scratch, JSGlobalPropertyCell::kValueOffset), + Immediate(Factory::the_hole_value())); + } else { + __ cmp(Operand::Cell(Handle<JSGlobalPropertyCell>(cell)), + Immediate(Factory::the_hole_value())); + } __ j(not_equal, miss, not_taken); return cell; } @@ -1326,8 +1331,12 @@ void CallStubCompiler::GenerateLoadFunctionFromCell(JSGlobalPropertyCell* cell, JSFunction* function, Label* miss) { // Get the value from the cell. - __ mov(edi, Immediate(Handle<JSGlobalPropertyCell>(cell))); - __ mov(edi, FieldOperand(edi, JSGlobalPropertyCell::kValueOffset)); + if (Serializer::enabled()) { + __ mov(edi, Immediate(Handle<JSGlobalPropertyCell>(cell))); + __ mov(edi, FieldOperand(edi, JSGlobalPropertyCell::kValueOffset)); + } else { + __ mov(edi, Operand::Cell(Handle<JSGlobalPropertyCell>(cell))); + } // Check that the cell contains the same function. if (Heap::InNewSpace(function)) { @@ -1710,7 +1719,7 @@ MaybeObject* CallStubCompiler::CompileStringCharCodeAtCall( char_code_at_generator.GenerateFast(masm()); __ ret((argc + 1) * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_code_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1785,7 +1794,7 @@ MaybeObject* CallStubCompiler::CompileStringCharAtCall( char_at_generator.GenerateFast(masm()); __ ret((argc + 1) * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1858,7 +1867,7 @@ MaybeObject* CallStubCompiler::CompileStringFromCharCodeCall( char_from_code_generator.GenerateFast(masm()); __ ret(2 * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_from_code_generator.GenerateSlow(masm(), call_helper); // Tail call the full function. We do not have to patch the receiver @@ -2124,8 +2133,8 @@ MaybeObject* CallStubCompiler::CompileCallConstant(Object* object, // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, NULL, function, name); Object* result; @@ -2366,8 +2375,8 @@ MaybeObject* CallStubCompiler::CompileCallGlobal(JSObject* object, // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, cell, function, name); Object* result; @@ -2399,10 +2408,18 @@ MaybeObject* CallStubCompiler::CompileCallGlobal(JSObject* object, // Jump to the cached code (tail call). __ IncrementCounter(&Counters::call_global_inline, 1); ASSERT(function->is_compiled()); - Handle<Code> code(function->code()); ParameterCount expected(function->shared()->formal_parameter_count()); - __ InvokeCode(code, expected, arguments(), - RelocInfo::CODE_TARGET, JUMP_FUNCTION); + if (V8::UseCrankshaft()) { + // TODO(kasperl): For now, we always call indirectly through the + // code field in the function to allow recompilation to take effect + // without changing any of the call sites. + __ InvokeCode(FieldOperand(edi, JSFunction::kCodeEntryOffset), + expected, arguments(), JUMP_FUNCTION); + } else { + Handle<Code> code(function->code()); + __ InvokeCode(code, expected, arguments(), + RelocInfo::CODE_TARGET, JUMP_FUNCTION); + } // Handle call cache miss. __ bind(&miss); @@ -2565,8 +2582,12 @@ MaybeObject* StoreStubCompiler::CompileStoreGlobal(GlobalObject* object, __ j(not_equal, &miss, not_taken); // Store the value in the cell. - __ mov(ecx, Immediate(Handle<JSGlobalPropertyCell>(cell))); - __ mov(FieldOperand(ecx, JSGlobalPropertyCell::kValueOffset), eax); + if (Serializer::enabled()) { + __ mov(ecx, Immediate(Handle<JSGlobalPropertyCell>(cell))); + __ mov(FieldOperand(ecx, JSGlobalPropertyCell::kValueOffset), eax); + } else { + __ mov(Operand::Cell(Handle<JSGlobalPropertyCell>(cell)), eax); + } // Return the value (register eax). __ IncrementCounter(&Counters::named_store_global_inline, 1); @@ -2620,6 +2641,63 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreField(JSObject* object, } +MaybeObject* KeyedStoreStubCompiler::CompileStoreSpecialized( + JSObject* receiver) { + // ----------- S t a t e ------------- + // -- eax : value + // -- ecx : key + // -- edx : receiver + // -- esp[0] : return address + // ----------------------------------- + Label miss; + + // Check that the receiver isn't a smi. + __ test(edx, Immediate(kSmiTagMask)); + __ j(zero, &miss, not_taken); + + // Check that the map matches. + __ cmp(FieldOperand(edx, HeapObject::kMapOffset), + Immediate(Handle<Map>(receiver->map()))); + __ j(not_equal, &miss, not_taken); + + // Check that the key is a smi. + __ test(ecx, Immediate(kSmiTagMask)); + __ j(not_zero, &miss, not_taken); + + // Get the elements array and make sure it is a fast element array, not 'cow'. + __ mov(edi, FieldOperand(edx, JSObject::kElementsOffset)); + __ cmp(FieldOperand(edi, HeapObject::kMapOffset), + Immediate(Factory::fixed_array_map())); + __ j(not_equal, &miss, not_taken); + + // Check that the key is within bounds. + if (receiver->IsJSArray()) { + __ cmp(ecx, FieldOperand(edx, JSArray::kLengthOffset)); // Compare smis. + __ j(above_equal, &miss, not_taken); + } else { + __ cmp(ecx, FieldOperand(edi, FixedArray::kLengthOffset)); // Compare smis. + __ j(above_equal, &miss, not_taken); + } + + // Do the store and update the write barrier. Make sure to preserve + // the value in register eax. + __ mov(edx, Operand(eax)); + __ mov(FieldOperand(edi, ecx, times_2, FixedArray::kHeaderSize), eax); + __ RecordWrite(edi, 0, edx, ecx); + + // Done. + __ ret(0); + + // Handle store cache miss. + __ bind(&miss); + Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Miss)); + __ jmp(ic, RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + MaybeObject* LoadStubCompiler::CompileLoadNonexistent(String* name, JSObject* object, JSObject* last) { @@ -2793,8 +2871,12 @@ MaybeObject* LoadStubCompiler::CompileLoadGlobal(JSObject* object, CheckPrototypes(object, eax, holder, ebx, edx, edi, name, &miss); // Get the value from the cell. - __ mov(ebx, Immediate(Handle<JSGlobalPropertyCell>(cell))); - __ mov(ebx, FieldOperand(ebx, JSGlobalPropertyCell::kValueOffset)); + if (Serializer::enabled()) { + __ mov(ebx, Immediate(Handle<JSGlobalPropertyCell>(cell))); + __ mov(ebx, FieldOperand(ebx, JSGlobalPropertyCell::kValueOffset)); + } else { + __ mov(ebx, Operand::Cell(Handle<JSGlobalPropertyCell>(cell))); + } // Check for deleted property if property can actually be deleted. if (!is_dont_delete) { @@ -3019,6 +3101,51 @@ MaybeObject* KeyedLoadStubCompiler::CompileLoadFunctionPrototype(String* name) { } +MaybeObject* KeyedLoadStubCompiler::CompileLoadSpecialized(JSObject* receiver) { + // ----------- S t a t e ------------- + // -- eax : key + // -- edx : receiver + // -- esp[0] : return address + // ----------------------------------- + Label miss; + + // Check that the receiver isn't a smi. + __ test(edx, Immediate(kSmiTagMask)); + __ j(zero, &miss, not_taken); + + // Check that the map matches. + __ cmp(FieldOperand(edx, HeapObject::kMapOffset), + Immediate(Handle<Map>(receiver->map()))); + __ j(not_equal, &miss, not_taken); + + // Check that the key is a smi. + __ test(eax, Immediate(kSmiTagMask)); + __ j(not_zero, &miss, not_taken); + + // Get the elements array. + __ mov(ecx, FieldOperand(edx, JSObject::kElementsOffset)); + __ AssertFastElements(ecx); + + // Check that the key is within bounds. + __ cmp(eax, FieldOperand(ecx, FixedArray::kLengthOffset)); + __ j(above_equal, &miss, not_taken); + + // Load the result and make sure it's not the hole. + __ mov(ebx, Operand(ecx, eax, times_2, + FixedArray::kHeaderSize - kHeapObjectTag)); + __ cmp(ebx, Factory::the_hole_value()); + __ j(equal, &miss, not_taken); + __ mov(eax, ebx); + __ ret(0); + + __ bind(&miss); + GenerateLoadMiss(masm(), Code::KEYED_LOAD_IC); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + // Specialized stub for constructing objects from functions which only have only // simple assignments of the form this.x = ...; in their body. MaybeObject* ConstructStubCompiler::CompileConstructStub(JSFunction* function) { diff --git a/src/ic-inl.h b/src/ic-inl.h index 94dbd5f5..8fbc1843 100644 --- a/src/ic-inl.h +++ b/src/ic-inl.h @@ -75,7 +75,7 @@ Code* IC::GetTargetAtAddress(Address address) { void IC::SetTargetAtAddress(Address address, Code* target) { - ASSERT(target->is_inline_cache_stub()); + ASSERT(target->is_inline_cache_stub() || target->is_compare_ic_stub()); Assembler::set_target_address_at(address, target->instruction_start()); } @@ -30,6 +30,7 @@ #include "accessors.h" #include "api.h" #include "arguments.h" +#include "codegen.h" #include "execution.h" #include "ic-inl.h" #include "runtime.h" @@ -156,7 +157,7 @@ static bool HasNormalObjectsInPrototypeChain(LookupResult* lookup, IC::State IC::StateFrom(Code* target, Object* receiver, Object* name) { IC::State state = target->ic_state(); - if (state != MONOMORPHIC) return state; + if (state != MONOMORPHIC || !name->IsString()) return state; if (receiver->IsUndefined() || receiver->IsNull()) return state; InlineCacheHolderFlag cache_holder = @@ -259,8 +260,12 @@ void IC::Clear(Address address) { case Code::KEYED_STORE_IC: return KeyedStoreIC::Clear(address, target); case Code::CALL_IC: return CallIC::Clear(address, target); case Code::KEYED_CALL_IC: return KeyedCallIC::Clear(address, target); - case Code::BINARY_OP_IC: return; // Clearing these is tricky and does not - // make any performance difference. + case Code::BINARY_OP_IC: + case Code::TYPE_RECORDING_BINARY_OP_IC: + case Code::COMPARE_IC: + // Clearing these is tricky and does not + // make any performance difference. + return; default: UNREACHABLE(); } } @@ -1134,9 +1139,20 @@ MaybeObject* KeyedLoadIC::Load(State state, stub = external_array_stub(receiver->GetElementsKind()); } else if (receiver->HasIndexedInterceptor()) { stub = indexed_interceptor_stub(); + } else if (state == UNINITIALIZED && + key->IsSmi() && + receiver->map()->has_fast_elements()) { + MaybeObject* probe = StubCache::ComputeKeyedLoadSpecialized(*receiver); + stub = + probe->IsFailure() ? NULL : Code::cast(probe->ToObjectUnchecked()); } } - set_target(stub); + if (stub != NULL) set_target(stub); + +#ifdef DEBUG + TraceIC("KeyedLoadIC", key, state, target()); +#endif // DEBUG + // For JSObjects with fast elements that are not value wrappers // and that do not have indexed interceptors, we initialize the // inlined fast case (if present) by patching the inlined map @@ -1360,6 +1376,17 @@ MaybeObject* StoreIC::Store(State state, } } + if (receiver->IsJSGlobalProxy()) { + // Generate a generic stub that goes to the runtime when we see a global + // proxy as receiver. + if (target() != global_proxy_stub()) { + set_target(global_proxy_stub()); +#ifdef DEBUG + TraceIC("StoreIC", name, state, target()); +#endif + } + } + // Set the property. return receiver->SetProperty(*name, *value, NONE); } @@ -1503,9 +1530,15 @@ MaybeObject* KeyedStoreIC::Store(State state, Handle<JSObject> receiver = Handle<JSObject>::cast(object); if (receiver->HasExternalArrayElements()) { stub = external_array_stub(receiver->GetElementsKind()); + } else if (state == UNINITIALIZED && + key->IsSmi() && + receiver->map()->has_fast_elements()) { + MaybeObject* probe = StubCache::ComputeKeyedStoreSpecialized(*receiver); + stub = + probe->IsFailure() ? NULL : Code::cast(probe->ToObjectUnchecked()); } } - set_target(stub); + if (stub != NULL) set_target(stub); } // Set the property. @@ -1750,6 +1783,7 @@ void BinaryOpIC::patch(Code* code) { const char* BinaryOpIC::GetName(TypeInfo type_info) { switch (type_info) { + case UNINIT_OR_SMI: return "UninitOrSmi"; case DEFAULT: return "Default"; case GENERIC: return "Generic"; case HEAP_NUMBERS: return "HeapNumbers"; @@ -1761,23 +1795,26 @@ const char* BinaryOpIC::GetName(TypeInfo type_info) { BinaryOpIC::State BinaryOpIC::ToState(TypeInfo type_info) { switch (type_info) { - // DEFAULT is mapped to UNINITIALIZED so that calls to DEFAULT stubs - // are not cleared at GC. - case DEFAULT: return UNINITIALIZED; - - // Could have mapped GENERIC to MONOMORPHIC just as well but MEGAMORPHIC is - // conceptually closer. - case GENERIC: return MEGAMORPHIC; - - default: return MONOMORPHIC; + case UNINIT_OR_SMI: + return UNINITIALIZED; + case DEFAULT: + case HEAP_NUMBERS: + case STRINGS: + return MONOMORPHIC; + case GENERIC: + return MEGAMORPHIC; } + UNREACHABLE(); + return UNINITIALIZED; } BinaryOpIC::TypeInfo BinaryOpIC::GetTypeInfo(Object* left, Object* right) { if (left->IsSmi() && right->IsSmi()) { - return GENERIC; + // If we have two smi inputs we can reach here because + // of an overflow. Enter default state. + return DEFAULT; } if (left->IsNumber() && right->IsNumber()) { @@ -1794,43 +1831,225 @@ BinaryOpIC::TypeInfo BinaryOpIC::GetTypeInfo(Object* left, } -// defined in codegen-<arch>.cc +// defined in code-stubs-<arch>.cc Handle<Code> GetBinaryOpStub(int key, BinaryOpIC::TypeInfo type_info); MUST_USE_RESULT MaybeObject* BinaryOp_Patch(Arguments args) { ASSERT(args.length() == 5); + HandleScope scope; Handle<Object> left = args.at<Object>(0); Handle<Object> right = args.at<Object>(1); int key = Smi::cast(args[2])->value(); Token::Value op = static_cast<Token::Value>(Smi::cast(args[3])->value()); -#ifdef DEBUG - BinaryOpIC::TypeInfo prev_type_info = + BinaryOpIC::TypeInfo previous_type = static_cast<BinaryOpIC::TypeInfo>(Smi::cast(args[4])->value()); -#endif // DEBUG - { HandleScope scope; - BinaryOpIC::TypeInfo type_info = BinaryOpIC::GetTypeInfo(*left, *right); - Handle<Code> code = GetBinaryOpStub(key, type_info); - if (!code.is_null()) { - BinaryOpIC ic; - ic.patch(*code); -#ifdef DEBUG - if (FLAG_trace_ic) { - PrintF("[BinaryOpIC (%s->%s)#%s]\n", - BinaryOpIC::GetName(prev_type_info), - BinaryOpIC::GetName(type_info), - Token::Name(op)); - } -#endif // DEBUG + + BinaryOpIC::TypeInfo type = BinaryOpIC::GetTypeInfo(*left, *right); + Handle<Code> code = GetBinaryOpStub(key, type); + if (!code.is_null()) { + BinaryOpIC ic; + ic.patch(*code); + if (FLAG_trace_ic) { + PrintF("[BinaryOpIC (%s->%s)#%s]\n", + BinaryOpIC::GetName(previous_type), + BinaryOpIC::GetName(type), + Token::Name(op)); } } - HandleScope scope; Handle<JSBuiltinsObject> builtins = Top::builtins(); - Object* builtin = NULL; // Initialization calms down the compiler. + switch (op) { + case Token::ADD: + builtin = builtins->javascript_builtin(Builtins::ADD); + break; + case Token::SUB: + builtin = builtins->javascript_builtin(Builtins::SUB); + break; + case Token::MUL: + builtin = builtins->javascript_builtin(Builtins::MUL); + break; + case Token::DIV: + builtin = builtins->javascript_builtin(Builtins::DIV); + break; + case Token::MOD: + builtin = builtins->javascript_builtin(Builtins::MOD); + break; + case Token::BIT_AND: + builtin = builtins->javascript_builtin(Builtins::BIT_AND); + break; + case Token::BIT_OR: + builtin = builtins->javascript_builtin(Builtins::BIT_OR); + break; + case Token::BIT_XOR: + builtin = builtins->javascript_builtin(Builtins::BIT_XOR); + break; + case Token::SHR: + builtin = builtins->javascript_builtin(Builtins::SHR); + break; + case Token::SAR: + builtin = builtins->javascript_builtin(Builtins::SAR); + break; + case Token::SHL: + builtin = builtins->javascript_builtin(Builtins::SHL); + break; + default: + UNREACHABLE(); + } + Handle<JSFunction> builtin_function(JSFunction::cast(builtin)); + + bool caught_exception; + Object** builtin_args[] = { right.location() }; + Handle<Object> result = Execution::Call(builtin_function, + left, + ARRAY_SIZE(builtin_args), + builtin_args, + &caught_exception); + if (caught_exception) { + return Failure::Exception(); + } + return *result; +} + + +void TRBinaryOpIC::patch(Code* code) { + set_target(code); +} + + +const char* TRBinaryOpIC::GetName(TypeInfo type_info) { + switch (type_info) { + case UNINITIALIZED: return "Uninitialized"; + case SMI: return "SMI"; + case INT32: return "Int32s"; + case HEAP_NUMBER: return "HeapNumbers"; + case STRING: return "Strings"; + case GENERIC: return "Generic"; + default: return "Invalid"; + } +} + + +TRBinaryOpIC::State TRBinaryOpIC::ToState(TypeInfo type_info) { + switch (type_info) { + case UNINITIALIZED: + return ::v8::internal::UNINITIALIZED; + case SMI: + case INT32: + case HEAP_NUMBER: + case STRING: + return MONOMORPHIC; + case GENERIC: + return MEGAMORPHIC; + } + UNREACHABLE(); + return ::v8::internal::UNINITIALIZED; +} + + +TRBinaryOpIC::TypeInfo TRBinaryOpIC::JoinTypes(TRBinaryOpIC::TypeInfo x, + TRBinaryOpIC::TypeInfo y) { + if (x == UNINITIALIZED) return y; + if (y == UNINITIALIZED) return x; + if (x == STRING && y == STRING) return STRING; + if (x == STRING || y == STRING) return GENERIC; + if (x >= y) return x; + return y; +} + +TRBinaryOpIC::TypeInfo TRBinaryOpIC::GetTypeInfo(Handle<Object> left, + Handle<Object> right) { + ::v8::internal::TypeInfo left_type = + ::v8::internal::TypeInfo::TypeFromValue(left); + ::v8::internal::TypeInfo right_type = + ::v8::internal::TypeInfo::TypeFromValue(right); + + if (left_type.IsSmi() && right_type.IsSmi()) { + return SMI; + } + + if (left_type.IsInteger32() && right_type.IsInteger32()) { + return INT32; + } + + if (left_type.IsNumber() && right_type.IsNumber()) { + return HEAP_NUMBER; + } + + if (left_type.IsString() || right_type.IsString()) { + // Patching for fast string ADD makes sense even if only one of the + // arguments is a string. + return STRING; + } + + return GENERIC; +} + + +// defined in code-stubs-<arch>.cc +// Only needed to remove dependency of ic.cc on code-stubs-<arch>.h. +Handle<Code> GetTypeRecordingBinaryOpStub(int key, + TRBinaryOpIC::TypeInfo type_info, + TRBinaryOpIC::TypeInfo result_type); + + +MaybeObject* TypeRecordingBinaryOp_Patch(Arguments args) { + ASSERT(args.length() == 5); + + HandleScope scope; + Handle<Object> left = args.at<Object>(0); + Handle<Object> right = args.at<Object>(1); + int key = Smi::cast(args[2])->value(); + Token::Value op = static_cast<Token::Value>(Smi::cast(args[3])->value()); + TRBinaryOpIC::TypeInfo previous_type = + static_cast<TRBinaryOpIC::TypeInfo>(Smi::cast(args[4])->value()); + + TRBinaryOpIC::TypeInfo type = TRBinaryOpIC::GetTypeInfo(left, right); + type = TRBinaryOpIC::JoinTypes(type, previous_type); + TRBinaryOpIC::TypeInfo result_type = TRBinaryOpIC::UNINITIALIZED; + if (type == TRBinaryOpIC::STRING && op != Token::ADD) { + type = TRBinaryOpIC::GENERIC; + } + if (type == TRBinaryOpIC::SMI && + previous_type == TRBinaryOpIC::SMI) { + if (op == Token::DIV || op == Token::MUL) { + // Arithmetic on two Smi inputs has yielded a heap number. + // That is the only way to get here from the Smi stub. + result_type = TRBinaryOpIC::HEAP_NUMBER; + } else { + // Other operations on SMIs that overflow yield int32s. + result_type = TRBinaryOpIC::INT32; + } + } + if (type == TRBinaryOpIC::INT32 && + previous_type == TRBinaryOpIC::INT32) { + // We must be here because an operation on two INT32 types overflowed. + result_type = TRBinaryOpIC::HEAP_NUMBER; + } + + Handle<Code> code = GetTypeRecordingBinaryOpStub(key, type, result_type); + if (!code.is_null()) { + TRBinaryOpIC ic; + ic.patch(*code); + if (FLAG_trace_ic) { + PrintF("[TypeRecordingBinaryOpIC (%s->(%s->%s))#%s]\n", + TRBinaryOpIC::GetName(previous_type), + TRBinaryOpIC::GetName(type), + TRBinaryOpIC::GetName(result_type), + Token::Name(op)); + } + + // Activate inlined smi code. + if (previous_type == TRBinaryOpIC::UNINITIALIZED) { + PatchInlinedSmiCode(ic.address()); + } + } + + Handle<JSBuiltinsObject> builtins = Top::builtins(); + Object* builtin = NULL; // Initialization calms down the compiler. switch (op) { case Token::ADD: builtin = builtins->javascript_builtin(Builtins::ADD); @@ -1885,6 +2104,59 @@ MUST_USE_RESULT MaybeObject* BinaryOp_Patch(Arguments args) { } +Handle<Code> CompareIC::GetUninitialized(Token::Value op) { + ICCompareStub stub(op, UNINITIALIZED); + return stub.GetCode(); +} + + +CompareIC::State CompareIC::ComputeState(Code* target) { + int key = target->major_key(); + if (key == CodeStub::Compare) return GENERIC; + ASSERT(key == CodeStub::CompareIC); + return static_cast<State>(target->compare_state()); +} + + +const char* CompareIC::GetStateName(State state) { + switch (state) { + case UNINITIALIZED: return "UNINITIALIZED"; + case SMIS: return "SMIS"; + case HEAP_NUMBERS: return "HEAP_NUMBERS"; + case OBJECTS: return "OBJECTS"; + case GENERIC: return "GENERIC"; + default: + UNREACHABLE(); + return NULL; + } +} + + +CompareIC::State CompareIC::TargetState(State state, + bool has_inlined_smi_code, + Handle<Object> x, + Handle<Object> y) { + if (!has_inlined_smi_code && state != UNINITIALIZED) return GENERIC; + if (state == UNINITIALIZED && x->IsSmi() && y->IsSmi()) return SMIS; + if ((state == UNINITIALIZED || (state == SMIS && has_inlined_smi_code)) && + x->IsNumber() && y->IsNumber()) return HEAP_NUMBERS; + if (op_ != Token::EQ && op_ != Token::EQ_STRICT) return GENERIC; + if (state == UNINITIALIZED && + x->IsJSObject() && y->IsJSObject()) return OBJECTS; + return GENERIC; +} + + +// Used from ic_<arch>.cc. +Code* CompareIC_Miss(Arguments args) { + NoHandleAllocation na; + ASSERT(args.length() == 3); + CompareIC ic(static_cast<Token::Value>(Smi::cast(args[2])->value())); + ic.UpdateCaches(args.at<Object>(0), args.at<Object>(1)); + return ic.target(); +} + + static Address IC_utilities[] = { #define ADDR(name) FUNCTION_ADDR(name), IC_UTIL_LIST(ADDR) @@ -28,7 +28,7 @@ #ifndef V8_IC_H_ #define V8_IC_H_ -#include "assembler.h" +#include "macro-assembler.h" namespace v8 { namespace internal { @@ -53,8 +53,9 @@ namespace internal { ICU(LoadPropertyWithInterceptorForCall) \ ICU(KeyedLoadPropertyWithInterceptor) \ ICU(StoreInterceptorProperty) \ - ICU(BinaryOp_Patch) - + ICU(BinaryOp_Patch) \ + ICU(TypeRecordingBinaryOp_Patch) \ + ICU(CompareIC_Miss) // // IC is the base class for LoadIC, StoreIC, CallIC, KeyedLoadIC, // and KeyedStoreIC. @@ -403,6 +404,7 @@ class StoreIC: public IC { static void GenerateMegamorphic(MacroAssembler* masm); static void GenerateArrayLength(MacroAssembler* masm); static void GenerateNormal(MacroAssembler* masm); + static void GenerateGlobalProxy(MacroAssembler* masm); // Clear the use of an inlined version. static void ClearInlinedVersion(Address address); @@ -426,6 +428,9 @@ class StoreIC: public IC { static Code* initialize_stub() { return Builtins::builtin(Builtins::StoreIC_Initialize); } + static Code* global_proxy_stub() { + return Builtins::builtin(Builtins::StoreIC_GlobalProxy); + } static void Clear(Address address, Code* target); @@ -503,6 +508,7 @@ class BinaryOpIC: public IC { public: enum TypeInfo { + UNINIT_OR_SMI, DEFAULT, // Initial state. When first executed, patches to one // of the following states depending on the operands types. HEAP_NUMBERS, // Both arguments are HeapNumbers. @@ -514,8 +520,6 @@ class BinaryOpIC: public IC { void patch(Code* code); - static void Clear(Address address, Code* target); - static const char* GetName(TypeInfo type_info); static State ToState(TypeInfo type_info); @@ -523,6 +527,74 @@ class BinaryOpIC: public IC { static TypeInfo GetTypeInfo(Object* left, Object* right); }; + +// Type Recording BinaryOpIC, that records the types of the inputs and outputs. +class TRBinaryOpIC: public IC { + public: + + enum TypeInfo { + UNINITIALIZED, + SMI, + INT32, + HEAP_NUMBER, + STRING, // Only used for addition operation. At least one string operand. + GENERIC + }; + + TRBinaryOpIC() : IC(NO_EXTRA_FRAME) { } + + void patch(Code* code); + + static const char* GetName(TypeInfo type_info); + + static State ToState(TypeInfo type_info); + + static TypeInfo GetTypeInfo(Handle<Object> left, Handle<Object> right); + + static TypeInfo JoinTypes(TypeInfo x, TypeInfo y); +}; + + +class CompareIC: public IC { + public: + enum State { + UNINITIALIZED, + SMIS, + HEAP_NUMBERS, + OBJECTS, + GENERIC + }; + + explicit CompareIC(Token::Value op) : IC(EXTRA_CALL_FRAME), op_(op) { } + + // Update the inline cache for the given operands. + void UpdateCaches(Handle<Object> x, Handle<Object> y); + + // Factory method for getting an uninitialized compare stub. + static Handle<Code> GetUninitialized(Token::Value op); + + // Helper function for computing the condition for a compare operation. + static Condition ComputeCondition(Token::Value op); + + // Helper function for determining the state of a compare IC. + static State ComputeState(Code* target); + + static const char* GetStateName(State state); + + private: + State TargetState(State state, bool has_inlined_smi_code, + Handle<Object> x, Handle<Object> y); + + bool strict() const { return op_ == Token::EQ_STRICT; } + Condition GetCondition() const { return ComputeCondition(op_); } + State GetState() { return ComputeState(target()); } + + Token::Value op_; +}; + +// Helper for TRBinaryOpIC and CompareIC. +void PatchInlinedSmiCode(Address address); + } } // namespace v8::internal #endif // V8_IC_H_ diff --git a/src/json.js b/src/json.js index 5993100f..89009a96 100644 --- a/src/json.js +++ b/src/json.js @@ -66,51 +66,10 @@ function JSONParse(text, reviver) { } } -var characterQuoteCache = { - '\b': '\\b', // ASCII 8, Backspace - '\t': '\\t', // ASCII 9, Tab - '\n': '\\n', // ASCII 10, Newline - '\f': '\\f', // ASCII 12, Formfeed - '\r': '\\r', // ASCII 13, Carriage Return - '\"': '\\"', - '\\': '\\\\' -}; - -function QuoteSingleJSONCharacter(c) { - if (c in characterQuoteCache) { - return characterQuoteCache[c]; - } - var charCode = c.charCodeAt(0); - var result; - if (charCode < 16) result = '\\u000'; - else if (charCode < 256) result = '\\u00'; - else if (charCode < 4096) result = '\\u0'; - else result = '\\u'; - result += charCode.toString(16); - characterQuoteCache[c] = result; - return result; -} - -function QuoteJSONString(str) { - var quotable = /[\\\"\x00-\x1f]/g; - return '"' + str.replace(quotable, QuoteSingleJSONCharacter) + '"'; -} - -function StackContains(stack, val) { - var length = stack.length; - for (var i = 0; i < length; i++) { - if (stack[i] === val) { - return true; - } - } - return false; -} - function SerializeArray(value, replacer, stack, indent, gap) { - if (StackContains(stack, value)) { + if (!%PushIfAbsent(stack, value)) { throw MakeTypeError('circular_structure', []); } - stack.push(value); var stepback = indent; indent += gap; var partial = []; @@ -138,10 +97,9 @@ function SerializeArray(value, replacer, stack, indent, gap) { } function SerializeObject(value, replacer, stack, indent, gap) { - if (StackContains(stack, value)) { + if (!%PushIfAbsent(stack, value)) { throw MakeTypeError('circular_structure', []); } - stack.push(value); var stepback = indent; indent += gap; var partial = []; @@ -152,7 +110,7 @@ function SerializeObject(value, replacer, stack, indent, gap) { var p = replacer[i]; var strP = JSONSerialize(p, value, replacer, stack, indent, gap); if (!IS_UNDEFINED(strP)) { - var member = QuoteJSONString(p) + ":"; + var member = %QuoteJSONString(p) + ":"; if (gap != "") member += " "; member += strP; partial.push(member); @@ -164,7 +122,7 @@ function SerializeObject(value, replacer, stack, indent, gap) { if (ObjectHasOwnProperty.call(value, p)) { var strP = JSONSerialize(p, value, replacer, stack, indent, gap); if (!IS_UNDEFINED(strP)) { - var member = QuoteJSONString(p) + ":"; + var member = %QuoteJSONString(p) + ":"; if (gap != "") member += " "; member += strP; partial.push(member); @@ -188,71 +146,159 @@ function SerializeObject(value, replacer, stack, indent, gap) { function JSONSerialize(key, holder, replacer, stack, indent, gap) { var value = holder[key]; - if (IS_OBJECT(value) && value) { + if (IS_SPEC_OBJECT(value)) { var toJSON = value.toJSON; if (IS_FUNCTION(toJSON)) { - value = toJSON.call(value, key); + value = %_CallFunction(value, key, toJSON); } } if (IS_FUNCTION(replacer)) { - value = replacer.call(holder, key, value); + value = %_CallFunction(holder, key, value, replacer); } - // Unwrap value if necessary - if (IS_OBJECT(value)) { - if (IS_NUMBER_WRAPPER(value)) { - value = $Number(value); + if (IS_STRING(value)) { + return %QuoteJSONString(value); + } else if (IS_NUMBER(value)) { + return $isFinite(value) ? $String(value) : "null"; + } else if (IS_BOOLEAN(value)) { + return value ? "true" : "false"; + } else if (IS_NULL(value)) { + return "null"; + } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) { + // Non-callable object. If it's a primitive wrapper, it must be unwrapped. + if (IS_ARRAY(value)) { + return SerializeArray(value, replacer, stack, indent, gap); + } else if (IS_NUMBER_WRAPPER(value)) { + value = ToNumber(value); + return $isFinite(value) ? ToString(value) : "null"; } else if (IS_STRING_WRAPPER(value)) { - value = $String(value); + return %QuoteJSONString(ToString(value)); } else if (IS_BOOLEAN_WRAPPER(value)) { - value = %_ValueOf(value); + return %_ValueOf(value) ? "true" : "false"; + } else { + return SerializeObject(value, replacer, stack, indent, gap); } } - switch (typeof value) { - case "string": - return QuoteJSONString(value); - case "object": - if (!value) { - return "null"; - } else if (IS_ARRAY(value)) { - return SerializeArray(value, replacer, stack, indent, gap); + // Undefined or a callable object. + return void 0; +} + + +function BasicSerializeArray(value, stack, builder) { + if (!%PushIfAbsent(stack, value)) { + throw MakeTypeError('circular_structure', []); + } + builder.push("["); + var len = value.length; + for (var i = 0; i < len; i++) { + var before = builder.length; + BasicJSONSerialize(i, value, stack, builder); + if (before == builder.length) builder.push("null"); + builder.push(","); + } + stack.pop(); + if (builder.pop() != ",") { + builder.push("[]"); // Zero length array. Push "[" back on. + } else { + builder.push("]"); + } + +} + + +function BasicSerializeObject(value, stack, builder) { + if (!%PushIfAbsent(stack, value)) { + throw MakeTypeError('circular_structure', []); + } + builder.push("{"); + for (var p in value) { + if (%HasLocalProperty(value, p)) { + builder.push(%QuoteJSONString(p)); + builder.push(":"); + var before = builder.length; + BasicJSONSerialize(p, value, stack, builder); + if (before == builder.length) { + builder.pop(); + builder.pop(); } else { - return SerializeObject(value, replacer, stack, indent, gap); + builder.push(","); } - case "number": - return $isFinite(value) ? $String(value) : "null"; - case "boolean": - return value ? "true" : "false"; + } + } + stack.pop(); + if (builder.pop() != ",") { + builder.push("{}"); // Object has no own properties. Push "{" back on. + } else { + builder.push("}"); } } + +function BasicJSONSerialize(key, holder, stack, builder) { + var value = holder[key]; + if (IS_SPEC_OBJECT(value)) { + var toJSON = value.toJSON; + if (IS_FUNCTION(toJSON)) { + value = %_CallFunction(value, ToString(key), toJSON); + } + } + if (IS_STRING(value)) { + builder.push(%QuoteJSONString(value)); + } else if (IS_NUMBER(value)) { + builder.push(($isFinite(value) ? %_NumberToString(value) : "null")); + } else if (IS_BOOLEAN(value)) { + builder.push(value ? "true" : "false"); + } else if (IS_NULL(value)) { + builder.push("null"); + } else if (IS_SPEC_OBJECT(value) && !(typeof value == "function")) { + // Value is a non-callable object. + // Unwrap value if necessary + if (IS_NUMBER_WRAPPER(value)) { + value = ToNumber(value); + builder.push(($isFinite(value) ? %_NumberToString(value) : "null")); + } else if (IS_STRING_WRAPPER(value)) { + builder.push(%QuoteJSONString(ToString(value))); + } else if (IS_BOOLEAN_WRAPPER(value)) { + builder.push(%_ValueOf(value) ? "true" : "false"); + } else if (IS_ARRAY(value)) { + BasicSerializeArray(value, stack, builder); + } else { + BasicSerializeObject(value, stack, builder); + } + } +} + + function JSONStringify(value, replacer, space) { - var stack = []; - var indent = ""; + if (%_ArgumentsLength() == 1) { + var builder = []; + BasicJSONSerialize('', {'': value}, [], builder); + if (builder.length == 0) return; + var result = %_FastAsciiArrayJoin(builder, ""); + if (!IS_UNDEFINED(result)) return result; + return %StringBuilderConcat(builder, builder.length, ""); + } if (IS_OBJECT(space)) { // Unwrap 'space' if it is wrapped if (IS_NUMBER_WRAPPER(space)) { - space = $Number(space); + space = ToNumber(space); } else if (IS_STRING_WRAPPER(space)) { - space = $String(space); + space = ToString(space); } } var gap; if (IS_NUMBER(space)) { - space = $Math.min(ToInteger(space), 10); - gap = ""; - for (var i = 0; i < space; i++) { - gap += " "; - } + space = MathMax(0, MathMin(ToInteger(space), 10)); + gap = SubString(" ", 0, space); } else if (IS_STRING(space)) { if (space.length > 10) { - gap = space.substring(0, 10); + gap = SubString(space, 0, 10); } else { gap = space; } } else { gap = ""; } - return JSONSerialize('', {'': value}, replacer, stack, indent, gap); + return JSONSerialize('', {'': value}, replacer, [], "", gap); } function SetupJSON() { diff --git a/src/jsregexp.cc b/src/jsregexp.cc index 8cd13bc4..e0f2e621 100644 --- a/src/jsregexp.cc +++ b/src/jsregexp.cc @@ -33,6 +33,7 @@ #include "factory.h" #include "jsregexp.h" #include "platform.h" +#include "string-search.h" #include "runtime.h" #include "top.h" #include "compilation-cache.h" @@ -120,7 +121,7 @@ Handle<Object> RegExpImpl::Compile(Handle<JSRegExp> re, re->set_data(*cached); return re; } - FlattenString(pattern); + pattern = FlattenGetString(pattern); CompilationZoneScope zone_scope(DELETE_ON_EXIT); PostponeInterruptsScope postpone; RegExpCompileData parse_result; @@ -205,23 +206,61 @@ static void SetAtomLastCapture(FixedArray* array, RegExpImpl::SetCapture(array, 1, to); } + /* template <typename SubjectChar>, typename PatternChar> +static int ReStringMatch(Vector<const SubjectChar> sub_vector, + Vector<const PatternChar> pat_vector, + int start_index) { + int pattern_length = pat_vector.length(); + if (pattern_length == 0) return start_index; + + int subject_length = sub_vector.length(); + if (start_index + pattern_length > subject_length) return -1; + return SearchString(sub_vector, pat_vector, start_index); +} + */ Handle<Object> RegExpImpl::AtomExec(Handle<JSRegExp> re, Handle<String> subject, int index, Handle<JSArray> last_match_info) { - Handle<String> needle(String::cast(re->DataAt(JSRegExp::kAtomPatternIndex))); - - uint32_t start_index = index; + ASSERT(0 <= index); + ASSERT(index <= subject->length()); - int value = Runtime::StringMatch(subject, needle, start_index); - if (value == -1) return Factory::null_value(); + if (!subject->IsFlat()) FlattenString(subject); + AssertNoAllocation no_heap_allocation; // ensure vectors stay valid + // Extract flattened substrings of cons strings before determining asciiness. + String* seq_sub = *subject; + if (seq_sub->IsConsString()) seq_sub = ConsString::cast(seq_sub)->first(); + + String* needle = String::cast(re->DataAt(JSRegExp::kAtomPatternIndex)); + int needle_len = needle->length(); + + if (needle_len != 0) { + if (index + needle_len > subject->length()) return Factory::null_value(); + // dispatch on type of strings + index = (needle->IsAsciiRepresentation() + ? (seq_sub->IsAsciiRepresentation() + ? SearchString(seq_sub->ToAsciiVector(), + needle->ToAsciiVector(), + index) + : SearchString(seq_sub->ToUC16Vector(), + needle->ToAsciiVector(), + index)) + : (seq_sub->IsAsciiRepresentation() + ? SearchString(seq_sub->ToAsciiVector(), + needle->ToUC16Vector(), + index) + : SearchString(seq_sub->ToUC16Vector(), + needle->ToUC16Vector(), + index))); + if (index == -1) return Factory::null_value(); + } ASSERT(last_match_info->HasFastElements()); { NoHandleAllocation no_handles; FixedArray* array = FixedArray::cast(last_match_info->elements()); - SetAtomLastCapture(array, *subject, value, value + needle->length()); + SetAtomLastCapture(array, *subject, index, index + needle_len); } return last_match_info; } @@ -364,7 +403,7 @@ int RegExpImpl::IrregexpPrepare(Handle<JSRegExp> regexp, AssertNoAllocation no_gc; String* sequential_string = *subject; if (subject->IsConsString()) { - sequential_string = ConsString::cast(*subject)->first(); + sequential_string = ConsString::cast(*subject)->first(); } is_ascii = sequential_string->IsAsciiRepresentation(); } @@ -1611,41 +1650,64 @@ RegExpNode::LimitResult RegExpNode::LimitVersions(RegExpCompiler* compiler, } -int ActionNode::EatsAtLeast(int still_to_find, int recursion_depth) { +int ActionNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { if (recursion_depth > RegExpCompiler::kMaxRecursion) return 0; if (type_ == POSITIVE_SUBMATCH_SUCCESS) return 0; // Rewinds input! - return on_success()->EatsAtLeast(still_to_find, recursion_depth + 1); + return on_success()->EatsAtLeast(still_to_find, + recursion_depth + 1, + not_at_start); } -int AssertionNode::EatsAtLeast(int still_to_find, int recursion_depth) { +int AssertionNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { if (recursion_depth > RegExpCompiler::kMaxRecursion) return 0; - return on_success()->EatsAtLeast(still_to_find, recursion_depth + 1); + // If we know we are not at the start and we are asked "how many characters + // will you match if you succeed?" then we can answer anything since false + // implies false. So lets just return the max answer (still_to_find) since + // that won't prevent us from preloading a lot of characters for the other + // branches in the node graph. + if (type() == AT_START && not_at_start) return still_to_find; + return on_success()->EatsAtLeast(still_to_find, + recursion_depth + 1, + not_at_start); } -int BackReferenceNode::EatsAtLeast(int still_to_find, int recursion_depth) { +int BackReferenceNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { if (recursion_depth > RegExpCompiler::kMaxRecursion) return 0; - return on_success()->EatsAtLeast(still_to_find, recursion_depth + 1); + return on_success()->EatsAtLeast(still_to_find, + recursion_depth + 1, + not_at_start); } -int TextNode::EatsAtLeast(int still_to_find, int recursion_depth) { +int TextNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { int answer = Length(); if (answer >= still_to_find) return answer; if (recursion_depth > RegExpCompiler::kMaxRecursion) return answer; + // We are not at start after this node so we set the last argument to 'true'. return answer + on_success()->EatsAtLeast(still_to_find - answer, - recursion_depth + 1); + recursion_depth + 1, + true); } int NegativeLookaheadChoiceNode::EatsAtLeast(int still_to_find, - int recursion_depth) { + int recursion_depth, + bool not_at_start) { if (recursion_depth > RegExpCompiler::kMaxRecursion) return 0; // Alternative 0 is the negative lookahead, alternative 1 is what comes // afterwards. RegExpNode* node = alternatives_->at(1).node(); - return node->EatsAtLeast(still_to_find, recursion_depth + 1); + return node->EatsAtLeast(still_to_find, recursion_depth + 1, not_at_start); } @@ -1663,7 +1725,8 @@ void NegativeLookaheadChoiceNode::GetQuickCheckDetails( int ChoiceNode::EatsAtLeastHelper(int still_to_find, int recursion_depth, - RegExpNode* ignore_this_node) { + RegExpNode* ignore_this_node, + bool not_at_start) { if (recursion_depth > RegExpCompiler::kMaxRecursion) return 0; int min = 100; int choice_count = alternatives_->length(); @@ -1671,20 +1734,31 @@ int ChoiceNode::EatsAtLeastHelper(int still_to_find, RegExpNode* node = alternatives_->at(i).node(); if (node == ignore_this_node) continue; int node_eats_at_least = node->EatsAtLeast(still_to_find, - recursion_depth + 1); + recursion_depth + 1, + not_at_start); if (node_eats_at_least < min) min = node_eats_at_least; } return min; } -int LoopChoiceNode::EatsAtLeast(int still_to_find, int recursion_depth) { - return EatsAtLeastHelper(still_to_find, recursion_depth, loop_node_); +int LoopChoiceNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { + return EatsAtLeastHelper(still_to_find, + recursion_depth, + loop_node_, + not_at_start); } -int ChoiceNode::EatsAtLeast(int still_to_find, int recursion_depth) { - return EatsAtLeastHelper(still_to_find, recursion_depth, NULL); +int ChoiceNode::EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { + return EatsAtLeastHelper(still_to_find, + recursion_depth, + NULL, + not_at_start); } @@ -2591,8 +2665,9 @@ void LoopChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { } -int ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler) { - int preload_characters = EatsAtLeast(4, 0); +int ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler, + bool not_at_start) { + int preload_characters = EatsAtLeast(4, 0, not_at_start); if (compiler->macro_assembler()->CanReadUnaligned()) { bool ascii = compiler->ascii(); if (ascii) { @@ -2800,7 +2875,9 @@ void ChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { int first_normal_choice = greedy_loop ? 1 : 0; - int preload_characters = CalculatePreloadCharacters(compiler); + int preload_characters = + CalculatePreloadCharacters(compiler, + current_trace->at_start() == Trace::FALSE); bool preload_is_current = (current_trace->characters_preloaded() == preload_characters); bool preload_has_checked_bounds = preload_is_current; diff --git a/src/jsregexp.h b/src/jsregexp.h index 87adf556..6f04be36 100644 --- a/src/jsregexp.h +++ b/src/jsregexp.h @@ -596,8 +596,13 @@ class RegExpNode: public ZoneObject { // How many characters must this node consume at a minimum in order to // succeed. If we have found at least 'still_to_find' characters that // must be consumed there is no need to ask any following nodes whether - // they are sure to eat any more characters. - virtual int EatsAtLeast(int still_to_find, int recursion_depth) = 0; + // they are sure to eat any more characters. The not_at_start argument is + // used to indicate that we know we are not at the start of the input. In + // this case anchored branches will always fail and can be ignored when + // determining how many characters are consumed on success. + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) = 0; // Emits some quick code that checks whether the preloaded characters match. // Falls through on certain failure, jumps to the label on possible success. // If the node cannot make a quick check it does nothing and returns false. @@ -765,7 +770,9 @@ class ActionNode: public SeqRegExpNode { RegExpNode* on_success); virtual void Accept(NodeVisitor* visitor); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int filled_in, @@ -829,7 +836,9 @@ class TextNode: public SeqRegExpNode { } virtual void Accept(NodeVisitor* visitor); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, @@ -897,7 +906,9 @@ class AssertionNode: public SeqRegExpNode { } virtual void Accept(NodeVisitor* visitor); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int filled_in, @@ -925,7 +936,9 @@ class BackReferenceNode: public SeqRegExpNode { int start_register() { return start_reg_; } int end_register() { return end_reg_; } virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, @@ -946,7 +959,9 @@ class EndNode: public RegExpNode { explicit EndNode(Action action) : action_(action) { } virtual void Accept(NodeVisitor* visitor); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth) { return 0; } + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start) { return 0; } virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, @@ -1028,10 +1043,13 @@ class ChoiceNode: public RegExpNode { ZoneList<GuardedAlternative>* alternatives() { return alternatives_; } DispatchTable* GetTable(bool ignore_case); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); int EatsAtLeastHelper(int still_to_find, int recursion_depth, - RegExpNode* ignore_this_node); + RegExpNode* ignore_this_node, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, @@ -1054,7 +1072,7 @@ class ChoiceNode: public RegExpNode { void GenerateGuard(RegExpMacroAssembler* macro_assembler, Guard* guard, Trace* trace); - int CalculatePreloadCharacters(RegExpCompiler* compiler); + int CalculatePreloadCharacters(RegExpCompiler* compiler, bool not_at_start); void EmitOutOfLineContinuation(RegExpCompiler* compiler, Trace* trace, GuardedAlternative alternative, @@ -1077,7 +1095,9 @@ class NegativeLookaheadChoiceNode: public ChoiceNode { AddAlternative(this_must_fail); AddAlternative(then_do_this); } - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, @@ -1102,7 +1122,9 @@ class LoopChoiceNode: public ChoiceNode { void AddLoopAlternative(GuardedAlternative alt); void AddContinueAlternative(GuardedAlternative alt); virtual void Emit(RegExpCompiler* compiler, Trace* trace); - virtual int EatsAtLeast(int still_to_find, int recursion_depth); + virtual int EatsAtLeast(int still_to_find, + int recursion_depth, + bool not_at_start); virtual void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, diff --git a/src/jump-target-light.h b/src/jump-target-light.h index 5ca4d606..0d653063 100644 --- a/src/jump-target-light.h +++ b/src/jump-target-light.h @@ -152,6 +152,7 @@ class BreakTarget : public JumpTarget { public: // Construct a break target. inline BreakTarget(); + inline BreakTarget(JumpTarget::Directionality direction); virtual ~BreakTarget() {} diff --git a/src/list-inl.h b/src/list-inl.h index e277bc87..eeaea65f 100644 --- a/src/list-inl.h +++ b/src/list-inl.h @@ -96,6 +96,17 @@ Vector<T> List<T, P>::AddBlock(T value, int count) { template<typename T, class P> +void List<T, P>::InsertAt(int index, const T& elm) { + ASSERT(index >= 0 && index <= length_); + Add(elm); + for (int i = length_ - 1; i > index; --i) { + data_[i] = data_[i - 1]; + } + data_[index] = elm; +} + + +template<typename T, class P> T List<T, P>::Remove(int i) { T element = at(i); length_--; @@ -108,6 +119,18 @@ T List<T, P>::Remove(int i) { template<typename T, class P> +bool List<T, P>::RemoveElement(const T& elm) { + for (int i = 0; i < length_; i++) { + if (data_[i] == elm) { + Remove(i); + return true; + } + } + return false; +} + + +template<typename T, class P> void List<T, P>::Clear() { DeleteData(data_); Initialize(0); @@ -134,7 +157,7 @@ void List<T, P>::Iterate(Visitor* visitor) { template<typename T, class P> -bool List<T, P>::Contains(const T& elm) { +bool List<T, P>::Contains(const T& elm) const { for (int i = 0; i < length_; i++) { if (data_[i] == elm) return true; @@ -144,6 +167,16 @@ bool List<T, P>::Contains(const T& elm) { template<typename T, class P> +int List<T, P>::CountOccurrences(const T& elm, int start, int end) const { + int result = 0; + for (int i = start; i <= end; i++) { + if (data_[i] == elm) ++result; + } + return result; +} + + +template<typename T, class P> void List<T, P>::Sort(int (*cmp)(const T* x, const T* y)) { ToVector().Sort(cmp); #ifdef DEBUG @@ -91,6 +91,9 @@ class List { // Add all the elements from the argument list to this list. void AddAll(const List<T, P>& other); + // Inserts the element at the specific index. + void InsertAt(int index, const T& element); + // Added 'count' elements with the value 'value' and returns a // vector that allows access to the elements. The vector is valid // until the next change is made to this list. @@ -102,6 +105,10 @@ class List { // size of the list. T Remove(int i); + // Remove the given element from the list. Returns whether or not + // the input is included in the list in the first place. + bool RemoveElement(const T& elm); + // Removes the last element without deleting it even if T is a // pointer type. Returns the removed element. INLINE(T RemoveLast()) { return Remove(length_ - 1); } @@ -113,7 +120,11 @@ class List { // Drops all but the first 'pos' elements from the list. INLINE(void Rewind(int pos)); - bool Contains(const T& elm); + // Drop the last 'count' elements from the list. + INLINE(void RewindBy(int count)) { Rewind(length_ - count); } + + bool Contains(const T& elm) const; + int CountOccurrences(const T& elm, int start, int end) const; // Iterate through all list entries, starting at index 0. void Iterate(void (*callback)(T* x)); diff --git a/src/lithium-allocator.cc b/src/lithium-allocator.cc new file mode 100644 index 00000000..ac61c17b --- /dev/null +++ b/src/lithium-allocator.cc @@ -0,0 +1,2116 @@ +// Copyright 2010 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. + +#include "lithium-allocator.h" + +#include "data-flow.h" +#include "hydrogen.h" +#include "string-stream.h" + +#if V8_TARGET_ARCH_IA32 +#include "ia32/lithium-ia32.h" +#elif V8_TARGET_ARCH_X64 +#include "x64/lithium-x64.h" +#elif V8_TARGET_ARCH_ARM +#include "arm/lithium-arm.h" +#else +#error "Unknown architecture." +#endif + +namespace v8 { +namespace internal { + + +#define DEFINE_OPERAND_CACHE(name, type) \ + name name::cache[name::kNumCachedOperands]; \ + void name::SetupCache() { \ + for (int i = 0; i < kNumCachedOperands; i++) { \ + cache[i].ConvertTo(type, i); \ + } \ + } + +DEFINE_OPERAND_CACHE(LConstantOperand, CONSTANT_OPERAND) +DEFINE_OPERAND_CACHE(LStackSlot, STACK_SLOT) +DEFINE_OPERAND_CACHE(LDoubleStackSlot, DOUBLE_STACK_SLOT) +DEFINE_OPERAND_CACHE(LRegister, REGISTER) +DEFINE_OPERAND_CACHE(LDoubleRegister, DOUBLE_REGISTER) + +#undef DEFINE_OPERAND_CACHE + + +static inline LifetimePosition Min(LifetimePosition a, LifetimePosition b) { + return a.Value() < b.Value() ? a : b; +} + + +static inline LifetimePosition Max(LifetimePosition a, LifetimePosition b) { + return a.Value() > b.Value() ? a : b; +} + + +void LOperand::PrintTo(StringStream* stream) { + LUnallocated* unalloc = NULL; + switch (kind()) { + case INVALID: + break; + case UNALLOCATED: + unalloc = LUnallocated::cast(this); + stream->Add("v%d", unalloc->virtual_register()); + switch (unalloc->policy()) { + case LUnallocated::NONE: + break; + case LUnallocated::FIXED_REGISTER: { + const char* register_name = + Register::AllocationIndexToString(unalloc->fixed_index()); + stream->Add("(=%s)", register_name); + break; + } + case LUnallocated::FIXED_DOUBLE_REGISTER: { + const char* double_register_name = + DoubleRegister::AllocationIndexToString(unalloc->fixed_index()); + stream->Add("(=%s)", double_register_name); + break; + } + case LUnallocated::FIXED_SLOT: + stream->Add("(=%dS)", unalloc->fixed_index()); + break; + case LUnallocated::MUST_HAVE_REGISTER: + stream->Add("(R)"); + break; + case LUnallocated::WRITABLE_REGISTER: + stream->Add("(WR)"); + break; + case LUnallocated::SAME_AS_FIRST_INPUT: + stream->Add("(1)"); + break; + case LUnallocated::SAME_AS_ANY_INPUT: + stream->Add("(A)"); + break; + case LUnallocated::ANY: + stream->Add("(-)"); + break; + case LUnallocated::IGNORE: + stream->Add("(0)"); + break; + } + break; + case CONSTANT_OPERAND: + stream->Add("[constant:%d]", index()); + break; + case STACK_SLOT: + stream->Add("[stack:%d]", index()); + break; + case DOUBLE_STACK_SLOT: + stream->Add("[double_stack:%d]", index()); + break; + case REGISTER: + stream->Add("[%s|R]", Register::AllocationIndexToString(index())); + break; + case DOUBLE_REGISTER: + stream->Add("[%s|R]", DoubleRegister::AllocationIndexToString(index())); + break; + case ARGUMENT: + stream->Add("[arg:%d]", index()); + break; + } +} + +int LOperand::VirtualRegister() { + LUnallocated* unalloc = LUnallocated::cast(this); + return unalloc->virtual_register(); +} + + +bool UsePosition::RequiresRegister() const { + return requires_reg_; +} + + +bool UsePosition::RegisterIsBeneficial() const { + return register_beneficial_; +} + + +void UseInterval::SplitAt(LifetimePosition pos) { + ASSERT(Contains(pos) && pos.Value() != start().Value()); + UseInterval* after = new UseInterval(pos, end_); + after->next_ = next_; + next_ = after; + end_ = pos; +} + + +#ifdef DEBUG + + +void LiveRange::Verify() const { + UsePosition* cur = first_pos_; + while (cur != NULL) { + ASSERT(Start().Value() <= cur->pos().Value() && + cur->pos().Value() <= End().Value()); + cur = cur->next(); + } +} + + +bool LiveRange::HasOverlap(UseInterval* target) const { + UseInterval* current_interval = first_interval_; + while (current_interval != NULL) { + // Intervals overlap if the start of one is contained in the other. + if (current_interval->Contains(target->start()) || + target->Contains(current_interval->start())) { + return true; + } + current_interval = current_interval->next(); + } + return false; +} + + +#endif + + +UsePosition* LiveRange::NextUsePosition(LifetimePosition start) { + UsePosition* use_pos = last_processed_use_; + if (use_pos == NULL) use_pos = first_pos(); + while (use_pos != NULL && use_pos->pos().Value() < start.Value()) { + use_pos = use_pos->next(); + } + last_processed_use_ = use_pos; + return use_pos; +} + + +UsePosition* LiveRange::NextUsePositionRegisterIsBeneficial( + LifetimePosition start) { + UsePosition* pos = NextUsePosition(start); + while (pos != NULL && !pos->RegisterIsBeneficial()) { + pos = pos->next(); + } + return pos; +} + + +UsePosition* LiveRange::NextRegisterPosition(LifetimePosition start) { + UsePosition* pos = NextUsePosition(start); + while (pos != NULL && !pos->RequiresRegister()) { + pos = pos->next(); + } + return pos; +} + + +bool LiveRange::CanBeSpilled(LifetimePosition pos) { + // TODO(kmillikin): Comment. Now. + if (pos.Value() <= Start().Value() && HasRegisterAssigned()) return false; + + // We cannot spill a live range that has a use requiring a register + // at the current or the immediate next position. + UsePosition* use_pos = NextRegisterPosition(pos); + if (use_pos == NULL) return true; + return use_pos->pos().Value() > pos.NextInstruction().Value(); +} + + +UsePosition* LiveRange::FirstPosWithHint() const { + UsePosition* pos = first_pos_; + while (pos != NULL && !pos->HasHint()) pos = pos->next(); + return pos; +} + + +LOperand* LiveRange::CreateAssignedOperand() { + LOperand* op = NULL; + if (HasRegisterAssigned()) { + ASSERT(!IsSpilled()); + if (IsDouble()) { + op = LDoubleRegister::Create(assigned_register()); + } else { + op = LRegister::Create(assigned_register()); + } + } else if (IsSpilled()) { + ASSERT(!HasRegisterAssigned()); + op = TopLevel()->GetSpillOperand(); + ASSERT(!op->IsUnallocated()); + } else { + LUnallocated* unalloc = new LUnallocated(LUnallocated::NONE); + unalloc->set_virtual_register(id_); + op = unalloc; + } + return op; +} + + +UseInterval* LiveRange::FirstSearchIntervalForPosition( + LifetimePosition position) const { + if (current_interval_ == NULL) return first_interval_; + if (current_interval_->start().Value() > position.Value()) { + current_interval_ = NULL; + return first_interval_; + } + return current_interval_; +} + + +void LiveRange::AdvanceLastProcessedMarker( + UseInterval* to_start_of, LifetimePosition but_not_past) const { + if (to_start_of == NULL) return; + if (to_start_of->start().Value() > but_not_past.Value()) return; + LifetimePosition start = + current_interval_ == NULL ? LifetimePosition::Invalid() + : current_interval_->start(); + if (to_start_of->start().Value() > start.Value()) { + current_interval_ = to_start_of; + } +} + + +void LiveRange::SplitAt(LifetimePosition position, LiveRange* result) { + ASSERT(Start().Value() < position.Value()); + ASSERT(result->IsEmpty()); + // Find the last interval that ends before the position. If the + // position is contained in one of the intervals in the chain, we + // split that interval and use the first part. + UseInterval* current = FirstSearchIntervalForPosition(position); + + // If the split position coincides with the beginning of a use interval + // we need to split use positons in a special way. + bool split_at_start = false; + + while (current != NULL) { + if (current->Contains(position)) { + current->SplitAt(position); + break; + } + UseInterval* next = current->next(); + if (next->start().Value() >= position.Value()) { + split_at_start = (next->start().Value() == position.Value()); + break; + } + current = next; + } + + // Partition original use intervals to the two live ranges. + UseInterval* before = current; + UseInterval* after = before->next(); + result->last_interval_ = (last_interval_ == before) + ? after // Only interval in the range after split. + : last_interval_; // Last interval of the original range. + result->first_interval_ = after; + last_interval_ = before; + + // Find the last use position before the split and the first use + // position after it. + UsePosition* use_after = first_pos_; + UsePosition* use_before = NULL; + if (split_at_start) { + // The split position coincides with the beginning of a use interval (the + // end of a lifetime hole). Use at this position should be attributed to + // the split child because split child owns use interval covering it. + while (use_after != NULL && use_after->pos().Value() < position.Value()) { + use_before = use_after; + use_after = use_after->next(); + } + } else { + while (use_after != NULL && use_after->pos().Value() <= position.Value()) { + use_before = use_after; + use_after = use_after->next(); + } + } + + // Partition original use positions to the two live ranges. + if (use_before != NULL) { + use_before->next_ = NULL; + } else { + first_pos_ = NULL; + } + result->first_pos_ = use_after; + + // Link the new live range in the chain before any of the other + // ranges linked from the range before the split. + result->parent_ = (parent_ == NULL) ? this : parent_; + result->next_ = next_; + next_ = result; + +#ifdef DEBUG + Verify(); + result->Verify(); +#endif +} + + +// This implements an ordering on live ranges so that they are ordered by their +// start positions. This is needed for the correctness of the register +// allocation algorithm. If two live ranges start at the same offset then there +// is a tie breaker based on where the value is first used. This part of the +// ordering is merely a heuristic. +bool LiveRange::ShouldBeAllocatedBefore(const LiveRange* other) const { + LifetimePosition start = Start(); + LifetimePosition other_start = other->Start(); + if (start.Value() == other_start.Value()) { + UsePosition* pos = FirstPosWithHint(); + if (pos == NULL) return false; + UsePosition* other_pos = other->first_pos(); + if (other_pos == NULL) return true; + return pos->pos().Value() < other_pos->pos().Value(); + } + return start.Value() < other_start.Value(); +} + + +void LiveRange::ShortenTo(LifetimePosition start) { + LAllocator::TraceAlloc("Shorten live range %d to [%d\n", id_, start.Value()); + ASSERT(first_interval_ != NULL); + ASSERT(first_interval_->start().Value() <= start.Value()); + ASSERT(start.Value() < first_interval_->end().Value()); + first_interval_->set_start(start); +} + + +void LiveRange::EnsureInterval(LifetimePosition start, LifetimePosition end) { + LAllocator::TraceAlloc("Ensure live range %d in interval [%d %d[\n", + id_, + start.Value(), + end.Value()); + LifetimePosition new_end = end; + while (first_interval_ != NULL && + first_interval_->start().Value() <= end.Value()) { + if (first_interval_->end().Value() > end.Value()) { + new_end = first_interval_->end(); + } + first_interval_ = first_interval_->next(); + } + + UseInterval* new_interval = new UseInterval(start, new_end); + new_interval->next_ = first_interval_; + first_interval_ = new_interval; + if (new_interval->next() == NULL) { + last_interval_ = new_interval; + } +} + + +void LiveRange::AddUseInterval(LifetimePosition start, LifetimePosition end) { + LAllocator::TraceAlloc("Add to live range %d interval [%d %d[\n", + id_, + start.Value(), + end.Value()); + if (first_interval_ == NULL) { + UseInterval* interval = new UseInterval(start, end); + first_interval_ = interval; + last_interval_ = interval; + } else { + if (end.Value() == first_interval_->start().Value()) { + first_interval_->set_start(start); + } else if (end.Value() < first_interval_->start().Value()) { + UseInterval* interval = new UseInterval(start, end); + interval->set_next(first_interval_); + first_interval_ = interval; + } else { + // Order of instruction's processing (see ProcessInstructions) guarantees + // that each new use interval either precedes or intersects with + // last added interval. + ASSERT(start.Value() < first_interval_->end().Value()); + first_interval_->start_ = Min(start, first_interval_->start_); + first_interval_->end_ = Max(end, first_interval_->end_); + } + } +} + + +UsePosition* LiveRange::AddUsePosition(LifetimePosition pos, + LOperand* operand) { + LAllocator::TraceAlloc("Add to live range %d use position %d\n", + id_, + pos.Value()); + UsePosition* use_pos = new UsePosition(pos, operand); + UsePosition* prev = NULL; + UsePosition* current = first_pos_; + while (current != NULL && current->pos().Value() < pos.Value()) { + prev = current; + current = current->next(); + } + + if (prev == NULL) { + use_pos->set_next(first_pos_); + first_pos_ = use_pos; + } else { + use_pos->next_ = prev->next_; + prev->next_ = use_pos; + } + + return use_pos; +} + + +void LiveRange::ConvertOperands() { + LOperand* op = CreateAssignedOperand(); + UsePosition* use_pos = first_pos(); + while (use_pos != NULL) { + ASSERT(Start().Value() <= use_pos->pos().Value() && + use_pos->pos().Value() <= End().Value()); + + if (use_pos->HasOperand()) { + ASSERT(op->IsRegister() || op->IsDoubleRegister() || + !use_pos->RequiresRegister()); + use_pos->operand()->ConvertTo(op->kind(), op->index()); + } + use_pos = use_pos->next(); + } +} + + +UsePosition* LiveRange::AddUsePosition(LifetimePosition pos) { + return AddUsePosition(pos, CreateAssignedOperand()); +} + + +bool LiveRange::CanCover(LifetimePosition position) const { + if (IsEmpty()) return false; + return Start().Value() <= position.Value() && + position.Value() < End().Value(); +} + + +bool LiveRange::Covers(LifetimePosition position) { + if (!CanCover(position)) return false; + UseInterval* start_search = FirstSearchIntervalForPosition(position); + for (UseInterval* interval = start_search; + interval != NULL; + interval = interval->next()) { + ASSERT(interval->next() == NULL || + interval->next()->start().Value() >= interval->start().Value()); + AdvanceLastProcessedMarker(interval, position); + if (interval->Contains(position)) return true; + if (interval->start().Value() > position.Value()) return false; + } + return false; +} + + +LifetimePosition LiveRange::FirstIntersection(LiveRange* other) { + UseInterval* b = other->first_interval(); + if (b == NULL) return LifetimePosition::Invalid(); + LifetimePosition advance_last_processed_up_to = b->start(); + UseInterval* a = FirstSearchIntervalForPosition(b->start()); + while (a != NULL && b != NULL) { + if (a->start().Value() > other->End().Value()) break; + if (b->start().Value() > End().Value()) break; + LifetimePosition cur_intersection = a->Intersect(b); + if (cur_intersection.IsValid()) { + return cur_intersection; + } + if (a->start().Value() < b->start().Value()) { + a = a->next(); + if (a == NULL || a->start().Value() > other->End().Value()) break; + AdvanceLastProcessedMarker(a, advance_last_processed_up_to); + } else { + b = b->next(); + } + } + return LifetimePosition::Invalid(); +} + + +void LAllocator::InitializeLivenessAnalysis() { + // Initialize the live_in sets for each block to NULL. + int block_count = graph()->blocks()->length(); + live_in_sets_.Initialize(block_count); + live_in_sets_.AddBlock(NULL, block_count); +} + + +BitVector* LAllocator::ComputeLiveOut(HBasicBlock* block) { + // Compute live out for the given block, except not including backward + // successor edges. + BitVector* live_out = new BitVector(next_virtual_register_); + + // Process all successor blocks. + HBasicBlock* successor = block->end()->FirstSuccessor(); + while (successor != NULL) { + // Add values live on entry to the successor. Note the successor's + // live_in will not be computed yet for backwards edges. + BitVector* live_in = live_in_sets_[successor->block_id()]; + if (live_in != NULL) live_out->Union(*live_in); + + // All phi input operands corresponding to this successor edge are live + // out from this block. + int index = successor->PredecessorIndexOf(block); + const ZoneList<HPhi*>* phis = successor->phis(); + for (int i = 0; i < phis->length(); ++i) { + HPhi* phi = phis->at(i); + if (!phi->OperandAt(index)->IsConstant()) { + live_out->Add(phi->OperandAt(index)->id()); + } + } + + // Check if we are done with second successor. + if (successor == block->end()->SecondSuccessor()) break; + + successor = block->end()->SecondSuccessor(); + } + + return live_out; +} + + +void LAllocator::AddInitialIntervals(HBasicBlock* block, + BitVector* live_out) { + // Add an interval that includes the entire block to the live range for + // each live_out value. + LifetimePosition start = LifetimePosition::FromInstructionIndex( + block->first_instruction_index()); + LifetimePosition end = LifetimePosition::FromInstructionIndex( + block->last_instruction_index()).NextInstruction(); + BitVector::Iterator iterator(live_out); + while (!iterator.Done()) { + int operand_index = iterator.Current(); + LiveRange* range = LiveRangeFor(operand_index); + range->AddUseInterval(start, end); + iterator.Advance(); + } +} + + +int LAllocator::FixedDoubleLiveRangeID(int index) { + return -index - 1 - Register::kNumAllocatableRegisters; +} + + +LOperand* LAllocator::AllocateFixed(LUnallocated* operand, + int pos, + bool is_tagged) { + TraceAlloc("Allocating fixed reg for op %d\n", operand->virtual_register()); + ASSERT(operand->HasFixedPolicy()); + if (operand->policy() == LUnallocated::FIXED_SLOT) { + operand->ConvertTo(LOperand::STACK_SLOT, operand->fixed_index()); + } else if (operand->policy() == LUnallocated::FIXED_REGISTER) { + int reg_index = operand->fixed_index(); + operand->ConvertTo(LOperand::REGISTER, reg_index); + } else if (operand->policy() == LUnallocated::FIXED_DOUBLE_REGISTER) { + int reg_index = operand->fixed_index(); + operand->ConvertTo(LOperand::DOUBLE_REGISTER, reg_index); + } else { + UNREACHABLE(); + } + if (is_tagged) { + TraceAlloc("Fixed reg is tagged at %d\n", pos); + LInstruction* instr = chunk_->instructions()->at(pos); + if (instr->HasPointerMap()) { + instr->pointer_map()->RecordPointer(operand); + } + } + return operand; +} + + +LiveRange* LAllocator::FixedLiveRangeFor(int index) { + if (index >= fixed_live_ranges_.length()) { + fixed_live_ranges_.AddBlock(NULL, + index - fixed_live_ranges_.length() + 1); + } + + LiveRange* result = fixed_live_ranges_[index]; + if (result == NULL) { + result = new LiveRange(FixedLiveRangeID(index)); + ASSERT(result->IsFixed()); + result->set_assigned_register(index, GENERAL_REGISTERS); + fixed_live_ranges_[index] = result; + } + return result; +} + + +LiveRange* LAllocator::FixedDoubleLiveRangeFor(int index) { + if (index >= fixed_double_live_ranges_.length()) { + fixed_double_live_ranges_.AddBlock(NULL, + index - fixed_double_live_ranges_.length() + 1); + } + + LiveRange* result = fixed_double_live_ranges_[index]; + if (result == NULL) { + result = new LiveRange(FixedDoubleLiveRangeID(index)); + ASSERT(result->IsFixed()); + result->set_assigned_register(index, DOUBLE_REGISTERS); + fixed_double_live_ranges_[index] = result; + } + return result; +} + +LiveRange* LAllocator::LiveRangeFor(int index) { + if (index >= live_ranges_.length()) { + live_ranges_.AddBlock(NULL, index - live_ranges_.length() + 1); + } + LiveRange* result = live_ranges_[index]; + if (result == NULL) { + result = new LiveRange(index); + live_ranges_[index] = result; + } + return result; +} + + +LGap* LAllocator::GetLastGap(HBasicBlock* block) const { + int last_instruction = block->last_instruction_index(); + int index = chunk_->NearestGapPos(last_instruction); + return chunk_->GetGapAt(index); +} + + +HPhi* LAllocator::LookupPhi(LOperand* operand) const { + if (!operand->IsUnallocated()) return NULL; + int index = operand->VirtualRegister(); + HValue* instr = graph()->LookupValue(index); + if (instr != NULL && instr->IsPhi()) { + return HPhi::cast(instr); + } + return NULL; +} + + +LiveRange* LAllocator::LiveRangeFor(LOperand* operand) { + if (operand->IsUnallocated()) { + return LiveRangeFor(LUnallocated::cast(operand)->virtual_register()); + } else if (operand->IsRegister()) { + return FixedLiveRangeFor(operand->index()); + } else if (operand->IsDoubleRegister()) { + return FixedDoubleLiveRangeFor(operand->index()); + } else { + return NULL; + } +} + + +void LAllocator::Define(LifetimePosition position, + LOperand* operand, + LOperand* hint) { + LiveRange* range = LiveRangeFor(operand); + if (range == NULL) return; + + if (range->IsEmpty() || range->Start().Value() > position.Value()) { + // Can happen if there is a definition without use. + range->AddUseInterval(position, position.NextInstruction()); + range->AddUsePosition(position.NextInstruction(), NULL); + } else { + range->ShortenTo(position); + } + + if (operand->IsUnallocated()) { + LUnallocated* unalloc_operand = LUnallocated::cast(operand); + range->AddUsePosition(position, unalloc_operand)->set_hint(hint); + } +} + + +void LAllocator::Use(LifetimePosition block_start, + LifetimePosition position, + LOperand* operand, + LOperand* hint) { + LiveRange* range = LiveRangeFor(operand); + if (range == NULL) return; + if (operand->IsUnallocated()) { + LUnallocated* unalloc_operand = LUnallocated::cast(operand); + range->AddUsePosition(position, unalloc_operand)->set_hint(hint); + } + range->AddUseInterval(block_start, position); +} + + +void LAllocator::AddConstraintsGapMove(int index, + LOperand* from, + LOperand* to) { + LGap* gap = chunk_->GetGapAt(index); + LParallelMove* move = gap->GetOrCreateParallelMove(LGap::START); + if (from->IsUnallocated()) { + const ZoneList<LMoveOperands>* move_operands = move->move_operands(); + for (int i = 0; i < move_operands->length(); ++i) { + LMoveOperands cur = move_operands->at(i); + LOperand* cur_to = cur.to(); + if (cur_to->IsUnallocated()) { + if (cur_to->VirtualRegister() == from->VirtualRegister()) { + move->AddMove(cur.from(), to); + return; + } + } + } + } + move->AddMove(from, to); +} + + +void LAllocator::MeetRegisterConstraints(HBasicBlock* block) { + int start = block->first_instruction_index(); + int end = block->last_instruction_index(); + for (int i = start; i <= end; ++i) { + if (chunk_->IsGapAt(i)) { + InstructionSummary* summary = NULL; + InstructionSummary* prev_summary = NULL; + if (i < end) summary = GetSummary(i + 1); + if (i > start) prev_summary = GetSummary(i - 1); + MeetConstraintsBetween(prev_summary, summary, i); + } + } +} + + +void LAllocator::MeetConstraintsBetween(InstructionSummary* first, + InstructionSummary* second, + int gap_index) { + // Handle fixed temporaries. + if (first != NULL) { + for (int i = 0; i < first->TempCount(); ++i) { + LUnallocated* temp = LUnallocated::cast(first->TempAt(i)); + if (temp->HasFixedPolicy()) { + AllocateFixed(temp, gap_index - 1, false); + } + } + } + + // Handle fixed output operand. + if (first != NULL && first->Output() != NULL) { + LUnallocated* first_output = LUnallocated::cast(first->Output()); + LiveRange* range = LiveRangeFor(first_output->VirtualRegister()); + bool assigned = false; + if (first_output->HasFixedPolicy()) { + LUnallocated* output_copy = first_output->CopyUnconstrained(); + bool is_tagged = HasTaggedValue(first_output->VirtualRegister()); + AllocateFixed(first_output, gap_index, is_tagged); + + // This value is produced on the stack, we never need to spill it. + if (first_output->IsStackSlot()) { + range->SetSpillOperand(first_output); + range->SetSpillStartIndex(gap_index - 1); + assigned = true; + } + chunk_->AddGapMove(gap_index, first_output, output_copy); + } + + if (!assigned) { + range->SetSpillStartIndex(gap_index); + + // This move to spill operand is not a real use. Liveness analysis + // and splitting of live ranges do not account for it. + // Thus it should be inserted to a lifetime position corresponding to + // the instruction end. + LGap* gap = chunk_->GetGapAt(gap_index); + LParallelMove* move = gap->GetOrCreateParallelMove(LGap::BEFORE); + move->AddMove(first_output, range->GetSpillOperand()); + } + } + + // Handle fixed input operands of second instruction. + if (second != NULL) { + for (int i = 0; i < second->InputCount(); ++i) { + LUnallocated* cur_input = LUnallocated::cast(second->InputAt(i)); + if (cur_input->HasFixedPolicy()) { + LUnallocated* input_copy = cur_input->CopyUnconstrained(); + bool is_tagged = HasTaggedValue(cur_input->VirtualRegister()); + AllocateFixed(cur_input, gap_index + 1, is_tagged); + AddConstraintsGapMove(gap_index, input_copy, cur_input); + } else if (cur_input->policy() == LUnallocated::WRITABLE_REGISTER) { + LUnallocated* input_copy = cur_input->CopyUnconstrained(); + cur_input->set_virtual_register(next_virtual_register_++); + second->AddTemp(cur_input); + AddConstraintsGapMove(gap_index, input_copy, cur_input); + } + } + } + + // Handle "output same as input" for second instruction. + if (second != NULL && second->Output() != NULL) { + LUnallocated* second_output = LUnallocated::cast(second->Output()); + if (second_output->HasSameAsInputPolicy()) { + LUnallocated* cur_input = LUnallocated::cast(second->InputAt(0)); + int output_vreg = second_output->VirtualRegister(); + int input_vreg = cur_input->VirtualRegister(); + + LUnallocated* input_copy = cur_input->CopyUnconstrained(); + cur_input->set_virtual_register(second_output->virtual_register()); + AddConstraintsGapMove(gap_index, input_copy, cur_input); + + if (HasTaggedValue(input_vreg) && !HasTaggedValue(output_vreg)) { + int index = gap_index + 1; + LInstruction* instr = chunk_->instructions()->at(index); + if (instr->HasPointerMap()) { + instr->pointer_map()->RecordPointer(input_copy); + } + } else if (!HasTaggedValue(input_vreg) && HasTaggedValue(output_vreg)) { + // The input is assumed to immediately have a tagged representation, + // before the pointer map can be used. I.e. the pointer map at the + // instruction will include the output operand (whose value at the + // beginning of the instruction is equal to the input operand). If + // this is not desired, then the pointer map at this instruction needs + // to be adjusted manually. + } + } + } +} + + +void LAllocator::ProcessInstructions(HBasicBlock* block, BitVector* live) { + int block_start = block->first_instruction_index(); + int index = block->last_instruction_index(); + + LifetimePosition block_start_position = + LifetimePosition::FromInstructionIndex(block_start); + + while (index >= block_start) { + LifetimePosition curr_position = + LifetimePosition::FromInstructionIndex(index); + + if (chunk_->IsGapAt(index)) { + // We have a gap at this position. + LGap* gap = chunk_->GetGapAt(index); + LParallelMove* move = gap->GetOrCreateParallelMove(LGap::START); + const ZoneList<LMoveOperands>* move_operands = move->move_operands(); + for (int i = 0; i < move_operands->length(); ++i) { + LMoveOperands* cur = &move_operands->at(i); + if (cur->IsIgnored()) continue; + LOperand* from = cur->from(); + LOperand* to = cur->to(); + HPhi* phi = LookupPhi(to); + LOperand* hint = to; + if (phi != NULL) { + // This is a phi resolving move. + if (!phi->block()->IsLoopHeader()) { + hint = LiveRangeFor(phi->id())->FirstHint(); + } + } else { + if (to->IsUnallocated()) { + if (live->Contains(to->VirtualRegister())) { + Define(curr_position, to, from); + live->Remove(to->VirtualRegister()); + } else { + cur->Eliminate(); + continue; + } + } else { + Define(curr_position, to, from); + } + } + Use(block_start_position, curr_position, from, hint); + if (from->IsUnallocated()) { + live->Add(from->VirtualRegister()); + } + } + } else { + ASSERT(!chunk_->IsGapAt(index)); + InstructionSummary* summary = GetSummary(index); + + if (summary != NULL) { + LOperand* output = summary->Output(); + if (output != NULL) { + if (output->IsUnallocated()) live->Remove(output->VirtualRegister()); + Define(curr_position, output, NULL); + } + + if (summary->IsCall()) { + for (int i = 0; i < Register::kNumAllocatableRegisters; ++i) { + if (output == NULL || !output->IsRegister() || + output->index() != i) { + LiveRange* range = FixedLiveRangeFor(i); + range->AddUseInterval(curr_position, + curr_position.InstructionEnd()); + } + } + for (int i = 0; i < DoubleRegister::kNumAllocatableRegisters; ++i) { + if (output == NULL || !output->IsDoubleRegister() || + output->index() != i) { + LiveRange* range = FixedDoubleLiveRangeFor(i); + range->AddUseInterval(curr_position, + curr_position.InstructionEnd()); + } + } + } + + for (int i = 0; i < summary->InputCount(); ++i) { + LOperand* input = summary->InputAt(i); + + LifetimePosition use_pos; + if (input->IsUnallocated() && + LUnallocated::cast(input)->IsUsedAtStart()) { + use_pos = curr_position; + } else { + use_pos = curr_position.InstructionEnd(); + } + + Use(block_start_position, use_pos, input, NULL); + if (input->IsUnallocated()) live->Add(input->VirtualRegister()); + } + + for (int i = 0; i < summary->TempCount(); ++i) { + LOperand* temp = summary->TempAt(i); + if (summary->IsCall()) { + if (temp->IsRegister()) continue; + if (temp->IsUnallocated()) { + LUnallocated* temp_unalloc = LUnallocated::cast(temp); + if (temp_unalloc->HasFixedPolicy()) { + continue; + } + } + } + Use(block_start_position, curr_position.InstructionEnd(), temp, NULL); + Define(curr_position, temp, NULL); + } + } + } + + index = index - 1; + } +} + + +void LAllocator::ResolvePhis(HBasicBlock* block) { + const ZoneList<HPhi*>* phis = block->phis(); + for (int i = 0; i < phis->length(); ++i) { + HPhi* phi = phis->at(i); + LUnallocated* phi_operand = new LUnallocated(LUnallocated::NONE); + phi_operand->set_virtual_register(phi->id()); + for (int j = 0; j < phi->OperandCount(); ++j) { + HValue* op = phi->OperandAt(j); + LOperand* operand = NULL; + if (op->IsConstant() && op->EmitAtUses()) { + HConstant* constant = HConstant::cast(op); + operand = chunk_->DefineConstantOperand(constant); + } else { + ASSERT(!op->EmitAtUses()); + LUnallocated* unalloc = new LUnallocated(LUnallocated::NONE); + unalloc->set_virtual_register(op->id()); + operand = unalloc; + } + HBasicBlock* cur_block = block->predecessors()->at(j); + // The gap move must be added without any special processing as in + // the AddConstraintsGapMove. + chunk_->AddGapMove(cur_block->last_instruction_index() - 1, + operand, + phi_operand); + } + + LiveRange* live_range = LiveRangeFor(phi->id()); + LLabel* label = chunk_->GetLabel(phi->block()->block_id()); + label->GetOrCreateParallelMove(LGap::START)-> + AddMove(phi_operand, live_range->GetSpillOperand()); + live_range->SetSpillStartIndex(phi->block()->first_instruction_index()); + } +} + + +void LAllocator::Allocate(LChunk* chunk) { + ASSERT(chunk_ == NULL); + chunk_ = chunk; + MeetRegisterConstraints(); + ResolvePhis(); + BuildLiveRanges(); + AllocateGeneralRegisters(); + AllocateDoubleRegisters(); + PopulatePointerMaps(); + if (has_osr_entry_) ProcessOsrEntry(); + ConnectRanges(); + ResolveControlFlow(); +} + + +void LAllocator::MeetRegisterConstraints() { + HPhase phase("Register constraints", chunk()); + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int i = 0; i < blocks->length(); ++i) { + HBasicBlock* block = blocks->at(i); + MeetRegisterConstraints(block); + } +} + + +void LAllocator::ResolvePhis() { + HPhase phase("Resolve phis", chunk()); + + // Process the blocks in reverse order. + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int block_id = blocks->length() - 1; block_id >= 0; --block_id) { + HBasicBlock* block = blocks->at(block_id); + ResolvePhis(block); + } +} + + +void LAllocator::ResolveControlFlow(LiveRange* range, + HBasicBlock* block, + HBasicBlock* pred) { + LifetimePosition pred_end = + LifetimePosition::FromInstructionIndex(pred->last_instruction_index()). + PrevInstruction(); + + LifetimePosition cur_start = + LifetimePosition::FromInstructionIndex(block->first_instruction_index()); + LiveRange* pred_cover = NULL; + LiveRange* cur_cover = NULL; + LiveRange* cur_range = range; + while (cur_range != NULL && (cur_cover == NULL || pred_cover == NULL)) { + if (cur_range->CanCover(cur_start)) { + ASSERT(cur_cover == NULL); + cur_cover = cur_range; + } + if (cur_range->CanCover(pred_end)) { + ASSERT(pred_cover == NULL); + pred_cover = cur_range; + } + cur_range = cur_range->next(); + } + + if (cur_cover->IsSpilled()) return; + ASSERT(pred_cover != NULL && cur_cover != NULL); + if (pred_cover != cur_cover) { + LOperand* pred_op = pred_cover->CreateAssignedOperand(); + LOperand* cur_op = cur_cover->CreateAssignedOperand(); + if (!pred_op->Equals(cur_op)) { + LGap* gap = NULL; + if (block->predecessors()->length() == 1) { + gap = chunk_->GetGapAt(block->first_instruction_index()); + } else { + ASSERT(pred->end()->SecondSuccessor() == NULL); + gap = GetLastGap(pred); + } + gap->GetOrCreateParallelMove(LGap::START)->AddMove(pred_op, cur_op); + } + } +} + + +LParallelMove* LAllocator::GetConnectingParallelMove(LifetimePosition pos) { + int index = pos.InstructionIndex(); + if (chunk_->IsGapAt(index)) { + LGap* gap = chunk_->GetGapAt(index); + return gap->GetOrCreateParallelMove( + pos.IsInstructionStart() ? LGap::START : LGap::END); + } + int gap_pos = pos.IsInstructionStart() ? (index - 1) : (index + 1); + return chunk_->GetGapAt(gap_pos)->GetOrCreateParallelMove( + (gap_pos < index) ? LGap::AFTER : LGap::BEFORE); +} + + +HBasicBlock* LAllocator::GetBlock(LifetimePosition pos) { + LGap* gap = chunk_->GetGapAt(chunk_->NearestGapPos(pos.InstructionIndex())); + return gap->block(); +} + + +void LAllocator::ConnectRanges() { + HPhase phase("Connect ranges", this); + for (int i = 0; i < live_ranges()->length(); ++i) { + LiveRange* first_range = live_ranges()->at(i); + if (first_range == NULL || first_range->parent() != NULL) continue; + + LiveRange* second_range = first_range->next(); + while (second_range != NULL) { + LifetimePosition pos = second_range->Start(); + + if (!second_range->IsSpilled()) { + // Add gap move if the two live ranges touch and there is no block + // boundary. + if (first_range->End().Value() == pos.Value()) { + bool should_insert = true; + if (IsBlockBoundary(pos)) { + should_insert = CanEagerlyResolveControlFlow(GetBlock(pos)); + } + if (should_insert) { + LParallelMove* move = GetConnectingParallelMove(pos); + LOperand* prev_operand = first_range->CreateAssignedOperand(); + LOperand* cur_operand = second_range->CreateAssignedOperand(); + move->AddMove(prev_operand, cur_operand); + } + } + } + + first_range = second_range; + second_range = second_range->next(); + } + } +} + + +bool LAllocator::CanEagerlyResolveControlFlow(HBasicBlock* block) const { + if (block->predecessors()->length() != 1) return false; + return block->predecessors()->first()->block_id() == block->block_id() - 1; +} + + +void LAllocator::ResolveControlFlow() { + HPhase phase("Resolve control flow", this); + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int block_id = 1; block_id < blocks->length(); ++block_id) { + HBasicBlock* block = blocks->at(block_id); + if (CanEagerlyResolveControlFlow(block)) continue; + BitVector* live = live_in_sets_[block->block_id()]; + BitVector::Iterator iterator(live); + while (!iterator.Done()) { + int operand_index = iterator.Current(); + for (int i = 0; i < block->predecessors()->length(); ++i) { + HBasicBlock* cur = block->predecessors()->at(i); + LiveRange* cur_range = LiveRangeFor(operand_index); + ResolveControlFlow(cur_range, block, cur); + } + iterator.Advance(); + } + } +} + + +void LAllocator::BuildLiveRanges() { + HPhase phase("Build live ranges", this); + InitializeLivenessAnalysis(); + // Process the blocks in reverse order. + const ZoneList<HBasicBlock*>* blocks = graph()->blocks(); + for (int block_id = blocks->length() - 1; block_id >= 0; --block_id) { + HBasicBlock* block = blocks->at(block_id); + BitVector* live = ComputeLiveOut(block); + // Initially consider all live_out values live for the entire block. We + // will shorten these intervals if necessary. + AddInitialIntervals(block, live); + + // Process the instructions in reverse order, generating and killing + // live values. + ProcessInstructions(block, live); + // All phi output operands are killed by this block. + const ZoneList<HPhi*>* phis = block->phis(); + for (int i = 0; i < phis->length(); ++i) { + // The live range interval already ends at the first instruction of the + // block. + HPhi* phi = phis->at(i); + live->Remove(phi->id()); + + LOperand* hint = NULL; + LOperand* phi_operand = NULL; + LGap* gap = GetLastGap(phi->block()->predecessors()->at(0)); + LParallelMove* move = gap->GetOrCreateParallelMove(LGap::START); + for (int j = 0; j < move->move_operands()->length(); ++j) { + LOperand* to = move->move_operands()->at(j).to(); + if (to->IsUnallocated() && to->VirtualRegister() == phi->id()) { + hint = move->move_operands()->at(j).from(); + phi_operand = to; + break; + } + } + ASSERT(hint != NULL); + + LifetimePosition block_start = LifetimePosition::FromInstructionIndex( + block->first_instruction_index()); + Define(block_start, phi_operand, hint); + } + + // Now live is live_in for this block except not including values live + // out on backward successor edges. + live_in_sets_[block_id] = live; + + // If this block is a loop header go back and patch up the necessary + // predecessor blocks. + if (block->IsLoopHeader()) { + // TODO(kmillikin): Need to be able to get the last block of the loop + // in the loop information. Add a live range stretching from the first + // loop instruction to the last for each value live on entry to the + // header. + HBasicBlock* back_edge = block->loop_information()->GetLastBackEdge(); + BitVector::Iterator iterator(live); + LifetimePosition start = LifetimePosition::FromInstructionIndex( + block->first_instruction_index()); + LifetimePosition end = LifetimePosition::FromInstructionIndex( + back_edge->last_instruction_index()); + while (!iterator.Done()) { + int operand_index = iterator.Current(); + LiveRange* range = LiveRangeFor(operand_index); + range->EnsureInterval(start, end); + iterator.Advance(); + } + + for (int i = block->block_id() + 1; i <= back_edge->block_id(); ++i) { + live_in_sets_[i]->Union(*live); + } + } + +#ifdef DEBUG + if (block_id == 0) { + BitVector::Iterator iterator(live); + bool found = false; + while (!iterator.Done()) { + found = true; + int operand_index = iterator.Current(); + PrintF("Function: %s\n", + *graph()->info()->function()->debug_name()->ToCString()); + PrintF("Value %d used before first definition!\n", operand_index); + LiveRange* range = LiveRangeFor(operand_index); + PrintF("First use is at %d\n", range->first_pos()->pos().Value()); + iterator.Advance(); + } + ASSERT(!found); + } +#endif + } +} + + +bool LAllocator::SafePointsAreInOrder() const { + const ZoneList<LPointerMap*>* pointer_maps = chunk_->pointer_maps(); + int safe_point = 0; + for (int i = 0; i < pointer_maps->length(); ++i) { + LPointerMap* map = pointer_maps->at(i); + if (safe_point > map->lithium_position()) return false; + safe_point = map->lithium_position(); + } + return true; +} + + +void LAllocator::PopulatePointerMaps() { + HPhase phase("Populate pointer maps", this); + const ZoneList<LPointerMap*>* pointer_maps = chunk_->pointer_maps(); + + ASSERT(SafePointsAreInOrder()); + + // Iterate over all safe point positions and record a pointer + // for all spilled live ranges at this point. + int first_safe_point_index = 0; + int last_range_start = 0; + for (int range_idx = 0; range_idx < live_ranges()->length(); ++range_idx) { + LiveRange* range = live_ranges()->at(range_idx); + if (range == NULL) continue; + // Iterate over the first parts of multi-part live ranges. + if (range->parent() != NULL) continue; + // Skip non-pointer values. + if (!HasTaggedValue(range->id())) continue; + // Skip empty live ranges. + if (range->IsEmpty()) continue; + + // Find the extent of the range and its children. + int start = range->Start().InstructionIndex(); + int end = 0; + for (LiveRange* cur = range; cur != NULL; cur = cur->next()) { + LifetimePosition this_end = cur->End(); + if (this_end.InstructionIndex() > end) end = this_end.InstructionIndex(); + ASSERT(cur->Start().InstructionIndex() >= start); + } + + // Most of the ranges are in order, but not all. Keep an eye on when + // they step backwards and reset the first_safe_point_index so we don't + // miss any safe points. + if (start < last_range_start) { + first_safe_point_index = 0; + } + last_range_start = start; + + // Step across all the safe points that are before the start of this range, + // recording how far we step in order to save doing this for the next range. + while (first_safe_point_index < pointer_maps->length()) { + LPointerMap* map = pointer_maps->at(first_safe_point_index); + int safe_point = map->lithium_position(); + if (safe_point >= start) break; + first_safe_point_index++; + } + + // Step through the safe points to see whether they are in the range. + for (int safe_point_index = first_safe_point_index; + safe_point_index < pointer_maps->length(); + ++safe_point_index) { + LPointerMap* map = pointer_maps->at(safe_point_index); + int safe_point = map->lithium_position(); + + // The safe points are sorted so we can stop searching here. + if (safe_point - 1 > end) break; + + // Advance to the next active range that covers the current + // safe point position. + LifetimePosition safe_point_pos = + LifetimePosition::FromInstructionIndex(safe_point); + LiveRange* cur = range; + while (cur != NULL && !cur->Covers(safe_point_pos.PrevInstruction())) { + cur = cur->next(); + } + if (cur == NULL) continue; + + // Check if the live range is spilled and the safe point is after + // the spill position. + if (range->HasAllocatedSpillOperand() && + safe_point >= range->spill_start_index()) { + TraceAlloc("Pointer for range %d (spilled at %d) at safe point %d\n", + range->id(), range->spill_start_index(), safe_point); + map->RecordPointer(range->GetSpillOperand()); + } + + if (!cur->IsSpilled()) { + TraceAlloc("Pointer in register for range %d (start at %d) " + "at safe point %d\n", + cur->id(), cur->Start().Value(), safe_point); + LOperand* operand = cur->CreateAssignedOperand(); + ASSERT(!operand->IsStackSlot()); + map->RecordPointer(operand); + } + } + } +} + + +void LAllocator::ProcessOsrEntry() { + const ZoneList<LInstruction*>* instrs = chunk_->instructions(); + + // Linear search for the OSR entry instruction in the chunk. + int index = -1; + while (++index < instrs->length() && + !instrs->at(index)->IsOsrEntry()) { + } + ASSERT(index < instrs->length()); + LOsrEntry* instruction = LOsrEntry::cast(instrs->at(index)); + + LifetimePosition position = LifetimePosition::FromInstructionIndex(index); + for (int i = 0; i < live_ranges()->length(); ++i) { + LiveRange* range = live_ranges()->at(i); + if (range != NULL) { + if (range->Covers(position) && + range->HasRegisterAssigned() && + range->TopLevel()->HasAllocatedSpillOperand()) { + int reg_index = range->assigned_register(); + LOperand* spill_operand = range->TopLevel()->GetSpillOperand(); + if (range->IsDouble()) { + instruction->MarkSpilledDoubleRegister(reg_index, spill_operand); + } else { + instruction->MarkSpilledRegister(reg_index, spill_operand); + } + } + } + } +} + + +void LAllocator::AllocateGeneralRegisters() { + HPhase phase("Allocate general registers", this); + num_registers_ = Register::kNumAllocatableRegisters; + mode_ = GENERAL_REGISTERS; + AllocateRegisters(); +} + + +void LAllocator::AllocateDoubleRegisters() { + HPhase phase("Allocate double registers", this); + num_registers_ = DoubleRegister::kNumAllocatableRegisters; + mode_ = DOUBLE_REGISTERS; + AllocateRegisters(); +} + + +void LAllocator::AllocateRegisters() { + ASSERT(mode_ != NONE); + reusable_slots_.Clear(); + + for (int i = 0; i < live_ranges_.length(); ++i) { + if (live_ranges_[i] != NULL) { + if (RequiredRegisterKind(live_ranges_[i]->id()) == mode_) { + AddToUnhandledUnsorted(live_ranges_[i]); + } + } + } + SortUnhandled(); + ASSERT(UnhandledIsSorted()); + + ASSERT(active_live_ranges_.is_empty()); + ASSERT(inactive_live_ranges_.is_empty()); + + if (mode_ == DOUBLE_REGISTERS) { + for (int i = 0; i < fixed_double_live_ranges_.length(); ++i) { + LiveRange* current = fixed_double_live_ranges_.at(i); + if (current != NULL) { + AddToInactive(current); + } + } + } else { + for (int i = 0; i < fixed_live_ranges_.length(); ++i) { + LiveRange* current = fixed_live_ranges_.at(i); + if (current != NULL) { + AddToInactive(current); + } + } + } + + while (!unhandled_live_ranges_.is_empty()) { + ASSERT(UnhandledIsSorted()); + LiveRange* current = unhandled_live_ranges_.RemoveLast(); + ASSERT(UnhandledIsSorted()); + LifetimePosition position = current->Start(); + TraceAlloc("Processing interval %d start=%d\n", + current->id(), + position.Value()); + + if (current->HasAllocatedSpillOperand()) { + TraceAlloc("Live range %d already has a spill operand\n", current->id()); + LifetimePosition next_pos = position; + if (chunk_->IsGapAt(next_pos.InstructionIndex())) { + next_pos = next_pos.NextInstruction(); + } + UsePosition* pos = current->NextUsePositionRegisterIsBeneficial(next_pos); + // If the range already has a spill operand and it doesn't need a + // register immediately, split it and spill the first part of the range. + if (pos == NULL) { + Spill(current); + continue; + } else if (pos->pos().Value() > + current->Start().NextInstruction().Value()) { + // Do not spill live range eagerly if use position that can benefit from + // the register is too close to the start of live range. + SpillBetween(current, current->Start(), pos->pos()); + ASSERT(UnhandledIsSorted()); + continue; + } + } + + for (int i = 0; i < active_live_ranges_.length(); ++i) { + LiveRange* cur_active = active_live_ranges_.at(i); + if (cur_active->End().Value() <= position.Value()) { + ActiveToHandled(cur_active); + --i; // The live range was removed from the list of active live ranges. + } else if (!cur_active->Covers(position)) { + ActiveToInactive(cur_active); + --i; // The live range was removed from the list of active live ranges. + } + } + + for (int i = 0; i < inactive_live_ranges_.length(); ++i) { + LiveRange* cur_inactive = inactive_live_ranges_.at(i); + if (cur_inactive->End().Value() <= position.Value()) { + InactiveToHandled(cur_inactive); + --i; // Live range was removed from the list of inactive live ranges. + } else if (cur_inactive->Covers(position)) { + InactiveToActive(cur_inactive); + --i; // Live range was removed from the list of inactive live ranges. + } + } + + ASSERT(!current->HasRegisterAssigned() && !current->IsSpilled()); + + bool result = TryAllocateFreeReg(current); + if (!result) { + AllocateBlockedReg(current); + } + + if (current->HasRegisterAssigned()) { + AddToActive(current); + } + } + + active_live_ranges_.Clear(); + inactive_live_ranges_.Clear(); +} + + +void LAllocator::Setup() { + LConstantOperand::SetupCache(); + LStackSlot::SetupCache(); + LDoubleStackSlot::SetupCache(); + LRegister::SetupCache(); + LDoubleRegister::SetupCache(); +} + + +const char* LAllocator::RegisterName(int allocation_index) { + ASSERT(mode_ != NONE); + if (mode_ == GENERAL_REGISTERS) { + return Register::AllocationIndexToString(allocation_index); + } else { + return DoubleRegister::AllocationIndexToString(allocation_index); + } +} + + +void LAllocator::TraceAlloc(const char* msg, ...) { + if (FLAG_trace_alloc) { + va_list arguments; + va_start(arguments, msg); + OS::VPrint(msg, arguments); + va_end(arguments); + } +} + + +void LAllocator::RecordUse(HValue* value, LUnallocated* operand) { + operand->set_virtual_register(value->id()); + current_summary()->AddInput(operand); +} + + +bool LAllocator::HasTaggedValue(int virtual_register) const { + HValue* value = graph()->LookupValue(virtual_register); + if (value == NULL) return false; + return value->representation().IsTagged(); +} + + +RegisterKind LAllocator::RequiredRegisterKind(int virtual_register) const { + HValue* value = graph()->LookupValue(virtual_register); + if (value != NULL && value->representation().IsDouble()) { + return DOUBLE_REGISTERS; + } + return GENERAL_REGISTERS; +} + + +void LAllocator::MarkAsCall() { + current_summary()->MarkAsCall(); +} + + +void LAllocator::RecordDefinition(HInstruction* instr, LUnallocated* operand) { + operand->set_virtual_register(instr->id()); + current_summary()->SetOutput(operand); +} + + +void LAllocator::RecordTemporary(LUnallocated* operand) { + ASSERT(next_virtual_register_ < LUnallocated::kMaxVirtualRegisters); + if (!operand->HasFixedPolicy()) { + operand->set_virtual_register(next_virtual_register_++); + } + current_summary()->AddTemp(operand); +} + + +int LAllocator::max_initial_value_ids() { + return LUnallocated::kMaxVirtualRegisters / 32; +} + + +void LAllocator::BeginInstruction() { + if (next_summary_ == NULL) { + next_summary_ = new InstructionSummary(); + } + summary_stack_.Add(next_summary_); + next_summary_ = NULL; +} + + +void LAllocator::SummarizeInstruction(int index) { + InstructionSummary* sum = summary_stack_.RemoveLast(); + if (summaries_.length() <= index) { + summaries_.AddBlock(NULL, index + 1 - summaries_.length()); + } + ASSERT(summaries_[index] == NULL); + if (sum->Output() != NULL || sum->InputCount() > 0 || sum->TempCount() > 0) { + summaries_[index] = sum; + } else { + next_summary_ = sum; + } +} + + +void LAllocator::OmitInstruction() { + summary_stack_.RemoveLast(); +} + + +void LAllocator::AddToActive(LiveRange* range) { + TraceAlloc("Add live range %d to active\n", range->id()); + active_live_ranges_.Add(range); +} + + +void LAllocator::AddToInactive(LiveRange* range) { + TraceAlloc("Add live range %d to inactive\n", range->id()); + inactive_live_ranges_.Add(range); +} + + +void LAllocator::AddToUnhandledSorted(LiveRange* range) { + if (range == NULL || range->IsEmpty()) return; + ASSERT(!range->HasRegisterAssigned() && !range->IsSpilled()); + for (int i = unhandled_live_ranges_.length() - 1; i >= 0; --i) { + LiveRange* cur_range = unhandled_live_ranges_.at(i); + if (range->ShouldBeAllocatedBefore(cur_range)) { + TraceAlloc("Add live range %d to unhandled at %d\n", range->id(), i + 1); + unhandled_live_ranges_.InsertAt(i + 1, range); + ASSERT(UnhandledIsSorted()); + return; + } + } + TraceAlloc("Add live range %d to unhandled at start\n", range->id()); + unhandled_live_ranges_.InsertAt(0, range); + ASSERT(UnhandledIsSorted()); +} + + +void LAllocator::AddToUnhandledUnsorted(LiveRange* range) { + if (range == NULL || range->IsEmpty()) return; + ASSERT(!range->HasRegisterAssigned() && !range->IsSpilled()); + TraceAlloc("Add live range %d to unhandled unsorted at end\n", range->id()); + unhandled_live_ranges_.Add(range); +} + + +static int UnhandledSortHelper(LiveRange* const* a, LiveRange* const* b) { + ASSERT(!(*a)->ShouldBeAllocatedBefore(*b) || + !(*b)->ShouldBeAllocatedBefore(*a)); + if ((*a)->ShouldBeAllocatedBefore(*b)) return 1; + if ((*b)->ShouldBeAllocatedBefore(*a)) return -1; + return (*a)->id() - (*b)->id(); +} + + +// Sort the unhandled live ranges so that the ranges to be processed first are +// at the end of the array list. This is convenient for the register allocation +// algorithm because it is efficient to remove elements from the end. +void LAllocator::SortUnhandled() { + TraceAlloc("Sort unhandled\n"); + unhandled_live_ranges_.Sort(&UnhandledSortHelper); +} + + +bool LAllocator::UnhandledIsSorted() { + int len = unhandled_live_ranges_.length(); + for (int i = 1; i < len; i++) { + LiveRange* a = unhandled_live_ranges_.at(i - 1); + LiveRange* b = unhandled_live_ranges_.at(i); + if (a->Start().Value() < b->Start().Value()) return false; + } + return true; +} + + +void LAllocator::FreeSpillSlot(LiveRange* range) { + // Check that we are the last range. + if (range->next() != NULL) return; + + if (!range->TopLevel()->HasAllocatedSpillOperand()) return; + + int index = range->TopLevel()->GetSpillOperand()->index(); + if (index >= 0) { + reusable_slots_.Add(range); + } +} + + +LOperand* LAllocator::TryReuseSpillSlot(LiveRange* range) { + if (reusable_slots_.is_empty()) return NULL; + if (reusable_slots_.first()->End().Value() > + range->TopLevel()->Start().Value()) { + return NULL; + } + LOperand* result = reusable_slots_.first()->TopLevel()->GetSpillOperand(); + reusable_slots_.Remove(0); + return result; +} + + +void LAllocator::ActiveToHandled(LiveRange* range) { + ASSERT(active_live_ranges_.Contains(range)); + active_live_ranges_.RemoveElement(range); + TraceAlloc("Moving live range %d from active to handled\n", range->id()); + FreeSpillSlot(range); +} + + +void LAllocator::ActiveToInactive(LiveRange* range) { + ASSERT(active_live_ranges_.Contains(range)); + active_live_ranges_.RemoveElement(range); + inactive_live_ranges_.Add(range); + TraceAlloc("Moving live range %d from active to inactive\n", range->id()); +} + + +void LAllocator::InactiveToHandled(LiveRange* range) { + ASSERT(inactive_live_ranges_.Contains(range)); + inactive_live_ranges_.RemoveElement(range); + TraceAlloc("Moving live range %d from inactive to handled\n", range->id()); + FreeSpillSlot(range); +} + + +void LAllocator::InactiveToActive(LiveRange* range) { + ASSERT(inactive_live_ranges_.Contains(range)); + inactive_live_ranges_.RemoveElement(range); + active_live_ranges_.Add(range); + TraceAlloc("Moving live range %d from inactive to active\n", range->id()); +} + + +// TryAllocateFreeReg and AllocateBlockedReg assume this +// when allocating local arrays. +STATIC_ASSERT(DoubleRegister::kNumAllocatableRegisters >= + Register::kNumAllocatableRegisters); + + +bool LAllocator::TryAllocateFreeReg(LiveRange* current) { + LifetimePosition free_until_pos[DoubleRegister::kNumAllocatableRegisters]; + + for (int i = 0; i < DoubleRegister::kNumAllocatableRegisters; i++) { + free_until_pos[i] = LifetimePosition::MaxPosition(); + } + + for (int i = 0; i < active_live_ranges_.length(); ++i) { + LiveRange* cur_active = active_live_ranges_.at(i); + free_until_pos[cur_active->assigned_register()] = + LifetimePosition::FromInstructionIndex(0); + } + + for (int i = 0; i < inactive_live_ranges_.length(); ++i) { + LiveRange* cur_inactive = inactive_live_ranges_.at(i); + ASSERT(cur_inactive->End().Value() > current->Start().Value()); + LifetimePosition next_intersection = + cur_inactive->FirstIntersection(current); + if (!next_intersection.IsValid()) continue; + int cur_reg = cur_inactive->assigned_register(); + free_until_pos[cur_reg] = Min(free_until_pos[cur_reg], next_intersection); + } + + UsePosition* hinted_use = current->FirstPosWithHint(); + if (hinted_use != NULL) { + LOperand* hint = hinted_use->hint(); + if (hint->IsRegister() || hint->IsDoubleRegister()) { + int register_index = hint->index(); + TraceAlloc( + "Found reg hint %s (free until [%d) for live range %d (end %d[).\n", + RegisterName(register_index), + free_until_pos[register_index].Value(), + current->id(), + current->End().Value()); + + // The desired register is free until the end of the current live range. + if (free_until_pos[register_index].Value() >= current->End().Value()) { + TraceAlloc("Assigning preferred reg %s to live range %d\n", + RegisterName(register_index), + current->id()); + current->set_assigned_register(register_index, mode_); + return true; + } + } + } + + // Find the register which stays free for the longest time. + int reg = 0; + for (int i = 1; i < RegisterCount(); ++i) { + if (free_until_pos[i].Value() > free_until_pos[reg].Value()) { + reg = i; + } + } + + LifetimePosition pos = free_until_pos[reg]; + + if (pos.Value() <= current->Start().Value()) { + // All registers are blocked. + return false; + } + + if (pos.Value() < current->End().Value()) { + // Register reg is available at the range start but becomes blocked before + // the range end. Split current at position where it becomes blocked. + LiveRange* tail = SplitAt(current, pos); + AddToUnhandledSorted(tail); + } + + + // Register reg is available at the range start and is free until + // the range end. + ASSERT(pos.Value() >= current->End().Value()); + TraceAlloc("Assigning free reg %s to live range %d\n", + RegisterName(reg), + current->id()); + current->set_assigned_register(reg, mode_); + + return true; +} + + +void LAllocator::AllocateBlockedReg(LiveRange* current) { + UsePosition* register_use = current->NextRegisterPosition(current->Start()); + if (register_use == NULL) { + // There is no use in the current live range that requires a register. + // We can just spill it. + Spill(current); + return; + } + + + LifetimePosition use_pos[DoubleRegister::kNumAllocatableRegisters]; + LifetimePosition block_pos[DoubleRegister::kNumAllocatableRegisters]; + + for (int i = 0; i < DoubleRegister::kNumAllocatableRegisters; i++) { + use_pos[i] = block_pos[i] = LifetimePosition::MaxPosition(); + } + + for (int i = 0; i < active_live_ranges_.length(); ++i) { + LiveRange* range = active_live_ranges_[i]; + int cur_reg = range->assigned_register(); + if (range->IsFixed() || !range->CanBeSpilled(current->Start())) { + block_pos[cur_reg] = use_pos[cur_reg] = + LifetimePosition::FromInstructionIndex(0); + } else { + UsePosition* next_use = range->NextUsePositionRegisterIsBeneficial( + current->Start()); + if (next_use == NULL) { + use_pos[cur_reg] = range->End(); + } else { + use_pos[cur_reg] = next_use->pos(); + } + } + } + + for (int i = 0; i < inactive_live_ranges_.length(); ++i) { + LiveRange* range = inactive_live_ranges_.at(i); + ASSERT(range->End().Value() > current->Start().Value()); + LifetimePosition next_intersection = range->FirstIntersection(current); + if (!next_intersection.IsValid()) continue; + int cur_reg = range->assigned_register(); + if (range->IsFixed()) { + block_pos[cur_reg] = Min(block_pos[cur_reg], next_intersection); + use_pos[cur_reg] = Min(block_pos[cur_reg], use_pos[cur_reg]); + } else { + use_pos[cur_reg] = Min(use_pos[cur_reg], next_intersection); + } + } + + int reg = 0; + for (int i = 1; i < RegisterCount(); ++i) { + if (use_pos[i].Value() > use_pos[reg].Value()) { + reg = i; + } + } + + LifetimePosition pos = use_pos[reg]; + + if (pos.Value() < register_use->pos().Value()) { + // All registers are blocked before the first use that requires a register. + // Spill starting part of live range up to that use. + // + // Corner case: the first use position is equal to the start of the range. + // In this case we have nothing to spill and SpillBetween will just return + // this range to the list of unhandled ones. This will lead to the infinite + // loop. + ASSERT(current->Start().Value() < register_use->pos().Value()); + SpillBetween(current, current->Start(), register_use->pos()); + return; + } + + if (block_pos[reg].Value() < current->End().Value()) { + // Register becomes blocked before the current range end. Split before that + // position. + LiveRange* tail = SplitBetween(current, + current->Start(), + block_pos[reg].InstructionStart()); + AddToUnhandledSorted(tail); + } + + // Register reg is not blocked for the whole range. + ASSERT(block_pos[reg].Value() >= current->End().Value()); + TraceAlloc("Assigning blocked reg %s to live range %d\n", + RegisterName(reg), + current->id()); + current->set_assigned_register(reg, mode_); + + // This register was not free. Thus we need to find and spill + // parts of active and inactive live regions that use the same register + // at the same lifetime positions as current. + SplitAndSpillIntersecting(current); +} + + +void LAllocator::SplitAndSpillIntersecting(LiveRange* current) { + ASSERT(current->HasRegisterAssigned()); + int reg = current->assigned_register(); + LifetimePosition split_pos = current->Start(); + for (int i = 0; i < active_live_ranges_.length(); ++i) { + LiveRange* range = active_live_ranges_[i]; + if (range->assigned_register() == reg) { + UsePosition* next_pos = range->NextRegisterPosition(current->Start()); + if (next_pos == NULL) { + SpillAfter(range, split_pos); + } else { + SpillBetween(range, split_pos, next_pos->pos()); + } + ActiveToHandled(range); + --i; + } + } + + for (int i = 0; i < inactive_live_ranges_.length(); ++i) { + LiveRange* range = inactive_live_ranges_[i]; + ASSERT(range->End().Value() > current->Start().Value()); + if (range->assigned_register() == reg && !range->IsFixed()) { + LifetimePosition next_intersection = range->FirstIntersection(current); + if (next_intersection.IsValid()) { + UsePosition* next_pos = range->NextRegisterPosition(current->Start()); + if (next_pos == NULL) { + SpillAfter(range, split_pos); + } else { + next_intersection = Min(next_intersection, next_pos->pos()); + SpillBetween(range, split_pos, next_intersection); + } + InactiveToHandled(range); + --i; + } + } + } +} + + +bool LAllocator::IsBlockBoundary(LifetimePosition pos) { + return pos.IsInstructionStart() && + chunk_->instructions()->at(pos.InstructionIndex())->IsLabel(); +} + + +void LAllocator::AddGapMove(int pos, LiveRange* prev, LiveRange* next) { + UsePosition* prev_pos = prev->AddUsePosition( + LifetimePosition::FromInstructionIndex(pos)); + UsePosition* next_pos = next->AddUsePosition( + LifetimePosition::FromInstructionIndex(pos)); + LOperand* prev_operand = prev_pos->operand(); + LOperand* next_operand = next_pos->operand(); + LGap* gap = chunk_->GetGapAt(pos); + gap->GetOrCreateParallelMove(LGap::START)-> + AddMove(prev_operand, next_operand); + next_pos->set_hint(prev_operand); +} + + +LiveRange* LAllocator::SplitAt(LiveRange* range, LifetimePosition pos) { + ASSERT(!range->IsFixed()); + TraceAlloc("Splitting live range %d at %d\n", range->id(), pos.Value()); + + if (pos.Value() <= range->Start().Value()) return range; + + LiveRange* result = LiveRangeFor(next_virtual_register_++); + range->SplitAt(pos, result); + return result; +} + + +LiveRange* LAllocator::SplitBetween(LiveRange* range, + LifetimePosition start, + LifetimePosition end) { + ASSERT(!range->IsFixed()); + TraceAlloc("Splitting live range %d in position between [%d, %d]\n", + range->id(), + start.Value(), + end.Value()); + + LifetimePosition split_pos = FindOptimalSplitPos(start, end); + ASSERT(split_pos.Value() >= start.Value()); + return SplitAt(range, split_pos); +} + + +LifetimePosition LAllocator::FindOptimalSplitPos(LifetimePosition start, + LifetimePosition end) { + int start_instr = start.InstructionIndex(); + int end_instr = end.InstructionIndex(); + ASSERT(start_instr <= end_instr); + + // We have no choice + if (start_instr == end_instr) return end; + + HBasicBlock* end_block = GetBlock(start); + HBasicBlock* start_block = GetBlock(end); + + if (end_block == start_block) { + // The interval is split in the same basic block. Split at latest possible + // position. + return end; + } + + HBasicBlock* block = end_block; + // Find header of outermost loop. + while (block->parent_loop_header() != NULL && + block->parent_loop_header()->block_id() > start_block->block_id()) { + block = block->parent_loop_header(); + } + + if (block == end_block) return end; + + return LifetimePosition::FromInstructionIndex( + block->first_instruction_index()); +} + + +void LAllocator::SpillAfter(LiveRange* range, LifetimePosition pos) { + LiveRange* second_part = SplitAt(range, pos); + Spill(second_part); +} + + +void LAllocator::SpillBetween(LiveRange* range, + LifetimePosition start, + LifetimePosition end) { + ASSERT(start.Value() < end.Value()); + LiveRange* second_part = SplitAt(range, start); + + if (second_part->Start().Value() < end.Value()) { + // The split result intersects with [start, end[. + // Split it at position between ]start+1, end[, spill the middle part + // and put the rest to unhandled. + LiveRange* third_part = SplitBetween( + second_part, + second_part->Start().InstructionEnd(), + end.PrevInstruction().InstructionEnd()); + + ASSERT(third_part != second_part); + + Spill(second_part); + AddToUnhandledSorted(third_part); + } else { + // The split result does not intersect with [start, end[. + // Nothing to spill. Just put it to unhandled as whole. + AddToUnhandledSorted(second_part); + } +} + + +void LAllocator::Spill(LiveRange* range) { + ASSERT(!range->IsSpilled()); + TraceAlloc("Spilling live range %d\n", range->id()); + LiveRange* first = range->TopLevel(); + + if (!first->HasAllocatedSpillOperand()) { + LOperand* op = TryReuseSpillSlot(range); + if (op == NULL) op = chunk_->GetNextSpillSlot(mode_ == DOUBLE_REGISTERS); + first->SetSpillOperand(op); + } + range->MakeSpilled(); +} + + +int LAllocator::RegisterCount() const { + return num_registers_; +} + + +#ifdef DEBUG + + +void LAllocator::Verify() const { + for (int i = 0; i < live_ranges()->length(); ++i) { + LiveRange* current = live_ranges()->at(i); + if (current != NULL) current->Verify(); + } +} + + +#endif + + +} } // namespace v8::internal diff --git a/src/lithium-allocator.h b/src/lithium-allocator.h new file mode 100644 index 00000000..3ec984e2 --- /dev/null +++ b/src/lithium-allocator.h @@ -0,0 +1,989 @@ +// Copyright 2010 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. + +#ifndef V8_LITHIUM_ALLOCATOR_H_ +#define V8_LITHIUM_ALLOCATOR_H_ + +#include "v8.h" + +#include "zone.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class HBasicBlock; +class HGraph; +class HInstruction; +class HPhi; +class HTracer; +class HValue; +class BitVector; +class StringStream; + +class LArgument; +class LChunk; +class LConstantOperand; +class LGap; +class LInstruction; +class LParallelMove; +class LPointerMap; +class LStackSlot; +class LRegister; + + +// This class represents a single point of a LOperand's lifetime. +// For each lithium instruction there are exactly two lifetime positions: +// the beginning and the end of the instruction. Lifetime positions for +// different lithium instructions are disjoint. +class LifetimePosition { + public: + // Return the lifetime position that corresponds to the beginning of + // the instruction with the given index. + static LifetimePosition FromInstructionIndex(int index) { + return LifetimePosition(index * kStep); + } + + // Returns a numeric representation of this lifetime position. + int Value() const { + return value_; + } + + // Returns the index of the instruction to which this lifetime position + // corresponds. + int InstructionIndex() const { + ASSERT(IsValid()); + return value_ / kStep; + } + + // Returns true if this lifetime position corresponds to the instruction + // start. + bool IsInstructionStart() const { + return (value_ & (kStep - 1)) == 0; + } + + // Returns the lifetime position for the start of the instruction which + // corresponds to this lifetime position. + LifetimePosition InstructionStart() const { + ASSERT(IsValid()); + return LifetimePosition(value_ & ~(kStep - 1)); + } + + // Returns the lifetime position for the end of the instruction which + // corresponds to this lifetime position. + LifetimePosition InstructionEnd() const { + ASSERT(IsValid()); + return LifetimePosition(InstructionStart().Value() + kStep/2); + } + + // Returns the lifetime position for the beginning of the next instruction. + LifetimePosition NextInstruction() const { + ASSERT(IsValid()); + return LifetimePosition(InstructionStart().Value() + kStep); + } + + // Returns the lifetime position for the beginning of the previous + // instruction. + LifetimePosition PrevInstruction() const { + ASSERT(IsValid()); + ASSERT(value_ > 1); + return LifetimePosition(InstructionStart().Value() - kStep); + } + + // Constructs the lifetime position which does not correspond to any + // instruction. + LifetimePosition() : value_(-1) {} + + // Returns true if this lifetime positions corrensponds to some + // instruction. + bool IsValid() const { return value_ != -1; } + + static inline LifetimePosition Invalid() { return LifetimePosition(); } + + static inline LifetimePosition MaxPosition() { + // We have to use this kind of getter instead of static member due to + // crash bug in GDB. + return LifetimePosition(kMaxInt); + } + + private: + static const int kStep = 2; + + // Code relies on kStep being a power of two. + STATIC_ASSERT(IS_POWER_OF_TWO(kStep)); + + explicit LifetimePosition(int value) : value_(value) { } + + int value_; +}; + + +enum RegisterKind { + NONE, + GENERAL_REGISTERS, + DOUBLE_REGISTERS +}; + + +class LOperand: public ZoneObject { + public: + enum Kind { + INVALID, + UNALLOCATED, + CONSTANT_OPERAND, + STACK_SLOT, + DOUBLE_STACK_SLOT, + REGISTER, + DOUBLE_REGISTER, + ARGUMENT + }; + + LOperand() : value_(KindField::encode(INVALID)) { } + + Kind kind() const { return KindField::decode(value_); } + int index() const { return static_cast<int>(value_) >> kKindFieldWidth; } + bool IsConstantOperand() const { return kind() == CONSTANT_OPERAND; } + bool IsStackSlot() const { return kind() == STACK_SLOT; } + bool IsDoubleStackSlot() const { return kind() == DOUBLE_STACK_SLOT; } + bool IsRegister() const { return kind() == REGISTER; } + bool IsDoubleRegister() const { return kind() == DOUBLE_REGISTER; } + bool IsArgument() const { return kind() == ARGUMENT; } + bool IsUnallocated() const { return kind() == UNALLOCATED; } + bool Equals(LOperand* other) const { return value_ == other->value_; } + int VirtualRegister(); + + void PrintTo(StringStream* stream); + void ConvertTo(Kind kind, int index) { + value_ = KindField::encode(kind); + value_ |= index << kKindFieldWidth; + ASSERT(this->index() == index); + } + + protected: + static const int kKindFieldWidth = 3; + class KindField : public BitField<Kind, 0, kKindFieldWidth> { }; + + LOperand(Kind kind, int index) { ConvertTo(kind, index); } + + unsigned value_; +}; + + +class LUnallocated: public LOperand { + public: + enum Policy { + NONE, + ANY, + FIXED_REGISTER, + FIXED_DOUBLE_REGISTER, + FIXED_SLOT, + MUST_HAVE_REGISTER, + WRITABLE_REGISTER, + SAME_AS_FIRST_INPUT, + SAME_AS_ANY_INPUT, + IGNORE + }; + + // Lifetime of operand inside the instruction. + enum Lifetime { + // USED_AT_START operand is guaranteed to be live only at + // instruction start. Register allocator is free to assign the same register + // to some other operand used inside instruction (i.e. temporary or + // output). + USED_AT_START, + + // USED_AT_END operand is treated as live until the end of + // instruction. This means that register allocator will not reuse it's + // register for any other operand inside instruction. + USED_AT_END + }; + + explicit LUnallocated(Policy policy) : LOperand(UNALLOCATED, 0) { + Initialize(policy, 0, USED_AT_END); + } + + LUnallocated(Policy policy, int fixed_index) : LOperand(UNALLOCATED, 0) { + Initialize(policy, fixed_index, USED_AT_END); + } + + LUnallocated(Policy policy, Lifetime lifetime) : LOperand(UNALLOCATED, 0) { + Initialize(policy, 0, lifetime); + } + + // The superclass has a KindField. Some policies have a signed fixed + // index in the upper bits. + static const int kPolicyWidth = 4; + static const int kLifetimeWidth = 1; + static const int kVirtualRegisterWidth = 17; + + static const int kPolicyShift = kKindFieldWidth; + static const int kLifetimeShift = kPolicyShift + kPolicyWidth; + static const int kVirtualRegisterShift = kLifetimeShift + kLifetimeWidth; + static const int kFixedIndexShift = + kVirtualRegisterShift + kVirtualRegisterWidth; + + class PolicyField : public BitField<Policy, kPolicyShift, kPolicyWidth> { }; + + class LifetimeField + : public BitField<Lifetime, kLifetimeShift, kLifetimeWidth> { + }; + + class VirtualRegisterField + : public BitField<unsigned, + kVirtualRegisterShift, + kVirtualRegisterWidth> { + }; + + static const int kMaxVirtualRegisters = 1 << (kVirtualRegisterWidth + 1); + static const int kMaxFixedIndices = 128; + + bool HasIgnorePolicy() const { return policy() == IGNORE; } + bool HasNoPolicy() const { return policy() == NONE; } + bool HasAnyPolicy() const { + return policy() == ANY; + } + bool HasFixedPolicy() const { + return policy() == FIXED_REGISTER || + policy() == FIXED_DOUBLE_REGISTER || + policy() == FIXED_SLOT; + } + bool HasRegisterPolicy() const { + return policy() == WRITABLE_REGISTER || policy() == MUST_HAVE_REGISTER; + } + bool HasSameAsInputPolicy() const { + return policy() == SAME_AS_FIRST_INPUT || policy() == SAME_AS_ANY_INPUT; + } + Policy policy() const { return PolicyField::decode(value_); } + void set_policy(Policy policy) { + value_ &= ~PolicyField::mask(); + value_ |= PolicyField::encode(policy); + } + int fixed_index() const { + return static_cast<int>(value_) >> kFixedIndexShift; + } + + unsigned virtual_register() const { + return VirtualRegisterField::decode(value_); + } + + void set_virtual_register(unsigned id) { + value_ &= ~VirtualRegisterField::mask(); + value_ |= VirtualRegisterField::encode(id); + } + + LUnallocated* CopyUnconstrained() { + LUnallocated* result = new LUnallocated(ANY); + result->set_virtual_register(virtual_register()); + return result; + } + + static LUnallocated* cast(LOperand* op) { + ASSERT(op->IsUnallocated()); + return reinterpret_cast<LUnallocated*>(op); + } + + bool IsUsedAtStart() { + return LifetimeField::decode(value_) == USED_AT_START; + } + + private: + void Initialize(Policy policy, int fixed_index, Lifetime lifetime) { + value_ |= PolicyField::encode(policy); + value_ |= LifetimeField::encode(lifetime); + value_ |= fixed_index << kFixedIndexShift; + ASSERT(this->fixed_index() == fixed_index); + } +}; + + +class LMoveOperands BASE_EMBEDDED { + public: + LMoveOperands(LOperand* from, LOperand* to) : from_(from), to_(to) { } + + LOperand* from() const { return from_; } + LOperand* to() const { return to_; } + bool IsRedundant() const { + return IsEliminated() || from_->Equals(to_) || IsIgnored(); + } + bool IsEliminated() const { return from_ == NULL; } + bool IsIgnored() const { + if (to_ != NULL && to_->IsUnallocated() && + LUnallocated::cast(to_)->HasIgnorePolicy()) { + return true; + } + return false; + } + + void Eliminate() { from_ = to_ = NULL; } + + private: + LOperand* from_; + LOperand* to_; +}; + + +class LConstantOperand: public LOperand { + public: + static LConstantOperand* Create(int index) { + ASSERT(index >= 0); + if (index < kNumCachedOperands) return &cache[index]; + return new LConstantOperand(index); + } + + static LConstantOperand* cast(LOperand* op) { + ASSERT(op->IsConstantOperand()); + return reinterpret_cast<LConstantOperand*>(op); + } + + static void SetupCache(); + + private: + static const int kNumCachedOperands = 128; + static LConstantOperand cache[]; + + LConstantOperand() : LOperand() { } + explicit LConstantOperand(int index) : LOperand(CONSTANT_OPERAND, index) { } +}; + + +class LArgument: public LOperand { + public: + explicit LArgument(int index) : LOperand(ARGUMENT, index) { } + + static LArgument* cast(LOperand* op) { + ASSERT(op->IsArgument()); + return reinterpret_cast<LArgument*>(op); + } +}; + + +class LStackSlot: public LOperand { + public: + static LStackSlot* Create(int index) { + ASSERT(index >= 0); + if (index < kNumCachedOperands) return &cache[index]; + return new LStackSlot(index); + } + + static LStackSlot* cast(LOperand* op) { + ASSERT(op->IsStackSlot()); + return reinterpret_cast<LStackSlot*>(op); + } + + static void SetupCache(); + + private: + static const int kNumCachedOperands = 128; + static LStackSlot cache[]; + + LStackSlot() : LOperand() { } + explicit LStackSlot(int index) : LOperand(STACK_SLOT, index) { } +}; + + +class LDoubleStackSlot: public LOperand { + public: + static LDoubleStackSlot* Create(int index) { + ASSERT(index >= 0); + if (index < kNumCachedOperands) return &cache[index]; + return new LDoubleStackSlot(index); + } + + static LDoubleStackSlot* cast(LOperand* op) { + ASSERT(op->IsStackSlot()); + return reinterpret_cast<LDoubleStackSlot*>(op); + } + + static void SetupCache(); + + private: + static const int kNumCachedOperands = 128; + static LDoubleStackSlot cache[]; + + LDoubleStackSlot() : LOperand() { } + explicit LDoubleStackSlot(int index) : LOperand(DOUBLE_STACK_SLOT, index) { } +}; + + +class LRegister: public LOperand { + public: + static LRegister* Create(int index) { + ASSERT(index >= 0); + if (index < kNumCachedOperands) return &cache[index]; + return new LRegister(index); + } + + static LRegister* cast(LOperand* op) { + ASSERT(op->IsRegister()); + return reinterpret_cast<LRegister*>(op); + } + + static void SetupCache(); + + private: + static const int kNumCachedOperands = 16; + static LRegister cache[]; + + LRegister() : LOperand() { } + explicit LRegister(int index) : LOperand(REGISTER, index) { } +}; + + +class LDoubleRegister: public LOperand { + public: + static LDoubleRegister* Create(int index) { + ASSERT(index >= 0); + if (index < kNumCachedOperands) return &cache[index]; + return new LDoubleRegister(index); + } + + static LDoubleRegister* cast(LOperand* op) { + ASSERT(op->IsDoubleRegister()); + return reinterpret_cast<LDoubleRegister*>(op); + } + + static void SetupCache(); + + private: + static const int kNumCachedOperands = 16; + static LDoubleRegister cache[]; + + LDoubleRegister() : LOperand() { } + explicit LDoubleRegister(int index) : LOperand(DOUBLE_REGISTER, index) { } +}; + + +// A register-allocator view of a Lithium instruction. It contains the id of +// the output operand and a list of input operand uses. +class InstructionSummary: public ZoneObject { + public: + InstructionSummary() + : output_operand_(NULL), input_count_(0), operands_(4), is_call_(false) {} + + // Output operands. + LOperand* Output() const { return output_operand_; } + void SetOutput(LOperand* output) { + ASSERT(output_operand_ == NULL); + output_operand_ = output; + } + + // Input operands. + int InputCount() const { return input_count_; } + LOperand* InputAt(int i) const { + ASSERT(i < input_count_); + return operands_[i]; + } + void AddInput(LOperand* input) { + operands_.InsertAt(input_count_, input); + input_count_++; + } + + // Temporary operands. + int TempCount() const { return operands_.length() - input_count_; } + LOperand* TempAt(int i) const { return operands_[i + input_count_]; } + void AddTemp(LOperand* temp) { operands_.Add(temp); } + + void MarkAsCall() { is_call_ = true; } + bool IsCall() const { return is_call_; } + + private: + LOperand* output_operand_; + int input_count_; + ZoneList<LOperand*> operands_; + bool is_call_; +}; + +// Representation of the non-empty interval [start,end[. +class UseInterval: public ZoneObject { + public: + UseInterval(LifetimePosition start, LifetimePosition end) + : start_(start), end_(end), next_(NULL) { + ASSERT(start.Value() < end.Value()); + } + + LifetimePosition start() const { return start_; } + LifetimePosition end() const { return end_; } + UseInterval* next() const { return next_; } + + // Split this interval at the given position without effecting the + // live range that owns it. The interval must contain the position. + void SplitAt(LifetimePosition pos); + + // If this interval intersects with other return smallest position + // that belongs to both of them. + LifetimePosition Intersect(const UseInterval* other) const { + if (other->start().Value() < start_.Value()) return other->Intersect(this); + if (other->start().Value() < end_.Value()) return other->start(); + return LifetimePosition::Invalid(); + } + + bool Contains(LifetimePosition point) const { + return start_.Value() <= point.Value() && point.Value() < end_.Value(); + } + + private: + void set_start(LifetimePosition start) { start_ = start; } + void set_next(UseInterval* next) { next_ = next; } + + LifetimePosition start_; + LifetimePosition end_; + UseInterval* next_; + + friend class LiveRange; // Assigns to start_. +}; + +// Representation of a use position. +class UsePosition: public ZoneObject { + public: + UsePosition(LifetimePosition pos, LOperand* operand) + : operand_(operand), + hint_(NULL), + pos_(pos), + next_(NULL), + requires_reg_(false), + register_beneficial_(true) { + if (operand_ != NULL && operand_->IsUnallocated()) { + LUnallocated* unalloc = LUnallocated::cast(operand_); + requires_reg_ = unalloc->HasRegisterPolicy(); + register_beneficial_ = !unalloc->HasAnyPolicy(); + } + ASSERT(pos_.IsValid()); + } + + LOperand* operand() const { return operand_; } + bool HasOperand() const { return operand_ != NULL; } + + LOperand* hint() const { return hint_; } + void set_hint(LOperand* hint) { hint_ = hint; } + bool HasHint() const { return hint_ != NULL && !hint_->IsUnallocated(); } + bool RequiresRegister() const; + bool RegisterIsBeneficial() const; + + LifetimePosition pos() const { return pos_; } + UsePosition* next() const { return next_; } + + private: + void set_next(UsePosition* next) { next_ = next; } + + LOperand* operand_; + LOperand* hint_; + LifetimePosition pos_; + UsePosition* next_; + bool requires_reg_; + bool register_beneficial_; + + friend class LiveRange; +}; + +// Representation of SSA values' live ranges as a collection of (continuous) +// intervals over the instruction ordering. +class LiveRange: public ZoneObject { + public: + static const int kInvalidAssignment = 0x7fffffff; + + explicit LiveRange(int id) + : id_(id), + spilled_(false), + assigned_register_(kInvalidAssignment), + assigned_register_kind_(NONE), + last_interval_(NULL), + first_interval_(NULL), + first_pos_(NULL), + parent_(NULL), + next_(NULL), + current_interval_(NULL), + last_processed_use_(NULL), + spill_start_index_(kMaxInt) { + spill_operand_ = new LUnallocated(LUnallocated::IGNORE); + } + + UseInterval* first_interval() const { return first_interval_; } + UsePosition* first_pos() const { return first_pos_; } + LiveRange* parent() const { return parent_; } + LiveRange* TopLevel() { return (parent_ == NULL) ? this : parent_; } + LiveRange* next() const { return next_; } + bool IsChild() const { return parent() != NULL; } + bool IsParent() const { return parent() == NULL; } + int id() const { return id_; } + bool IsFixed() const { return id_ < 0; } + bool IsEmpty() const { return first_interval() == NULL; } + LOperand* CreateAssignedOperand(); + int assigned_register() const { return assigned_register_; } + int spill_start_index() const { return spill_start_index_; } + void set_assigned_register(int reg, RegisterKind register_kind) { + ASSERT(!HasRegisterAssigned() && !IsSpilled()); + assigned_register_ = reg; + assigned_register_kind_ = register_kind; + ConvertOperands(); + } + void MakeSpilled() { + ASSERT(!IsSpilled()); + ASSERT(TopLevel()->HasAllocatedSpillOperand()); + spilled_ = true; + assigned_register_ = kInvalidAssignment; + ConvertOperands(); + } + + // Returns use position in this live range that follows both start + // and last processed use position. + // Modifies internal state of live range! + UsePosition* NextUsePosition(LifetimePosition start); + + // Returns use position for which register is required in this live + // range and which follows both start and last processed use position + // Modifies internal state of live range! + UsePosition* NextRegisterPosition(LifetimePosition start); + + // Returns use position for which register is beneficial in this live + // range and which follows both start and last processed use position + // Modifies internal state of live range! + UsePosition* NextUsePositionRegisterIsBeneficial(LifetimePosition start); + + // Can this live range be spilled at this position. + bool CanBeSpilled(LifetimePosition pos); + + // Split this live range at the given position which must follow the start of + // the range. + // All uses following the given position will be moved from this + // live range to the result live range. + void SplitAt(LifetimePosition position, LiveRange* result); + + bool IsDouble() const { return assigned_register_kind_ == DOUBLE_REGISTERS; } + bool HasRegisterAssigned() const { + return assigned_register_ != kInvalidAssignment; + } + bool IsSpilled() const { return spilled_; } + UsePosition* FirstPosWithHint() const; + + LOperand* FirstHint() const { + UsePosition* pos = FirstPosWithHint(); + if (pos != NULL) return pos->hint(); + return NULL; + } + + LifetimePosition Start() const { + ASSERT(!IsEmpty()); + return first_interval()->start(); + } + + LifetimePosition End() const { + ASSERT(!IsEmpty()); + return last_interval_->end(); + } + + bool HasAllocatedSpillOperand() const { + return spill_operand_ != NULL && !spill_operand_->IsUnallocated(); + } + LOperand* GetSpillOperand() const { return spill_operand_; } + void SetSpillOperand(LOperand* operand) { + ASSERT(!operand->IsUnallocated()); + ASSERT(spill_operand_ != NULL); + ASSERT(spill_operand_->IsUnallocated()); + spill_operand_->ConvertTo(operand->kind(), operand->index()); + } + + void SetSpillStartIndex(int start) { + spill_start_index_ = Min(start, spill_start_index_); + } + + bool ShouldBeAllocatedBefore(const LiveRange* other) const; + bool CanCover(LifetimePosition position) const; + bool Covers(LifetimePosition position); + LifetimePosition FirstIntersection(LiveRange* other); + + + // Add a new interval or a new use position to this live range. + void EnsureInterval(LifetimePosition start, LifetimePosition end); + void AddUseInterval(LifetimePosition start, LifetimePosition end); + UsePosition* AddUsePosition(LifetimePosition pos, LOperand* operand); + UsePosition* AddUsePosition(LifetimePosition pos); + + // Shorten the most recently added interval by setting a new start. + void ShortenTo(LifetimePosition start); + +#ifdef DEBUG + // True if target overlaps an existing interval. + bool HasOverlap(UseInterval* target) const; + void Verify() const; +#endif + + private: + void ConvertOperands(); + UseInterval* FirstSearchIntervalForPosition(LifetimePosition position) const; + void AdvanceLastProcessedMarker(UseInterval* to_start_of, + LifetimePosition but_not_past) const; + + int id_; + bool spilled_; + int assigned_register_; + RegisterKind assigned_register_kind_; + UseInterval* last_interval_; + UseInterval* first_interval_; + UsePosition* first_pos_; + LiveRange* parent_; + LiveRange* next_; + // This is used as a cache, it doesn't affect correctness. + mutable UseInterval* current_interval_; + UsePosition* last_processed_use_; + LOperand* spill_operand_; + int spill_start_index_; +}; + + +class LAllocator BASE_EMBEDDED { + public: + explicit LAllocator(int first_virtual_register, HGraph* graph) + : chunk_(NULL), + summaries_(0), + next_summary_(NULL), + summary_stack_(2), + live_in_sets_(0), + live_ranges_(16), + fixed_live_ranges_(8), + fixed_double_live_ranges_(8), + unhandled_live_ranges_(8), + active_live_ranges_(8), + inactive_live_ranges_(8), + reusable_slots_(8), + next_virtual_register_(first_virtual_register), + mode_(NONE), + num_registers_(-1), + graph_(graph), + has_osr_entry_(false) {} + + static void Setup(); + static void TraceAlloc(const char* msg, ...); + + // Lithium translation support. + // Record a use of an input operand in the current instruction. + void RecordUse(HValue* value, LUnallocated* operand); + // Record the definition of the output operand. + void RecordDefinition(HInstruction* instr, LUnallocated* operand); + // Record a temporary operand. + void RecordTemporary(LUnallocated* operand); + + // Marks the current instruction as a call. + void MarkAsCall(); + + // Checks whether the value of a given virtual register is tagged. + bool HasTaggedValue(int virtual_register) const; + + // Returns the register kind required by the given virtual register. + RegisterKind RequiredRegisterKind(int virtual_register) const; + + // Begin a new instruction. + void BeginInstruction(); + + // Summarize the current instruction. + void SummarizeInstruction(int index); + + // Summarize the current instruction. + void OmitInstruction(); + + // Control max function size. + static int max_initial_value_ids(); + + void Allocate(LChunk* chunk); + + const ZoneList<LiveRange*>* live_ranges() const { return &live_ranges_; } + const ZoneList<LiveRange*>* fixed_live_ranges() const { + return &fixed_live_ranges_; + } + const ZoneList<LiveRange*>* fixed_double_live_ranges() const { + return &fixed_double_live_ranges_; + } + + LChunk* chunk() const { return chunk_; } + HGraph* graph() const { return graph_; } + + void MarkAsOsrEntry() { + // There can be only one. + ASSERT(!has_osr_entry_); + // Simply set a flag to find and process instruction later. + has_osr_entry_ = true; + } + +#ifdef DEBUG + void Verify() const; +#endif + + private: + void MeetRegisterConstraints(); + void ResolvePhis(); + void BuildLiveRanges(); + void AllocateGeneralRegisters(); + void AllocateDoubleRegisters(); + void ConnectRanges(); + void ResolveControlFlow(); + void PopulatePointerMaps(); + void ProcessOsrEntry(); + void AllocateRegisters(); + bool CanEagerlyResolveControlFlow(HBasicBlock* block) const; + inline bool SafePointsAreInOrder() const; + + // Liveness analysis support. + void InitializeLivenessAnalysis(); + BitVector* ComputeLiveOut(HBasicBlock* block); + void AddInitialIntervals(HBasicBlock* block, BitVector* live_out); + void ProcessInstructions(HBasicBlock* block, BitVector* live); + void MeetRegisterConstraints(HBasicBlock* block); + void MeetConstraintsBetween(InstructionSummary* first, + InstructionSummary* second, + int gap_index); + void ResolvePhis(HBasicBlock* block); + + // Helper methods for building intervals. + LOperand* AllocateFixed(LUnallocated* operand, int pos, bool is_tagged); + LiveRange* LiveRangeFor(LOperand* operand); + void Define(LifetimePosition position, LOperand* operand, LOperand* hint); + void Use(LifetimePosition block_start, + LifetimePosition position, + LOperand* operand, + LOperand* hint); + void AddConstraintsGapMove(int index, LOperand* from, LOperand* to); + + // Helper methods for updating the life range lists. + void AddToActive(LiveRange* range); + void AddToInactive(LiveRange* range); + void AddToUnhandledSorted(LiveRange* range); + void AddToUnhandledUnsorted(LiveRange* range); + void SortUnhandled(); + bool UnhandledIsSorted(); + void ActiveToHandled(LiveRange* range); + void ActiveToInactive(LiveRange* range); + void InactiveToHandled(LiveRange* range); + void InactiveToActive(LiveRange* range); + void FreeSpillSlot(LiveRange* range); + LOperand* TryReuseSpillSlot(LiveRange* range); + + // Helper methods for allocating registers. + bool TryAllocateFreeReg(LiveRange* range); + void AllocateBlockedReg(LiveRange* range); + + // Live range splitting helpers. + + // Split the given range at the given position. + // If range starts at or after the given position then the + // original range is returned. + // Otherwise returns the live range that starts at pos and contains + // all uses from the original range that follow pos. Uses at pos will + // still be owned by the original range after splitting. + LiveRange* SplitAt(LiveRange* range, LifetimePosition pos); + + // Split the given range in a position from the interval [start, end]. + LiveRange* SplitBetween(LiveRange* range, + LifetimePosition start, + LifetimePosition end); + + // Find a lifetime position in the interval [start, end] which + // is optimal for splitting: it is either header of the outermost + // loop covered by this interval or the latest possible position. + LifetimePosition FindOptimalSplitPos(LifetimePosition start, + LifetimePosition end); + + // Spill the given life range after position pos. + void SpillAfter(LiveRange* range, LifetimePosition pos); + + // Spill the given life range after position start and up to position end. + void SpillBetween(LiveRange* range, + LifetimePosition start, + LifetimePosition end); + + void SplitAndSpillIntersecting(LiveRange* range); + + void Spill(LiveRange* range); + bool IsBlockBoundary(LifetimePosition pos); + void AddGapMove(int pos, LiveRange* prev, LiveRange* next); + + // Helper methods for resolving control flow. + void ResolveControlFlow(LiveRange* range, + HBasicBlock* block, + HBasicBlock* pred); + + // Return parallel move that should be used to connect ranges split at the + // given position. + LParallelMove* GetConnectingParallelMove(LifetimePosition pos); + + // Return the block which contains give lifetime position. + HBasicBlock* GetBlock(LifetimePosition pos); + + // Current active summary. + InstructionSummary* current_summary() const { return summary_stack_.last(); } + + // Get summary for given instruction index. + InstructionSummary* GetSummary(int index) const { return summaries_[index]; } + + // Helper methods for the fixed registers. + int RegisterCount() const; + static int FixedLiveRangeID(int index) { return -index - 1; } + static int FixedDoubleLiveRangeID(int index); + LiveRange* FixedLiveRangeFor(int index); + LiveRange* FixedDoubleLiveRangeFor(int index); + LiveRange* LiveRangeFor(int index); + HPhi* LookupPhi(LOperand* operand) const; + LGap* GetLastGap(HBasicBlock* block) const; + + const char* RegisterName(int allocation_index); + + LChunk* chunk_; + ZoneList<InstructionSummary*> summaries_; + InstructionSummary* next_summary_; + + ZoneList<InstructionSummary*> summary_stack_; + + // During liveness analysis keep a mapping from block id to live_in sets + // for blocks already analyzed. + ZoneList<BitVector*> live_in_sets_; + + // Liveness analysis results. + ZoneList<LiveRange*> live_ranges_; + + // Lists of live ranges + ZoneList<LiveRange*> fixed_live_ranges_; + ZoneList<LiveRange*> fixed_double_live_ranges_; + ZoneList<LiveRange*> unhandled_live_ranges_; + ZoneList<LiveRange*> active_live_ranges_; + ZoneList<LiveRange*> inactive_live_ranges_; + ZoneList<LiveRange*> reusable_slots_; + + // Next virtual register number to be assigned to temporaries. + int next_virtual_register_; + + RegisterKind mode_; + int num_registers_; + + HGraph* graph_; + + bool has_osr_entry_; + + DISALLOW_COPY_AND_ASSIGN(LAllocator); +}; + + +} } // namespace v8::internal + +#endif // V8_LITHIUM_ALLOCATOR_H_ diff --git a/src/liveedit-debugger.js b/src/liveedit-debugger.js index 83b703f8..0f7c12d7 100644 --- a/src/liveedit-debugger.js +++ b/src/liveedit-debugger.js @@ -140,9 +140,13 @@ Debug.LiveEdit = new function() { // Collect shared infos for functions whose code need to be patched. var replaced_function_infos = new Array(); for (var i = 0; i < replace_code_list.length; i++) { - var info_wrapper = replace_code_list[i].live_shared_info_wrapper; - if (info_wrapper) { - replaced_function_infos.push(info_wrapper); + var live_shared_function_infos = + replace_code_list[i].live_shared_function_infos; + + if (live_shared_function_infos) { + for (var i = 0; i < live_shared_function_infos.length; i++) { + replaced_function_infos.push(live_shared_function_infos[i]); + } } } @@ -204,6 +208,13 @@ Debug.LiveEdit = new function() { // unchanged and whether positions changed at all. PatchPositions(update_positions_list[i], diff_array, position_patch_report); + + if (update_positions_list[i].live_shared_function_infos) { + update_positions_list[i].live_shared_function_infos. + forEach(function (info) { + %LiveEditFunctionSourceUpdated(info.raw_array); + }); + } } break_points_restorer(pos_translator, old_script); @@ -294,29 +305,34 @@ Debug.LiveEdit = new function() { // Replaces function's Code. function PatchFunctionCode(old_node, change_log) { var new_info = old_node.corresponding_node.info; - var shared_info_wrapper = old_node.live_shared_info_wrapper; - if (shared_info_wrapper) { - %LiveEditReplaceFunctionCode(new_info.raw_array, - shared_info_wrapper.raw_array); - - // The function got a new code. However, this new code brings all new - // instances of SharedFunctionInfo for nested functions. However, - // we want the original instances to be used wherever possible. - // (This is because old instances and new instances will be both - // linked to a script and breakpoints subsystem does not really - // expects this; neither does LiveEdit subsystem on next call). - for (var i = 0; i < old_node.children.length; i++) { - if (old_node.children[i].corresponding_node) { - var corresponding_child = old_node.children[i].corresponding_node; - var child_shared_info_wrapper = - old_node.children[i].live_shared_info_wrapper; - if (child_shared_info_wrapper) { - %LiveEditReplaceRefToNestedFunction(shared_info_wrapper.info, - corresponding_child.info.shared_function_info, - child_shared_info_wrapper.info); + if (old_node.live_shared_function_infos) { + old_node.live_shared_function_infos.forEach(function (old_info) { + %LiveEditReplaceFunctionCode(new_info.raw_array, + old_info.raw_array); + + // The function got a new code. However, this new code brings all new + // instances of SharedFunctionInfo for nested functions. However, + // we want the original instances to be used wherever possible. + // (This is because old instances and new instances will be both + // linked to a script and breakpoints subsystem does not really + // expects this; neither does LiveEdit subsystem on next call). + for (var i = 0; i < old_node.children.length; i++) { + if (old_node.children[i].corresponding_node) { + var corresponding_child_info = + old_node.children[i].corresponding_node.info. + shared_function_info; + + if (old_node.children[i].live_shared_function_infos) { + old_node.children[i].live_shared_function_infos. + forEach(function (old_child_info) { + %LiveEditReplaceRefToNestedFunction(old_info.info, + corresponding_child_info, + old_child_info.info); + }); + } } } - } + }); change_log.push( {function_patched: new_info.function_name} ); } else { @@ -330,10 +346,13 @@ Debug.LiveEdit = new function() { // one representing its old version). This way the function still // may access its own text. function LinkToOldScript(old_info_node, old_script, report_array) { - var shared_info = old_info_node.live_shared_info_wrapper; - if (shared_info) { - %LiveEditFunctionSetScript(shared_info.info, old_script); - report_array.push( { name: shared_info.function_name } ); + if (old_info_node.live_shared_function_infos) { + old_info_node.live_shared_function_infos. + forEach(function (info) { + %LiveEditFunctionSetScript(info.info, old_script); + }); + + report_array.push( { name: old_info_node.info.function_name } ); } else { report_array.push( { name: old_info_node.info.function_name, not_found: true } ); @@ -525,7 +544,7 @@ Debug.LiveEdit = new function() { this.textual_corresponding_node = void 0; this.textually_unmatched_new_nodes = void 0; - this.live_shared_info_wrapper = void 0; + this.live_shared_function_infos = void 0; } // From array of function infos that is implicitly a tree creates @@ -765,23 +784,27 @@ Debug.LiveEdit = new function() { shared_infos.push(new SharedInfoWrapper(shared_raw_list[i])); } - // Finds SharedFunctionInfo that corresponds compile info with index + // Finds all SharedFunctionInfos that corresponds to compile info // in old version of the script. - function FindFunctionInfo(compile_info) { + function FindFunctionInfos(compile_info) { + var wrappers = []; + for (var i = 0; i < shared_infos.length; i++) { var wrapper = shared_infos[i]; if (wrapper.start_position == compile_info.start_position && wrapper.end_position == compile_info.end_position) { - return wrapper; + wrappers.push(wrapper); } } + + if (wrappers.length > 0) { + return wrappers; + } } function TraverseTree(node) { - var info_wrapper = FindFunctionInfo(node.info); - if (info_wrapper) { - node.live_shared_info_wrapper = info_wrapper; - } + node.live_shared_function_infos = FindFunctionInfos(node.info); + for (var i = 0; i < node.children.length; i++) { TraverseTree(node.children[i]); } @@ -817,16 +840,18 @@ Debug.LiveEdit = new function() { // Changes positions (including all statments) in function. function PatchPositions(old_info_node, diff_array, report_array) { - var shared_info_wrapper = old_info_node.live_shared_info_wrapper; - if (!shared_info_wrapper) { + if (old_info_node.live_shared_function_infos) { + old_info_node.live_shared_function_infos.forEach(function (info) { + %LiveEditPatchFunctionPositions(info.raw_array, + diff_array); + }); + + report_array.push( { name: old_info_node.info.function_name } ); + } else { // TODO(LiveEdit): function is not compiled yet or is already collected. report_array.push( { name: old_info_node.info.function_name, info_not_found: true } ); - return; } - %LiveEditPatchFunctionPositions(shared_info_wrapper.raw_array, - diff_array); - report_array.push( { name: old_info_node.info.function_name } ); } // Adds a suffix to script name to mark that it is old version. diff --git a/src/liveedit.cc b/src/liveedit.cc index 642b3e6a..c4cb68e7 100644 --- a/src/liveedit.cc +++ b/src/liveedit.cc @@ -31,7 +31,9 @@ #include "liveedit.h" #include "compiler.h" +#include "compilation-cache.h" #include "debug.h" +#include "deoptimizer.h" #include "global-handles.h" #include "memory.h" #include "oprofile-agent.h" @@ -605,18 +607,18 @@ class FunctionInfoListener { void FunctionDone() { HandleScope scope; - Object* element = - result_->GetElementNoExceptionThrown(current_parent_index_); - FunctionInfoWrapper info = FunctionInfoWrapper::cast(element); + FunctionInfoWrapper info = + FunctionInfoWrapper::cast( + result_->GetElementNoExceptionThrown(current_parent_index_)); current_parent_index_ = info.GetParentIndex(); } // Saves only function code, because for a script function we // may never create a SharedFunctionInfo object. void FunctionCode(Handle<Code> function_code) { - Object* element = - result_->GetElementNoExceptionThrown(current_parent_index_); - FunctionInfoWrapper info = FunctionInfoWrapper::cast(element); + FunctionInfoWrapper info = + FunctionInfoWrapper::cast( + result_->GetElementNoExceptionThrown(current_parent_index_)); info.SetFunctionCode(function_code, Handle<Object>(Heap::null_value())); } @@ -626,9 +628,9 @@ class FunctionInfoListener { if (!shared->IsSharedFunctionInfo()) { return; } - Object* element = - result_->GetElementNoExceptionThrown(current_parent_index_); - FunctionInfoWrapper info = FunctionInfoWrapper::cast(element); + FunctionInfoWrapper info = + FunctionInfoWrapper::cast( + result_->GetElementNoExceptionThrown(current_parent_index_)); info.SetFunctionCode(Handle<Code>(shared->code()), Handle<Object>(shared->scope_info())); info.SetSharedFunctionInfo(shared); @@ -828,6 +830,61 @@ static bool IsJSFunctionCode(Code* code) { } +// Returns true if an instance of candidate were inlined into function's code. +static bool IsInlined(JSFunction* function, SharedFunctionInfo* candidate) { + AssertNoAllocation no_gc; + + if (function->code()->kind() != Code::OPTIMIZED_FUNCTION) return false; + + DeoptimizationInputData* data = + DeoptimizationInputData::cast(function->code()->deoptimization_data()); + + if (data == Heap::empty_fixed_array()) return false; + + FixedArray* literals = data->LiteralArray(); + + int inlined_count = data->InlinedFunctionCount()->value(); + for (int i = 0; i < inlined_count; ++i) { + JSFunction* inlined = JSFunction::cast(literals->get(i)); + if (inlined->shared() == candidate) return true; + } + + return false; +} + + +class DependentFunctionsDeoptimizingVisitor : public OptimizedFunctionVisitor { + public: + explicit DependentFunctionsDeoptimizingVisitor( + SharedFunctionInfo* function_info) + : function_info_(function_info) {} + + virtual void EnterContext(Context* context) { + } + + virtual void VisitFunction(JSFunction* function) { + if (function->shared() == function_info_ || + IsInlined(function, function_info_)) { + Deoptimizer::DeoptimizeFunction(function); + } + } + + virtual void LeaveContext(Context* context) { + } + + private: + SharedFunctionInfo* function_info_; +}; + + +static void DeoptimizeDependentFunctions(SharedFunctionInfo* function_info) { + AssertNoAllocation no_allocation; + + DependentFunctionsDeoptimizingVisitor visitor(function_info); + Deoptimizer::VisitAllOptimizedFunctions(&visitor); +} + + MaybeObject* LiveEdit::ReplaceFunctionCode( Handle<JSArray> new_compile_info_array, Handle<JSArray> shared_info_array) { @@ -864,17 +921,38 @@ MaybeObject* LiveEdit::ReplaceFunctionCode( shared_info->set_construct_stub( Builtins::builtin(Builtins::JSConstructStubGeneric)); + DeoptimizeDependentFunctions(*shared_info); + CompilationCache::Remove(shared_info); + + return Heap::undefined_value(); +} + + +MaybeObject* LiveEdit::FunctionSourceUpdated( + Handle<JSArray> shared_info_array) { + HandleScope scope; + + if (!SharedInfoWrapper::IsInstance(shared_info_array)) { + return Top::ThrowIllegalOperation(); + } + + SharedInfoWrapper shared_info_wrapper(shared_info_array); + Handle<SharedFunctionInfo> shared_info = shared_info_wrapper.GetInfo(); + + DeoptimizeDependentFunctions(*shared_info); + CompilationCache::Remove(shared_info); + return Heap::undefined_value(); } -// TODO(635): Eval caches its scripts (same text -- same compiled info). -// Make sure we clear such caches. void LiveEdit::SetFunctionScript(Handle<JSValue> function_wrapper, Handle<Object> script_handle) { Handle<SharedFunctionInfo> shared_info = Handle<SharedFunctionInfo>::cast(UnwrapJSValue(function_wrapper)); shared_info->set_script(*script_handle); + + CompilationCache::Remove(shared_info); } @@ -1135,11 +1213,14 @@ void LiveEdit::ReplaceRefToNestedFunction( // Check an activation against list of functions. If there is a function // that matches, its status in result array is changed to status argument value. static bool CheckActivation(Handle<JSArray> shared_info_array, - Handle<JSArray> result, StackFrame* frame, + Handle<JSArray> result, + StackFrame* frame, LiveEdit::FunctionPatchabilityStatus status) { - if (!frame->is_java_script()) { - return false; - } + if (!frame->is_java_script()) return false; + + Handle<JSFunction> function( + JSFunction::cast(JavaScriptFrame::cast(frame)->function())); + int len = Smi::cast(shared_info_array->length())->value(); for (int i = 0; i < len; i++) { JSValue* wrapper = @@ -1147,7 +1228,7 @@ static bool CheckActivation(Handle<JSArray> shared_info_array, Handle<SharedFunctionInfo> shared( SharedFunctionInfo::cast(wrapper->value())); - if (frame->code() == shared->code()) { + if (function->shared() == *shared || IsInlined(*function, *shared)) { SetElement(result, i, Handle<Smi>(Smi::FromInt(status))); return true; } diff --git a/src/liveedit.h b/src/liveedit.h index c9bf96d4..3632180f 100644 --- a/src/liveedit.h +++ b/src/liveedit.h @@ -87,6 +87,8 @@ class LiveEdit : AllStatic { Handle<JSArray> new_compile_info_array, Handle<JSArray> shared_info_array); + static MaybeObject* FunctionSourceUpdated(Handle<JSArray> shared_info_array); + // Updates script field in FunctionSharedInfo. static void SetFunctionScript(Handle<JSValue> function_wrapper, Handle<Object> script_handle); diff --git a/src/log-utils.cc b/src/log-utils.cc index d6d8754b..c7b75679 100644 --- a/src/log-utils.cc +++ b/src/log-utils.cc @@ -273,29 +273,7 @@ void LogMessageBuilder::Append(String* str) { void LogMessageBuilder::AppendAddress(Address addr) { - static Address last_address_ = NULL; - AppendAddress(addr, last_address_); - last_address_ = addr; -} - - -void LogMessageBuilder::AppendAddress(Address addr, Address bias) { - if (!FLAG_compress_log) { - Append("0x%" V8PRIxPTR, addr); - } else if (bias == NULL) { - Append("%" V8PRIxPTR, addr); - } else { - uintptr_t delta; - char sign; - if (addr >= bias) { - delta = addr - bias; - sign = '+'; - } else { - delta = bias - addr; - sign = '-'; - } - Append("%c%" V8PRIxPTR, sign, delta); - } + Append("0x%" V8PRIxPTR, addr); } @@ -343,24 +321,6 @@ void LogMessageBuilder::AppendStringPart(const char* str, int len) { } -bool LogMessageBuilder::StoreInCompressor(LogRecordCompressor* compressor) { - return compressor->Store(Vector<const char>(Log::message_buffer_, pos_)); -} - - -bool LogMessageBuilder::RetrieveCompressedPrevious( - LogRecordCompressor* compressor, const char* prefix) { - pos_ = 0; - if (prefix[0] != '\0') Append(prefix); - Vector<char> prev_record(Log::message_buffer_ + pos_, - Log::kMessageBufferSize - pos_); - const bool has_prev = compressor->RetrievePreviousCompressed(&prev_record); - if (!has_prev) return false; - pos_ += prev_record.length(); - return true; -} - - void LogMessageBuilder::WriteToLogFile() { ASSERT(pos_ <= Log::kMessageBufferSize); const int written = Log::Write(Log::message_buffer_, pos_); @@ -369,145 +329,6 @@ void LogMessageBuilder::WriteToLogFile() { } } - -// Formatting string for back references to the whole line. E.g. "#2" means -// "the second line above". -const char* LogRecordCompressor::kLineBackwardReferenceFormat = "#%d"; - -// Formatting string for back references. E.g. "#2:10" means -// "the second line above, start from char 10 (0-based)". -const char* LogRecordCompressor::kBackwardReferenceFormat = "#%d:%d"; - - -LogRecordCompressor::~LogRecordCompressor() { - for (int i = 0; i < buffer_.length(); ++i) { - buffer_[i].Dispose(); - } -} - - -static int GetNumberLength(int number) { - ASSERT(number >= 0); - ASSERT(number < 10000); - if (number < 10) return 1; - if (number < 100) return 2; - if (number < 1000) return 3; - return 4; -} - - -int LogRecordCompressor::GetBackwardReferenceSize(int distance, int pos) { - // See kLineBackwardReferenceFormat and kBackwardReferenceFormat. - return pos == 0 ? GetNumberLength(distance) + 1 - : GetNumberLength(distance) + GetNumberLength(pos) + 2; -} - - -void LogRecordCompressor::PrintBackwardReference(Vector<char> dest, - int distance, - int pos) { - if (pos == 0) { - OS::SNPrintF(dest, kLineBackwardReferenceFormat, distance); - } else { - OS::SNPrintF(dest, kBackwardReferenceFormat, distance, pos); - } -} - - -bool LogRecordCompressor::Store(const Vector<const char>& record) { - // Check if the record is the same as the last stored one. - if (curr_ != -1) { - Vector<const char>& curr = buffer_[curr_]; - if (record.length() == curr.length() - && strncmp(record.start(), curr.start(), record.length()) == 0) { - return false; - } - } - // buffer_ is circular. - prev_ = curr_++; - curr_ %= buffer_.length(); - Vector<char> record_copy = Vector<char>::New(record.length()); - memcpy(record_copy.start(), record.start(), record.length()); - buffer_[curr_].Dispose(); - buffer_[curr_] = - Vector<const char>(record_copy.start(), record_copy.length()); - return true; -} - - -bool LogRecordCompressor::RetrievePreviousCompressed( - Vector<char>* prev_record) { - if (prev_ == -1) return false; - - int index = prev_; - // Distance from prev_. - int distance = 0; - // Best compression result among records in the buffer. - struct { - intptr_t truncated_len; - int distance; - int copy_from_pos; - int backref_size; - } best = {-1, 0, 0, 0}; - Vector<const char>& prev = buffer_[prev_]; - const char* const prev_start = prev.start(); - const char* const prev_end = prev.start() + prev.length(); - do { - // We're moving backwards until we reach the current record. - // Remember that buffer_ is circular. - if (--index == -1) index = buffer_.length() - 1; - ++distance; - if (index == curr_) break; - - Vector<const char>& data = buffer_[index]; - if (data.start() == NULL) break; - const char* const data_end = data.start() + data.length(); - const char* prev_ptr = prev_end; - const char* data_ptr = data_end; - // Compare strings backwards, stop on the last matching character. - while (prev_ptr != prev_start && data_ptr != data.start() - && *(prev_ptr - 1) == *(data_ptr - 1)) { - --prev_ptr; - --data_ptr; - } - const intptr_t truncated_len = prev_end - prev_ptr; - const int copy_from_pos = static_cast<int>(data_ptr - data.start()); - // Check if the length of compressed tail is enough. - if (truncated_len <= kMaxBackwardReferenceSize - && truncated_len <= GetBackwardReferenceSize(distance, copy_from_pos)) { - continue; - } - - // Record compression results. - if (truncated_len > best.truncated_len) { - best.truncated_len = truncated_len; - best.distance = distance; - best.copy_from_pos = copy_from_pos; - best.backref_size = GetBackwardReferenceSize(distance, copy_from_pos); - } - } while (true); - - if (best.distance == 0) { - // Can't compress the previous record. Return as is. - ASSERT(prev_record->length() >= prev.length()); - memcpy(prev_record->start(), prev.start(), prev.length()); - prev_record->Truncate(prev.length()); - } else { - // Copy the uncompressible part unchanged. - const intptr_t unchanged_len = prev.length() - best.truncated_len; - // + 1 for '\0'. - ASSERT(prev_record->length() >= unchanged_len + best.backref_size + 1); - memcpy(prev_record->start(), prev.start(), unchanged_len); - // Append the backward reference. - Vector<char> backref( - prev_record->start() + unchanged_len, best.backref_size + 1); - PrintBackwardReference(backref, best.distance, best.copy_from_pos); - ASSERT(strlen(backref.start()) - best.backref_size == 0); - prev_record->Truncate(static_cast<int>(unchanged_len + best.backref_size)); - } - return true; -} - #endif // ENABLE_LOGGING_AND_PROFILING } } // namespace v8::internal diff --git a/src/log-utils.h b/src/log-utils.h index ffea9282..719d3703 100644 --- a/src/log-utils.h +++ b/src/log-utils.h @@ -176,50 +176,6 @@ class Log : public AllStatic { friend class Logger; friend class LogMessageBuilder; - friend class LogRecordCompressor; -}; - - -// An utility class for performing backward reference compression -// of string ends. It operates using a window of previous strings. -class LogRecordCompressor { - public: - // 'window_size' is the size of backward lookup window. - explicit LogRecordCompressor(int window_size) - : buffer_(window_size + kNoCompressionWindowSize), - kMaxBackwardReferenceSize( - GetBackwardReferenceSize(window_size, Log::kMessageBufferSize)), - curr_(-1), prev_(-1) { - } - - ~LogRecordCompressor(); - - // Fills vector with a compressed version of the previous record. - // Returns false if there is no previous record. - bool RetrievePreviousCompressed(Vector<char>* prev_record); - - // Stores a record if it differs from a previous one (or there's no previous). - // Returns true, if the record has been stored. - bool Store(const Vector<const char>& record); - - private: - // The minimum size of a buffer: a place needed for the current and - // the previous record. Since there is no place for precedessors of a previous - // record, it can't be compressed at all. - static const int kNoCompressionWindowSize = 2; - - // Formatting strings for back references. - static const char* kLineBackwardReferenceFormat; - static const char* kBackwardReferenceFormat; - - static int GetBackwardReferenceSize(int distance, int pos); - - static void PrintBackwardReference(Vector<char> dest, int distance, int pos); - - ScopedVector< Vector<const char> > buffer_; - const int kMaxBackwardReferenceSize; - int curr_; - int prev_; }; @@ -244,32 +200,14 @@ class LogMessageBuilder BASE_EMBEDDED { // Append a heap string. void Append(String* str); - // Appends an address, compressing it if needed by offsetting - // from Logger::last_address_. + // Appends an address. void AppendAddress(Address addr); - // Appends an address, compressing it if needed. - void AppendAddress(Address addr, Address bias); - void AppendDetailed(String* str, bool show_impl_info); // Append a portion of a string. void AppendStringPart(const char* str, int len); - // Stores log message into compressor, returns true if the message - // was stored (i.e. doesn't repeat the previous one). - bool StoreInCompressor(LogRecordCompressor* compressor); - - // Sets log message to a previous version of compressed message. - // Returns false, if there is no previous message. - bool RetrieveCompressedPrevious(LogRecordCompressor* compressor) { - return RetrieveCompressedPrevious(compressor, ""); - } - - // Does the same at the version without arguments, and sets a prefix. - bool RetrieveCompressedPrevious(LogRecordCompressor* compressor, - const char* prefix); - // Write the log message to the log file currently opened. void WriteToLogFile(); @@ -31,11 +31,14 @@ #include "bootstrapper.h" #include "code-stubs.h" +#include "deoptimizer.h" #include "global-handles.h" #include "log.h" #include "macro-assembler.h" +#include "runtime-profiler.h" #include "serialize.h" #include "string-stream.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -147,6 +150,7 @@ void StackTracer::Trace(TickSample* sample) { sample->function = NULL; sample->frames_count = 0; + // Avoid collecting traces while doing GC. if (sample->state == GC) return; const Address js_entry_sp = Top::js_entry_sp(Top::GetCurrentThread()); @@ -155,15 +159,18 @@ void StackTracer::Trace(TickSample* sample) { return; } - const Address functionAddr = + const Address function_address = sample->fp + JavaScriptFrameConstants::kFunctionOffset; if (SafeStackFrameIterator::IsWithinBounds(sample->sp, js_entry_sp, - functionAddr)) { - sample->function = Memory::Address_at(functionAddr) - kHeapObjectTag; + function_address)) { + Object* object = Memory::Object_at(function_address); + if (object->IsHeapObject()) { + sample->function = HeapObject::cast(object)->address(); + } } int i = 0; - const Address callback = VMState::external_callback(); + const Address callback = Top::external_callback(); // Surprisingly, PC can point _exactly_ to callback start, with good // probability, and this will result in reporting fake nested // callback call. @@ -174,9 +181,10 @@ void StackTracer::Trace(TickSample* sample) { SafeStackTraceFrameIterator it(sample->fp, sample->sp, sample->sp, js_entry_sp); while (!it.done() && i < TickSample::kMaxFramesCount) { - sample->stack[i++] = - reinterpret_cast<Address>(it.frame()->function_slot_object()) - - kHeapObjectTag; + Object* object = it.frame()->function_slot_object(); + if (object->IsHeapObject()) { + sample->stack[i++] = HeapObject::cast(object)->address(); + } it.Advance(); } sample->frames_count = i; @@ -189,8 +197,10 @@ void StackTracer::Trace(TickSample* sample) { // class Ticker: public Sampler { public: - explicit Ticker(int interval): - Sampler(interval, FLAG_prof), window_(NULL), profiler_(NULL) {} + explicit Ticker(int interval) : + Sampler(interval), + window_(NULL), + profiler_(NULL) {} ~Ticker() { if (IsActive()) Stop(); } @@ -206,22 +216,24 @@ class Ticker: public Sampler { void ClearWindow() { window_ = NULL; - if (!profiler_ && IsActive()) Stop(); + if (!profiler_ && IsActive() && !RuntimeProfiler::IsEnabled()) Stop(); } void SetProfiler(Profiler* profiler) { + ASSERT(profiler_ == NULL); profiler_ = profiler; + IncreaseProfilingDepth(); if (!FLAG_prof_lazy && !IsActive()) Start(); } void ClearProfiler() { + DecreaseProfilingDepth(); profiler_ = NULL; - if (!window_ && IsActive()) Stop(); + if (!window_ && IsActive() && !RuntimeProfiler::IsEnabled()) Stop(); } protected: virtual void DoSampleStack(TickSample* sample) { - ASSERT(IsSynchronous()); StackTracer::Trace(sample); } @@ -291,7 +303,6 @@ void Profiler::Engage() { Logger::ticker_->SetProfiler(this); Logger::ProfilerBeginEvent(); - Logger::LogAliases(); } @@ -331,43 +342,21 @@ void Profiler::Run() { Ticker* Logger::ticker_ = NULL; Profiler* Logger::profiler_ = NULL; SlidingStateWindow* Logger::sliding_state_window_ = NULL; -const char** Logger::log_events_ = NULL; -CompressionHelper* Logger::compression_helper_ = NULL; int Logger::logging_nesting_ = 0; int Logger::cpu_profiler_nesting_ = 0; int Logger::heap_profiler_nesting_ = 0; -#define DECLARE_LONG_EVENT(ignore1, long_name, ignore2) long_name, -const char* kLongLogEventsNames[Logger::NUMBER_OF_LOG_EVENTS] = { - LOG_EVENTS_AND_TAGS_LIST(DECLARE_LONG_EVENT) +#define DECLARE_EVENT(ignore1, name) name, +const char* kLogEventsNames[Logger::NUMBER_OF_LOG_EVENTS] = { + LOG_EVENTS_AND_TAGS_LIST(DECLARE_EVENT) }; -#undef DECLARE_LONG_EVENT - -#define DECLARE_SHORT_EVENT(ignore1, ignore2, short_name) short_name, -const char* kCompressedLogEventsNames[Logger::NUMBER_OF_LOG_EVENTS] = { - LOG_EVENTS_AND_TAGS_LIST(DECLARE_SHORT_EVENT) -}; -#undef DECLARE_SHORT_EVENT +#undef DECLARE_EVENT void Logger::ProfilerBeginEvent() { if (!Log::IsEnabled()) return; LogMessageBuilder msg; msg.Append("profiler,\"begin\",%d\n", kSamplingIntervalMs); - if (FLAG_compress_log) { - msg.Append("profiler,\"compression\",%d\n", kCompressionWindowSize); - } - msg.WriteToLogFile(); -} - - -void Logger::LogAliases() { - if (!Log::IsEnabled() || !FLAG_compress_log) return; - LogMessageBuilder msg; - for (int i = 0; i < NUMBER_OF_LOG_EVENTS; ++i) { - msg.Append("alias,%s,%s\n", - kCompressedLogEventsNames[i], kLongLogEventsNames[i]); - } msg.WriteToLogFile(); } @@ -675,54 +664,15 @@ void Logger::DeleteEvent(const char* name, void* object) { #ifdef ENABLE_LOGGING_AND_PROFILING - -// A class that contains all common code dealing with record compression. -class CompressionHelper { - public: - explicit CompressionHelper(int window_size) - : compressor_(window_size), repeat_count_(0) { } - - // Handles storing message in compressor, retrieving the previous one and - // prefixing it with repeat count, if needed. - // Returns true if message needs to be written to log. - bool HandleMessage(LogMessageBuilder* msg) { - if (!msg->StoreInCompressor(&compressor_)) { - // Current message repeats the previous one, don't write it. - ++repeat_count_; - return false; - } - if (repeat_count_ == 0) { - return msg->RetrieveCompressedPrevious(&compressor_); - } - OS::SNPrintF(prefix_, "%s,%d,", - Logger::log_events_[Logger::REPEAT_META_EVENT], - repeat_count_ + 1); - repeat_count_ = 0; - return msg->RetrieveCompressedPrevious(&compressor_, prefix_.start()); - } - - private: - LogRecordCompressor compressor_; - int repeat_count_; - EmbeddedVector<char, 20> prefix_; -}; - -#endif // ENABLE_LOGGING_AND_PROFILING - - -#ifdef ENABLE_LOGGING_AND_PROFILING void Logger::CallbackEventInternal(const char* prefix, const char* name, Address entry_point) { if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; msg.Append("%s,%s,", - log_events_[CODE_CREATION_EVENT], log_events_[CALLBACK_TAG]); + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[CALLBACK_TAG]); msg.AppendAddress(entry_point); msg.Append(",1,\"%s%s\"", prefix, name); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); } @@ -759,15 +709,28 @@ void Logger::SetterCallbackEvent(String* name, Address entry_point) { } +#ifdef ENABLE_LOGGING_AND_PROFILING +static const char* ComputeMarker(Code* code) { + switch (code->kind()) { + case Code::FUNCTION: return code->optimizable() ? "~" : ""; + case Code::OPTIMIZED_FUNCTION: return "*"; + default: return ""; + } +} +#endif + + void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, const char* comment) { #ifdef ENABLE_LOGGING_AND_PROFILING if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; - msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]); + msg.Append("%s,%s,", + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[tag]); msg.AppendAddress(code->address()); - msg.Append(",%d,\"", code->ExecutableSize()); + msg.Append(",%d,\"%s", code->ExecutableSize(), ComputeMarker(code)); for (const char* p = comment; *p != '\0'; p++) { if (*p == '"') { msg.Append('\\'); @@ -776,10 +739,6 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, } msg.Append('"'); LowLevelCodeCreateEvent(code, &msg); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -792,14 +751,12 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, String* name) { LogMessageBuilder msg; SmartPointer<char> str = name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL); - msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]); + msg.Append("%s,%s,", + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[tag]); msg.AppendAddress(code->address()); - msg.Append(",%d,\"%s\"", code->ExecutableSize(), *str); + msg.Append(",%d,\"%s%s\"", code->ExecutableSize(), ComputeMarker(code), *str); LowLevelCodeCreateEvent(code, &msg); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -816,15 +773,17 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL); SmartPointer<char> sourcestr = source->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL); - msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]); + msg.Append("%s,%s,", + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[tag]); msg.AppendAddress(code->address()); - msg.Append(",%d,\"%s %s:%d\"", - code->ExecutableSize(), *str, *sourcestr, line); + msg.Append(",%d,\"%s%s %s:%d\"", + code->ExecutableSize(), + ComputeMarker(code), + *str, + *sourcestr, + line); LowLevelCodeCreateEvent(code, &msg); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -835,14 +794,12 @@ void Logger::CodeCreateEvent(LogEventsAndTags tag, Code* code, int args_count) { #ifdef ENABLE_LOGGING_AND_PROFILING if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; - msg.Append("%s,%s,", log_events_[CODE_CREATION_EVENT], log_events_[tag]); + msg.Append("%s,%s,", + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[tag]); msg.AppendAddress(code->address()); msg.Append(",%d,\"args_count: %d\"", code->ExecutableSize(), args_count); LowLevelCodeCreateEvent(code, &msg); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -853,7 +810,7 @@ void Logger::CodeMovingGCEvent() { #ifdef ENABLE_LOGGING_AND_PROFILING if (!Log::IsEnabled() || !FLAG_log_code || !FLAG_ll_prof) return; LogMessageBuilder msg; - msg.Append("%s\n", log_events_[CODE_MOVING_GC]); + msg.Append("%s\n", kLogEventsNames[CODE_MOVING_GC]); msg.WriteToLogFile(); OS::SignalCodeMovingGC(); #endif @@ -865,16 +822,13 @@ void Logger::RegExpCodeCreateEvent(Code* code, String* source) { if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; msg.Append("%s,%s,", - log_events_[CODE_CREATION_EVENT], log_events_[REG_EXP_TAG]); + kLogEventsNames[CODE_CREATION_EVENT], + kLogEventsNames[REG_EXP_TAG]); msg.AppendAddress(code->address()); msg.Append(",%d,\"", code->ExecutableSize()); msg.AppendDetailed(source, false); msg.Append('\"'); LowLevelCodeCreateEvent(code, &msg); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -899,13 +853,9 @@ void Logger::SnapshotPositionEvent(Address addr, int pos) { #ifdef ENABLE_LOGGING_AND_PROFILING if (!Log::IsEnabled() || !FLAG_log_snapshot_positions) return; LogMessageBuilder msg; - msg.Append("%s,", log_events_[SNAPSHOT_POSITION_EVENT]); + msg.Append("%s,", kLogEventsNames[SNAPSHOT_POSITION_EVENT]); msg.AppendAddress(addr); msg.Append(",%d", pos); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -917,18 +867,12 @@ void Logger::FunctionCreateEvent(JSFunction* function) { // This function can be called from GC iterators (during Scavenge, // MC, and MS), so marking bits can be set on objects. That's // why unchecked accessors are used here. - static Address prev_code = NULL; if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; - msg.Append("%s,", log_events_[FUNCTION_CREATION_EVENT]); + msg.Append("%s,", kLogEventsNames[FUNCTION_CREATION_EVENT]); msg.AppendAddress(function->address()); msg.Append(','); - msg.AppendAddress(function->unchecked_code()->address(), prev_code); - prev_code = function->unchecked_code()->address(); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } + msg.AppendAddress(function->unchecked_code()->address()); msg.Append('\n'); msg.WriteToLogFile(); #endif @@ -962,18 +906,12 @@ void Logger::FunctionDeleteEvent(Address from) { void Logger::MoveEventInternal(LogEventsAndTags event, Address from, Address to) { - static Address prev_to_ = NULL; if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; - msg.Append("%s,", log_events_[event]); + msg.Append("%s,", kLogEventsNames[event]); msg.AppendAddress(from); msg.Append(','); - msg.AppendAddress(to, prev_to_); - prev_to_ = to; - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } + msg.AppendAddress(to); msg.Append('\n'); msg.WriteToLogFile(); } @@ -984,12 +922,8 @@ void Logger::MoveEventInternal(LogEventsAndTags event, void Logger::DeleteEventInternal(LogEventsAndTags event, Address from) { if (!Log::IsEnabled() || !FLAG_log_code) return; LogMessageBuilder msg; - msg.Append("%s,", log_events_[event]); + msg.Append("%s,", kLogEventsNames[event]); msg.AppendAddress(from); - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; - } msg.Append('\n'); msg.WriteToLogFile(); } @@ -1177,30 +1111,20 @@ void Logger::DebugEvent(const char* event_type, Vector<uint16_t> parameter) { #ifdef ENABLE_LOGGING_AND_PROFILING void Logger::TickEvent(TickSample* sample, bool overflow) { if (!Log::IsEnabled() || !FLAG_prof) return; - static Address prev_sp = NULL; - static Address prev_function = NULL; LogMessageBuilder msg; - msg.Append("%s,", log_events_[TICK_EVENT]); - Address prev_addr = sample->pc; - msg.AppendAddress(prev_addr); + msg.Append("%s,", kLogEventsNames[TICK_EVENT]); + msg.AppendAddress(sample->pc); msg.Append(','); - msg.AppendAddress(sample->sp, prev_sp); - prev_sp = sample->sp; + msg.AppendAddress(sample->sp); msg.Append(','); - msg.AppendAddress(sample->function, prev_function); - prev_function = sample->function; + msg.AppendAddress(sample->function); msg.Append(",%d", static_cast<int>(sample->state)); if (overflow) { msg.Append(",overflow"); } for (int i = 0; i < sample->frames_count; ++i) { msg.Append(','); - msg.AppendAddress(sample->stack[i], prev_addr); - prev_addr = sample->stack[i]; - } - if (FLAG_compress_log) { - ASSERT(compression_helper_ != NULL); - if (!compression_helper_->HandleMessage(&msg)) return; + msg.AppendAddress(sample->stack[i]); } msg.Append('\n'); msg.WriteToLogFile(); @@ -1226,7 +1150,9 @@ void Logger::PauseProfiler(int flags, int tag) { if (--cpu_profiler_nesting_ == 0) { profiler_->pause(); if (FLAG_prof_lazy) { - if (!FLAG_sliding_state_window) ticker_->Stop(); + if (!FLAG_sliding_state_window && !RuntimeProfiler::IsEnabled()) { + ticker_->Stop(); + } FLAG_log_code = false; // Must be the same message as Log::kDynamicBufferSeal. LOG(UncheckedStringEvent("profiler", "pause")); @@ -1262,7 +1188,9 @@ void Logger::ResumeProfiler(int flags, int tag) { LogCompiledFunctions(); LogFunctionObjects(); LogAccessorCallbacks(); - if (!FLAG_sliding_state_window) ticker_->Start(); + if (!FLAG_sliding_state_window && !ticker_->IsActive()) { + ticker_->Start(); + } } profiler_->resume(); } @@ -1295,9 +1223,41 @@ int Logger::GetLogLines(int from_pos, char* dest_buf, int max_size) { } -static int EnumerateCompiledFunctions(Handle<SharedFunctionInfo>* sfis) { +class EnumerateOptimizedFunctionsVisitor: public OptimizedFunctionVisitor { + public: + EnumerateOptimizedFunctionsVisitor(Handle<SharedFunctionInfo>* sfis, + Handle<Code>* code_objects, + int* count) + : sfis_(sfis), code_objects_(code_objects), count_(count) { } + + virtual void EnterContext(Context* context) {} + virtual void LeaveContext(Context* context) {} + + virtual void VisitFunction(JSFunction* function) { + if (sfis_ != NULL) { + sfis_[*count_] = Handle<SharedFunctionInfo>(function->shared()); + } + if (code_objects_ != NULL) { + ASSERT(function->code()->kind() == Code::OPTIMIZED_FUNCTION); + code_objects_[*count_] = Handle<Code>(function->code()); + } + *count_ = *count_ + 1; + } + + private: + Handle<SharedFunctionInfo>* sfis_; + Handle<Code>* code_objects_; + int* count_; +}; + + +static int EnumerateCompiledFunctions(Handle<SharedFunctionInfo>* sfis, + Handle<Code>* code_objects) { AssertNoAllocation no_alloc; int compiled_funcs_count = 0; + + // Iterate the heap to find shared function info objects and record + // the unoptimized code for them. HeapIterator iterator; for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { if (!obj->IsSharedFunctionInfo()) continue; @@ -1305,11 +1265,22 @@ static int EnumerateCompiledFunctions(Handle<SharedFunctionInfo>* sfis) { if (sfi->is_compiled() && (!sfi->script()->IsScript() || Script::cast(sfi->script())->HasValidSource())) { - if (sfis != NULL) + if (sfis != NULL) { sfis[compiled_funcs_count] = Handle<SharedFunctionInfo>(sfi); + } + if (code_objects != NULL) { + code_objects[compiled_funcs_count] = Handle<Code>(sfi->code()); + } ++compiled_funcs_count; } } + + // Iterate all optimized functions in all contexts. + EnumerateOptimizedFunctionsVisitor visitor(sfis, + code_objects, + &compiled_funcs_count); + Deoptimizer::VisitAllOptimizedFunctions(&visitor); + return compiled_funcs_count; } @@ -1321,9 +1292,11 @@ void Logger::LogCodeObject(Object* object) { const char* description = "Unknown code from the snapshot"; switch (code_object->kind()) { case Code::FUNCTION: + case Code::OPTIMIZED_FUNCTION: return; // We log this later using LogCompiledFunctions. - case Code::BINARY_OP_IC: - // fall through + case Code::BINARY_OP_IC: // fall through + case Code::TYPE_RECORDING_BINARY_OP_IC: // fall through + case Code::COMPARE_IC: // fall through case Code::STUB: description = CodeStub::MajorName(CodeStub::GetMajorKey(code_object), true); @@ -1406,9 +1379,10 @@ void Logger::LogCodeObjects() { void Logger::LogCompiledFunctions() { HandleScope scope; - const int compiled_funcs_count = EnumerateCompiledFunctions(NULL); + const int compiled_funcs_count = EnumerateCompiledFunctions(NULL, NULL); ScopedVector< Handle<SharedFunctionInfo> > sfis(compiled_funcs_count); - EnumerateCompiledFunctions(sfis.start()); + ScopedVector< Handle<Code> > code_objects(compiled_funcs_count); + EnumerateCompiledFunctions(sfis.start(), code_objects.start()); // During iteration, there can be heap allocation due to // GetScriptLineNumber call. @@ -1425,18 +1399,18 @@ void Logger::LogCompiledFunctions() { if (line_num > 0) { PROFILE(CodeCreateEvent( Logger::ToNativeByScript(Logger::LAZY_COMPILE_TAG, *script), - shared->code(), *func_name, + *code_objects[i], *func_name, *script_name, line_num + 1)); } else { // Can't distinguish eval and script here, so always use Script. PROFILE(CodeCreateEvent( Logger::ToNativeByScript(Logger::SCRIPT_TAG, *script), - shared->code(), *script_name)); + *code_objects[i], *script_name)); } } else { PROFILE(CodeCreateEvent( Logger::ToNativeByScript(Logger::LAZY_COMPILE_TAG, *script), - shared->code(), *func_name)); + *code_objects[i], *func_name)); } } else if (shared->IsApiFunction()) { // API function. @@ -1450,7 +1424,7 @@ void Logger::LogCompiledFunctions() { } } else { PROFILE(CodeCreateEvent( - Logger::LAZY_COMPILE_TAG, shared->code(), *func_name)); + Logger::LAZY_COMPILE_TAG, *code_objects[i], *func_name)); } } } @@ -1571,8 +1545,6 @@ bool Logger::Setup() { } } - ASSERT(VMState::is_outermost_external()); - if (FLAG_ll_prof) LogCodeInfo(); ticker_ = new Ticker(kSamplingIntervalMs); @@ -1581,12 +1553,6 @@ bool Logger::Setup() { sliding_state_window_ = new SlidingStateWindow(); } - log_events_ = FLAG_compress_log ? - kCompressedLogEventsNames : kLongLogEventsNames; - if (FLAG_compress_log) { - compression_helper_ = new CompressionHelper(kCompressionWindowSize); - } - if (start_logging) { logging_nesting_ = 1; } @@ -1604,7 +1570,6 @@ bool Logger::Setup() { } LogMessageBuilder::set_write_failure_handler(StopLoggingAndProfiling); - return true; #else @@ -1613,6 +1578,21 @@ bool Logger::Setup() { } +void Logger::EnsureTickerStarted() { +#ifdef ENABLE_LOGGING_AND_PROFILING + ASSERT(ticker_ != NULL); + if (!ticker_->IsActive()) ticker_->Start(); +#endif +} + + +void Logger::EnsureTickerStopped() { +#ifdef ENABLE_LOGGING_AND_PROFILING + if (ticker_ != NULL && ticker_->IsActive()) ticker_->Stop(); +#endif +} + + void Logger::TearDown() { #ifdef ENABLE_LOGGING_AND_PROFILING LogMessageBuilder::set_write_failure_handler(NULL); @@ -1624,9 +1604,6 @@ void Logger::TearDown() { profiler_ = NULL; } - delete compression_helper_; - compression_helper_ = NULL; - delete sliding_state_window_; sliding_state_window_ = NULL; @@ -74,7 +74,6 @@ class Profiler; class Semaphore; class SlidingStateWindow; class LogMessageBuilder; -class CompressionHelper; #undef LOG #ifdef ENABLE_LOGGING_AND_PROFILING @@ -88,58 +87,55 @@ class CompressionHelper; #endif #define LOG_EVENTS_AND_TAGS_LIST(V) \ - V(CODE_CREATION_EVENT, "code-creation", "cc") \ - V(CODE_MOVE_EVENT, "code-move", "cm") \ - V(CODE_DELETE_EVENT, "code-delete", "cd") \ - V(CODE_MOVING_GC, "code-moving-gc", "cg") \ - V(FUNCTION_CREATION_EVENT, "function-creation", "fc") \ - V(FUNCTION_MOVE_EVENT, "function-move", "fm") \ - V(FUNCTION_DELETE_EVENT, "function-delete", "fd") \ - V(SNAPSHOT_POSITION_EVENT, "snapshot-pos", "sp") \ - V(TICK_EVENT, "tick", "t") \ - V(REPEAT_META_EVENT, "repeat", "r") \ - V(BUILTIN_TAG, "Builtin", "bi") \ - V(CALL_DEBUG_BREAK_TAG, "CallDebugBreak", "cdb") \ - V(CALL_DEBUG_PREPARE_STEP_IN_TAG, "CallDebugPrepareStepIn", "cdbsi") \ - V(CALL_IC_TAG, "CallIC", "cic") \ - V(CALL_INITIALIZE_TAG, "CallInitialize", "ci") \ - V(CALL_MEGAMORPHIC_TAG, "CallMegamorphic", "cmm") \ - V(CALL_MISS_TAG, "CallMiss", "cm") \ - V(CALL_NORMAL_TAG, "CallNormal", "cn") \ - V(CALL_PRE_MONOMORPHIC_TAG, "CallPreMonomorphic", "cpm") \ - V(KEYED_CALL_DEBUG_BREAK_TAG, "KeyedCallDebugBreak", "kcdb") \ - V(KEYED_CALL_DEBUG_PREPARE_STEP_IN_TAG, \ - "KeyedCallDebugPrepareStepIn", \ - "kcdbsi") \ - V(KEYED_CALL_IC_TAG, "KeyedCallIC", "kcic") \ - V(KEYED_CALL_INITIALIZE_TAG, "KeyedCallInitialize", "kci") \ - V(KEYED_CALL_MEGAMORPHIC_TAG, "KeyedCallMegamorphic", "kcmm") \ - V(KEYED_CALL_MISS_TAG, "KeyedCallMiss", "kcm") \ - V(KEYED_CALL_NORMAL_TAG, "KeyedCallNormal", "kcn") \ - V(KEYED_CALL_PRE_MONOMORPHIC_TAG, \ - "KeyedCallPreMonomorphic", \ - "kcpm") \ - V(CALLBACK_TAG, "Callback", "cb") \ - V(EVAL_TAG, "Eval", "e") \ - V(FUNCTION_TAG, "Function", "f") \ - V(KEYED_LOAD_IC_TAG, "KeyedLoadIC", "klic") \ - V(KEYED_STORE_IC_TAG, "KeyedStoreIC", "ksic") \ - V(LAZY_COMPILE_TAG, "LazyCompile", "lc") \ - V(LOAD_IC_TAG, "LoadIC", "lic") \ - V(REG_EXP_TAG, "RegExp", "re") \ - V(SCRIPT_TAG, "Script", "sc") \ - V(STORE_IC_TAG, "StoreIC", "sic") \ - V(STUB_TAG, "Stub", "s") \ - V(NATIVE_FUNCTION_TAG, "Function", "f") \ - V(NATIVE_LAZY_COMPILE_TAG, "LazyCompile", "lc") \ - V(NATIVE_SCRIPT_TAG, "Script", "sc") + V(CODE_CREATION_EVENT, "code-creation") \ + V(CODE_MOVE_EVENT, "code-move") \ + V(CODE_DELETE_EVENT, "code-delete") \ + V(CODE_MOVING_GC, "code-moving-gc") \ + V(FUNCTION_CREATION_EVENT, "function-creation") \ + V(FUNCTION_MOVE_EVENT, "function-move") \ + V(FUNCTION_DELETE_EVENT, "function-delete") \ + V(SNAPSHOT_POSITION_EVENT, "snapshot-pos") \ + V(TICK_EVENT, "tick") \ + V(REPEAT_META_EVENT, "repeat") \ + V(BUILTIN_TAG, "Builtin") \ + V(CALL_DEBUG_BREAK_TAG, "CallDebugBreak") \ + V(CALL_DEBUG_PREPARE_STEP_IN_TAG, "CallDebugPrepareStepIn") \ + V(CALL_IC_TAG, "CallIC") \ + V(CALL_INITIALIZE_TAG, "CallInitialize") \ + V(CALL_MEGAMORPHIC_TAG, "CallMegamorphic") \ + V(CALL_MISS_TAG, "CallMiss") \ + V(CALL_NORMAL_TAG, "CallNormal") \ + V(CALL_PRE_MONOMORPHIC_TAG, "CallPreMonomorphic") \ + V(KEYED_CALL_DEBUG_BREAK_TAG, "KeyedCallDebugBreak") \ + V(KEYED_CALL_DEBUG_PREPARE_STEP_IN_TAG, \ + "KeyedCallDebugPrepareStepIn") \ + V(KEYED_CALL_IC_TAG, "KeyedCallIC") \ + V(KEYED_CALL_INITIALIZE_TAG, "KeyedCallInitialize") \ + V(KEYED_CALL_MEGAMORPHIC_TAG, "KeyedCallMegamorphic") \ + V(KEYED_CALL_MISS_TAG, "KeyedCallMiss") \ + V(KEYED_CALL_NORMAL_TAG, "KeyedCallNormal") \ + V(KEYED_CALL_PRE_MONOMORPHIC_TAG, "KeyedCallPreMonomorphic") \ + V(CALLBACK_TAG, "Callback") \ + V(EVAL_TAG, "Eval") \ + V(FUNCTION_TAG, "Function") \ + V(KEYED_LOAD_IC_TAG, "KeyedLoadIC") \ + V(KEYED_STORE_IC_TAG, "KeyedStoreIC") \ + V(LAZY_COMPILE_TAG, "LazyCompile") \ + V(LOAD_IC_TAG, "LoadIC") \ + V(REG_EXP_TAG, "RegExp") \ + V(SCRIPT_TAG, "Script") \ + V(STORE_IC_TAG, "StoreIC") \ + V(STUB_TAG, "Stub") \ + V(NATIVE_FUNCTION_TAG, "Function") \ + V(NATIVE_LAZY_COMPILE_TAG, "LazyCompile") \ + V(NATIVE_SCRIPT_TAG, "Script") // Note that 'NATIVE_' cases for functions and scripts are mapped onto // original tags when writing to the log. class Logger { public: -#define DECLARE_ENUM(enum_item, ignore1, ignore2) enum_item, +#define DECLARE_ENUM(enum_item, ignore) enum_item, enum LogEventsAndTags { LOG_EVENTS_AND_TAGS_LIST(DECLARE_ENUM) NUMBER_OF_LOG_EVENTS @@ -149,6 +145,9 @@ class Logger { // Acquires resources for logging if the right flags are set. static bool Setup(); + static void EnsureTickerStarted(); + static void EnsureTickerStopped(); + // Frees resources acquired in Setup. static void TearDown(); @@ -289,9 +288,6 @@ class Logger { private: - // Size of window used for log records compression. - static const int kCompressionWindowSize = 4; - // Emits the profiler's first message. static void ProfilerBeginEvent(); @@ -309,9 +305,6 @@ class Logger { static void DeleteEventInternal(LogEventsAndTags event, Address from); - // Emits aliases for compressed messages. - static void LogAliases(); - // Emits the source code of a regexp. Used by regexp events. static void LogRegExpSource(Handle<JSRegExp> regexp); @@ -354,15 +347,8 @@ class Logger { // recent VM states. static SlidingStateWindow* sliding_state_window_; - // An array of log events names. - static const char** log_events_; - - // An instance of helper created if log compression is enabled. - static CompressionHelper* compression_helper_; - // Internal implementation classes with access to // private members. - friend class CompressionHelper; friend class EventLog; friend class TimeLog; friend class Profiler; diff --git a/src/macros.py b/src/macros.py index 1ceb6201..6d66defb 100644 --- a/src/macros.py +++ b/src/macros.py @@ -140,15 +140,14 @@ macro NUMBER_OF_CAPTURES(array) = ((array)[0]); # Limit according to ECMA 262 15.9.1.1 const MAX_TIME_MS = 8640000000000000; +# Limit which is MAX_TIME_MS + msPerMonth. +const MAX_TIME_BEFORE_UTC = 8640002592000000; # Gets the value of a Date object. If arg is not a Date object # a type error is thrown. macro DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError()); macro DAY(time) = ($floor(time / 86400000)); -macro MONTH_FROM_TIME(time) = (MonthFromTime(time)); -macro DATE_FROM_TIME(time) = (DateFromTime(time)); -macro NAN_OR_DATE_FROM_TIME(time) = (NUMBER_IS_NAN(time) ? time : DATE_FROM_TIME(time)); -macro YEAR_FROM_TIME(time) = (YearFromTime(time)); +macro NAN_OR_DATE_FROM_TIME(time) = (NUMBER_IS_NAN(time) ? time : DateFromTime(time)); macro HOUR_FROM_TIME(time) = (Modulo($floor(time / 3600000), 24)); macro MIN_FROM_TIME(time) = (Modulo($floor(time / 60000), 60)); macro NAN_OR_MIN_FROM_TIME(time) = (NUMBER_IS_NAN(time) ? time : MIN_FROM_TIME(time)); diff --git a/src/mark-compact.cc b/src/mark-compact.cc index 40194e36..8ade41cd 100644 --- a/src/mark-compact.cc +++ b/src/mark-compact.cc @@ -215,6 +215,121 @@ void MarkCompactCollector::Finish() { static MarkingStack marking_stack; +class FlushCode : public AllStatic { + public: + static void AddCandidate(SharedFunctionInfo* shared_info) { + SetNextCandidate(shared_info, shared_function_info_candidates_head_); + shared_function_info_candidates_head_ = shared_info; + } + + + static void AddCandidate(JSFunction* function) { + ASSERT(function->unchecked_code() == + function->unchecked_shared()->unchecked_code()); + + SetNextCandidate(function, jsfunction_candidates_head_); + jsfunction_candidates_head_ = function; + } + + + static void ProcessCandidates() { + ProcessSharedFunctionInfoCandidates(); + ProcessJSFunctionCandidates(); + } + + private: + static void ProcessJSFunctionCandidates() { + Code* lazy_compile = Builtins::builtin(Builtins::LazyCompile); + + JSFunction* candidate = jsfunction_candidates_head_; + JSFunction* next_candidate; + while (candidate != NULL) { + next_candidate = GetNextCandidate(candidate); + + SharedFunctionInfo* shared = candidate->unchecked_shared(); + + Code* code = shared->unchecked_code(); + if (!code->IsMarked()) { + shared->set_code(lazy_compile); + candidate->set_code(lazy_compile); + } else { + candidate->set_code(shared->unchecked_code()); + } + + candidate = next_candidate; + } + + jsfunction_candidates_head_ = NULL; + } + + + static void ProcessSharedFunctionInfoCandidates() { + Code* lazy_compile = Builtins::builtin(Builtins::LazyCompile); + + SharedFunctionInfo* candidate = shared_function_info_candidates_head_; + SharedFunctionInfo* next_candidate; + while (candidate != NULL) { + next_candidate = GetNextCandidate(candidate); + SetNextCandidate(candidate, NULL); + + Code* code = candidate->unchecked_code(); + if (!code->IsMarked()) { + candidate->set_code(lazy_compile); + } + + candidate = next_candidate; + } + + shared_function_info_candidates_head_ = NULL; + } + + + static JSFunction** GetNextCandidateField(JSFunction* candidate) { + return reinterpret_cast<JSFunction**>( + candidate->address() + JSFunction::kCodeEntryOffset); + } + + + static JSFunction* GetNextCandidate(JSFunction* candidate) { + return *GetNextCandidateField(candidate); + } + + + static void SetNextCandidate(JSFunction* candidate, + JSFunction* next_candidate) { + *GetNextCandidateField(candidate) = next_candidate; + } + + + STATIC_ASSERT(kPointerSize <= Code::kHeaderSize - Code::kHeaderPaddingStart); + + + static SharedFunctionInfo** GetNextCandidateField( + SharedFunctionInfo* candidate) { + Code* code = candidate->unchecked_code(); + return reinterpret_cast<SharedFunctionInfo**>( + code->address() + Code::kHeaderPaddingStart); + } + + + static SharedFunctionInfo* GetNextCandidate(SharedFunctionInfo* candidate) { + return *GetNextCandidateField(candidate); + } + + + static void SetNextCandidate(SharedFunctionInfo* candidate, + SharedFunctionInfo* next_candidate) { + *GetNextCandidateField(candidate) = next_candidate; + } + + static JSFunction* jsfunction_candidates_head_; + + static SharedFunctionInfo* shared_function_info_candidates_head_; +}; + +JSFunction* FlushCode::jsfunction_candidates_head_ = NULL; + +SharedFunctionInfo* FlushCode::shared_function_info_candidates_head_ = NULL; static inline HeapObject* ShortCircuitConsString(Object** p) { // Optimization: If the heap object pointed to by p is a non-symbol @@ -260,8 +375,13 @@ class StaticMarkingVisitor : public StaticVisitorBase { static void EnableCodeFlushing(bool enabled) { if (enabled) { table_.Register(kVisitJSFunction, &VisitJSFunctionAndFlushCode); + table_.Register(kVisitSharedFunctionInfo, + &VisitSharedFunctionInfoAndFlushCode); + } else { table_.Register(kVisitJSFunction, &VisitJSFunction); + table_.Register(kVisitSharedFunctionInfo, + &VisitSharedFunctionInfoGeneric); } } @@ -287,8 +407,6 @@ class StaticMarkingVisitor : public StaticVisitorBase { Context::MarkCompactBodyDescriptor, void>::Visit); - table_.Register(kVisitSharedFunctionInfo, &VisitSharedFunctionInfo); - table_.Register(kVisitByteArray, &DataObjectVisitor::Visit); table_.Register(kVisitSeqAsciiString, &DataObjectVisitor::Visit); table_.Register(kVisitSeqTwoByteString, &DataObjectVisitor::Visit); @@ -304,7 +422,11 @@ class StaticMarkingVisitor : public StaticVisitorBase { table_.Register(kVisitCode, &VisitCode); - table_.Register(kVisitJSFunction, &VisitJSFunctionAndFlushCode); + table_.Register(kVisitSharedFunctionInfo, + &VisitSharedFunctionInfoAndFlushCode); + + table_.Register(kVisitJSFunction, + &VisitJSFunctionAndFlushCode); table_.Register(kVisitPropertyCell, &FixedBodyVisitor<StaticMarkingVisitor, @@ -350,6 +472,16 @@ class StaticMarkingVisitor : public StaticVisitorBase { } } + static void VisitGlobalPropertyCell(RelocInfo* rinfo) { + ASSERT(rinfo->rmode() == RelocInfo::GLOBAL_PROPERTY_CELL); + Object* cell = rinfo->target_cell(); + Object* old_cell = cell; + VisitPointer(&cell); + if (cell != old_cell) { + rinfo->set_target_cell(reinterpret_cast<JSGlobalPropertyCell*>(cell)); + } + } + static inline void VisitDebugTarget(RelocInfo* rinfo) { ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) && rinfo->IsPatchedReturnSequence()) || @@ -446,62 +578,75 @@ class StaticMarkingVisitor : public StaticVisitorBase { function->unchecked_code() != Builtins::builtin(Builtins::LazyCompile); } - - static void FlushCodeForFunction(JSFunction* function) { + inline static bool IsFlushable(JSFunction* function) { SharedFunctionInfo* shared_info = function->unchecked_shared(); - if (shared_info->IsMarked()) return; - - // Special handling if the function and shared info objects - // have different code objects. - if (function->unchecked_code() != shared_info->unchecked_code()) { - // If the shared function has been flushed but the function has not, - // we flush the function if possible. - if (!IsCompiled(shared_info) && - IsCompiled(function) && - !function->unchecked_code()->IsMarked()) { - function->set_code(shared_info->unchecked_code()); - } - return; + // Code is either on stack, in compilation cache or referenced + // by optimized version of function. + if (function->unchecked_code()->IsMarked()) { + shared_info->set_code_age(0); + return false; } - // Code is either on stack or in compilation cache. + // We do not flush code for optimized functions. + if (function->code() != shared_info->unchecked_code()) { + return false; + } + + return IsFlushable(shared_info); + } + + inline static bool IsFlushable(SharedFunctionInfo* shared_info) { + // Code is either on stack, in compilation cache or referenced + // by optimized version of function. if (shared_info->unchecked_code()->IsMarked()) { shared_info->set_code_age(0); - return; + return false; } // The function must be compiled and have the source code available, // to be able to recompile it in case we need the function again. - if (!(shared_info->is_compiled() && HasSourceCode(shared_info))) return; + if (!(shared_info->is_compiled() && HasSourceCode(shared_info))) { + return false; + } // We never flush code for Api functions. Object* function_data = shared_info->function_data(); if (function_data->IsHeapObject() && (SafeMap(function_data)->instance_type() == FUNCTION_TEMPLATE_INFO_TYPE)) { - return; + return false; } // Only flush code for functions. - if (shared_info->code()->kind() != Code::FUNCTION) return; + if (shared_info->code()->kind() != Code::FUNCTION) return false; // Function must be lazy compilable. - if (!shared_info->allows_lazy_compilation()) return; + if (!shared_info->allows_lazy_compilation()) return false; // If this is a full script wrapped in a function we do no flush the code. - if (shared_info->is_toplevel()) return; + if (shared_info->is_toplevel()) return false; // Age this shared function info. if (shared_info->code_age() < kCodeAgeThreshold) { shared_info->set_code_age(shared_info->code_age() + 1); - return; + return false; } - // Compute the lazy compilable version of the code. - Code* code = Builtins::builtin(Builtins::LazyCompile); - shared_info->set_code(code); - function->set_code(code); + return true; + } + + + static bool FlushCodeForFunction(JSFunction* function) { + if (!IsFlushable(function)) return false; + + // This function's code looks flushable. But we have to postpone the + // decision until we see all functions that point to the same + // SharedFunctionInfo because some of them might be optimized. + // That would make the nonoptimized version of the code nonflushable, + // because it is required for bailing out from optimized code. + FlushCode::AddCandidate(function); + return true; } @@ -539,17 +684,38 @@ class StaticMarkingVisitor : public StaticVisitorBase { } - static void VisitSharedFunctionInfo(Map* map, HeapObject* object) { + static void VisitSharedFunctionInfoGeneric(Map* map, HeapObject* object) { SharedFunctionInfo* shared = reinterpret_cast<SharedFunctionInfo*>(object); - if (shared->IsInobjectSlackTrackingInProgress()) { - shared->DetachInitialMap(); - } + + if (shared->IsInobjectSlackTrackingInProgress()) shared->DetachInitialMap(); + FixedBodyVisitor<StaticMarkingVisitor, SharedFunctionInfo::BodyDescriptor, void>::Visit(map, object); } + static void VisitSharedFunctionInfoAndFlushCode(Map* map, + HeapObject* object) { + VisitSharedFunctionInfoAndFlushCodeGeneric(map, object, false); + } + + + static void VisitSharedFunctionInfoAndFlushCodeGeneric( + Map* map, HeapObject* object, bool known_flush_code_candidate) { + SharedFunctionInfo* shared = reinterpret_cast<SharedFunctionInfo*>(object); + + if (shared->IsInobjectSlackTrackingInProgress()) shared->DetachInitialMap(); + + if (!known_flush_code_candidate) { + known_flush_code_candidate = IsFlushable(shared); + if (known_flush_code_candidate) FlushCode::AddCandidate(shared); + } + + VisitSharedFunctionInfoFields(object, known_flush_code_candidate); + } + + static void VisitCodeEntry(Address entry_address) { Object* code = Code::GetObjectFromEntryAddress(entry_address); Object* old_code = code; @@ -564,30 +730,98 @@ class StaticMarkingVisitor : public StaticVisitorBase { static void VisitJSFunctionAndFlushCode(Map* map, HeapObject* object) { JSFunction* jsfunction = reinterpret_cast<JSFunction*>(object); // The function must have a valid context and not be a builtin. + bool flush_code_candidate = false; if (IsValidNotBuiltinContext(jsfunction->unchecked_context())) { - FlushCodeForFunction(jsfunction); + flush_code_candidate = FlushCodeForFunction(jsfunction); } - VisitJSFunction(map, object); + + if (!flush_code_candidate) { + MarkCompactCollector::MarkObject( + jsfunction->unchecked_shared()->unchecked_code()); + + if (jsfunction->unchecked_code()->kind() == Code::OPTIMIZED_FUNCTION) { + // For optimized functions we should retain both non-optimized version + // of it's code and non-optimized version of all inlined functions. + // This is required to support bailing out from inlined code. + DeoptimizationInputData* data = + reinterpret_cast<DeoptimizationInputData*>( + jsfunction->unchecked_code()->unchecked_deoptimization_data()); + + FixedArray* literals = data->UncheckedLiteralArray(); + + for (int i = 0, count = data->InlinedFunctionCount()->value(); + i < count; + i++) { + JSFunction* inlined = reinterpret_cast<JSFunction*>(literals->get(i)); + MarkCompactCollector::MarkObject( + inlined->unchecked_shared()->unchecked_code()); + } + } + } + + VisitJSFunctionFields(map, + reinterpret_cast<JSFunction*>(object), + flush_code_candidate); } static void VisitJSFunction(Map* map, HeapObject* object) { -#define SLOT_ADDR(obj, offset) \ - reinterpret_cast<Object**>((obj)->address() + offset) + VisitJSFunctionFields(map, + reinterpret_cast<JSFunction*>(object), + false); + } + + +#define SLOT_ADDR(obj, offset) \ + reinterpret_cast<Object**>((obj)->address() + offset) + + static inline void VisitJSFunctionFields(Map* map, + JSFunction* object, + bool flush_code_candidate) { VisitPointers(SLOT_ADDR(object, JSFunction::kPropertiesOffset), SLOT_ADDR(object, JSFunction::kCodeEntryOffset)); - VisitCodeEntry(object->address() + JSFunction::kCodeEntryOffset); + if (!flush_code_candidate) { + VisitCodeEntry(object->address() + JSFunction::kCodeEntryOffset); + } else { + // Don't visit code object. + + // Visit shared function info to avoid double checking of it's + // flushability. + SharedFunctionInfo* shared_info = object->unchecked_shared(); + if (!shared_info->IsMarked()) { + Map* shared_info_map = shared_info->map(); + MarkCompactCollector::SetMark(shared_info); + MarkCompactCollector::MarkObject(shared_info_map); + VisitSharedFunctionInfoAndFlushCodeGeneric(shared_info_map, + shared_info, + true); + } + } VisitPointers(SLOT_ADDR(object, JSFunction::kCodeEntryOffset + kPointerSize), - SLOT_ADDR(object, JSFunction::kSize)); + SLOT_ADDR(object, JSFunction::kNonWeakFieldsEndOffset)); -#undef SLOT_ADDR + // Don't visit the next function list field as it is a weak reference. } + static void VisitSharedFunctionInfoFields(HeapObject* object, + bool flush_code_candidate) { + VisitPointer(SLOT_ADDR(object, SharedFunctionInfo::kNameOffset)); + + if (!flush_code_candidate) { + VisitPointer(SLOT_ADDR(object, SharedFunctionInfo::kCodeOffset)); + } + + VisitPointers(SLOT_ADDR(object, SharedFunctionInfo::kScopeInfoOffset), + SLOT_ADDR(object, SharedFunctionInfo::kSize)); + } + + #undef SLOT_ADDR + typedef void (*Callback)(Map* map, HeapObject* object); static VisitorDispatchTable<Callback> table_; @@ -612,6 +846,10 @@ class MarkingVisitor : public ObjectVisitor { StaticMarkingVisitor::VisitCodeTarget(rinfo); } + void VisitGlobalPropertyCell(RelocInfo* rinfo) { + StaticMarkingVisitor::VisitGlobalPropertyCell(rinfo); + } + void VisitDebugTarget(RelocInfo* rinfo) { StaticMarkingVisitor::VisitDebugTarget(rinfo); } @@ -636,8 +874,10 @@ class SharedFunctionInfoMarkingVisitor : public ObjectVisitor { void VisitPointer(Object** slot) { Object* obj = *slot; - if (obj->IsHeapObject()) { - MarkCompactCollector::MarkObject(HeapObject::cast(obj)); + if (obj->IsSharedFunctionInfo()) { + SharedFunctionInfo* shared = reinterpret_cast<SharedFunctionInfo*>(obj); + MarkCompactCollector::MarkObject(shared->unchecked_code()); + MarkCompactCollector::MarkObject(shared); } } }; @@ -673,6 +913,7 @@ void MarkCompactCollector::PrepareForCodeFlushing() { SharedFunctionInfoMarkingVisitor visitor; CompilationCache::IterateFunctions(&visitor); + HandleScopeImplementer::Iterate(&visitor); ProcessMarkingStack(); } @@ -1040,6 +1281,11 @@ void MarkCompactCollector::ProcessObjectGroups() { void MarkCompactCollector::MarkLiveObjects() { GCTracer::Scope gc_scope(tracer_, GCTracer::Scope::MC_MARK); + // The recursive GC marker detects when it is nearing stack overflow, + // and switches to a different marking system. JS interrupts interfere + // with the C stack limit check. + PostponeInterruptsScope postpone; + #ifdef DEBUG ASSERT(state_ == PREPARE_GC); state_ = MARK_LIVE_OBJECTS; @@ -1096,6 +1342,9 @@ void MarkCompactCollector::MarkLiveObjects() { // Remove object groups after marking phase. GlobalHandles::RemoveObjectGroups(); + + // Flush code from collected candidates. + FlushCode::ProcessCandidates(); } @@ -1305,8 +1554,8 @@ MUST_USE_RESULT inline MaybeObject* MCAllocateFromMapSpace( } -MUST_USE_RESULT inline MaybeObject* MCAllocateFromCellSpace( - HeapObject* ignore, int object_size) { +MUST_USE_RESULT inline MaybeObject* MCAllocateFromCellSpace(HeapObject* ignore, + int object_size) { return Heap::cell_space()->MCAllocateRaw(object_size); } @@ -2292,8 +2541,9 @@ void MarkCompactCollector::UpdatePointers() { // Large objects do not move, the map word can be updated directly. LargeObjectIterator it(Heap::lo_space()); - for (HeapObject* obj = it.next(); obj != NULL; obj = it.next()) + for (HeapObject* obj = it.next(); obj != NULL; obj = it.next()) { UpdatePointersInNewObject(obj); + } USE(live_maps_size); USE(live_pointer_olds_size); diff --git a/src/math.js b/src/math.js index fc3b132f..90667d76 100644 --- a/src/math.js +++ b/src/math.js @@ -113,7 +113,7 @@ function MathFloor(x) { // ECMA 262 - 15.8.2.10 function MathLog(x) { if (!IS_NUMBER(x)) x = ToNumber(x); - return %Math_log(x); + return %_MathLog(x); } // ECMA 262 - 15.8.2.11 diff --git a/src/memory.h b/src/memory.h index 27f32f7a..901e78d2 100644 --- a/src/memory.h +++ b/src/memory.h @@ -60,6 +60,10 @@ class Memory { return *reinterpret_cast<int*>(addr); } + static double& double_at(Address addr) { + return *reinterpret_cast<double*>(addr); + } + static Address& Address_at(Address addr) { return *reinterpret_cast<Address*>(addr); } diff --git a/src/messages.js b/src/messages.js index 7f9c0f8d..c19f4a9a 100644 --- a/src/messages.js +++ b/src/messages.js @@ -190,7 +190,6 @@ function FormatMessage(message) { illegal_return: "Illegal return statement", error_loading_debugger: "Error loading debugger", no_input_to_regexp: "No input to %0", - result_not_primitive: "Result of %0 must be a primitive, was %1", invalid_json: "String '%0' is not valid JSON", circular_structure: "Converting circular structure to JSON", obj_ctor_property_non_object: "Object.%0 called on non-object", @@ -904,11 +903,12 @@ function FormatStackTrace(error, frames) { function FormatRawStackTrace(error, raw_stack) { var frames = [ ]; - for (var i = 0; i < raw_stack.length; i += 3) { + for (var i = 0; i < raw_stack.length; i += 4) { var recv = raw_stack[i]; - var fun = raw_stack[i+1]; - var pc = raw_stack[i+2]; - var pos = %FunctionGetPositionForOffset(fun, pc); + var fun = raw_stack[i + 1]; + var code = raw_stack[i + 2]; + var pc = raw_stack[i + 3]; + var pos = %FunctionGetPositionForOffset(code, pc); frames.push(new CallSite(recv, fun, pos)); } if (IS_FUNCTION($Error.prepareStackTrace)) { diff --git a/src/mirror-debugger.js b/src/mirror-debugger.js index 6b9e9655..55836ce7 100644 --- a/src/mirror-debugger.js +++ b/src/mirror-debugger.js @@ -1533,9 +1533,9 @@ FrameMirror.prototype.scope = function(index) { }; -FrameMirror.prototype.evaluate = function(source, disable_break) { +FrameMirror.prototype.evaluate = function(source, disable_break, opt_context_object) { var result = %DebugEvaluate(this.break_id_, this.details_.frameId(), - source, Boolean(disable_break)); + source, Boolean(disable_break), opt_context_object); return MakeMirror(result); }; diff --git a/src/objects-debug.cc b/src/objects-debug.cc index 69219ee3..53296d92 100644 --- a/src/objects-debug.cc +++ b/src/objects-debug.cc @@ -35,32 +35,34 @@ namespace v8 { namespace internal { -#ifdef DEBUG +#ifdef OBJECT_PRINT static const char* TypeToString(InstanceType type); -void MaybeObject::Print() { +void MaybeObject::Print(FILE* out) { Object* this_as_object; if (ToObject(&this_as_object)) { if (this_as_object->IsSmi()) { - Smi::cast(this_as_object)->SmiPrint(); + Smi::cast(this_as_object)->SmiPrint(out); } else { - HeapObject::cast(this_as_object)->HeapObjectPrint(); + HeapObject::cast(this_as_object)->HeapObjectPrint(out); } } else { - Failure::cast(this)->FailurePrint(); + Failure::cast(this)->FailurePrint(out); } - Flush(); + Flush(out); } -void MaybeObject::PrintLn() { - Print(); - PrintF("\n"); +void MaybeObject::PrintLn(FILE* out) { + Print(out); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void MaybeObject::Verify() { Object* this_as_object; if (ToObject(&this_as_object)) { @@ -92,114 +94,120 @@ void Smi::SmiVerify() { void Failure::FailureVerify() { ASSERT(IsFailure()); } +#endif // DEBUG -void HeapObject::PrintHeader(const char* id) { - PrintF("%p: [%s]\n", reinterpret_cast<void*>(this), id); +#ifdef OBJECT_PRINT +void HeapObject::PrintHeader(FILE* out, const char* id) { + PrintF(out, "%p: [%s]\n", reinterpret_cast<void*>(this), id); } -void HeapObject::HeapObjectPrint() { +void HeapObject::HeapObjectPrint(FILE* out) { InstanceType instance_type = map()->instance_type(); HandleScope scope; if (instance_type < FIRST_NONSTRING_TYPE) { - String::cast(this)->StringPrint(); + String::cast(this)->StringPrint(out); return; } switch (instance_type) { case MAP_TYPE: - Map::cast(this)->MapPrint(); + Map::cast(this)->MapPrint(out); break; case HEAP_NUMBER_TYPE: - HeapNumber::cast(this)->HeapNumberPrint(); + HeapNumber::cast(this)->HeapNumberPrint(out); break; case FIXED_ARRAY_TYPE: - FixedArray::cast(this)->FixedArrayPrint(); + FixedArray::cast(this)->FixedArrayPrint(out); break; case BYTE_ARRAY_TYPE: - ByteArray::cast(this)->ByteArrayPrint(); + ByteArray::cast(this)->ByteArrayPrint(out); break; case PIXEL_ARRAY_TYPE: - PixelArray::cast(this)->PixelArrayPrint(); + PixelArray::cast(this)->PixelArrayPrint(out); break; case EXTERNAL_BYTE_ARRAY_TYPE: - ExternalByteArray::cast(this)->ExternalByteArrayPrint(); + ExternalByteArray::cast(this)->ExternalByteArrayPrint(out); break; case EXTERNAL_UNSIGNED_BYTE_ARRAY_TYPE: - ExternalUnsignedByteArray::cast(this)->ExternalUnsignedByteArrayPrint(); + ExternalUnsignedByteArray::cast(this) + ->ExternalUnsignedByteArrayPrint(out); break; case EXTERNAL_SHORT_ARRAY_TYPE: - ExternalShortArray::cast(this)->ExternalShortArrayPrint(); + ExternalShortArray::cast(this)->ExternalShortArrayPrint(out); break; case EXTERNAL_UNSIGNED_SHORT_ARRAY_TYPE: - ExternalUnsignedShortArray::cast(this)->ExternalUnsignedShortArrayPrint(); + ExternalUnsignedShortArray::cast(this) + ->ExternalUnsignedShortArrayPrint(out); break; case EXTERNAL_INT_ARRAY_TYPE: - ExternalIntArray::cast(this)->ExternalIntArrayPrint(); + ExternalIntArray::cast(this)->ExternalIntArrayPrint(out); break; case EXTERNAL_UNSIGNED_INT_ARRAY_TYPE: - ExternalUnsignedIntArray::cast(this)->ExternalUnsignedIntArrayPrint(); + ExternalUnsignedIntArray::cast(this)->ExternalUnsignedIntArrayPrint(out); break; case EXTERNAL_FLOAT_ARRAY_TYPE: - ExternalFloatArray::cast(this)->ExternalFloatArrayPrint(); + ExternalFloatArray::cast(this)->ExternalFloatArrayPrint(out); break; case FILLER_TYPE: - PrintF("filler"); + PrintF(out, "filler"); break; case JS_OBJECT_TYPE: // fall through case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_ARRAY_TYPE: case JS_REGEXP_TYPE: - JSObject::cast(this)->JSObjectPrint(); + JSObject::cast(this)->JSObjectPrint(out); break; case ODDBALL_TYPE: - Oddball::cast(this)->to_string()->Print(); + Oddball::cast(this)->to_string()->Print(out); break; case JS_FUNCTION_TYPE: - JSFunction::cast(this)->JSFunctionPrint(); + JSFunction::cast(this)->JSFunctionPrint(out); break; case JS_GLOBAL_PROXY_TYPE: - JSGlobalProxy::cast(this)->JSGlobalProxyPrint(); + JSGlobalProxy::cast(this)->JSGlobalProxyPrint(out); break; case JS_GLOBAL_OBJECT_TYPE: - JSGlobalObject::cast(this)->JSGlobalObjectPrint(); + JSGlobalObject::cast(this)->JSGlobalObjectPrint(out); break; case JS_BUILTINS_OBJECT_TYPE: - JSBuiltinsObject::cast(this)->JSBuiltinsObjectPrint(); + JSBuiltinsObject::cast(this)->JSBuiltinsObjectPrint(out); break; case JS_VALUE_TYPE: - PrintF("Value wrapper around:"); - JSValue::cast(this)->value()->Print(); + PrintF(out, "Value wrapper around:"); + JSValue::cast(this)->value()->Print(out); break; case CODE_TYPE: - Code::cast(this)->CodePrint(); + Code::cast(this)->CodePrint(out); break; case PROXY_TYPE: - Proxy::cast(this)->ProxyPrint(); + Proxy::cast(this)->ProxyPrint(out); break; case SHARED_FUNCTION_INFO_TYPE: - SharedFunctionInfo::cast(this)->SharedFunctionInfoPrint(); + SharedFunctionInfo::cast(this)->SharedFunctionInfoPrint(out); break; case JS_GLOBAL_PROPERTY_CELL_TYPE: - JSGlobalPropertyCell::cast(this)->JSGlobalPropertyCellPrint(); + JSGlobalPropertyCell::cast(this)->JSGlobalPropertyCellPrint(out); break; #define MAKE_STRUCT_CASE(NAME, Name, name) \ case NAME##_TYPE: \ - Name::cast(this)->Name##Print(); \ + Name::cast(this)->Name##Print(out); \ break; STRUCT_LIST(MAKE_STRUCT_CASE) #undef MAKE_STRUCT_CASE default: - PrintF("UNKNOWN TYPE %d", map()->instance_type()); + PrintF(out, "UNKNOWN TYPE %d", map()->instance_type()); UNREACHABLE(); break; } } +#endif // OBJECT_PRINT +#ifdef DEBUG void HeapObject::HeapObjectVerify() { InstanceType instance_type = map()->instance_type(); @@ -312,53 +320,57 @@ void HeapObject::VerifyHeapPointer(Object* p) { void HeapNumber::HeapNumberVerify() { ASSERT(IsHeapNumber()); } +#endif // DEBUG -void ByteArray::ByteArrayPrint() { - PrintF("byte array, data starts at %p", GetDataStartAddress()); +#ifdef OBJECT_PRINT +void ByteArray::ByteArrayPrint(FILE* out) { + PrintF(out, "byte array, data starts at %p", GetDataStartAddress()); } -void PixelArray::PixelArrayPrint() { - PrintF("pixel array"); +void PixelArray::PixelArrayPrint(FILE* out) { + PrintF(out, "pixel array"); } -void ExternalByteArray::ExternalByteArrayPrint() { - PrintF("external byte array"); +void ExternalByteArray::ExternalByteArrayPrint(FILE* out) { + PrintF(out, "external byte array"); } -void ExternalUnsignedByteArray::ExternalUnsignedByteArrayPrint() { - PrintF("external unsigned byte array"); +void ExternalUnsignedByteArray::ExternalUnsignedByteArrayPrint(FILE* out) { + PrintF(out, "external unsigned byte array"); } -void ExternalShortArray::ExternalShortArrayPrint() { - PrintF("external short array"); +void ExternalShortArray::ExternalShortArrayPrint(FILE* out) { + PrintF(out, "external short array"); } -void ExternalUnsignedShortArray::ExternalUnsignedShortArrayPrint() { - PrintF("external unsigned short array"); +void ExternalUnsignedShortArray::ExternalUnsignedShortArrayPrint(FILE* out) { + PrintF(out, "external unsigned short array"); } -void ExternalIntArray::ExternalIntArrayPrint() { - PrintF("external int array"); +void ExternalIntArray::ExternalIntArrayPrint(FILE* out) { + PrintF(out, "external int array"); } -void ExternalUnsignedIntArray::ExternalUnsignedIntArrayPrint() { - PrintF("external unsigned int array"); +void ExternalUnsignedIntArray::ExternalUnsignedIntArrayPrint(FILE* out) { + PrintF(out, "external unsigned int array"); } -void ExternalFloatArray::ExternalFloatArrayPrint() { - PrintF("external float array"); +void ExternalFloatArray::ExternalFloatArrayPrint(FILE* out) { + PrintF(out, "external float array"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void ByteArray::ByteArrayVerify() { ASSERT(IsByteArray()); } @@ -402,38 +414,40 @@ void ExternalUnsignedIntArray::ExternalUnsignedIntArrayVerify() { void ExternalFloatArray::ExternalFloatArrayVerify() { ASSERT(IsExternalFloatArray()); } +#endif // DEBUG -void JSObject::PrintProperties() { +#ifdef OBJECT_PRINT +void JSObject::PrintProperties(FILE* out) { if (HasFastProperties()) { DescriptorArray* descs = map()->instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { - PrintF(" "); - descs->GetKey(i)->StringPrint(); - PrintF(": "); + PrintF(out, " "); + descs->GetKey(i)->StringPrint(out); + PrintF(out, ": "); switch (descs->GetType(i)) { case FIELD: { int index = descs->GetFieldIndex(i); - FastPropertyAt(index)->ShortPrint(); - PrintF(" (field at offset %d)\n", index); + FastPropertyAt(index)->ShortPrint(out); + PrintF(out, " (field at offset %d)\n", index); break; } case CONSTANT_FUNCTION: - descs->GetConstantFunction(i)->ShortPrint(); - PrintF(" (constant function)\n"); + descs->GetConstantFunction(i)->ShortPrint(out); + PrintF(out, " (constant function)\n"); break; case CALLBACKS: - descs->GetCallbacksObject(i)->ShortPrint(); - PrintF(" (callback)\n"); + descs->GetCallbacksObject(i)->ShortPrint(out); + PrintF(out, " (callback)\n"); break; case MAP_TRANSITION: - PrintF(" (map transition)\n"); + PrintF(out, " (map transition)\n"); break; case CONSTANT_TRANSITION: - PrintF(" (constant transition)\n"); + PrintF(out, " (constant transition)\n"); break; case NULL_DESCRIPTOR: - PrintF(" (null descriptor)\n"); + PrintF(out, " (null descriptor)\n"); break; default: UNREACHABLE(); @@ -441,34 +455,34 @@ void JSObject::PrintProperties() { } } } else { - property_dictionary()->Print(); + property_dictionary()->Print(out); } } -void JSObject::PrintElements() { +void JSObject::PrintElements(FILE* out) { switch (GetElementsKind()) { case FAST_ELEMENTS: { // Print in array notation for non-sparse arrays. FixedArray* p = FixedArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: ", i); - p->get(i)->ShortPrint(); - PrintF("\n"); + PrintF(out, " %d: ", i); + p->get(i)->ShortPrint(out); + PrintF(out, "\n"); } break; } case PIXEL_ELEMENTS: { PixelArray* p = PixelArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, p->get(i)); + PrintF(out, " %d: %d\n", i, p->get(i)); } break; } case EXTERNAL_BYTE_ELEMENTS: { ExternalByteArray* p = ExternalByteArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } @@ -476,14 +490,14 @@ void JSObject::PrintElements() { ExternalUnsignedByteArray* p = ExternalUnsignedByteArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } case EXTERNAL_SHORT_ELEMENTS: { ExternalShortArray* p = ExternalShortArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } @@ -491,14 +505,14 @@ void JSObject::PrintElements() { ExternalUnsignedShortArray* p = ExternalUnsignedShortArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } case EXTERNAL_INT_ELEMENTS: { ExternalIntArray* p = ExternalIntArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } @@ -506,19 +520,19 @@ void JSObject::PrintElements() { ExternalUnsignedIntArray* p = ExternalUnsignedIntArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %d\n", i, static_cast<int>(p->get(i))); + PrintF(out, " %d: %d\n", i, static_cast<int>(p->get(i))); } break; } case EXTERNAL_FLOAT_ELEMENTS: { ExternalFloatArray* p = ExternalFloatArray::cast(elements()); for (int i = 0; i < p->length(); i++) { - PrintF(" %d: %f\n", i, p->get(i)); + PrintF(out, " %d: %f\n", i, p->get(i)); } break; } case DICTIONARY_ELEMENTS: - elements()->Print(); + elements()->Print(out); break; default: UNREACHABLE(); @@ -527,17 +541,19 @@ void JSObject::PrintElements() { } -void JSObject::JSObjectPrint() { - PrintF("%p: [JSObject]\n", reinterpret_cast<void*>(this)); - PrintF(" - map = %p\n", reinterpret_cast<void*>(map())); - PrintF(" - prototype = %p\n", reinterpret_cast<void*>(GetPrototype())); - PrintF(" {\n"); - PrintProperties(); - PrintElements(); - PrintF(" }\n"); +void JSObject::JSObjectPrint(FILE* out) { + PrintF(out, "%p: [JSObject]\n", reinterpret_cast<void*>(this)); + PrintF(out, " - map = %p\n", reinterpret_cast<void*>(map())); + PrintF(out, " - prototype = %p\n", reinterpret_cast<void*>(GetPrototype())); + PrintF(out, " {\n"); + PrintProperties(out); + PrintElements(out); + PrintF(out, " }\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSObject::JSObjectVerify() { VerifyHeapPointer(properties()); VerifyHeapPointer(elements()); @@ -551,8 +567,10 @@ void JSObject::JSObjectVerify() { elements()->map() == Heap::fixed_cow_array_map())); ASSERT(map()->has_fast_elements() == HasFastElements()); } +#endif // DEBUG +#ifdef OBJECT_PRINT static const char* TypeToString(InstanceType type) { switch (type) { case INVALID_TYPE: return "INVALID"; @@ -608,42 +626,44 @@ static const char* TypeToString(InstanceType type) { } -void Map::MapPrint() { - HeapObject::PrintHeader("Map"); - PrintF(" - type: %s\n", TypeToString(instance_type())); - PrintF(" - instance size: %d\n", instance_size()); - PrintF(" - inobject properties: %d\n", inobject_properties()); - PrintF(" - pre-allocated property fields: %d\n", +void Map::MapPrint(FILE* out) { + HeapObject::PrintHeader(out, "Map"); + PrintF(out, " - type: %s\n", TypeToString(instance_type())); + PrintF(out, " - instance size: %d\n", instance_size()); + PrintF(out, " - inobject properties: %d\n", inobject_properties()); + PrintF(out, " - pre-allocated property fields: %d\n", pre_allocated_property_fields()); - PrintF(" - unused property fields: %d\n", unused_property_fields()); + PrintF(out, " - unused property fields: %d\n", unused_property_fields()); if (is_hidden_prototype()) { - PrintF(" - hidden_prototype\n"); + PrintF(out, " - hidden_prototype\n"); } if (has_named_interceptor()) { - PrintF(" - named_interceptor\n"); + PrintF(out, " - named_interceptor\n"); } if (has_indexed_interceptor()) { - PrintF(" - indexed_interceptor\n"); + PrintF(out, " - indexed_interceptor\n"); } if (is_undetectable()) { - PrintF(" - undetectable\n"); + PrintF(out, " - undetectable\n"); } if (has_instance_call_handler()) { - PrintF(" - instance_call_handler\n"); + PrintF(out, " - instance_call_handler\n"); } if (is_access_check_needed()) { - PrintF(" - access_check_needed\n"); + PrintF(out, " - access_check_needed\n"); } - PrintF(" - instance descriptors: "); - instance_descriptors()->ShortPrint(); - PrintF("\n - prototype: "); - prototype()->ShortPrint(); - PrintF("\n - constructor: "); - constructor()->ShortPrint(); - PrintF("\n"); + PrintF(out, " - instance descriptors: "); + instance_descriptors()->ShortPrint(out); + PrintF(out, "\n - prototype: "); + prototype()->ShortPrint(out); + PrintF(out, "\n - constructor: "); + constructor()->ShortPrint(out); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void Map::MapVerify() { ASSERT(!Heap::InNewSpace(this)); ASSERT(FIRST_TYPE <= instance_type() && instance_type() <= LAST_TYPE); @@ -665,17 +685,21 @@ void Map::SharedMapVerify() { ASSERT_EQ(StaticVisitorBase::GetVisitorId(instance_type(), instance_size()), visitor_id()); } +#endif // DEBUG -void CodeCache::CodeCachePrint() { - HeapObject::PrintHeader("CodeCache"); - PrintF("\n - default_cache: "); - default_cache()->ShortPrint(); - PrintF("\n - normal_type_cache: "); - normal_type_cache()->ShortPrint(); +#ifdef OBJECT_PRINT +void CodeCache::CodeCachePrint(FILE* out) { + HeapObject::PrintHeader(out, "CodeCache"); + PrintF(out, "\n - default_cache: "); + default_cache()->ShortPrint(out); + PrintF(out, "\n - normal_type_cache: "); + normal_type_cache()->ShortPrint(out); } +#endif // OBJECT_PRINT +#ifdef DEBUG void CodeCache::CodeCacheVerify() { VerifyHeapPointer(default_cache()); VerifyHeapPointer(normal_type_cache()); @@ -683,19 +707,23 @@ void CodeCache::CodeCacheVerify() { ASSERT(normal_type_cache()->IsUndefined() || normal_type_cache()->IsCodeCacheHashTable()); } +#endif // DEBUG -void FixedArray::FixedArrayPrint() { - HeapObject::PrintHeader("FixedArray"); - PrintF(" - length: %d", length()); +#ifdef OBJECT_PRINT +void FixedArray::FixedArrayPrint(FILE* out) { + HeapObject::PrintHeader(out, "FixedArray"); + PrintF(out, " - length: %d", length()); for (int i = 0; i < length(); i++) { - PrintF("\n [%d]: ", i); - get(i)->ShortPrint(); + PrintF(out, "\n [%d]: ", i); + get(i)->ShortPrint(out); } - PrintF("\n"); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void FixedArray::FixedArrayVerify() { for (int i = 0; i < length(); i++) { Object* e = get(i); @@ -706,39 +734,57 @@ void FixedArray::FixedArrayVerify() { } } } +#endif // DEBUG -void JSValue::JSValuePrint() { - HeapObject::PrintHeader("ValueObject"); - value()->Print(); +#ifdef OBJECT_PRINT +void JSValue::JSValuePrint(FILE* out) { + HeapObject::PrintHeader(out, "ValueObject"); + value()->Print(out); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSValue::JSValueVerify() { Object* v = value(); if (v->IsHeapObject()) { VerifyHeapPointer(v); } } +#endif // DEBUG -void String::StringPrint() { +#ifdef OBJECT_PRINT +void String::StringPrint(FILE* out) { if (StringShape(this).IsSymbol()) { - PrintF("#"); + PrintF(out, "#"); } else if (StringShape(this).IsCons()) { - PrintF("c\""); + PrintF(out, "c\""); } else { - PrintF("\""); + PrintF(out, "\""); } - for (int i = 0; i < length(); i++) { - PrintF("%c", Get(i)); + const char truncated_epilogue[] = "...<truncated>"; + int len = length(); + if (!FLAG_use_verbose_printer) { + if (len > 100) { + len = 100 - sizeof(truncated_epilogue); + } + } + for (int i = 0; i < len; i++) { + PrintF(out, "%c", Get(i)); + } + if (len != length()) { + PrintF(out, "%s", truncated_epilogue); } - if (!StringShape(this).IsSymbol()) PrintF("\""); + if (!StringShape(this).IsSymbol()) PrintF(out, "\""); } +#endif // OBJECT_PRINT +#ifdef DEBUG void String::StringVerify() { CHECK(IsString()); CHECK(length() >= 0 && length() <= Smi::kMaxValue); @@ -746,66 +792,78 @@ void String::StringVerify() { CHECK(!Heap::InNewSpace(this)); } } +#endif // DEBUG -void JSFunction::JSFunctionPrint() { - HeapObject::PrintHeader("Function"); - PrintF(" - map = 0x%p\n", reinterpret_cast<void*>(map())); - PrintF(" - initial_map = "); +#ifdef OBJECT_PRINT +void JSFunction::JSFunctionPrint(FILE* out) { + HeapObject::PrintHeader(out, "Function"); + PrintF(out, " - map = 0x%p\n", reinterpret_cast<void*>(map())); + PrintF(out, " - initial_map = "); if (has_initial_map()) { - initial_map()->ShortPrint(); + initial_map()->ShortPrint(out); } - PrintF("\n - shared_info = "); - shared()->ShortPrint(); - PrintF("\n - name = "); - shared()->name()->Print(); - PrintF("\n - context = "); - unchecked_context()->ShortPrint(); - PrintF("\n - code = "); - code()->ShortPrint(); - PrintF("\n"); + PrintF(out, "\n - shared_info = "); + shared()->ShortPrint(out); + PrintF(out, "\n - name = "); + shared()->name()->Print(out); + PrintF(out, "\n - context = "); + unchecked_context()->ShortPrint(out); + PrintF(out, "\n - code = "); + code()->ShortPrint(out); + PrintF(out, "\n"); - PrintProperties(); - PrintElements(); + PrintProperties(out); + PrintElements(out); - PrintF("\n"); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSFunction::JSFunctionVerify() { CHECK(IsJSFunction()); VerifyObjectField(kPrototypeOrInitialMapOffset); + VerifyObjectField(kNextFunctionLinkOffset); + CHECK(next_function_link()->IsUndefined() || + next_function_link()->IsJSFunction()); } +#endif // DEBUG -void SharedFunctionInfo::SharedFunctionInfoPrint() { - HeapObject::PrintHeader("SharedFunctionInfo"); - PrintF(" - name: "); - name()->ShortPrint(); - PrintF("\n - expected_nof_properties: %d", expected_nof_properties()); - PrintF("\n - instance class name = "); - instance_class_name()->Print(); - PrintF("\n - code = "); - code()->ShortPrint(); - PrintF("\n - source code = "); - GetSourceCode()->ShortPrint(); +#ifdef OBJECT_PRINT +void SharedFunctionInfo::SharedFunctionInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "SharedFunctionInfo"); + PrintF(out, " - name: "); + name()->ShortPrint(out); + PrintF(out, "\n - expected_nof_properties: %d", expected_nof_properties()); + PrintF(out, "\n - instance class name = "); + instance_class_name()->Print(out); + PrintF(out, "\n - code = "); + code()->ShortPrint(out); + PrintF(out, "\n - source code = "); + GetSourceCode()->ShortPrint(out); // Script files are often large, hard to read. - // PrintF("\n - script ="); - // script()->Print(); - PrintF("\n - function token position = %d", function_token_position()); - PrintF("\n - start position = %d", start_position()); - PrintF("\n - end position = %d", end_position()); - PrintF("\n - is expression = %d", is_expression()); - PrintF("\n - debug info = "); - debug_info()->ShortPrint(); - PrintF("\n - length = %d", length()); - PrintF("\n - has_only_simple_this_property_assignments = %d", + // PrintF(out, "\n - script ="); + // script()->Print(out); + PrintF(out, "\n - function token position = %d", function_token_position()); + PrintF(out, "\n - start position = %d", start_position()); + PrintF(out, "\n - end position = %d", end_position()); + PrintF(out, "\n - is expression = %d", is_expression()); + PrintF(out, "\n - debug info = "); + debug_info()->ShortPrint(out); + PrintF(out, "\n - length = %d", length()); + PrintF(out, "\n - has_only_simple_this_property_assignments = %d", has_only_simple_this_property_assignments()); - PrintF("\n - this_property_assignments = "); - this_property_assignments()->ShortPrint(); - PrintF("\n"); + PrintF(out, "\n - this_property_assignments = "); + this_property_assignments()->ShortPrint(out); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void SharedFunctionInfo::SharedFunctionInfoVerify() { CHECK(IsSharedFunctionInfo()); VerifyObjectField(kNameOffset); @@ -816,17 +874,21 @@ void SharedFunctionInfo::SharedFunctionInfoVerify() { VerifyObjectField(kScriptOffset); VerifyObjectField(kDebugInfoOffset); } +#endif // DEBUG -void JSGlobalProxy::JSGlobalProxyPrint() { - PrintF("global_proxy"); - JSObjectPrint(); - PrintF("context : "); - context()->ShortPrint(); - PrintF("\n"); +#ifdef OBJECT_PRINT +void JSGlobalProxy::JSGlobalProxyPrint(FILE* out) { + PrintF(out, "global_proxy"); + JSObjectPrint(out); + PrintF(out, "context : "); + context()->ShortPrint(out); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSGlobalProxy::JSGlobalProxyVerify() { CHECK(IsJSGlobalProxy()); JSObjectVerify(); @@ -836,17 +898,21 @@ void JSGlobalProxy::JSGlobalProxyVerify() { CHECK(HasFastElements()); CHECK_EQ(0, FixedArray::cast(elements())->length()); } +#endif // DEBUG -void JSGlobalObject::JSGlobalObjectPrint() { - PrintF("global "); - JSObjectPrint(); - PrintF("global context : "); - global_context()->ShortPrint(); - PrintF("\n"); +#ifdef OBJECT_PRINT +void JSGlobalObject::JSGlobalObjectPrint(FILE* out) { + PrintF(out, "global "); + JSObjectPrint(out); + PrintF(out, "global context : "); + global_context()->ShortPrint(out); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSGlobalObject::JSGlobalObjectVerify() { CHECK(IsJSGlobalObject()); JSObjectVerify(); @@ -856,14 +922,18 @@ void JSGlobalObject::JSGlobalObjectVerify() { VerifyObjectField(i); } } +#endif // DEBUG -void JSBuiltinsObject::JSBuiltinsObjectPrint() { - PrintF("builtins "); - JSObjectPrint(); +#ifdef OBJECT_PRINT +void JSBuiltinsObject::JSBuiltinsObjectPrint(FILE* out) { + PrintF(out, "builtins "); + JSObjectPrint(out); } +#endif // OBJECT_PRINT +#ifdef DEBUG void JSBuiltinsObject::JSBuiltinsObjectVerify() { CHECK(IsJSBuiltinsObject()); JSObjectVerify(); @@ -894,21 +964,27 @@ void JSGlobalPropertyCell::JSGlobalPropertyCellVerify() { CHECK(IsJSGlobalPropertyCell()); VerifyObjectField(kValueOffset); } +#endif // DEBUG -void JSGlobalPropertyCell::JSGlobalPropertyCellPrint() { - HeapObject::PrintHeader("JSGlobalPropertyCell"); +#ifdef OBJECT_PRINT +void JSGlobalPropertyCell::JSGlobalPropertyCellPrint(FILE* out) { + HeapObject::PrintHeader(out, "JSGlobalPropertyCell"); } -void Code::CodePrint() { - HeapObject::PrintHeader("Code"); +void Code::CodePrint(FILE* out) { + HeapObject::PrintHeader(out, "Code"); #ifdef ENABLE_DISASSEMBLER - Disassemble(NULL); + if (FLAG_use_verbose_printer) { + Disassemble(NULL, out); + } #endif } +#endif // OBJECT_PRINT +#ifdef DEBUG void Code::CodeVerify() { CHECK(IsAligned(reinterpret_cast<intptr_t>(instruction_start()), kCodeAlignment)); @@ -963,13 +1039,17 @@ void JSRegExp::JSRegExpVerify() { break; } } +#endif // DEBUG -void Proxy::ProxyPrint() { - PrintF("proxy to %p", proxy()); +#ifdef OBJECT_PRINT +void Proxy::ProxyPrint(FILE* out) { + PrintF(out, "proxy to %p", proxy()); } +#endif // OBJECT_PRINT +#ifdef DEBUG void Proxy::ProxyVerify() { ASSERT(IsProxy()); } @@ -983,38 +1063,50 @@ void AccessorInfo::AccessorInfoVerify() { VerifyPointer(data()); VerifyPointer(flag()); } +#endif // DEBUG + -void AccessorInfo::AccessorInfoPrint() { - HeapObject::PrintHeader("AccessorInfo"); - PrintF("\n - getter: "); - getter()->ShortPrint(); - PrintF("\n - setter: "); - setter()->ShortPrint(); - PrintF("\n - name: "); - name()->ShortPrint(); - PrintF("\n - data: "); - data()->ShortPrint(); - PrintF("\n - flag: "); - flag()->ShortPrint(); +#ifdef OBJECT_PRINT +void AccessorInfo::AccessorInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "AccessorInfo"); + PrintF(out, "\n - getter: "); + getter()->ShortPrint(out); + PrintF(out, "\n - setter: "); + setter()->ShortPrint(out); + PrintF(out, "\n - name: "); + name()->ShortPrint(out); + PrintF(out, "\n - data: "); + data()->ShortPrint(out); + PrintF(out, "\n - flag: "); + flag()->ShortPrint(out); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void AccessCheckInfo::AccessCheckInfoVerify() { CHECK(IsAccessCheckInfo()); VerifyPointer(named_callback()); VerifyPointer(indexed_callback()); VerifyPointer(data()); } +#endif // DEBUG -void AccessCheckInfo::AccessCheckInfoPrint() { - HeapObject::PrintHeader("AccessCheckInfo"); - PrintF("\n - named_callback: "); - named_callback()->ShortPrint(); - PrintF("\n - indexed_callback: "); - indexed_callback()->ShortPrint(); - PrintF("\n - data: "); - data()->ShortPrint(); + +#ifdef OBJECT_PRINT +void AccessCheckInfo::AccessCheckInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "AccessCheckInfo"); + PrintF(out, "\n - named_callback: "); + named_callback()->ShortPrint(out); + PrintF(out, "\n - indexed_callback: "); + indexed_callback()->ShortPrint(out); + PrintF(out, "\n - data: "); + data()->ShortPrint(out); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void InterceptorInfo::InterceptorInfoVerify() { CHECK(IsInterceptorInfo()); VerifyPointer(getter()); @@ -1024,38 +1116,50 @@ void InterceptorInfo::InterceptorInfoVerify() { VerifyPointer(enumerator()); VerifyPointer(data()); } +#endif // DEBUG + -void InterceptorInfo::InterceptorInfoPrint() { - HeapObject::PrintHeader("InterceptorInfo"); - PrintF("\n - getter: "); - getter()->ShortPrint(); - PrintF("\n - setter: "); - setter()->ShortPrint(); - PrintF("\n - query: "); - query()->ShortPrint(); - PrintF("\n - deleter: "); - deleter()->ShortPrint(); - PrintF("\n - enumerator: "); - enumerator()->ShortPrint(); - PrintF("\n - data: "); - data()->ShortPrint(); +#ifdef OBJECT_PRINT +void InterceptorInfo::InterceptorInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "InterceptorInfo"); + PrintF(out, "\n - getter: "); + getter()->ShortPrint(out); + PrintF(out, "\n - setter: "); + setter()->ShortPrint(out); + PrintF(out, "\n - query: "); + query()->ShortPrint(out); + PrintF(out, "\n - deleter: "); + deleter()->ShortPrint(out); + PrintF(out, "\n - enumerator: "); + enumerator()->ShortPrint(out); + PrintF(out, "\n - data: "); + data()->ShortPrint(out); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void CallHandlerInfo::CallHandlerInfoVerify() { CHECK(IsCallHandlerInfo()); VerifyPointer(callback()); VerifyPointer(data()); } +#endif // DEBUG + -void CallHandlerInfo::CallHandlerInfoPrint() { - HeapObject::PrintHeader("CallHandlerInfo"); - PrintF("\n - callback: "); - callback()->ShortPrint(); - PrintF("\n - data: "); - data()->ShortPrint(); - PrintF("\n - call_stub_cache: "); +#ifdef OBJECT_PRINT +void CallHandlerInfo::CallHandlerInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "CallHandlerInfo"); + PrintF(out, "\n - callback: "); + callback()->ShortPrint(out); + PrintF(out, "\n - data: "); + data()->ShortPrint(out); + PrintF(out, "\n - call_stub_cache: "); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void TemplateInfo::TemplateInfoVerify() { VerifyPointer(tag()); VerifyPointer(property_list()); @@ -1075,81 +1179,106 @@ void FunctionTemplateInfo::FunctionTemplateInfoVerify() { VerifyPointer(signature()); VerifyPointer(access_check_info()); } +#endif // DEBUG + + +#ifdef OBJECT_PRINT +void FunctionTemplateInfo::FunctionTemplateInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "FunctionTemplateInfo"); + PrintF(out, "\n - class name: "); + class_name()->ShortPrint(out); + PrintF(out, "\n - tag: "); + tag()->ShortPrint(out); + PrintF(out, "\n - property_list: "); + property_list()->ShortPrint(out); + PrintF(out, "\n - serial_number: "); + serial_number()->ShortPrint(out); + PrintF(out, "\n - call_code: "); + call_code()->ShortPrint(out); + PrintF(out, "\n - property_accessors: "); + property_accessors()->ShortPrint(out); + PrintF(out, "\n - prototype_template: "); + prototype_template()->ShortPrint(out); + PrintF(out, "\n - parent_template: "); + parent_template()->ShortPrint(out); + PrintF(out, "\n - named_property_handler: "); + named_property_handler()->ShortPrint(out); + PrintF(out, "\n - indexed_property_handler: "); + indexed_property_handler()->ShortPrint(out); + PrintF(out, "\n - instance_template: "); + instance_template()->ShortPrint(out); + PrintF(out, "\n - signature: "); + signature()->ShortPrint(out); + PrintF(out, "\n - access_check_info: "); + access_check_info()->ShortPrint(out); + PrintF(out, "\n - hidden_prototype: %s", + hidden_prototype() ? "true" : "false"); + PrintF(out, "\n - undetectable: %s", undetectable() ? "true" : "false"); + PrintF(out, "\n - need_access_check: %s", + needs_access_check() ? "true" : "false"); +} +#endif // OBJECT_PRINT -void FunctionTemplateInfo::FunctionTemplateInfoPrint() { - HeapObject::PrintHeader("FunctionTemplateInfo"); - PrintF("\n - class name: "); - class_name()->ShortPrint(); - PrintF("\n - tag: "); - tag()->ShortPrint(); - PrintF("\n - property_list: "); - property_list()->ShortPrint(); - PrintF("\n - serial_number: "); - serial_number()->ShortPrint(); - PrintF("\n - call_code: "); - call_code()->ShortPrint(); - PrintF("\n - property_accessors: "); - property_accessors()->ShortPrint(); - PrintF("\n - prototype_template: "); - prototype_template()->ShortPrint(); - PrintF("\n - parent_template: "); - parent_template()->ShortPrint(); - PrintF("\n - named_property_handler: "); - named_property_handler()->ShortPrint(); - PrintF("\n - indexed_property_handler: "); - indexed_property_handler()->ShortPrint(); - PrintF("\n - instance_template: "); - instance_template()->ShortPrint(); - PrintF("\n - signature: "); - signature()->ShortPrint(); - PrintF("\n - access_check_info: "); - access_check_info()->ShortPrint(); - PrintF("\n - hidden_prototype: %s", hidden_prototype() ? "true" : "false"); - PrintF("\n - undetectable: %s", undetectable() ? "true" : "false"); - PrintF("\n - need_access_check: %s", needs_access_check() ? "true" : "false"); -} +#ifdef DEBUG void ObjectTemplateInfo::ObjectTemplateInfoVerify() { CHECK(IsObjectTemplateInfo()); TemplateInfoVerify(); VerifyPointer(constructor()); VerifyPointer(internal_field_count()); } +#endif // DEBUG -void ObjectTemplateInfo::ObjectTemplateInfoPrint() { - HeapObject::PrintHeader("ObjectTemplateInfo"); - PrintF("\n - constructor: "); - constructor()->ShortPrint(); - PrintF("\n - internal_field_count: "); - internal_field_count()->ShortPrint(); + +#ifdef OBJECT_PRINT +void ObjectTemplateInfo::ObjectTemplateInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "ObjectTemplateInfo"); + PrintF(out, "\n - constructor: "); + constructor()->ShortPrint(out); + PrintF(out, "\n - internal_field_count: "); + internal_field_count()->ShortPrint(out); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void SignatureInfo::SignatureInfoVerify() { CHECK(IsSignatureInfo()); VerifyPointer(receiver()); VerifyPointer(args()); } +#endif // DEBUG + -void SignatureInfo::SignatureInfoPrint() { - HeapObject::PrintHeader("SignatureInfo"); - PrintF("\n - receiver: "); - receiver()->ShortPrint(); - PrintF("\n - args: "); - args()->ShortPrint(); +#ifdef OBJECT_PRINT +void SignatureInfo::SignatureInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "SignatureInfo"); + PrintF(out, "\n - receiver: "); + receiver()->ShortPrint(out); + PrintF(out, "\n - args: "); + args()->ShortPrint(out); } +#endif // OBJECT_PRINT + +#ifdef DEBUG void TypeSwitchInfo::TypeSwitchInfoVerify() { CHECK(IsTypeSwitchInfo()); VerifyPointer(types()); } +#endif // DEBUG + -void TypeSwitchInfo::TypeSwitchInfoPrint() { - HeapObject::PrintHeader("TypeSwitchInfo"); - PrintF("\n - types: "); - types()->ShortPrint(); +#ifdef OBJECT_PRINT +void TypeSwitchInfo::TypeSwitchInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "TypeSwitchInfo"); + PrintF(out, "\n - types: "); + types()->ShortPrint(out); } +#endif // OBJECT_PRINT +#ifdef DEBUG void Script::ScriptVerify() { CHECK(IsScript()); VerifyPointer(source()); @@ -1162,41 +1291,45 @@ void Script::ScriptVerify() { VerifyPointer(line_ends()); VerifyPointer(id()); } +#endif // DEBUG -void Script::ScriptPrint() { - HeapObject::PrintHeader("Script"); - PrintF("\n - source: "); - source()->ShortPrint(); - PrintF("\n - name: "); - name()->ShortPrint(); - PrintF("\n - line_offset: "); - line_offset()->ShortPrint(); - PrintF("\n - column_offset: "); - column_offset()->ShortPrint(); - PrintF("\n - type: "); - type()->ShortPrint(); - PrintF("\n - id: "); - id()->ShortPrint(); - PrintF("\n - data: "); - data()->ShortPrint(); - PrintF("\n - context data: "); - context_data()->ShortPrint(); - PrintF("\n - wrapper: "); - wrapper()->ShortPrint(); - PrintF("\n - compilation type: "); - compilation_type()->ShortPrint(); - PrintF("\n - line ends: "); - line_ends()->ShortPrint(); - PrintF("\n - eval from shared: "); - eval_from_shared()->ShortPrint(); - PrintF("\n - eval from instructions offset: "); - eval_from_instructions_offset()->ShortPrint(); - PrintF("\n"); -} +#ifdef OBJECT_PRINT +void Script::ScriptPrint(FILE* out) { + HeapObject::PrintHeader(out, "Script"); + PrintF(out, "\n - source: "); + source()->ShortPrint(out); + PrintF(out, "\n - name: "); + name()->ShortPrint(out); + PrintF(out, "\n - line_offset: "); + line_offset()->ShortPrint(out); + PrintF(out, "\n - column_offset: "); + column_offset()->ShortPrint(out); + PrintF(out, "\n - type: "); + type()->ShortPrint(out); + PrintF(out, "\n - id: "); + id()->ShortPrint(out); + PrintF(out, "\n - data: "); + data()->ShortPrint(out); + PrintF(out, "\n - context data: "); + context_data()->ShortPrint(out); + PrintF(out, "\n - wrapper: "); + wrapper()->ShortPrint(out); + PrintF(out, "\n - compilation type: "); + compilation_type()->ShortPrint(out); + PrintF(out, "\n - line ends: "); + line_ends()->ShortPrint(out); + PrintF(out, "\n - eval from shared: "); + eval_from_shared()->ShortPrint(out); + PrintF(out, "\n - eval from instructions offset: "); + eval_from_instructions_offset()->ShortPrint(out); + PrintF(out, "\n"); +} +#endif // OBJECT_PRINT #ifdef ENABLE_DEBUGGER_SUPPORT +#ifdef DEBUG void DebugInfo::DebugInfoVerify() { CHECK(IsDebugInfo()); VerifyPointer(shared()); @@ -1204,21 +1337,25 @@ void DebugInfo::DebugInfoVerify() { VerifyPointer(code()); VerifyPointer(break_points()); } +#endif // DEBUG -void DebugInfo::DebugInfoPrint() { - HeapObject::PrintHeader("DebugInfo"); - PrintF("\n - shared: "); - shared()->ShortPrint(); - PrintF("\n - original_code: "); - original_code()->ShortPrint(); - PrintF("\n - code: "); - code()->ShortPrint(); - PrintF("\n - break_points: "); - break_points()->Print(); +#ifdef OBJECT_PRINT +void DebugInfo::DebugInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "DebugInfo"); + PrintF(out, "\n - shared: "); + shared()->ShortPrint(out); + PrintF(out, "\n - original_code: "); + original_code()->ShortPrint(out); + PrintF(out, "\n - code: "); + code()->ShortPrint(out); + PrintF(out, "\n - break_points: "); + break_points()->Print(out); } +#endif // OBJECT_PRINT +#ifdef DEBUG void BreakPointInfo::BreakPointInfoVerify() { CHECK(IsBreakPointInfo()); code_position()->SmiVerify(); @@ -1226,19 +1363,23 @@ void BreakPointInfo::BreakPointInfoVerify() { statement_position()->SmiVerify(); VerifyPointer(break_point_objects()); } +#endif // DEBUG -void BreakPointInfo::BreakPointInfoPrint() { - HeapObject::PrintHeader("BreakPointInfo"); - PrintF("\n - code_position: %d", code_position()->value()); - PrintF("\n - source_position: %d", source_position()->value()); - PrintF("\n - statement_position: %d", statement_position()->value()); - PrintF("\n - break_point_objects: "); - break_point_objects()->ShortPrint(); +#ifdef OBJECT_PRINT +void BreakPointInfo::BreakPointInfoPrint(FILE* out) { + HeapObject::PrintHeader(out, "BreakPointInfo"); + PrintF(out, "\n - code_position: %d", code_position()->value()); + PrintF(out, "\n - source_position: %d", source_position()->value()); + PrintF(out, "\n - statement_position: %d", statement_position()->value()); + PrintF(out, "\n - break_point_objects: "); + break_point_objects()->ShortPrint(out); } -#endif +#endif // OBJECT_PRINT +#endif // ENABLE_DEBUGGER_SUPPORT +#ifdef DEBUG void JSObject::IncrementSpillStatistics(SpillInformation* info) { info->number_of_objects_++; // Named properties @@ -1321,20 +1462,24 @@ void JSObject::SpillInformation::Print() { PrintF("\n"); } +#endif // DEBUG -void DescriptorArray::PrintDescriptors() { - PrintF("Descriptor array %d\n", number_of_descriptors()); +#ifdef OBJECT_PRINT +void DescriptorArray::PrintDescriptors(FILE* out) { + PrintF(out, "Descriptor array %d\n", number_of_descriptors()); for (int i = 0; i < number_of_descriptors(); i++) { - PrintF(" %d: ", i); + PrintF(out, " %d: ", i); Descriptor desc; Get(i, &desc); - desc.Print(); + desc.Print(out); } - PrintF("\n"); + PrintF(out, "\n"); } +#endif // OBJECT_PRINT +#ifdef DEBUG bool DescriptorArray::IsSortedNoDuplicates() { String* current_key = NULL; uint32_t current = 0; diff --git a/src/objects-inl.h b/src/objects-inl.h index 499cb91d..79359124 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -459,6 +459,33 @@ bool Object::IsDescriptorArray() { } +bool Object::IsDeoptimizationInputData() { + // Must be a fixed array. + if (!IsFixedArray()) return false; + + // There's no sure way to detect the difference between a fixed array and + // a deoptimization data array. Since this is used for asserts we can + // check that the length is zero or else the fixed size plus a multiple of + // the entry size. + int length = FixedArray::cast(this)->length(); + if (length == 0) return true; + + length -= DeoptimizationInputData::kFirstDeoptEntryIndex; + return length >= 0 && + length % DeoptimizationInputData::kDeoptEntrySize == 0; +} + + +bool Object::IsDeoptimizationOutputData() { + if (!IsFixedArray()) return false; + // There's actually no way to see the difference between a fixed array and + // a deoptimization data array. Since this is used for asserts we can check + // that the length is plausible though. + if (FixedArray::cast(this)->length() % 2 != 0) return false; + return true; +} + + bool Object::IsContext() { return Object::IsHeapObject() && (HeapObject::cast(this)->map() == Heap::context_map() || @@ -1682,6 +1709,8 @@ void NumberDictionary::set_requires_slow_elements() { CAST_ACCESSOR(FixedArray) CAST_ACCESSOR(DescriptorArray) +CAST_ACCESSOR(DeoptimizationInputData) +CAST_ACCESSOR(DeoptimizationOutputData) CAST_ACCESSOR(SymbolTable) CAST_ACCESSOR(JSFunctionResultCache) CAST_ACCESSOR(NormalizedMapCache) @@ -2376,18 +2405,160 @@ int Code::arguments_count() { int Code::major_key() { - ASSERT(kind() == STUB || kind() == BINARY_OP_IC); + ASSERT(kind() == STUB || + kind() == BINARY_OP_IC || + kind() == TYPE_RECORDING_BINARY_OP_IC || + kind() == COMPARE_IC); return READ_BYTE_FIELD(this, kStubMajorKeyOffset); } void Code::set_major_key(int major) { - ASSERT(kind() == STUB || kind() == BINARY_OP_IC); + ASSERT(kind() == STUB || + kind() == BINARY_OP_IC || + kind() == TYPE_RECORDING_BINARY_OP_IC || + kind() == COMPARE_IC); ASSERT(0 <= major && major < 256); WRITE_BYTE_FIELD(this, kStubMajorKeyOffset, major); } +bool Code::optimizable() { + ASSERT(kind() == FUNCTION); + return READ_BYTE_FIELD(this, kOptimizableOffset) == 1; +} + + +void Code::set_optimizable(bool value) { + ASSERT(kind() == FUNCTION); + WRITE_BYTE_FIELD(this, kOptimizableOffset, value ? 1 : 0); +} + + +bool Code::has_deoptimization_support() { + ASSERT(kind() == FUNCTION); + return READ_BYTE_FIELD(this, kHasDeoptimizationSupportOffset) == 1; +} + + +void Code::set_has_deoptimization_support(bool value) { + ASSERT(kind() == FUNCTION); + WRITE_BYTE_FIELD(this, kHasDeoptimizationSupportOffset, value ? 1 : 0); +} + + +int Code::allow_osr_at_loop_nesting_level() { + ASSERT(kind() == FUNCTION); + return READ_BYTE_FIELD(this, kAllowOSRAtLoopNestingLevelOffset); +} + + +void Code::set_allow_osr_at_loop_nesting_level(int level) { + ASSERT(kind() == FUNCTION); + ASSERT(level >= 0 && level <= kMaxLoopNestingMarker); + WRITE_BYTE_FIELD(this, kAllowOSRAtLoopNestingLevelOffset, level); +} + + +unsigned Code::stack_slots() { + ASSERT(kind() == OPTIMIZED_FUNCTION); + return READ_UINT32_FIELD(this, kStackSlotsOffset); +} + + +void Code::set_stack_slots(unsigned slots) { + ASSERT(kind() == OPTIMIZED_FUNCTION); + WRITE_UINT32_FIELD(this, kStackSlotsOffset, slots); +} + + +unsigned Code::safepoint_table_start() { + ASSERT(kind() == OPTIMIZED_FUNCTION); + return READ_UINT32_FIELD(this, kSafepointTableStartOffset); +} + + +void Code::set_safepoint_table_start(unsigned offset) { + ASSERT(kind() == OPTIMIZED_FUNCTION); + ASSERT(IsAligned(offset, static_cast<unsigned>(kIntSize))); + WRITE_UINT32_FIELD(this, kSafepointTableStartOffset, offset); +} + + +unsigned Code::stack_check_table_start() { + ASSERT(kind() == FUNCTION); + return READ_UINT32_FIELD(this, kStackCheckTableStartOffset); +} + + +void Code::set_stack_check_table_start(unsigned offset) { + ASSERT(kind() == FUNCTION); + ASSERT(IsAligned(offset, static_cast<unsigned>(kIntSize))); + WRITE_UINT32_FIELD(this, kStackCheckTableStartOffset, offset); +} + + +CheckType Code::check_type() { + ASSERT(is_call_stub() || is_keyed_call_stub()); + byte type = READ_BYTE_FIELD(this, kCheckTypeOffset); + return static_cast<CheckType>(type); +} + + +void Code::set_check_type(CheckType value) { + ASSERT(is_call_stub() || is_keyed_call_stub()); + WRITE_BYTE_FIELD(this, kCheckTypeOffset, value); +} + + +byte Code::binary_op_type() { + ASSERT(is_binary_op_stub()); + return READ_BYTE_FIELD(this, kBinaryOpTypeOffset); +} + + +void Code::set_binary_op_type(byte value) { + ASSERT(is_binary_op_stub()); + WRITE_BYTE_FIELD(this, kBinaryOpTypeOffset, value); +} + + +byte Code::type_recording_binary_op_type() { + ASSERT(is_type_recording_binary_op_stub()); + return READ_BYTE_FIELD(this, kBinaryOpTypeOffset); +} + + +void Code::set_type_recording_binary_op_type(byte value) { + ASSERT(is_type_recording_binary_op_stub()); + WRITE_BYTE_FIELD(this, kBinaryOpTypeOffset, value); +} + + +byte Code::type_recording_binary_op_result_type() { + ASSERT(is_type_recording_binary_op_stub()); + return READ_BYTE_FIELD(this, kBinaryOpReturnTypeOffset); +} + + +void Code::set_type_recording_binary_op_result_type(byte value) { + ASSERT(is_type_recording_binary_op_stub()); + WRITE_BYTE_FIELD(this, kBinaryOpReturnTypeOffset, value); +} + + +byte Code::compare_state() { + ASSERT(is_compare_ic_stub()); + return READ_BYTE_FIELD(this, kCompareStateOffset); +} + + +void Code::set_compare_state(byte value) { + ASSERT(is_compare_ic_stub()); + WRITE_BYTE_FIELD(this, kCompareStateOffset, value); +} + + bool Code::is_inline_cache_stub() { Kind kind = this->kind(); return kind >= FIRST_IC_KIND && kind <= LAST_IC_KIND; @@ -2530,6 +2701,7 @@ ACCESSORS(Map, constructor, Object, kConstructorOffset) ACCESSORS(JSFunction, shared, SharedFunctionInfo, kSharedFunctionInfoOffset) ACCESSORS(JSFunction, literals, FixedArray, kLiteralsOffset) +ACCESSORS(JSFunction, next_function_link, Object, kNextFunctionLinkOffset) ACCESSORS(GlobalObject, builtins, JSBuiltinsObject, kBuiltinsOffset) ACCESSORS(GlobalObject, global_context, Context, kGlobalContextOffset) @@ -2667,6 +2839,7 @@ SMI_ACCESSORS(SharedFunctionInfo, compiler_hints, kCompilerHintsOffset) SMI_ACCESSORS(SharedFunctionInfo, this_property_assignments_count, kThisPropertyAssignmentsCountOffset) +SMI_ACCESSORS(SharedFunctionInfo, opt_count, kOptCountOffset) #else #define PSEUDO_SMI_ACCESSORS_LO(holder, name, offset) \ @@ -2716,6 +2889,7 @@ PSEUDO_SMI_ACCESSORS_HI(SharedFunctionInfo, PSEUDO_SMI_ACCESSORS_LO(SharedFunctionInfo, this_property_assignments_count, kThisPropertyAssignmentsCountOffset) +PSEUDO_SMI_ACCESSORS_HI(SharedFunctionInfo, opt_count, kOptCountOffset) #endif @@ -2749,6 +2923,23 @@ bool SharedFunctionInfo::IsInobjectSlackTrackingInProgress() { } +bool SharedFunctionInfo::optimization_disabled() { + return BooleanBit::get(compiler_hints(), kOptimizationDisabled); +} + + +void SharedFunctionInfo::set_optimization_disabled(bool disable) { + set_compiler_hints(BooleanBit::set(compiler_hints(), + kOptimizationDisabled, + disable)); + // If disabling optimizations we reflect that in the code object so + // it will not be counted as optimizable code. + if ((code()->kind() == Code::FUNCTION) && disable) { + code()->set_optimizable(false); + } +} + + ACCESSORS(CodeCache, default_cache, FixedArray, kDefaultCacheOffset) ACCESSORS(CodeCache, normal_type_cache, Object, kNormalTypeCacheOffset) @@ -2794,6 +2985,13 @@ Code* SharedFunctionInfo::unchecked_code() { void SharedFunctionInfo::set_code(Code* value, WriteBarrierMode mode) { + // If optimization has been disabled for the shared function info, + // reflect that in the code object so it will not be counted as + // optimizable code. + ASSERT(value->kind() != Code::FUNCTION || + !value->optimizable() || + this->code() == Builtins::builtin(Builtins::Illegal) || + this->allows_lazy_compilation()); WRITE_FIELD(this, kCodeOffset, value); CONDITIONAL_WRITE_BARRIER(this, kCodeOffset, mode); } @@ -2812,6 +3010,16 @@ void SharedFunctionInfo::set_scope_info(SerializedScopeInfo* value, } +Smi* SharedFunctionInfo::deopt_counter() { + return reinterpret_cast<Smi*>(READ_FIELD(this, kDeoptCounterOffset)); +} + + +void SharedFunctionInfo::set_deopt_counter(Smi* value) { + WRITE_FIELD(this, kDeoptCounterOffset, value); +} + + bool SharedFunctionInfo::is_compiled() { return code() != Builtins::builtin(Builtins::LazyCompile); } @@ -2828,14 +3036,20 @@ FunctionTemplateInfo* SharedFunctionInfo::get_api_func_data() { } -bool SharedFunctionInfo::HasCustomCallGenerator() { +bool SharedFunctionInfo::HasBuiltinFunctionId() { return function_data()->IsSmi(); } -int SharedFunctionInfo::custom_call_generator_id() { - ASSERT(HasCustomCallGenerator()); - return Smi::cast(function_data())->value(); +bool SharedFunctionInfo::IsBuiltinMathFunction() { + return HasBuiltinFunctionId() && + builtin_function_id() >= kFirstMathFunctionId; +} + + +BuiltinFunctionId SharedFunctionInfo::builtin_function_id() { + ASSERT(HasBuiltinFunctionId()); + return static_cast<BuiltinFunctionId>(Smi::cast(function_data())->value()); } @@ -2850,11 +3064,33 @@ void SharedFunctionInfo::set_code_age(int code_age) { } +bool SharedFunctionInfo::has_deoptimization_support() { + Code* code = this->code(); + return code->kind() == Code::FUNCTION && code->has_deoptimization_support(); +} + + bool JSFunction::IsBuiltin() { return context()->global()->IsJSBuiltinsObject(); } +bool JSFunction::NeedsArgumentsAdaption() { + return shared()->formal_parameter_count() != + SharedFunctionInfo::kDontAdaptArgumentsSentinel; +} + + +bool JSFunction::IsOptimized() { + return code()->kind() == Code::OPTIMIZED_FUNCTION; +} + + +bool JSFunction::IsMarkedForLazyRecompilation() { + return code() == Builtins::builtin(Builtins::LazyRecompile); +} + + Code* JSFunction::code() { return Code::cast(unchecked_code()); } @@ -2874,6 +3110,23 @@ void JSFunction::set_code(Code* value) { } +void JSFunction::ReplaceCode(Code* code) { + bool was_optimized = IsOptimized(); + bool is_optimized = code->kind() == Code::OPTIMIZED_FUNCTION; + + set_code(code); + + // Add/remove the function from the list of optimized functions for this + // context based on the state change. + if (!was_optimized && is_optimized) { + context()->global_context()->AddOptimizedFunction(this); + } + if (was_optimized && !is_optimized) { + context()->global_context()->RemoveOptimizedFunction(this); + } +} + + Context* JSFunction::context() { return Context::cast(READ_FIELD(this, kContextOffset)); } @@ -3007,6 +3260,7 @@ JSValue* JSValue::cast(Object* obj) { INT_ACCESSORS(Code, instruction_size, kInstructionSizeOffset) ACCESSORS(Code, relocation_info, ByteArray, kRelocationInfoOffset) +ACCESSORS(Code, deoptimization_data, FixedArray, kDeoptimizationDataOffset) byte* Code::instruction_start() { @@ -3024,6 +3278,12 @@ int Code::body_size() { } +FixedArray* Code::unchecked_deoptimization_data() { + return reinterpret_cast<FixedArray*>( + READ_FIELD(this, kDeoptimizationDataOffset)); +} + + ByteArray* Code::unchecked_relocation_info() { return reinterpret_cast<ByteArray*>(READ_FIELD(this, kRelocationInfoOffset)); } diff --git a/src/objects-visiting.h b/src/objects-visiting.h index ed76cb97..55a0a53a 100644 --- a/src/objects-visiting.h +++ b/src/objects-visiting.h @@ -352,6 +352,7 @@ VisitorDispatchTable<typename StaticNewSpaceVisitor<StaticVisitor>::Callback> void Code::CodeIterateBody(ObjectVisitor* v) { int mode_mask = RelocInfo::kCodeTargetMask | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) | + RelocInfo::ModeMask(RelocInfo::GLOBAL_PROPERTY_CELL) | RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::JS_RETURN) | RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) | @@ -361,9 +362,8 @@ void Code::CodeIterateBody(ObjectVisitor* v) { // the heap compaction in the next statement. RelocIterator it(this, mode_mask); - IteratePointers(v, - kRelocationInfoOffset, - kRelocationInfoOffset + kPointerSize); + IteratePointer(v, kRelocationInfoOffset); + IteratePointer(v, kDeoptimizationDataOffset); for (; !it.done(); it.next()) { it.rinfo()->Visit(v); @@ -375,6 +375,7 @@ template<typename StaticVisitor> void Code::CodeIterateBody() { int mode_mask = RelocInfo::kCodeTargetMask | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) | + RelocInfo::ModeMask(RelocInfo::GLOBAL_PROPERTY_CELL) | RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) | RelocInfo::ModeMask(RelocInfo::JS_RETURN) | RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) | @@ -386,6 +387,8 @@ void Code::CodeIterateBody() { StaticVisitor::VisitPointer( reinterpret_cast<Object**>(this->address() + kRelocationInfoOffset)); + StaticVisitor::VisitPointer( + reinterpret_cast<Object**>(this->address() + kDeoptimizationDataOffset)); for (; !it.done(); it.next()) { it.rinfo()->template Visit<StaticVisitor>(); diff --git a/src/objects.cc b/src/objects.cc index dcfd926f..ab2f9644 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -30,17 +30,24 @@ #include "api.h" #include "arguments.h" #include "bootstrapper.h" +#include "codegen.h" #include "debug.h" +#include "deoptimizer.h" #include "execution.h" +#include "full-codegen.h" +#include "hydrogen.h" #include "objects-inl.h" #include "objects-visiting.h" #include "macro-assembler.h" +#include "safepoint-table.h" #include "scanner-base.h" #include "scopeinfo.h" #include "string-stream.h" #include "utils.h" +#include "vm-state-inl.h" #ifdef ENABLE_DISASSEMBLER +#include "disasm.h" #include "disassembler.h" #endif @@ -546,11 +553,11 @@ Object* Object::GetPrototype() { } -void Object::ShortPrint() { +void Object::ShortPrint(FILE* out) { HeapStringAllocator allocator; StringStream accumulator(&allocator); ShortPrint(&accumulator); - accumulator.OutputToStdOut(); + accumulator.OutputToFile(out); } @@ -565,8 +572,8 @@ void Object::ShortPrint(StringStream* accumulator) { } -void Smi::SmiPrint() { - PrintF("%d", value()); +void Smi::SmiPrint(FILE* out) { + PrintF(out, "%d", value()); } @@ -580,8 +587,8 @@ void Failure::FailurePrint(StringStream* accumulator) { } -void Failure::FailurePrint() { - PrintF("Failure(%p)", reinterpret_cast<void*>(value())); +void Failure::FailurePrint(FILE* out) { + PrintF(out, "Failure(%p)", reinterpret_cast<void*>(value())); } @@ -1134,8 +1141,8 @@ Object* HeapNumber::HeapNumberToBoolean() { } -void HeapNumber::HeapNumberPrint() { - PrintF("%.16g", Number()); +void HeapNumber::HeapNumberPrint(FILE* out) { + PrintF(out, "%.16g", Number()); } @@ -1728,6 +1735,23 @@ void JSObject::LookupInDescriptor(String* name, LookupResult* result) { } +void Map::LookupInDescriptors(JSObject* holder, + String* name, + LookupResult* result) { + DescriptorArray* descriptors = instance_descriptors(); + int number = DescriptorLookupCache::Lookup(descriptors, name); + if (number == DescriptorLookupCache::kAbsent) { + number = descriptors->Search(name); + DescriptorLookupCache::Update(descriptors, name, number); + } + if (number != DescriptorArray::kNotFound) { + result->DescriptorResult(holder, descriptors->GetDetails(number), number); + } else { + result->NotFound(); + } +} + + void JSObject::LocalLookupRealNamedProperty(String* name, LookupResult* result) { if (IsJSGlobalProxy()) { @@ -3051,6 +3075,10 @@ MaybeObject* JSObject::SetPropertyCallback(String* name, if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; } set_map(Map::cast(new_map)); + // When running crankshaft, changing the map is not enough. We + // need to deoptimize all functions that rely on this global + // object. + Deoptimizer::DeoptimizeGlobalObject(this); } // Update the dictionary with the new CALLBACKS property. @@ -3069,8 +3097,9 @@ MaybeObject* JSObject::SetPropertyCallback(String* name, MaybeObject* JSObject::DefineAccessor(String* name, bool is_getter, - JSFunction* fun, + Object* fun, PropertyAttributes attributes) { + ASSERT(fun->IsJSFunction() || fun->IsUndefined()); // Check access rights if needed. if (IsAccessCheckNeeded() && !Top::MayNamedAccess(this, name, v8::ACCESS_SET)) { @@ -4123,6 +4152,22 @@ int DescriptorArray::LinearSearch(String* name, int len) { } +MaybeObject* DeoptimizationInputData::Allocate(int deopt_entry_count, + PretenureFlag pretenure) { + ASSERT(deopt_entry_count > 0); + return Heap::AllocateFixedArray(LengthFor(deopt_entry_count), + pretenure); +} + + +MaybeObject* DeoptimizationOutputData::Allocate(int number_of_deopt_points, + PretenureFlag pretenure) { + if (number_of_deopt_points == 0) return Heap::empty_fixed_array(); + return Heap::AllocateFixedArray(LengthOfFixedArray(number_of_deopt_points), + pretenure); +} + + #ifdef DEBUG bool DescriptorArray::IsEqualTo(DescriptorArray* other) { if (IsEmpty()) return other->IsEmpty(); @@ -5331,6 +5376,38 @@ void JSFunction::JSFunctionIterateBody(int object_size, ObjectVisitor* v) { } +void JSFunction::MarkForLazyRecompilation() { + ASSERT(is_compiled() && !IsOptimized()); + ASSERT(shared()->allows_lazy_compilation()); + ReplaceCode(Builtins::builtin(Builtins::LazyRecompile)); +} + + +uint32_t JSFunction::SourceHash() { + uint32_t hash = 0; + Object* script = shared()->script(); + if (!script->IsUndefined()) { + Object* source = Script::cast(script)->source(); + if (source->IsUndefined()) hash = String::cast(source)->Hash(); + } + hash ^= ComputeIntegerHash(shared()->start_position_and_type()); + hash += ComputeIntegerHash(shared()->end_position()); + return hash; +} + + +bool JSFunction::IsInlineable() { + if (IsBuiltin()) return false; + // Check that the function has a script associated with it. + if (!shared()->script()->IsScript()) return false; + Code* code = shared()->code(); + if (code->kind() == Code::OPTIMIZED_FUNCTION) return true; + // If we never ran this (unlikely) then lets try to optimize it. + if (code->kind() != Code::FUNCTION) return true; + return code->optimizable(); +} + + Object* JSFunction::SetInstancePrototype(Object* value) { ASSERT(value->IsJSObject()); @@ -5390,6 +5467,12 @@ Object* JSFunction::SetInstanceClassName(String* name) { } +void JSFunction::PrintName(FILE* out) { + SmartPointer<char> name = shared()->DebugName()->ToCString(); + PrintF(out, "%s", *name); +} + + Context* JSFunction::GlobalContextFromLiterals(FixedArray* literals) { return Context::cast(literals->get(JSFunction::kLiteralGlobalContextIndex)); } @@ -5420,15 +5503,19 @@ bool SharedFunctionInfo::HasSourceCode() { Object* SharedFunctionInfo::GetSourceCode() { + if (!HasSourceCode()) return Heap::undefined_value(); HandleScope scope; - if (script()->IsUndefined()) return Heap::undefined_value(); Object* source = Script::cast(script())->source(); - if (source->IsUndefined()) return Heap::undefined_value(); return *SubString(Handle<String>(String::cast(source)), start_position(), end_position()); } +int SharedFunctionInfo::SourceSize() { + return end_position() - start_position(); +} + + int SharedFunctionInfo::CalculateInstanceSize() { int instance_size = JSObject::kHeaderSize + @@ -5546,8 +5633,7 @@ Object* SharedFunctionInfo::GetThisPropertyAssignmentConstant(int index) { void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator, int max_length) { // For some native functions there is no source. - if (script()->IsUndefined() || - Script::cast(script())->source()->IsUndefined()) { + if (!HasSourceCode()) { accumulator->Add("<No Source>"); return; } @@ -5572,14 +5658,60 @@ void SharedFunctionInfo::SourceCodePrint(StringStream* accumulator, } int len = end_position() - start_position(); - if (len > max_length) { + if (len <= max_length || max_length < 0) { + accumulator->Put(script_source, start_position(), end_position()); + } else { accumulator->Put(script_source, start_position(), start_position() + max_length); accumulator->Add("...\n"); + } +} + + +static bool IsCodeEquivalent(Code* code, Code* recompiled) { + if (code->instruction_size() != recompiled->instruction_size()) return false; + ByteArray* code_relocation = code->relocation_info(); + ByteArray* recompiled_relocation = recompiled->relocation_info(); + int length = code_relocation->length(); + if (length != recompiled_relocation->length()) return false; + int compare = memcmp(code_relocation->GetDataStartAddress(), + recompiled_relocation->GetDataStartAddress(), + length); + return compare == 0; +} + + +void SharedFunctionInfo::EnableDeoptimizationSupport(Code* recompiled) { + ASSERT(!has_deoptimization_support()); + AssertNoAllocation no_allocation; + Code* code = this->code(); + if (IsCodeEquivalent(code, recompiled)) { + // Copy the deoptimization data from the recompiled code. + code->set_deoptimization_data(recompiled->deoptimization_data()); + code->set_has_deoptimization_support(true); } else { - accumulator->Put(script_source, start_position(), end_position()); + // TODO(3025757): In case the recompiled isn't equivalent to the + // old code, we have to replace it. We should try to avoid this + // altogether because it flushes valuable type feedback by + // effectively resetting all IC state. + set_code(recompiled); } + ASSERT(has_deoptimization_support()); +} + + +bool SharedFunctionInfo::VerifyBailoutId(int id) { + // TODO(srdjan): debugging ARM crashes in hydrogen. OK to disable while + // we are always bailing out on ARM. + + ASSERT(id != AstNode::kNoNumber); + Code* unoptimized = code(); + DeoptimizationOutputData* data = + DeoptimizationOutputData::cast(unoptimized->deoptimization_data()); + unsigned ignore = Deoptimizer::GetOutputInfo(data, id, this); + USE(ignore); + return true; // Return true if there was no ASSERT. } @@ -5703,6 +5835,17 @@ void ObjectVisitor::VisitCodeEntry(Address entry_address) { } +void ObjectVisitor::VisitGlobalPropertyCell(RelocInfo* rinfo) { + ASSERT(rinfo->rmode() == RelocInfo::GLOBAL_PROPERTY_CELL); + Object* cell = rinfo->target_cell(); + Object* old_cell = cell; + VisitPointer(&cell); + if (cell != old_cell) { + rinfo->set_target_cell(reinterpret_cast<JSGlobalPropertyCell*>(cell)); + } +} + + void ObjectVisitor::VisitDebugTarget(RelocInfo* rinfo) { ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) && rinfo->IsPatchedReturnSequence()) || @@ -5715,6 +5858,12 @@ void ObjectVisitor::VisitDebugTarget(RelocInfo* rinfo) { } +void Code::InvalidateRelocation() { + HandleScope scope; + set_relocation_info(Heap::empty_byte_array()); +} + + void Code::Relocate(intptr_t delta) { for (RelocIterator it(this, RelocInfo::kApplyMask); !it.done(); it.next()) { it.rinfo()->apply(delta); @@ -5736,6 +5885,7 @@ void Code::CopyFrom(const CodeDesc& desc) { intptr_t delta = instruction_start() - desc.buffer; int mode_mask = RelocInfo::kCodeTargetMask | RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) | + RelocInfo::ModeMask(RelocInfo::GLOBAL_PROPERTY_CELL) | RelocInfo::kApplyMask; Assembler* origin = desc.origin; // Needed to find target_object on X64. for (RelocIterator it(this, mode_mask); !it.done(); it.next()) { @@ -5743,6 +5893,9 @@ void Code::CopyFrom(const CodeDesc& desc) { if (mode == RelocInfo::EMBEDDED_OBJECT) { Handle<Object> p = it.rinfo()->target_object_handle(origin); it.rinfo()->set_target_object(*p); + } else if (mode == RelocInfo::GLOBAL_PROPERTY_CELL) { + Handle<JSGlobalPropertyCell> cell = it.rinfo()->target_cell_handle(); + it.rinfo()->set_target_cell(*cell); } else if (RelocInfo::IsCodeTarget(mode)) { // rewrite code handles in inline cache targets to direct // pointers to the first instruction in the code object @@ -5813,11 +5966,195 @@ int Code::SourceStatementPosition(Address pc) { } +uint8_t* Code::GetSafepointEntry(Address pc) { + SafepointTable table(this); + unsigned pc_offset = static_cast<unsigned>(pc - instruction_start()); + for (unsigned i = 0; i < table.length(); i++) { + // TODO(kasperl): Replace the linear search with binary search. + if (table.GetPcOffset(i) == pc_offset) return table.GetEntry(i); + } + return NULL; +} + + +void Code::SetNoStackCheckTable() { + // Indicate the absence of a stack-check table by a table start after the + // end of the instructions. Table start must be aligned, so round up. + set_stack_check_table_start(RoundUp(instruction_size(), kIntSize)); +} + + +Map* Code::FindFirstMap() { + ASSERT(is_inline_cache_stub()); + AssertNoAllocation no_allocation; + int mask = RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT); + for (RelocIterator it(this, mask); !it.done(); it.next()) { + RelocInfo* info = it.rinfo(); + Object* object = info->target_object(); + if (object->IsMap()) return Map::cast(object); + } + return NULL; +} + + #ifdef ENABLE_DISASSEMBLER + +#ifdef OBJECT_PRINT + +void DeoptimizationInputData::DeoptimizationInputDataPrint(FILE* out) { + disasm::NameConverter converter; + int deopt_count = DeoptCount(); + PrintF(out, "Deoptimization Input Data (deopt points = %d)\n", deopt_count); + if (0 == deopt_count) return; + + PrintF(out, "%6s %6s %6s %12s\n", "index", "ast id", "argc", "commands"); + for (int i = 0; i < deopt_count; i++) { + int command_count = 0; + PrintF(out, "%6d %6d %6d", + i, AstId(i)->value(), ArgumentsStackHeight(i)->value()); + int translation_index = TranslationIndex(i)->value(); + TranslationIterator iterator(TranslationByteArray(), translation_index); + Translation::Opcode opcode = + static_cast<Translation::Opcode>(iterator.Next()); + ASSERT(Translation::BEGIN == opcode); + int frame_count = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, " %s {count=%d}\n", Translation::StringFor(opcode), + frame_count); + } + + for (int i = 0; i < frame_count; ++i) { + opcode = static_cast<Translation::Opcode>(iterator.Next()); + ASSERT(Translation::FRAME == opcode); + int ast_id = iterator.Next(); + int function_id = iterator.Next(); + JSFunction* function = + JSFunction::cast(LiteralArray()->get(function_id)); + unsigned height = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "%24s %s {ast_id=%d, function=", + "", Translation::StringFor(opcode), ast_id); + function->PrintName(out); + PrintF(out, ", height=%u}\n", height); + } + + // Size of translation is height plus all incoming arguments including + // receiver. + int size = height + function->shared()->formal_parameter_count() + 1; + command_count += size; + for (int j = 0; j < size; ++j) { + opcode = static_cast<Translation::Opcode>(iterator.Next()); + if (FLAG_print_code_verbose) { + PrintF(out, "%24s %s ", "", Translation::StringFor(opcode)); + } + + if (opcode == Translation::DUPLICATE) { + opcode = static_cast<Translation::Opcode>(iterator.Next()); + if (FLAG_print_code_verbose) { + PrintF(out, "%s ", Translation::StringFor(opcode)); + } + --j; // Two commands share the same frame index. + } + + switch (opcode) { + case Translation::BEGIN: + case Translation::FRAME: + case Translation::DUPLICATE: + UNREACHABLE(); + break; + + case Translation::REGISTER: { + int reg_code = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); + } + break; + } + + case Translation::INT32_REGISTER: { + int reg_code = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%s}", converter.NameOfCPURegister(reg_code)); + } + break; + } + + case Translation::DOUBLE_REGISTER: { + int reg_code = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%s}", + DoubleRegister::AllocationIndexToString(reg_code)); + } + break; + } + + case Translation::STACK_SLOT: { + int input_slot_index = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%d}", input_slot_index); + } + break; + } + + case Translation::INT32_STACK_SLOT: { + int input_slot_index = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%d}", input_slot_index); + } + break; + } + + case Translation::DOUBLE_STACK_SLOT: { + int input_slot_index = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{input=%d}", input_slot_index); + } + break; + } + + case Translation::LITERAL: { + unsigned literal_index = iterator.Next(); + if (FLAG_print_code_verbose) { + PrintF(out, "{literal_id=%u}", literal_index); + } + break; + } + + case Translation::ARGUMENTS_OBJECT: + break; + } + if (FLAG_print_code_verbose) PrintF(out, "\n"); + } + } + if (!FLAG_print_code_verbose) PrintF(out, " %12d\n", command_count); + } +} + + +void DeoptimizationOutputData::DeoptimizationOutputDataPrint(FILE* out) { + PrintF(out, "Deoptimization Output Data (deopt points = %d)\n", + this->DeoptPoints()); + if (this->DeoptPoints() == 0) return; + + PrintF("%6s %8s %s\n", "ast id", "pc", "state"); + for (int i = 0; i < this->DeoptPoints(); i++) { + int pc_and_state = this->PcAndState(i)->value(); + PrintF("%6d %8d %s\n", + this->AstId(i)->value(), + FullCodeGenerator::PcField::decode(pc_and_state), + FullCodeGenerator::State2String( + FullCodeGenerator::StateField::decode(pc_and_state))); + } +} + +#endif + + // Identify kind of code. const char* Code::Kind2String(Kind kind) { switch (kind) { case FUNCTION: return "FUNCTION"; + case OPTIMIZED_FUNCTION: return "OPTIMIZED_FUNCTION"; case STUB: return "STUB"; case BUILTIN: return "BUILTIN"; case LOAD_IC: return "LOAD_IC"; @@ -5827,6 +6164,8 @@ const char* Code::Kind2String(Kind kind) { case CALL_IC: return "CALL_IC"; case KEYED_CALL_IC: return "KEYED_CALL_IC"; case BINARY_OP_IC: return "BINARY_OP_IC"; + case TYPE_RECORDING_BINARY_OP_IC: return "TYPE_RECORDING_BINARY_OP_IC"; + case COMPARE_IC: return "COMPARE_IC"; } UNREACHABLE(); return NULL; @@ -5863,27 +6202,78 @@ const char* Code::PropertyType2String(PropertyType type) { return NULL; } -void Code::Disassemble(const char* name) { - PrintF("kind = %s\n", Kind2String(kind())); + +void Code::Disassemble(const char* name, FILE* out) { + PrintF(out, "kind = %s\n", Kind2String(kind())); if (is_inline_cache_stub()) { - PrintF("ic_state = %s\n", ICState2String(ic_state())); - PrintF("ic_in_loop = %d\n", ic_in_loop() == IN_LOOP); + PrintF(out, "ic_state = %s\n", ICState2String(ic_state())); + PrintF(out, "ic_in_loop = %d\n", ic_in_loop() == IN_LOOP); if (ic_state() == MONOMORPHIC) { - PrintF("type = %s\n", PropertyType2String(type())); + PrintF(out, "type = %s\n", PropertyType2String(type())); } } if ((name != NULL) && (name[0] != '\0')) { - PrintF("name = %s\n", name); + PrintF(out, "name = %s\n", name); } + if (kind() == OPTIMIZED_FUNCTION) { + PrintF(out, "stack_slots = %d\n", stack_slots()); + } + + PrintF(out, "Instructions (size = %d)\n", instruction_size()); + Disassembler::Decode(out, this); + PrintF(out, "\n"); - PrintF("Instructions (size = %d)\n", instruction_size()); - Disassembler::Decode(NULL, this); +#ifdef DEBUG + if (kind() == FUNCTION) { + DeoptimizationOutputData* data = + DeoptimizationOutputData::cast(this->deoptimization_data()); + data->DeoptimizationOutputDataPrint(out); + } else if (kind() == OPTIMIZED_FUNCTION) { + DeoptimizationInputData* data = + DeoptimizationInputData::cast(this->deoptimization_data()); + data->DeoptimizationInputDataPrint(out); + } PrintF("\n"); +#endif + + if (kind() == OPTIMIZED_FUNCTION) { + SafepointTable table(this); + PrintF(out, "Safepoints (size = %u)\n", table.size()); + for (unsigned i = 0; i < table.length(); i++) { + unsigned pc_offset = table.GetPcOffset(i); + PrintF(out, "%p %4d ", (instruction_start() + pc_offset), pc_offset); + table.PrintEntry(i); + PrintF(out, " (sp -> fp)"); + int deoptimization_index = table.GetDeoptimizationIndex(i); + if (deoptimization_index != Safepoint::kNoDeoptimizationIndex) { + PrintF(out, " %6d", deoptimization_index); + } else { + PrintF(out, " <none>"); + } + PrintF(out, "\n"); + } + PrintF(out, "\n"); + } else if (kind() == FUNCTION) { + unsigned offset = stack_check_table_start(); + // If there is no stack check table, the "table start" will at or after + // (due to alignment) the end of the instruction stream. + if (static_cast<int>(offset) < instruction_size()) { + unsigned* address = + reinterpret_cast<unsigned*>(instruction_start() + offset); + unsigned length = address[0]; + PrintF(out, "Stack checks (size = %u)\n", length); + PrintF(out, "ast_id pc_offset\n"); + for (unsigned i = 0; i < length; ++i) { + unsigned index = (2 * i) + 1; + PrintF(out, "%6u %9u\n", address[index], address[index + 1]); + } + PrintF(out, "\n"); + } + } PrintF("RelocInfo (size = %d)\n", relocation_size()); - for (RelocIterator it(this); !it.done(); it.next()) - it.rinfo()->Print(); - PrintF("\n"); + for (RelocIterator it(this); !it.done(); it.next()) it.rinfo()->Print(out); + PrintF(out, "\n"); } #endif // ENABLE_DISASSEMBLER @@ -6265,13 +6655,6 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { return UNDEFINED_ELEMENT; } - if (IsJSGlobalProxy()) { - Object* proto = GetPrototype(); - if (proto->IsNull()) return UNDEFINED_ELEMENT; - ASSERT(proto->IsJSGlobalObject()); - return JSObject::cast(proto)->HasLocalElement(index); - } - // Check for lookup interceptor if (HasIndexedInterceptor()) { return HasElementWithInterceptor(this, index) ? INTERCEPTED_ELEMENT @@ -7039,22 +7422,22 @@ bool JSObject::ShouldConvertToFastElements() { // class. This requires us to have the template functions put // together, so even though this function belongs in objects-debug.cc, // we keep it here instead to satisfy certain compilers. -#ifdef DEBUG +#ifdef OBJECT_PRINT template<typename Shape, typename Key> -void Dictionary<Shape, Key>::Print() { +void Dictionary<Shape, Key>::Print(FILE* out) { int capacity = HashTable<Shape, Key>::Capacity(); for (int i = 0; i < capacity; i++) { Object* k = HashTable<Shape, Key>::KeyAt(i); if (HashTable<Shape, Key>::IsKey(k)) { - PrintF(" "); + PrintF(out, " "); if (k->IsString()) { - String::cast(k)->StringPrint(); + String::cast(k)->StringPrint(out); } else { - k->ShortPrint(); + k->ShortPrint(out); } - PrintF(": "); - ValueAt(i)->ShortPrint(); - PrintF("\n"); + PrintF(out, ": "); + ValueAt(i)->ShortPrint(out); + PrintF(out, "\n"); } } } @@ -8311,11 +8694,10 @@ MaybeObject* ExternalFloatArray::SetValue(uint32_t index, Object* value) { } -Object* GlobalObject::GetPropertyCell(LookupResult* result) { +JSGlobalPropertyCell* GlobalObject::GetPropertyCell(LookupResult* result) { ASSERT(!HasFastProperties()); Object* value = property_dictionary()->ValueAt(result->GetDictionaryEntry()); - ASSERT(value->IsJSGlobalPropertyCell()); - return value; + return JSGlobalPropertyCell::cast(value); } @@ -8571,6 +8953,20 @@ MaybeObject* CompilationCacheTable::PutRegExp(String* src, } +void CompilationCacheTable::Remove(Object* value) { + for (int entry = 0, size = Capacity(); entry < size; entry++) { + int entry_index = EntryToIndex(entry); + int value_index = entry_index + 1; + if (get(value_index) == value) { + fast_set(this, entry_index, Heap::null_value()); + fast_set(this, value_index, Heap::null_value()); + ElementRemoved(); + } + } + return; +} + + // SymbolsKey used for HashTable where key is array of symbols. class SymbolsKey : public HashTableKey { public: diff --git a/src/objects.h b/src/objects.h index b52bac27..c5fda7d0 100644 --- a/src/objects.h +++ b/src/objects.h @@ -585,6 +585,7 @@ struct ValueInfo : public Malloced { // A template-ized version of the IsXXX functions. template <class C> static inline bool Is(Object* obj); + class MaybeObject BASE_EMBEDDED { public: inline bool IsFailure(); @@ -606,10 +607,18 @@ class MaybeObject BASE_EMBEDDED { return reinterpret_cast<Object*>(this); } -#ifdef DEBUG +#ifdef OBJECT_PRINT // Prints this object with details. - void Print(); - void PrintLn(); + inline void Print() { + Print(stdout); + }; + inline void PrintLn() { + PrintLn(stdout); + } + void Print(FILE* out); + void PrintLn(FILE* out); +#endif +#ifdef DEBUG // Verifies the object. void Verify(); #endif @@ -654,6 +663,8 @@ class Object : public MaybeObject { inline bool IsMap(); inline bool IsFixedArray(); inline bool IsDescriptorArray(); + inline bool IsDeoptimizationInputData(); + inline bool IsDeoptimizationOutputData(); inline bool IsContext(); inline bool IsCatchContext(); inline bool IsGlobalContext(); @@ -759,7 +770,10 @@ class Object : public MaybeObject { #endif // Prints this object without details. - void ShortPrint(); + inline void ShortPrint() { + ShortPrint(stdout); + } + void ShortPrint(FILE* out); // Prints this object without details to a message accumulator. void ShortPrint(StringStream* accumulator); @@ -798,7 +812,10 @@ class Smi: public Object { static inline Smi* cast(Object* object); // Dispatched behavior. - void SmiPrint(); + inline void SmiPrint() { + SmiPrint(stdout); + } + void SmiPrint(FILE* out); void SmiPrint(StringStream* accumulator); #ifdef DEBUG void SmiVerify(); @@ -867,7 +884,10 @@ class Failure: public MaybeObject { static inline Failure* cast(MaybeObject* object); // Dispatched behavior. - void FailurePrint(); + inline void FailurePrint() { + FailurePrint(stdout); + } + void FailurePrint(FILE* out); void FailurePrint(StringStream* accumulator); #ifdef DEBUG void FailureVerify(); @@ -1096,14 +1116,23 @@ class HeapObject: public Object { // Dispatched behavior. void HeapObjectShortPrint(StringStream* accumulator); +#ifdef OBJECT_PRINT + inline void HeapObjectPrint() { + HeapObjectPrint(stdout); + } + void HeapObjectPrint(FILE* out); +#endif #ifdef DEBUG - void HeapObjectPrint(); void HeapObjectVerify(); inline void VerifyObjectField(int offset); inline void VerifySmiField(int offset); +#endif - void PrintHeader(const char* id); +#ifdef OBJECT_PRINT + void PrintHeader(FILE* out, const char* id); +#endif +#ifdef DEBUG // Verify a pointer is a valid HeapObject pointer that points to object // areas in the heap. static void VerifyHeapPointer(Object* p); @@ -1186,7 +1215,10 @@ class HeapNumber: public HeapObject { // Dispatched behavior. Object* HeapNumberToBoolean(); - void HeapNumberPrint(); + inline void HeapNumberPrint() { + HeapNumberPrint(stdout); + } + void HeapNumberPrint(FILE* out); void HeapNumberPrint(StringStream* accumulator); #ifdef DEBUG void HeapNumberVerify(); @@ -1365,7 +1397,7 @@ class JSObject: public HeapObject { MUST_USE_RESULT MaybeObject* DefineAccessor(String* name, bool is_getter, - JSFunction* fun, + Object* fun, PropertyAttributes attributes); Object* LookupAccessor(String* name, bool is_getter); @@ -1646,12 +1678,28 @@ class JSObject: public HeapObject { // Dispatched behavior. void JSObjectShortPrint(StringStream* accumulator); +#ifdef OBJECT_PRINT + inline void JSObjectPrint() { + JSObjectPrint(stdout); + } + void JSObjectPrint(FILE* out); +#endif #ifdef DEBUG - void JSObjectPrint(); void JSObjectVerify(); - void PrintProperties(); - void PrintElements(); +#endif +#ifdef OBJECT_PRINT + inline void PrintProperties() { + PrintProperties(stdout); + } + void PrintProperties(FILE* out); + + inline void PrintElements() { + PrintElements(stdout); + } + void PrintElements(FILE* out); +#endif +#ifdef DEBUG // Structure for collecting spill information about JSObjects. class SpillInformation { public: @@ -1686,7 +1734,7 @@ class JSObject: public HeapObject { static const uint32_t kMaxGap = 1024; static const int kMaxFastElementsLength = 5000; static const int kInitialMaxFastElementArray = 100000; - static const int kMaxFastProperties = 8; + static const int kMaxFastProperties = 12; static const int kMaxInstanceSize = 255 * kPointerSize; // When extending the backing storage for property values, we increase // its size by more than the 1 entry necessary, so sequentially adding fields @@ -1832,8 +1880,13 @@ class FixedArray: public HeapObject { static const int kMaxLength = (kMaxSize - kHeaderSize) / kPointerSize; // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void FixedArrayPrint() { + FixedArrayPrint(stdout); + } + void FixedArrayPrint(FILE* out); +#endif #ifdef DEBUG - void FixedArrayPrint(); void FixedArrayVerify(); // Checks if two FixedArrays have identical contents. bool IsEqualTo(FixedArray* other); @@ -2009,10 +2062,15 @@ class DescriptorArray: public FixedArray { static const int kEnumCacheBridgeCacheOffset = kEnumCacheBridgeEnumOffset + kPointerSize; -#ifdef DEBUG +#ifdef OBJECT_PRINT // Print all the descriptors. - void PrintDescriptors(); + inline void PrintDescriptors() { + PrintDescriptors(stdout); + } + void PrintDescriptors(FILE* out); +#endif +#ifdef DEBUG // Is the descriptor array sorted and without duplicates? bool IsSortedNoDuplicates(); @@ -2393,8 +2451,11 @@ class Dictionary: public HashTable<Shape, Key> { // Ensure enough space for n additional elements. MUST_USE_RESULT MaybeObject* EnsureCapacity(int n, Key key); -#ifdef DEBUG - void Print(); +#ifdef OBJECT_PRINT + inline void Print() { + Print(stdout); + } + void Print(FILE* out); #endif // Returns the key (slow). Object* SlowReverseLookup(Object* value); @@ -2616,8 +2677,13 @@ class ByteArray: public HeapObject { inline int ByteArraySize() { return SizeFor(this->length()); } +#ifdef OBJECT_PRINT + inline void ByteArrayPrint() { + ByteArrayPrint(stdout); + } + void ByteArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ByteArrayPrint(); void ByteArrayVerify(); #endif @@ -2666,8 +2732,13 @@ class PixelArray: public HeapObject { // Casting. static inline PixelArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void PixelArrayPrint() { + PixelArrayPrint(stdout); + } + void PixelArrayPrint(FILE* out); +#endif #ifdef DEBUG - void PixelArrayPrint(); void PixelArrayVerify(); #endif // DEBUG @@ -2738,8 +2809,13 @@ class ExternalByteArray: public ExternalArray { // Casting. static inline ExternalByteArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalByteArrayPrint() { + ExternalByteArrayPrint(stdout); + } + void ExternalByteArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalByteArrayPrint(); void ExternalByteArrayVerify(); #endif // DEBUG @@ -2761,8 +2837,13 @@ class ExternalUnsignedByteArray: public ExternalArray { // Casting. static inline ExternalUnsignedByteArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalUnsignedByteArrayPrint() { + ExternalUnsignedByteArrayPrint(stdout); + } + void ExternalUnsignedByteArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalUnsignedByteArrayPrint(); void ExternalUnsignedByteArrayVerify(); #endif // DEBUG @@ -2784,8 +2865,13 @@ class ExternalShortArray: public ExternalArray { // Casting. static inline ExternalShortArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalShortArrayPrint() { + ExternalShortArrayPrint(stdout); + } + void ExternalShortArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalShortArrayPrint(); void ExternalShortArrayVerify(); #endif // DEBUG @@ -2807,8 +2893,13 @@ class ExternalUnsignedShortArray: public ExternalArray { // Casting. static inline ExternalUnsignedShortArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalUnsignedShortArrayPrint() { + ExternalUnsignedShortArrayPrint(stdout); + } + void ExternalUnsignedShortArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalUnsignedShortArrayPrint(); void ExternalUnsignedShortArrayVerify(); #endif // DEBUG @@ -2830,8 +2921,13 @@ class ExternalIntArray: public ExternalArray { // Casting. static inline ExternalIntArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalIntArrayPrint() { + ExternalIntArrayPrint(stdout); + } + void ExternalIntArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalIntArrayPrint(); void ExternalIntArrayVerify(); #endif // DEBUG @@ -2853,8 +2949,13 @@ class ExternalUnsignedIntArray: public ExternalArray { // Casting. static inline ExternalUnsignedIntArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalUnsignedIntArrayPrint() { + ExternalUnsignedIntArrayPrint(stdout); + } + void ExternalUnsignedIntArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalUnsignedIntArrayPrint(); void ExternalUnsignedIntArrayVerify(); #endif // DEBUG @@ -2876,8 +2977,13 @@ class ExternalFloatArray: public ExternalArray { // Casting. static inline ExternalFloatArray* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ExternalFloatArrayPrint() { + ExternalFloatArrayPrint(stdout); + } + void ExternalFloatArrayPrint(FILE* out); +#endif #ifdef DEBUG - void ExternalFloatArrayPrint(); void ExternalFloatArrayVerify(); #endif // DEBUG @@ -2886,6 +2992,122 @@ class ExternalFloatArray: public ExternalArray { }; +// DeoptimizationInputData is a fixed array used to hold the deoptimization +// data for code generated by the Hydrogen/Lithium compiler. It also +// contains information about functions that were inlined. If N different +// functions were inlined then first N elements of the literal array will +// contain these functions. +// +// It can be empty. +class DeoptimizationInputData: public FixedArray { + public: + // Layout description. Indices in the array. + static const int kTranslationByteArrayIndex = 0; + static const int kInlinedFunctionCountIndex = 1; + static const int kLiteralArrayIndex = 2; + static const int kOsrAstIdIndex = 3; + static const int kOsrPcOffsetIndex = 4; + static const int kFirstDeoptEntryIndex = 5; + + // Offsets of deopt entry elements relative to the start of the entry. + static const int kAstIdOffset = 0; + static const int kTranslationIndexOffset = 1; + static const int kArgumentsStackHeightOffset = 2; + static const int kDeoptEntrySize = 3; + + // Simple element accessors. +#define DEFINE_ELEMENT_ACCESSORS(name, type) \ + type* name() { \ + return type::cast(get(k##name##Index)); \ + } \ + void Set##name(type* value) { \ + set(k##name##Index, value); \ + } + + DEFINE_ELEMENT_ACCESSORS(TranslationByteArray, ByteArray) + DEFINE_ELEMENT_ACCESSORS(InlinedFunctionCount, Smi) + DEFINE_ELEMENT_ACCESSORS(LiteralArray, FixedArray) + DEFINE_ELEMENT_ACCESSORS(OsrAstId, Smi) + DEFINE_ELEMENT_ACCESSORS(OsrPcOffset, Smi) + + // Unchecked accessor to be used during GC. + FixedArray* UncheckedLiteralArray() { + return reinterpret_cast<FixedArray*>(get(kLiteralArrayIndex)); + } + +#undef DEFINE_ELEMENT_ACCESSORS + + // Accessors for elements of the ith deoptimization entry. +#define DEFINE_ENTRY_ACCESSORS(name, type) \ + type* name(int i) { \ + return type::cast(get(IndexForEntry(i) + k##name##Offset)); \ + } \ + void Set##name(int i, type* value) { \ + set(IndexForEntry(i) + k##name##Offset, value); \ + } + + DEFINE_ENTRY_ACCESSORS(AstId, Smi) + DEFINE_ENTRY_ACCESSORS(TranslationIndex, Smi) + DEFINE_ENTRY_ACCESSORS(ArgumentsStackHeight, Smi) + +#undef DEFINE_ENTRY_ACCESSORS + + int DeoptCount() { + return (length() - kFirstDeoptEntryIndex) / kDeoptEntrySize; + } + + // Allocates a DeoptimizationInputData. + MUST_USE_RESULT static MaybeObject* Allocate(int deopt_entry_count, + PretenureFlag pretenure); + + // Casting. + static inline DeoptimizationInputData* cast(Object* obj); + +#ifdef OBJECT_PRINT + void DeoptimizationInputDataPrint(FILE* out); +#endif + + private: + static int IndexForEntry(int i) { + return kFirstDeoptEntryIndex + (i * kDeoptEntrySize); + } + + static int LengthFor(int entry_count) { + return IndexForEntry(entry_count); + } +}; + + +// DeoptimizationOutputData is a fixed array used to hold the deoptimization +// data for code generated by the full compiler. +// The format of the these objects is +// [i * 2]: Ast ID for ith deoptimization. +// [i * 2 + 1]: PC and state of ith deoptimization +class DeoptimizationOutputData: public FixedArray { + public: + int DeoptPoints() { return length() / 2; } + Smi* AstId(int index) { return Smi::cast(get(index * 2)); } + void SetAstId(int index, Smi* id) { set(index * 2, id); } + Smi* PcAndState(int index) { return Smi::cast(get(1 + index * 2)); } + void SetPcAndState(int index, Smi* offset) { set(1 + index * 2, offset); } + + static int LengthOfFixedArray(int deopt_points) { + return deopt_points * 2; + } + + // Allocates a DeoptimizationOutputData. + MUST_USE_RESULT static MaybeObject* Allocate(int number_of_deopt_points, + PretenureFlag pretenure); + + // Casting. + static inline DeoptimizationOutputData* cast(Object* obj); + +#ifdef OBJECT_PRINT + void DeoptimizationOutputDataPrint(FILE* out); +#endif +}; + + // Code describes objects with on-the-fly generated machine code. class Code: public HeapObject { public: @@ -2900,6 +3122,7 @@ class Code: public HeapObject { enum Kind { FUNCTION, + OPTIMIZED_FUNCTION, STUB, BUILTIN, LOAD_IC, @@ -2909,13 +3132,15 @@ class Code: public HeapObject { STORE_IC, KEYED_STORE_IC, BINARY_OP_IC, + TYPE_RECORDING_BINARY_OP_IC, + COMPARE_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 = BINARY_OP_IC + LAST_IC_KIND = COMPARE_IC }; enum { @@ -2927,7 +3152,10 @@ class Code: public HeapObject { static const char* Kind2String(Kind kind); static const char* ICState2String(InlineCacheState state); static const char* PropertyType2String(PropertyType type); - void Disassemble(const char* name); + inline void Disassemble(const char* name) { + Disassemble(name, stdout); + } + void Disassemble(const char* name, FILE* out); #endif // ENABLE_DISASSEMBLER // [instruction_size]: Size of the native instructions @@ -2936,9 +3164,14 @@ class Code: public HeapObject { // [relocation_info]: Code relocation information DECL_ACCESSORS(relocation_info, ByteArray) + void InvalidateRelocation(); - // Unchecked accessor to be used during GC. + // [deoptimization_data]: Array containing data for deopt. + DECL_ACCESSORS(deoptimization_data, FixedArray) + + // Unchecked accessors to be used during GC. inline ByteArray* unchecked_relocation_info(); + inline FixedArray* unchecked_deoptimization_data(); inline int relocation_size(); @@ -2961,10 +3194,77 @@ 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_binary_op_stub() { return kind() == BINARY_OP_IC; } + inline bool is_type_recording_binary_op_stub() { + return kind() == TYPE_RECORDING_BINARY_OP_IC; + } + inline bool is_compare_ic_stub() { return kind() == COMPARE_IC; } // [major_key]: For kind STUB or BINARY_OP_IC, the major key. inline int major_key(); - inline void set_major_key(int major); + inline void set_major_key(int value); + + // [optimizable]: For FUNCTION kind, tells if it is optimizable. + inline bool optimizable(); + inline void set_optimizable(bool value); + + // [has_deoptimization_support]: For FUNCTION kind, tells if it has + // deoptimization support. + inline bool has_deoptimization_support(); + inline void set_has_deoptimization_support(bool value); + + // [allow_osr_at_loop_nesting_level]: For FUNCTION kind, tells for + // how long the function has been marked for OSR and therefore which + // level of loop nesting we are willing to do on-stack replacement + // for. + inline void set_allow_osr_at_loop_nesting_level(int level); + inline int allow_osr_at_loop_nesting_level(); + + // [stack_slots]: For kind OPTIMIZED_FUNCTION, the number of stack slots + // reserved in the code prologue. + inline unsigned stack_slots(); + inline void set_stack_slots(unsigned slots); + + // [safepoint_table_start]: For kind OPTIMIZED_CODE, the offset in + // the instruction stream where the safepoint table starts. + inline unsigned safepoint_table_start(); + inline void set_safepoint_table_start(unsigned offset); + + // [stack_check_table_start]: For kind FUNCTION, the offset in the + // instruction stream where the stack check table starts. + inline unsigned stack_check_table_start(); + inline void set_stack_check_table_start(unsigned offset); + + // [check type]: For kind CALL_IC, tells how to check if the + // receiver is valid for the given call. + inline CheckType check_type(); + inline void set_check_type(CheckType value); + + // [binary op type]: For all BINARY_OP_IC. + inline byte binary_op_type(); + inline void set_binary_op_type(byte value); + + // [type-recording binary op type]: For all TYPE_RECORDING_BINARY_OP_IC. + inline byte type_recording_binary_op_type(); + inline void set_type_recording_binary_op_type(byte value); + inline byte type_recording_binary_op_result_type(); + inline void set_type_recording_binary_op_result_type(byte value); + + // [compare state]: For kind compare IC stubs, tells what state the + // stub is in. + inline byte compare_state(); + inline void set_compare_state(byte value); + + // Get the safepoint entry for the given pc. Returns NULL for + // non-safepoint pcs. + uint8_t* GetSafepointEntry(Address pc); + + // Mark this code object as not having a stack check table. Assumes kind + // is FUNCTION. + void SetNoStackCheckTable(); + + // Find the first map in an IC stub. + Map* FindFirstMap(); // Flags operations. static inline Flags ComputeFlags(Kind kind, @@ -3048,22 +3348,54 @@ class Code: public HeapObject { template<typename StaticVisitor> inline void CodeIterateBody(); +#ifdef OBJECT_PRINT + inline void CodePrint() { + CodePrint(stdout); + } + void CodePrint(FILE* out); +#endif #ifdef DEBUG - void CodePrint(); void CodeVerify(); #endif + + // Max loop nesting marker used to postpose OSR. We don't take loop + // nesting that is deeper than 5 levels into account. + static const int kMaxLoopNestingMarker = 6; + // Layout description. static const int kInstructionSizeOffset = HeapObject::kHeaderSize; static const int kRelocationInfoOffset = kInstructionSizeOffset + kIntSize; - static const int kFlagsOffset = kRelocationInfoOffset + kPointerSize; + static const int kDeoptimizationDataOffset = + kRelocationInfoOffset + kPointerSize; + static const int kFlagsOffset = kDeoptimizationDataOffset + kPointerSize; static const int kKindSpecificFlagsOffset = kFlagsOffset + kIntSize; + + static const int kKindSpecificFlagsSize = 2 * kIntSize; + + static const int kHeaderPaddingStart = kKindSpecificFlagsOffset + + kKindSpecificFlagsSize; + // Add padding to align the instruction start following right after // the Code object header. static const int kHeaderSize = - CODE_POINTER_ALIGN(kKindSpecificFlagsOffset + kIntSize); + (kHeaderPaddingStart + kCodeAlignmentMask) & ~kCodeAlignmentMask; // Byte offsets within kKindSpecificFlagsOffset. - static const int kStubMajorKeyOffset = kKindSpecificFlagsOffset + 1; + static const int kStubMajorKeyOffset = kKindSpecificFlagsOffset; + static const int kOptimizableOffset = kKindSpecificFlagsOffset; + static const int kStackSlotsOffset = kKindSpecificFlagsOffset; + static const int kCheckTypeOffset = kKindSpecificFlagsOffset; + + static const int kCompareStateOffset = kStubMajorKeyOffset + 1; + static const int kBinaryOpTypeOffset = kStubMajorKeyOffset + 1; + static const int kHasDeoptimizationSupportOffset = kOptimizableOffset + 1; + + static const int kBinaryOpReturnTypeOffset = kBinaryOpTypeOffset + 1; + static const int kAllowOSRAtLoopNestingLevelOffset = + kHasDeoptimizationSupportOffset + 1; + + static const int kSafepointTableStartOffset = kStackSlotsOffset + kIntSize; + static const int kStackCheckTableStartOffset = kStackSlotsOffset + kIntSize; // Flags layout. static const int kFlagsICStateShift = 0; @@ -3239,6 +3571,13 @@ class Map: public HeapObject { // [stub cache]: contains stubs compiled for this map. DECL_ACCESSORS(code_cache, Object) + // Lookup in the map's instance descriptors and fill out the result + // with the given holder if the name is found. The holder may be + // NULL when this function is used from the compiler. + void LookupInDescriptors(JSObject* holder, + String* name, + LookupResult* result); + MUST_USE_RESULT MaybeObject* CopyDropDescriptors(); MUST_USE_RESULT MaybeObject* CopyNormalized(PropertyNormalizationMode mode, @@ -3303,8 +3642,13 @@ class Map: public HeapObject { void ClearNonLiveTransitions(Object* real_prototype); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void MapPrint() { + MapPrint(stdout); + } + void MapPrint(FILE* out); +#endif #ifdef DEBUG - void MapPrint(); void MapVerify(); void SharedMapVerify(); #endif @@ -3460,8 +3804,13 @@ class Script: public Struct { // resource is accessible. Otherwise, always return true. inline bool HasValidSource(); +#ifdef OBJECT_PRINT + inline void ScriptPrint() { + ScriptPrint(stdout); + } + void ScriptPrint(FILE* out); +#endif #ifdef DEBUG - void ScriptPrint(); void ScriptVerify(); #endif @@ -3486,6 +3835,52 @@ class Script: public Struct { }; +// List of builtin functions we want to identify to improve code +// generation. +// +// Each entry has a name of a global object property holding an object +// optionally followed by ".prototype", a name of a builtin function +// on the object (the one the id is set for), and a label. +// +// Installation of ids for the selected builtin functions is handled +// by the bootstrapper. +// +// NOTE: Order is important: math functions should be at the end of +// the list and MathFloor should be the first math function. +#define FUNCTIONS_WITH_ID_LIST(V) \ + V(Array.prototype, push, ArrayPush) \ + V(Array.prototype, pop, ArrayPop) \ + V(String.prototype, charCodeAt, StringCharCodeAt) \ + V(String.prototype, charAt, StringCharAt) \ + V(String, fromCharCode, StringFromCharCode) \ + V(Math, floor, MathFloor) \ + V(Math, round, MathRound) \ + V(Math, ceil, MathCeil) \ + V(Math, abs, MathAbs) \ + V(Math, log, MathLog) \ + V(Math, sin, MathSin) \ + V(Math, cos, MathCos) \ + V(Math, tan, MathTan) \ + V(Math, asin, MathASin) \ + V(Math, acos, MathACos) \ + V(Math, atan, MathATan) \ + V(Math, exp, MathExp) \ + V(Math, sqrt, MathSqrt) \ + V(Math, pow, MathPow) + + +enum BuiltinFunctionId { +#define DECLARE_FUNCTION_ID(ignored1, ignore2, name) \ + k##name, + FUNCTIONS_WITH_ID_LIST(DECLARE_FUNCTION_ID) +#undef DECLARE_FUNCTION_ID + // Fake id for a special case of Math.pow. Note, it continues the + // list of math functions. + kMathPowHalf, + kFirstMathFunctionId = kMathFloor +}; + + // SharedFunctionInfo describes the JSFunction information that can be // shared by multiple instances of the function. class SharedFunctionInfo: public HeapObject { @@ -3623,7 +4018,7 @@ class SharedFunctionInfo: public HeapObject { // [function data]: This field holds some additional data for function. // Currently it either has FunctionTemplateInfo to make benefit the API - // or Smi identifying a custom call generator. + // or Smi identifying a builtin function. // In the long run we don't want all functions to have this field but // we can fix that when we have a better model for storing hidden data // on objects. @@ -3631,8 +4026,9 @@ class SharedFunctionInfo: public HeapObject { inline bool IsApiFunction(); inline FunctionTemplateInfo* get_api_func_data(); - inline bool HasCustomCallGenerator(); - inline int custom_call_generator_id(); + inline bool HasBuiltinFunctionId(); + inline bool IsBuiltinMathFunction(); + inline BuiltinFunctionId builtin_function_id(); // [script info]: Script from which the function originates. DECL_ACCESSORS(script, Object) @@ -3687,6 +4083,11 @@ class SharedFunctionInfo: public HeapObject { inline int compiler_hints(); inline void set_compiler_hints(int value); + // A counter used to determine when to stress the deoptimizer with a + // deopt. + inline Smi* deopt_counter(); + inline void set_deopt_counter(Smi* counter); + // Add information on assignments of the form this.x = ...; void SetThisPropertyAssignmentsInfo( bool has_only_simple_this_property_assignments, @@ -3716,6 +4117,24 @@ class SharedFunctionInfo: public HeapObject { inline int code_age(); inline void set_code_age(int age); + // Indicates whether optimizations have been disabled for this + // shared function info. If a function is repeatedly optimized or if + // we cannot optimize the function we disable optimization to avoid + // spending time attempting to optimize it again. + inline bool optimization_disabled(); + inline void set_optimization_disabled(bool value); + + // Indicates whether or not the code in the shared function support + // deoptimization. + inline bool has_deoptimization_support(); + + // Enable deoptimization support through recompiled code. + void EnableDeoptimizationSupport(Code* recompiled); + + // Lookup the bailout ID and ASSERT that it exists in the non-optimized + // code, returns whether it asserted (i.e., always true if assertions are + // disabled). + bool VerifyBailoutId(int id); // Check whether a inlined constructor can be generated with the given // prototype. @@ -3739,6 +4158,12 @@ class SharedFunctionInfo: public HeapObject { bool HasSourceCode(); Object* GetSourceCode(); + inline int opt_count(); + inline void set_opt_count(int opt_count); + + // Source size of this function. + int SourceSize(); + // Calculate the instance size. int CalculateInstanceSize(); @@ -3748,8 +4173,13 @@ class SharedFunctionInfo: public HeapObject { // Dispatched behavior. // Set max_length to -1 for unlimited length. void SourceCodePrint(StringStream* accumulator, int max_length); +#ifdef OBJECT_PRINT + inline void SharedFunctionInfoPrint() { + SharedFunctionInfoPrint(stdout); + } + void SharedFunctionInfoPrint(FILE* out); +#endif #ifdef DEBUG - void SharedFunctionInfoPrint(); void SharedFunctionInfoVerify(); #endif @@ -3776,10 +4206,12 @@ class SharedFunctionInfo: public HeapObject { kInferredNameOffset + kPointerSize; static const int kThisPropertyAssignmentsOffset = kInitialMapOffset + kPointerSize; + static const int kDeoptCounterOffset = + kThisPropertyAssignmentsOffset + kPointerSize; #if V8_HOST_ARCH_32_BIT // Smi fields. static const int kLengthOffset = - kThisPropertyAssignmentsOffset + kPointerSize; + kDeoptCounterOffset + kPointerSize; static const int kFormalParameterCountOffset = kLengthOffset + kPointerSize; static const int kExpectedNofPropertiesOffset = kFormalParameterCountOffset + kPointerSize; @@ -3795,8 +4227,10 @@ class SharedFunctionInfo: public HeapObject { kFunctionTokenPositionOffset + kPointerSize; static const int kThisPropertyAssignmentsCountOffset = kCompilerHintsOffset + kPointerSize; + static const int kOptCountOffset = + kThisPropertyAssignmentsCountOffset + kPointerSize; // Total size. - static const int kSize = kThisPropertyAssignmentsCountOffset + kPointerSize; + static const int kSize = kOptCountOffset + kPointerSize; #else // The only reason to use smi fields instead of int fields // is to allow iteration without maps decoding during @@ -3808,7 +4242,7 @@ class SharedFunctionInfo: public HeapObject { // word is not set and thus this word cannot be treated as pointer // to HeapObject during old space traversal. static const int kLengthOffset = - kThisPropertyAssignmentsOffset + kPointerSize; + kDeoptCounterOffset + kPointerSize; static const int kFormalParameterCountOffset = kLengthOffset + kIntSize; @@ -3829,9 +4263,11 @@ class SharedFunctionInfo: public HeapObject { static const int kThisPropertyAssignmentsCountOffset = kCompilerHintsOffset + kIntSize; + static const int kOptCountOffset = + kThisPropertyAssignmentsCountOffset + kIntSize; // Total size. - static const int kSize = kThisPropertyAssignmentsCountOffset + kIntSize; + static const int kSize = kOptCountOffset + kIntSize; #endif @@ -3867,7 +4303,8 @@ class SharedFunctionInfo: public HeapObject { static const int kAllowLazyCompilation = 2; static const int kLiveObjectsMayExist = 3; static const int kCodeAgeShift = 4; - static const int kCodeAgeMask = 7; + static const int kCodeAgeMask = 0x7; + static const int kOptimizationDisabled = 7; DISALLOW_IMPLICIT_CONSTRUCTORS(SharedFunctionInfo); }; @@ -3895,13 +4332,34 @@ class JSFunction: public JSObject { // [[Call]] and [[Construct]] description in ECMA-262, section // 8.6.2, page 27. inline Code* code(); - inline void set_code(Code* value); + inline void set_code(Code* code); + inline void ReplaceCode(Code* code); inline Code* unchecked_code(); // Tells whether this function is builtin. inline bool IsBuiltin(); + // Tells whether or not the function needs arguments adaption. + inline bool NeedsArgumentsAdaption(); + + // Tells whether or not this function has been optimized. + inline bool IsOptimized(); + + // Mark this function for lazy recompilation. The function will be + // recompiled the next time it is executed. + void MarkForLazyRecompilation(); + + // Tells whether or not the function is already marked for lazy + // recompilation. + inline bool IsMarkedForLazyRecompilation(); + + // Compute a hash code for the source code of this function. + uint32_t SourceHash(); + + // Check whether or not this function is inlineable. + bool IsInlineable(); + // [literals]: Fixed array holding the materialized literals. // // If the function contains object, regexp or array literals, the @@ -3948,6 +4406,16 @@ class JSFunction: public JSObject { // Returns if this function has been compiled to native code yet. inline bool is_compiled(); + // [next_function_link]: Field for linking functions. This list is treated as + // a weak list by the GC. + DECL_ACCESSORS(next_function_link, Object) + + // Prints the name of the function using PrintF. + inline void PrintName() { + PrintName(stdout); + } + void PrintName(FILE* out); + // Casting. static inline JSFunction* cast(Object* obj); @@ -3956,8 +4424,13 @@ class JSFunction: public JSObject { void JSFunctionIterateBody(int object_size, ObjectVisitor* v); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSFunctionPrint() { + JSFunctionPrint(stdout); + } + void JSFunctionPrint(FILE* out); +#endif #ifdef DEBUG - void JSFunctionPrint(); void JSFunctionVerify(); #endif @@ -3967,7 +4440,8 @@ class JSFunction: public JSObject { // Retrieve the global context from a function's literal array. static Context* GlobalContextFromLiterals(FixedArray* literals); - // Layout descriptors. + // Layout descriptors. The last property (from kNonWeakFieldsEndOffset to + // kSize) is weak and has special handling during garbage collection. static const int kCodeEntryOffset = JSObject::kHeaderSize; static const int kPrototypeOrInitialMapOffset = kCodeEntryOffset + kPointerSize; @@ -3975,7 +4449,9 @@ class JSFunction: public JSObject { kPrototypeOrInitialMapOffset + kPointerSize; static const int kContextOffset = kSharedFunctionInfoOffset + kPointerSize; static const int kLiteralsOffset = kContextOffset + kPointerSize; - static const int kSize = kLiteralsOffset + kPointerSize; + static const int kNonWeakFieldsEndOffset = kLiteralsOffset + kPointerSize; + static const int kNextFunctionLinkOffset = kNonWeakFieldsEndOffset; + static const int kSize = kNextFunctionLinkOffset + kPointerSize; // Layout of the literals array. static const int kLiteralsPrefixSize = 1; @@ -4003,8 +4479,13 @@ class JSGlobalProxy : public JSObject { static inline JSGlobalProxy* cast(Object* obj); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSGlobalProxyPrint() { + JSGlobalProxyPrint(stdout); + } + void JSGlobalProxyPrint(FILE* out); +#endif #ifdef DEBUG - void JSGlobalProxyPrint(); void JSGlobalProxyVerify(); #endif @@ -4020,6 +4501,7 @@ class JSGlobalProxy : public JSObject { // Forward declaration. class JSBuiltinsObject; +class JSGlobalPropertyCell; // Common super class for JavaScript global objects and the special // builtins global objects. @@ -4035,7 +4517,7 @@ class GlobalObject: public JSObject { DECL_ACCESSORS(global_receiver, JSObject) // Retrieve the property cell used to store a property. - Object* GetPropertyCell(LookupResult* result); + JSGlobalPropertyCell* GetPropertyCell(LookupResult* result); // This is like GetProperty, but is used when you know the lookup won't fail // by throwing an exception. This is for the debug and builtins global @@ -4073,8 +4555,13 @@ class JSGlobalObject: public GlobalObject { static inline JSGlobalObject* cast(Object* obj); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSGlobalObjectPrint() { + JSGlobalObjectPrint(stdout); + } + void JSGlobalObjectPrint(FILE* out); +#endif #ifdef DEBUG - void JSGlobalObjectPrint(); void JSGlobalObjectVerify(); #endif @@ -4102,8 +4589,13 @@ class JSBuiltinsObject: public GlobalObject { static inline JSBuiltinsObject* cast(Object* obj); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSBuiltinsObjectPrint() { + JSBuiltinsObjectPrint(stdout); + } + void JSBuiltinsObjectPrint(FILE* out); +#endif #ifdef DEBUG - void JSBuiltinsObjectPrint(); void JSBuiltinsObjectVerify(); #endif @@ -4140,8 +4632,13 @@ class JSValue: public JSObject { static inline JSValue* cast(Object* obj); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSValuePrint() { + JSValuePrint(stdout); + } + void JSValuePrint(FILE* out); +#endif #ifdef DEBUG - void JSValuePrint(); void JSValueVerify(); #endif @@ -4297,6 +4794,9 @@ class CompilationCacheTable: public HashTable<CompilationCacheShape, MaybeObject* PutEval(String* src, Context* context, Object* value); MaybeObject* PutRegExp(String* src, JSRegExp::Flags flags, FixedArray* value); + // Remove given value from cache. + void Remove(Object* value); + static inline CompilationCacheTable* cast(Object* obj); private: @@ -4327,8 +4827,13 @@ class CodeCache: public Struct { static inline CodeCache* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void CodeCachePrint() { + CodeCachePrint(stdout); + } + void CodeCachePrint(FILE* out); +#endif #ifdef DEBUG - void CodeCachePrint(); void CodeCacheVerify(); #endif @@ -4629,8 +5134,13 @@ class String: public HeapObject { // Dispatched behavior. void StringShortPrint(StringStream* accumulator); +#ifdef OBJECT_PRINT + inline void StringPrint() { + StringPrint(stdout); + } + void StringPrint(FILE* out); +#endif #ifdef DEBUG - void StringPrint(); void StringVerify(); #endif inline bool IsFlat(); @@ -5185,7 +5695,12 @@ class JSGlobalPropertyCell: public HeapObject { #ifdef DEBUG void JSGlobalPropertyCellVerify(); - void JSGlobalPropertyCellPrint(); +#endif +#ifdef OBJECT_PRINT + inline void JSGlobalPropertyCellPrint() { + JSGlobalPropertyCellPrint(stdout); + } + void JSGlobalPropertyCellPrint(FILE* out); #endif // Layout description. @@ -5220,8 +5735,13 @@ class Proxy: public HeapObject { template<typename StaticVisitor> inline void ProxyIterateBody(); +#ifdef OBJECT_PRINT + inline void ProxyPrint() { + ProxyPrint(stdout); + } + void ProxyPrint(FILE* out); +#endif #ifdef DEBUG - void ProxyPrint(); void ProxyVerify(); #endif @@ -5270,8 +5790,13 @@ class JSArray: public JSObject { inline void EnsureSize(int minimum_size_of_backing_fixed_array); // Dispatched behavior. +#ifdef OBJECT_PRINT + inline void JSArrayPrint() { + JSArrayPrint(stdout); + } + void JSArrayPrint(FILE* out); +#endif #ifdef DEBUG - void JSArrayPrint(); void JSArrayVerify(); #endif @@ -5342,8 +5867,13 @@ class AccessorInfo: public Struct { static inline AccessorInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void AccessorInfoPrint() { + AccessorInfoPrint(stdout); + } + void AccessorInfoPrint(FILE* out); +#endif #ifdef DEBUG - void AccessorInfoPrint(); void AccessorInfoVerify(); #endif @@ -5373,8 +5903,13 @@ class AccessCheckInfo: public Struct { static inline AccessCheckInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void AccessCheckInfoPrint() { + AccessCheckInfoPrint(stdout); + } + void AccessCheckInfoPrint(FILE* out); +#endif #ifdef DEBUG - void AccessCheckInfoPrint(); void AccessCheckInfoVerify(); #endif @@ -5399,8 +5934,13 @@ class InterceptorInfo: public Struct { static inline InterceptorInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void InterceptorInfoPrint() { + InterceptorInfoPrint(stdout); + } + void InterceptorInfoPrint(FILE* out); +#endif #ifdef DEBUG - void InterceptorInfoPrint(); void InterceptorInfoVerify(); #endif @@ -5424,8 +5964,13 @@ class CallHandlerInfo: public Struct { static inline CallHandlerInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void CallHandlerInfoPrint() { + CallHandlerInfoPrint(stdout); + } + void CallHandlerInfoPrint(FILE* out); +#endif #ifdef DEBUG - void CallHandlerInfoPrint(); void CallHandlerInfoVerify(); #endif @@ -5481,8 +6026,13 @@ class FunctionTemplateInfo: public TemplateInfo { static inline FunctionTemplateInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void FunctionTemplateInfoPrint() { + FunctionTemplateInfoPrint(stdout); + } + void FunctionTemplateInfoPrint(FILE* out); +#endif #ifdef DEBUG - void FunctionTemplateInfoPrint(); void FunctionTemplateInfoVerify(); #endif @@ -5524,8 +6074,13 @@ class ObjectTemplateInfo: public TemplateInfo { static inline ObjectTemplateInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void ObjectTemplateInfoPrint() { + ObjectTemplateInfoPrint(stdout); + } + void ObjectTemplateInfoPrint(FILE* out); +#endif #ifdef DEBUG - void ObjectTemplateInfoPrint(); void ObjectTemplateInfoVerify(); #endif @@ -5543,8 +6098,13 @@ class SignatureInfo: public Struct { static inline SignatureInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void SignatureInfoPrint() { + SignatureInfoPrint(stdout); + } + void SignatureInfoPrint(FILE* out); +#endif #ifdef DEBUG - void SignatureInfoPrint(); void SignatureInfoVerify(); #endif @@ -5563,8 +6123,13 @@ class TypeSwitchInfo: public Struct { static inline TypeSwitchInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void TypeSwitchInfoPrint() { + TypeSwitchInfoPrint(stdout); + } + void TypeSwitchInfoPrint(FILE* out); +#endif #ifdef DEBUG - void TypeSwitchInfoPrint(); void TypeSwitchInfoVerify(); #endif @@ -5610,8 +6175,13 @@ class DebugInfo: public Struct { static inline DebugInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void DebugInfoPrint() { + DebugInfoPrint(stdout); + } + void DebugInfoPrint(FILE* out); +#endif #ifdef DEBUG - void DebugInfoPrint(); void DebugInfoVerify(); #endif @@ -5663,8 +6233,13 @@ class BreakPointInfo: public Struct { static inline BreakPointInfo* cast(Object* obj); +#ifdef OBJECT_PRINT + inline void BreakPointInfoPrint() { + BreakPointInfoPrint(stdout); + } + void BreakPointInfoPrint(FILE* out); +#endif #ifdef DEBUG - void BreakPointInfoPrint(); void BreakPointInfoVerify(); #endif @@ -5705,6 +6280,9 @@ class ObjectVisitor BASE_EMBEDDED { // Visits a code entry in a JS function. virtual void VisitCodeEntry(Address entry_address); + // Visits a global property cell reference in the instruction stream. + virtual void VisitGlobalPropertyCell(RelocInfo* rinfo); + // Visits a runtime entry in the instruction stream. virtual void VisitRuntimeEntry(RelocInfo* rinfo) {} diff --git a/src/parser.cc b/src/parser.cc index 186d1020..08f77b8f 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -593,7 +593,9 @@ Parser::Parser(Handle<Script> script, allow_natives_syntax_(allow_natives_syntax), extension_(extension), pre_data_(pre_data), - fni_(NULL) { + fni_(NULL), + stack_overflow_(false) { + AstNode::ResetIds(); } @@ -607,7 +609,25 @@ FunctionLiteral* Parser::ParseProgram(Handle<String> source, // Initialize parser state. source->TryFlatten(); - scanner_.Initialize(source); + if (source->IsExternalTwoByteString()) { + // Notice that the stream is destroyed at the end of the branch block. + // The last line of the blocks can't be moved outside, even though they're + // identical calls. + ExternalTwoByteStringUC16CharacterStream stream( + Handle<ExternalTwoByteString>::cast(source), 0, source->length()); + scanner_.Initialize(&stream, JavaScriptScanner::kAllLiterals); + return DoParseProgram(source, in_global_context, &zone_scope); + } else { + GenericStringUC16CharacterStream stream(source, 0, source->length()); + scanner_.Initialize(&stream, JavaScriptScanner::kAllLiterals); + return DoParseProgram(source, in_global_context, &zone_scope); + } +} + + +FunctionLiteral* Parser::DoParseProgram(Handle<String> source, + bool in_global_context, + ZoneScope* zone_scope) { ASSERT(target_stack_ == NULL); if (pre_data_ != NULL) pre_data_->Initialize(); @@ -643,7 +663,7 @@ FunctionLiteral* Parser::ParseProgram(Handle<String> source, source->length(), false, temp_scope.ContainsLoops()); - } else if (scanner().stack_overflow()) { + } else if (stack_overflow_) { Top::StackOverflow(); } } @@ -653,25 +673,45 @@ FunctionLiteral* Parser::ParseProgram(Handle<String> source, // If there was a syntax error we have to get rid of the AST // and it is not safe to do so before the scope has been deleted. - if (result == NULL) zone_scope.DeleteOnExit(); + if (result == NULL) zone_scope->DeleteOnExit(); return result; } - FunctionLiteral* Parser::ParseLazy(Handle<SharedFunctionInfo> info) { CompilationZoneScope zone_scope(DONT_DELETE_ON_EXIT); HistogramTimerScope timer(&Counters::parse_lazy); Handle<String> source(String::cast(script_->source())); Counters::total_parse_size.Increment(source->length()); + // Initialize parser state. + source->TryFlatten(); + if (source->IsExternalTwoByteString()) { + ExternalTwoByteStringUC16CharacterStream stream( + Handle<ExternalTwoByteString>::cast(source), + info->start_position(), + info->end_position()); + FunctionLiteral* result = ParseLazy(info, &stream, &zone_scope); + return result; + } else { + GenericStringUC16CharacterStream stream(source, + info->start_position(), + info->end_position()); + FunctionLiteral* result = ParseLazy(info, &stream, &zone_scope); + return result; + } +} + + +FunctionLiteral* Parser::ParseLazy(Handle<SharedFunctionInfo> info, + UC16CharacterStream* source, + ZoneScope* zone_scope) { + scanner_.Initialize(source, JavaScriptScanner::kAllLiterals); + ASSERT(target_stack_ == NULL); + Handle<String> name(String::cast(info->name())); fni_ = new FuncNameInferrer(); fni_->PushEnclosingName(name); - // Initialize parser state. - source->TryFlatten(); - scanner_.Initialize(source, info->start_position(), info->end_position()); - ASSERT(target_stack_ == NULL); mode_ = PARSE_EAGERLY; // Place holder for the result. @@ -693,7 +733,7 @@ FunctionLiteral* Parser::ParseLazy(Handle<SharedFunctionInfo> info) { // Make sure the results agree. ASSERT(ok == (result != NULL)); // The only errors should be stack overflows. - ASSERT(ok || scanner_.stack_overflow()); + ASSERT(ok || stack_overflow_); } // Make sure the target stack is empty. @@ -703,7 +743,10 @@ FunctionLiteral* Parser::ParseLazy(Handle<SharedFunctionInfo> info) { // not safe to do before scope has been deleted. if (result == NULL) { Top::StackOverflow(); - zone_scope.DeleteOnExit(); + zone_scope->DeleteOnExit(); + } else { + Handle<String> inferred_name(info->inferred_name()); + result->set_inferred_name(inferred_name); } return result; } @@ -714,12 +757,12 @@ Handle<String> Parser::GetSymbol(bool* ok) { if (pre_data() != NULL) { symbol_id = pre_data()->GetSymbolIdentifier(); } - return LookupSymbol(symbol_id, scanner_.literal()); + return LookupSymbol(symbol_id, scanner().literal()); } void Parser::ReportMessage(const char* type, Vector<const char*> args) { - Scanner::Location source_location = scanner_.location(); + Scanner::Location source_location = scanner().location(); ReportMessageAt(source_location, type, args); } @@ -1636,7 +1679,7 @@ Statement* Parser::ParseContinueStatement(bool* ok) { Expect(Token::CONTINUE, CHECK_OK); Handle<String> label = Handle<String>::null(); Token::Value tok = peek(); - if (!scanner_.has_line_terminator_before_next() && + if (!scanner().has_line_terminator_before_next() && tok != Token::SEMICOLON && tok != Token::RBRACE && tok != Token::EOS) { label = ParseIdentifier(CHECK_OK); } @@ -1662,7 +1705,7 @@ Statement* Parser::ParseBreakStatement(ZoneStringList* labels, bool* ok) { Expect(Token::BREAK, CHECK_OK); Handle<String> label; Token::Value tok = peek(); - if (!scanner_.has_line_terminator_before_next() && + if (!scanner().has_line_terminator_before_next() && tok != Token::SEMICOLON && tok != Token::RBRACE && tok != Token::EOS) { label = ParseIdentifier(CHECK_OK); } @@ -1707,7 +1750,7 @@ Statement* Parser::ParseReturnStatement(bool* ok) { } Token::Value tok = peek(); - if (scanner_.has_line_terminator_before_next() || + if (scanner().has_line_terminator_before_next() || tok == Token::SEMICOLON || tok == Token::RBRACE || tok == Token::EOS) { @@ -1793,7 +1836,7 @@ CaseClause* Parser::ParseCaseClause(bool* default_seen_ptr, bool* ok) { *default_seen_ptr = true; } Expect(Token::COLON, CHECK_OK); - + int pos = scanner().location().beg_pos; ZoneList<Statement*>* statements = new ZoneList<Statement*>(5); while (peek() != Token::CASE && peek() != Token::DEFAULT && @@ -1802,7 +1845,7 @@ CaseClause* Parser::ParseCaseClause(bool* default_seen_ptr, bool* ok) { statements->Add(stat); } - return new CaseClause(label, statements); + return new CaseClause(label, statements, pos); } @@ -1839,7 +1882,7 @@ Statement* Parser::ParseThrowStatement(bool* ok) { Expect(Token::THROW, CHECK_OK); int pos = scanner().location().beg_pos; - if (scanner_.has_line_terminator_before_next()) { + if (scanner().has_line_terminator_before_next()) { ReportMessage("newline_after_throw", Vector<const char*>::empty()); *ok = false; return NULL; @@ -1874,7 +1917,7 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { } Block* catch_block = NULL; - VariableProxy* catch_var = NULL; + Variable* catch_var = NULL; Block* finally_block = NULL; Token::Value tok = peek(); @@ -1904,7 +1947,8 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { // executing the finally block. catch_var = top_scope_->NewTemporary(Factory::catch_var_symbol()); Literal* name_literal = new Literal(name); - Expression* obj = new CatchExtensionObject(name_literal, catch_var); + VariableProxy* catch_var_use = new VariableProxy(catch_var); + Expression* obj = new CatchExtensionObject(name_literal, catch_var_use); { Target target(&this->target_stack_, &catch_collector); catch_block = WithHelper(obj, NULL, true, CHECK_OK); } @@ -1928,8 +1972,9 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { // 'try { try { } catch { } } finally { }' if (catch_block != NULL && finally_block != NULL) { + VariableProxy* catch_var_defn = new VariableProxy(catch_var); TryCatchStatement* statement = - new TryCatchStatement(try_block, catch_var, catch_block); + new TryCatchStatement(try_block, catch_var_defn, catch_block); statement->set_escaping_targets(collector.targets()); try_block = new Block(NULL, 1, false); try_block->AddStatement(statement); @@ -1939,7 +1984,8 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { TryStatement* result = NULL; if (catch_block != NULL) { ASSERT(finally_block == NULL); - result = new TryCatchStatement(try_block, catch_var, catch_block); + VariableProxy* catch_var_defn = new VariableProxy(catch_var); + result = new TryCatchStatement(try_block, catch_var_defn, catch_block); result->set_escaping_targets(collector.targets()); } else { ASSERT(finally_block != NULL); @@ -2400,7 +2446,8 @@ Expression* Parser::ParsePostfixExpression(bool* ok) { // LeftHandSideExpression ('++' | '--')? Expression* expression = ParseLeftHandSideExpression(CHECK_OK); - if (!scanner_.has_line_terminator_before_next() && Token::IsCountOp(peek())) { + if (!scanner().has_line_terminator_before_next() && + Token::IsCountOp(peek())) { // Signal a reference error if the expression is an invalid // left-hand side expression. We could report this as a syntax // error here but for compatibility with JSC we choose to report the @@ -2590,25 +2637,24 @@ void Parser::ReportUnexpectedToken(Token::Value token) { // We don't report stack overflows here, to avoid increasing the // stack depth even further. Instead we report it after parsing is // over, in ParseProgram/ParseJson. - if (token == Token::ILLEGAL && scanner().stack_overflow()) - return; + if (token == Token::ILLEGAL && stack_overflow_) return; // Four of the tokens are treated specially switch (token) { - case Token::EOS: - return ReportMessage("unexpected_eos", Vector<const char*>::empty()); - case Token::NUMBER: - return ReportMessage("unexpected_token_number", - Vector<const char*>::empty()); - case Token::STRING: - return ReportMessage("unexpected_token_string", - Vector<const char*>::empty()); - case Token::IDENTIFIER: - return ReportMessage("unexpected_token_identifier", - Vector<const char*>::empty()); - default: - const char* name = Token::String(token); - ASSERT(name != NULL); - ReportMessage("unexpected_token", Vector<const char*>(&name, 1)); + case Token::EOS: + return ReportMessage("unexpected_eos", Vector<const char*>::empty()); + case Token::NUMBER: + return ReportMessage("unexpected_token_number", + Vector<const char*>::empty()); + case Token::STRING: + return ReportMessage("unexpected_token_string", + Vector<const char*>::empty()); + case Token::IDENTIFIER: + return ReportMessage("unexpected_token_identifier", + Vector<const char*>::empty()); + default: + const char* name = Token::String(token); + ASSERT(name != NULL); + ReportMessage("unexpected_token", Vector<const char*>(&name, 1)); } } @@ -2670,7 +2716,7 @@ Expression* Parser::ParsePrimaryExpression(bool* ok) { case Token::NUMBER: { Consume(Token::NUMBER); double value = - StringToDouble(scanner_.literal(), ALLOW_HEX | ALLOW_OCTALS); + StringToDouble(scanner().literal(), ALLOW_HEX | ALLOW_OCTALS); result = NewNumberLiteral(value); break; } @@ -2814,6 +2860,7 @@ bool Parser::IsBoilerplateProperty(ObjectLiteral::Property* property) { bool CompileTimeValue::IsCompileTimeValue(Expression* expression) { + if (expression->AsLiteral() != NULL) return true; MaterializedLiteral* lit = expression->AsMaterializedLiteral(); return lit != NULL && lit->is_simple(); } @@ -3020,7 +3067,7 @@ Expression* Parser::ParseObjectLiteral(bool* ok) { case Token::NUMBER: { Consume(Token::NUMBER); double value = - StringToDouble(scanner_.literal(), ALLOW_HEX | ALLOW_OCTALS); + StringToDouble(scanner().literal(), ALLOW_HEX | ALLOW_OCTALS); key = NewNumberLiteral(value); break; } @@ -3081,7 +3128,7 @@ Expression* Parser::ParseObjectLiteral(bool* ok) { Expression* Parser::ParseRegExpLiteral(bool seen_equal, bool* ok) { - if (!scanner_.ScanRegExpPattern(seen_equal)) { + if (!scanner().ScanRegExpPattern(seen_equal)) { Next(); ReportMessage("unterminated_regexp", Vector<const char*>::empty()); *ok = false; @@ -3091,10 +3138,10 @@ Expression* Parser::ParseRegExpLiteral(bool seen_equal, bool* ok) { int literal_index = temp_scope_->NextMaterializedLiteralIndex(); Handle<String> js_pattern = - Factory::NewStringFromUtf8(scanner_.next_literal(), TENURED); - scanner_.ScanRegExpFlags(); + Factory::NewStringFromUtf8(scanner().next_literal(), TENURED); + scanner().ScanRegExpFlags(); Handle<String> js_flags = - Factory::NewStringFromUtf8(scanner_.next_literal(), TENURED); + Factory::NewStringFromUtf8(scanner().next_literal(), TENURED); Next(); return new RegExpLiteral(js_pattern, js_flags, literal_index); @@ -3150,7 +3197,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle<String> var_name, // FormalParameterList :: // '(' (Identifier)*[','] ')' Expect(Token::LPAREN, CHECK_OK); - int start_pos = scanner_.location().beg_pos; + int start_pos = scanner().location().beg_pos; bool done = (peek() == Token::RPAREN); while (!done) { Handle<String> param_name = ParseIdentifier(CHECK_OK); @@ -3187,7 +3234,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle<String> var_name, bool is_lazily_compiled = mode() == PARSE_LAZILY && top_scope_->HasTrivialOuterContext(); - int function_block_pos = scanner_.location().beg_pos; + int function_block_pos = scanner().location().beg_pos; int materialized_literal_count; int expected_property_count; int end_pos; @@ -3204,7 +3251,8 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle<String> var_name, ReportInvalidPreparseData(name, CHECK_OK); } Counters::total_preparse_skipped.Increment(end_pos - function_block_pos); - scanner_.SeekForward(end_pos); + // Seek to position just before terminal '}'. + scanner().SeekForward(end_pos - 1); materialized_literal_count = entry.literal_count(); expected_property_count = entry.property_count(); only_simple_this_property_assignments = false; @@ -3220,7 +3268,7 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle<String> var_name, this_property_assignments = temp_scope.this_property_assignments(); Expect(Token::RBRACE, CHECK_OK); - end_pos = scanner_.location().end_pos; + end_pos = scanner().location().end_pos; } FunctionLiteral* function_literal = @@ -3324,7 +3372,7 @@ void Parser::ExpectSemicolon(bool* ok) { Next(); return; } - if (scanner_.has_line_terminator_before_next() || + if (scanner().has_line_terminator_before_next() || tok == Token::RBRACE || tok == Token::EOS) { return; @@ -3375,8 +3423,8 @@ Handle<String> Parser::ParseIdentifierOrGetOrSet(bool* is_get, bool* ok) { Expect(Token::IDENTIFIER, ok); if (!*ok) return Handle<String>(); - if (scanner_.literal_length() == 3) { - const char* token = scanner_.literal_string(); + if (scanner().literal_length() == 3) { + const char* token = scanner().literal_string(); *is_get = strcmp(token, "get") == 0; *is_set = !*is_get && strcmp(token, "set") == 0; } @@ -3495,12 +3543,13 @@ Expression* Parser::NewThrowError(Handle<String> constructor, // ---------------------------------------------------------------------------- // JSON -Handle<Object> JsonParser::ParseJson(Handle<String> source) { - source->TryFlatten(); +Handle<Object> JsonParser::ParseJson(Handle<String> script, + UC16CharacterStream* source) { scanner_.Initialize(source); + stack_overflow_ = false; Handle<Object> result = ParseJsonValue(); if (result.is_null() || scanner_.Next() != Token::EOS) { - if (scanner_.stack_overflow()) { + if (stack_overflow_) { // Scanner failed. Top::StackOverflow(); } else { @@ -3531,7 +3580,7 @@ Handle<Object> JsonParser::ParseJson(Handle<String> source) { } Scanner::Location source_location = scanner_.location(); - MessageLocation location(Factory::NewScript(source), + MessageLocation location(Factory::NewScript(script), source_location.beg_pos, source_location.end_pos); int argc = (name_opt == NULL) ? 0 : 1; @@ -3598,6 +3647,10 @@ Handle<Object> JsonParser::ParseJsonObject() { if (scanner_.peek() == Token::RBRACE) { scanner_.Next(); } else { + if (StackLimitCheck().HasOverflowed()) { + stack_overflow_ = true; + return Handle<Object>::null(); + } do { if (scanner_.Next() != Token::STRING) { return ReportUnexpectedToken(); @@ -3632,6 +3685,10 @@ Handle<Object> JsonParser::ParseJsonArray() { if (token == Token::RBRACK) { scanner_.Next(); } else { + if (StackLimitCheck().HasOverflowed()) { + stack_overflow_ = true; + return Handle<Object>::null(); + } do { Handle<Object> element = ParseJsonValue(); if (element.is_null()) return Handle<Object>::null(); @@ -3673,7 +3730,7 @@ RegExpParser::RegExpParser(FlatStringReader* in, contains_anchor_(false), is_scanned_for_captures_(false), failed_(false) { - Advance(1); + Advance(); } @@ -3711,8 +3768,8 @@ void RegExpParser::Reset(int pos) { void RegExpParser::Advance(int dist) { - for (int i = 0; i < dist; i++) - Advance(); + next_pos_ += dist - 1; + Advance(); } @@ -4392,6 +4449,22 @@ CharacterRange RegExpParser::ParseClassAtom(uc16* char_class) { } +static const uc16 kNoCharClass = 0; + +// Adds range or pre-defined character class to character ranges. +// If char_class is not kInvalidClass, it's interpreted as a class +// escape (i.e., 's' means whitespace, from '\s'). +static inline void AddRangeOrEscape(ZoneList<CharacterRange>* ranges, + uc16 char_class, + CharacterRange range) { + if (char_class != kNoCharClass) { + CharacterRange::AddClassEscape(char_class, ranges); + } else { + ranges->Add(range); + } +} + + RegExpTree* RegExpParser::ParseCharacterClass() { static const char* kUnterminated = "Unterminated character class"; static const char* kRangeOutOfOrder = "Range out of order in character class"; @@ -4405,12 +4478,8 @@ RegExpTree* RegExpParser::ParseCharacterClass() { } ZoneList<CharacterRange>* ranges = new ZoneList<CharacterRange>(2); while (has_more() && current() != ']') { - uc16 char_class = 0; + uc16 char_class = kNoCharClass; CharacterRange first = ParseClassAtom(&char_class CHECK_FAILED); - if (char_class) { - CharacterRange::AddClassEscape(char_class, ranges); - continue; - } if (current() == '-') { Advance(); if (current() == kEndMarker) { @@ -4418,15 +4487,17 @@ RegExpTree* RegExpParser::ParseCharacterClass() { // following code report an error. break; } else if (current() == ']') { - ranges->Add(first); + AddRangeOrEscape(ranges, char_class, first); ranges->Add(CharacterRange::Singleton('-')); break; } - CharacterRange next = ParseClassAtom(&char_class CHECK_FAILED); - if (char_class) { - ranges->Add(first); + uc16 char_class_2 = kNoCharClass; + CharacterRange next = ParseClassAtom(&char_class_2 CHECK_FAILED); + if (char_class != kNoCharClass || char_class_2 != kNoCharClass) { + // Either end is an escaped character class. Treat the '-' verbatim. + AddRangeOrEscape(ranges, char_class, first); ranges->Add(CharacterRange::Singleton('-')); - CharacterRange::AddClassEscape(char_class, ranges); + AddRangeOrEscape(ranges, char_class_2, next); continue; } if (first.from() > next.to()) { @@ -4434,7 +4505,7 @@ RegExpTree* RegExpParser::ParseCharacterClass() { } ranges->Add(CharacterRange::Range(first.from(), next.to())); } else { - ranges->Add(first); + AddRangeOrEscape(ranges, char_class, first); } } if (!has_more()) { @@ -4524,15 +4595,17 @@ int ScriptDataImpl::ReadNumber(byte** source) { // Create a Scanner for the preparser to use as input, and preparse the source. -static ScriptDataImpl* DoPreParse(Handle<String> source, - unibrow::CharacterStream* stream, +static ScriptDataImpl* DoPreParse(UC16CharacterStream* source, bool allow_lazy, ParserRecorder* recorder, int literal_flags) { V8JavaScriptScanner scanner; - scanner.Initialize(source, stream, literal_flags); - preparser::PreParser preparser; - if (!preparser.PreParseProgram(&scanner, recorder, allow_lazy)) { + scanner.Initialize(source, literal_flags); + intptr_t stack_limit = StackGuard::real_climit(); + if (!preparser::PreParser::PreParseProgram(&scanner, + recorder, + allow_lazy, + stack_limit)) { Top::StackOverflow(); return NULL; } @@ -4546,8 +4619,7 @@ static ScriptDataImpl* DoPreParse(Handle<String> source, // Preparse, but only collect data that is immediately useful, // even if the preparser data is only used once. -ScriptDataImpl* ParserApi::PartialPreParse(Handle<String> source, - unibrow::CharacterStream* stream, +ScriptDataImpl* ParserApi::PartialPreParse(UC16CharacterStream* source, v8::Extension* extension) { bool allow_lazy = FLAG_lazy && (extension == NULL); if (!allow_lazy) { @@ -4556,22 +4628,19 @@ ScriptDataImpl* ParserApi::PartialPreParse(Handle<String> source, return NULL; } PartialParserRecorder recorder; - - return DoPreParse(source, stream, allow_lazy, &recorder, + return DoPreParse(source, allow_lazy, &recorder, JavaScriptScanner::kNoLiterals); } -ScriptDataImpl* ParserApi::PreParse(Handle<String> source, - unibrow::CharacterStream* stream, +ScriptDataImpl* ParserApi::PreParse(UC16CharacterStream* source, v8::Extension* extension) { Handle<Script> no_script; bool allow_lazy = FLAG_lazy && (extension == NULL); CompleteParserRecorder recorder; int kPreParseLiteralsFlags = JavaScriptScanner::kLiteralString | JavaScriptScanner::kLiteralIdentifier; - return DoPreParse(source, stream, allow_lazy, - &recorder, kPreParseLiteralsFlags); + return DoPreParse(source, allow_lazy, &recorder, kPreParseLiteralsFlags); } diff --git a/src/parser.h b/src/parser.h index a067bd7c..70d0e18f 100644 --- a/src/parser.h +++ b/src/parser.h @@ -169,14 +169,12 @@ class ParserApi { static bool Parse(CompilationInfo* info); // Generic preparser generating full preparse data. - static ScriptDataImpl* PreParse(Handle<String> source, - unibrow::CharacterStream* stream, + static ScriptDataImpl* PreParse(UC16CharacterStream* source, v8::Extension* extension); // Preparser that only does preprocessing that makes sense if only used // immediately after. - static ScriptDataImpl* PartialPreParse(Handle<String> source, - unibrow::CharacterStream* stream, + static ScriptDataImpl* PartialPreParse(UC16CharacterStream* source, v8::Extension* extension); }; @@ -435,18 +433,26 @@ class Parser { Vector<const char*> args); protected: + FunctionLiteral* ParseLazy(Handle<SharedFunctionInfo> info, + UC16CharacterStream* source, + ZoneScope* zone_scope); enum Mode { PARSE_LAZILY, PARSE_EAGERLY }; + // Called by ParseProgram after setting up the scanner. + FunctionLiteral* DoParseProgram(Handle<String> source, + bool in_global_context, + ZoneScope* zone_scope); + // Report syntax error void ReportUnexpectedToken(Token::Value token); void ReportInvalidPreparseData(Handle<String> name, bool* ok); void ReportMessage(const char* message, Vector<const char*> args); bool inside_with() const { return with_nesting_level_ > 0; } - Scanner& scanner() { return scanner_; } + V8JavaScriptScanner& scanner() { return scanner_; } Mode mode() const { return mode_; } ScriptDataImpl* pre_data() const { return pre_data_; } @@ -546,8 +552,27 @@ class Parser { // Magical syntax support. Expression* ParseV8Intrinsic(bool* ok); - INLINE(Token::Value peek()) { return scanner_.peek(); } - INLINE(Token::Value Next()) { return scanner_.NextCheckStack(); } + INLINE(Token::Value peek()) { + if (stack_overflow_) return Token::ILLEGAL; + return scanner().peek(); + } + + INLINE(Token::Value Next()) { + // BUG 1215673: Find a thread safe way to set a stack limit in + // pre-parse mode. Otherwise, we cannot safely pre-parse from other + // threads. + if (stack_overflow_) { + return Token::ILLEGAL; + } + if (StackLimitCheck().HasOverflowed()) { + // Any further calls to Next or peek will return the illegal token. + // The current call must return the next token, which might already + // have been peek'ed. + stack_overflow_ = true; + } + return scanner().Next(); + } + INLINE(void Consume(Token::Value token)); void Expect(Token::Value token, bool* ok); bool Check(Token::Value token); @@ -639,6 +664,7 @@ class Parser { bool is_pre_parsing_; ScriptDataImpl* pre_data_; FuncNameInferrer* fni_; + bool stack_overflow_; }; @@ -684,7 +710,14 @@ class JsonParser BASE_EMBEDDED { // Parse JSON input as a single JSON value. // Returns null handle and sets exception if parsing failed. static Handle<Object> Parse(Handle<String> source) { - return JsonParser().ParseJson(source); + if (source->IsExternalTwoByteString()) { + ExternalTwoByteStringUC16CharacterStream stream( + Handle<ExternalTwoByteString>::cast(source), 0, source->length()); + return JsonParser().ParseJson(source, &stream); + } else { + GenericStringUC16CharacterStream stream(source, 0, source->length()); + return JsonParser().ParseJson(source, &stream); + } } private: @@ -692,7 +725,7 @@ class JsonParser BASE_EMBEDDED { ~JsonParser() { } // Parse a string containing a single JSON value. - Handle<Object> ParseJson(Handle<String>); + Handle<Object> ParseJson(Handle<String> script, UC16CharacterStream* source); // Parse a single JSON value from input (grammar production JSONValue). // A JSON value is either a (double-quoted) string literal, a number literal, // one of "true", "false", or "null", or an object or array literal. @@ -718,6 +751,7 @@ class JsonParser BASE_EMBEDDED { Handle<String> GetString(); JsonScanner scanner_; + bool stack_overflow_; }; } } // namespace v8::internal diff --git a/src/platform-freebsd.cc b/src/platform-freebsd.cc index 0d89a16f..b58d0662 100644 --- a/src/platform-freebsd.cc +++ b/src/platform-freebsd.cc @@ -53,6 +53,7 @@ #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" namespace v8 { @@ -616,10 +617,9 @@ class Sampler::PlatformData : public Malloced { }; -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(); diff --git a/src/platform-linux.cc b/src/platform-linux.cc index cc7cbe5f..7efb25de 100644 --- a/src/platform-linux.cc +++ b/src/platform-linux.cc @@ -59,6 +59,7 @@ #include "platform.h" #include "top.h" #include "v8threads.h" +#include "vm-state-inl.h" namespace v8 { @@ -184,21 +185,10 @@ int OS::ActivationFrameAlignment() { } -#ifdef V8_TARGET_ARCH_ARM -// 0xffff0fa0 is the hard coded address of a function provided by -// the kernel which implements a memory barrier. On older -// ARM architecture revisions (pre-v6) this may be implemented using -// a syscall. This address is stable, and in active use (hard coded) -// by at least glibc-2.7 and the Android C library. -typedef void (*LinuxKernelMemoryBarrierFunc)(void); -LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) = - (LinuxKernelMemoryBarrierFunc) 0xffff0fa0; -#endif - void OS::ReleaseStore(volatile AtomicWord* ptr, AtomicWord value) { #if defined(V8_TARGET_ARCH_ARM) && defined(__arm__) // Only use on ARM hardware. - pLinuxKernelMemoryBarrier(); + MemoryBarrier(); #else __asm__ __volatile__("" : : : "memory"); // An x86 store acts as a release barrier. @@ -650,6 +640,16 @@ class LinuxMutex : public Mutex { return result; } + virtual bool TryLock() { + int result = pthread_mutex_trylock(&mutex_); + // Return false if the lock is busy and locking failed. + if (result == EBUSY) { + return false; + } + ASSERT(result == 0); // Verify no other errors. + return true; + } + private: pthread_mutex_t mutex_; // Pthread mutex for POSIX platforms. }; @@ -733,6 +733,7 @@ Semaphore* OS::CreateSemaphore(int count) { #ifdef ENABLE_LOGGING_AND_PROFILING static Sampler* active_sampler_ = NULL; +static int vm_tid_ = 0; #if !defined(__GLIBC__) && (defined(__arm__) || defined(__thumb__)) @@ -761,50 +762,51 @@ enum ArmRegisters {R15 = 15, R13 = 13, R11 = 11}; #endif +static int GetThreadID() { + // Glibc doesn't provide a wrapper for gettid(2). + return syscall(SYS_gettid); +} + + static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) { #ifndef V8_HOST_ARCH_MIPS USE(info); if (signal != SIGPROF) return; - if (active_sampler_ == NULL) return; + if (active_sampler_ == NULL || !active_sampler_->IsActive()) return; + if (vm_tid_ != GetThreadID()) return; TickSample sample_obj; TickSample* sample = CpuProfiler::TickSampleEvent(); if (sample == NULL) sample = &sample_obj; - // We always sample the VM state. - sample->state = VMState::current_state(); - - // If profiling, we extract the current pc and sp. - if (active_sampler_->IsProfiling()) { - // Extracting the sample from the context is extremely machine dependent. - ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context); - mcontext_t& mcontext = ucontext->uc_mcontext; + // Extracting the sample from the context is extremely machine dependent. + ucontext_t* ucontext = reinterpret_cast<ucontext_t*>(context); + mcontext_t& mcontext = ucontext->uc_mcontext; + sample->state = Top::current_vm_state(); #if V8_HOST_ARCH_IA32 - sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_EIP]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_ESP]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_EBP]); + sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_EIP]); + sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_ESP]); + sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_EBP]); #elif V8_HOST_ARCH_X64 - sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_RIP]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_RSP]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_RBP]); + sample->pc = reinterpret_cast<Address>(mcontext.gregs[REG_RIP]); + sample->sp = reinterpret_cast<Address>(mcontext.gregs[REG_RSP]); + sample->fp = reinterpret_cast<Address>(mcontext.gregs[REG_RBP]); #elif V8_HOST_ARCH_ARM // An undefined macro evaluates to 0, so this applies to Android's Bionic also. #if (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3)) - sample->pc = reinterpret_cast<Address>(mcontext.gregs[R15]); - sample->sp = reinterpret_cast<Address>(mcontext.gregs[R13]); - sample->fp = reinterpret_cast<Address>(mcontext.gregs[R11]); + sample->pc = reinterpret_cast<Address>(mcontext.gregs[R15]); + sample->sp = reinterpret_cast<Address>(mcontext.gregs[R13]); + sample->fp = reinterpret_cast<Address>(mcontext.gregs[R11]); #else - sample->pc = reinterpret_cast<Address>(mcontext.arm_pc); - sample->sp = reinterpret_cast<Address>(mcontext.arm_sp); - sample->fp = reinterpret_cast<Address>(mcontext.arm_fp); + sample->pc = reinterpret_cast<Address>(mcontext.arm_pc); + sample->sp = reinterpret_cast<Address>(mcontext.arm_sp); + sample->fp = reinterpret_cast<Address>(mcontext.arm_fp); #endif #elif V8_HOST_ARCH_MIPS - // Implement this on MIPS. - UNIMPLEMENTED(); + // Implement this on MIPS. + UNIMPLEMENTED(); #endif - active_sampler_->SampleStack(sample); - } - + active_sampler_->SampleStack(sample); active_sampler_->Tick(sample); #endif } @@ -812,43 +814,64 @@ static void ProfilerSignalHandler(int signal, siginfo_t* info, void* context) { class Sampler::PlatformData : public Malloced { public: + enum SleepInterval { + FULL_INTERVAL, + HALF_INTERVAL + }; + explicit PlatformData(Sampler* sampler) : sampler_(sampler), signal_handler_installed_(false), vm_tgid_(getpid()), - // Glibc doesn't provide a wrapper for gettid(2). - vm_tid_(syscall(SYS_gettid)), signal_sender_launched_(false) { } void SignalSender() { while (sampler_->IsActive()) { - // Glibc doesn't provide a wrapper for tgkill(2). - syscall(SYS_tgkill, vm_tgid_, vm_tid_, SIGPROF); - // Convert ms to us and subtract 100 us to compensate delays - // occuring during signal delivery. - const useconds_t interval = sampler_->interval_ * 1000 - 100; - int result = usleep(interval); -#ifdef DEBUG - if (result != 0 && errno != EINTR) { - fprintf(stderr, - "SignalSender usleep error; interval = %u, errno = %d\n", - interval, - errno); - ASSERT(result == 0 || errno == EINTR); + if (rate_limiter_.SuspendIfNecessary()) continue; + if (sampler_->IsProfiling() && RuntimeProfiler::IsEnabled()) { + SendProfilingSignal(); + Sleep(HALF_INTERVAL); + RuntimeProfiler::NotifyTick(); + Sleep(HALF_INTERVAL); + } else { + if (sampler_->IsProfiling()) SendProfilingSignal(); + if (RuntimeProfiler::IsEnabled()) RuntimeProfiler::NotifyTick(); + Sleep(FULL_INTERVAL); } -#endif - USE(result); } } + void SendProfilingSignal() { + // Glibc doesn't provide a wrapper for tgkill(2). + syscall(SYS_tgkill, vm_tgid_, vm_tid_, SIGPROF); + } + + void Sleep(SleepInterval full_or_half) { + // Convert ms to us and subtract 100 us to compensate delays + // occuring during signal delivery. + useconds_t interval = sampler_->interval_ * 1000 - 100; + if (full_or_half == HALF_INTERVAL) interval /= 2; + int result = usleep(interval); +#ifdef DEBUG + if (result != 0 && errno != EINTR) { + fprintf(stderr, + "SignalSender usleep error; interval = %u, errno = %d\n", + interval, + errno); + ASSERT(result == 0 || errno == EINTR); + } +#endif + USE(result); + } + Sampler* sampler_; bool signal_handler_installed_; struct sigaction old_signal_handler_; int vm_tgid_; - int vm_tid_; bool signal_sender_launched_; pthread_t signal_sender_thread_; + RuntimeProfilerRateLimiter rate_limiter_; }; @@ -860,10 +883,9 @@ static void* SenderEntry(void* arg) { } -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(this); @@ -879,7 +901,8 @@ Sampler::~Sampler() { void Sampler::Start() { // There can only be one active sampler at the time on POSIX // platforms. - if (active_sampler_ != NULL) return; + ASSERT(!IsActive()); + vm_tid_ = GetThreadID(); // Request profiling signals. struct sigaction sa; @@ -892,7 +915,7 @@ void Sampler::Start() { // Start a thread that sends SIGPROF signal to VM thread. // Sending the signal ourselves instead of relying on itimer provides // much better accuracy. - active_ = true; + SetActive(true); if (pthread_create( &data_->signal_sender_thread_, NULL, SenderEntry, data_) == 0) { data_->signal_sender_launched_ = true; @@ -904,11 +927,12 @@ void Sampler::Start() { void Sampler::Stop() { - active_ = false; + SetActive(false); // Wait for signal sender termination (it will exit after setting // active_ to false). if (data_->signal_sender_launched_) { + Top::WakeUpRuntimeProfilerThreadBeforeShutdown(); pthread_join(data_->signal_sender_thread_, NULL); data_->signal_sender_launched_ = false; } diff --git a/src/platform-macos.cc b/src/platform-macos.cc index c3f21dc5..85c70882 100644 --- a/src/platform-macos.cc +++ b/src/platform-macos.cc @@ -57,6 +57,7 @@ #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" // Manually define these here as weak imports, rather than including execinfo.h. // This lets us launch on 10.4 which does not have these calls. @@ -483,11 +484,20 @@ class MacOSMutex : public Mutex { pthread_mutex_init(&mutex_, &attr); } - ~MacOSMutex() { pthread_mutex_destroy(&mutex_); } + virtual ~MacOSMutex() { pthread_mutex_destroy(&mutex_); } - int Lock() { return pthread_mutex_lock(&mutex_); } + virtual int Lock() { return pthread_mutex_lock(&mutex_); } + virtual int Unlock() { return pthread_mutex_unlock(&mutex_); } - int Unlock() { return pthread_mutex_unlock(&mutex_); } + virtual bool TryLock() { + int result = pthread_mutex_trylock(&mutex_); + // Return false if the lock is busy and locking failed. + if (result == EBUSY) { + return false; + } + ASSERT(result == 0); // Verify no other errors. + return true; + } private: pthread_mutex_t mutex_; @@ -554,40 +564,38 @@ class Sampler::PlatformData : public Malloced { mach_port_t task_self_; thread_act_t profiled_thread_; pthread_t sampler_thread_; + RuntimeProfilerRateLimiter rate_limiter_; // Sampler thread handler. void Runner() { - // Loop until the sampler is disengaged, keeping the specified - // sampling frequency. - for ( ; sampler_->IsActive(); OS::Sleep(sampler_->interval_)) { + while (sampler_->IsActive()) { + if (rate_limiter_.SuspendIfNecessary()) continue; + Sample(); + OS::Sleep(sampler_->interval_); + } + } + + void Sample() { + if (sampler_->IsProfiling()) { TickSample sample_obj; TickSample* sample = CpuProfiler::TickSampleEvent(); if (sample == NULL) sample = &sample_obj; - // If the sampler runs in sync with the JS thread, we try to - // suspend it. If we fail, we skip the current sample. - if (sampler_->IsSynchronous()) { - if (KERN_SUCCESS != thread_suspend(profiled_thread_)) continue; - } + if (KERN_SUCCESS != thread_suspend(profiled_thread_)) return; - // We always sample the VM state. - sample->state = VMState::current_state(); - - // If profiling, we record the pc and sp of the profiled thread. - if (sampler_->IsProfiling()) { #if V8_HOST_ARCH_X64 - thread_state_flavor_t flavor = x86_THREAD_STATE64; - x86_thread_state64_t state; - mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; + thread_state_flavor_t flavor = x86_THREAD_STATE64; + x86_thread_state64_t state; + mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; #if __DARWIN_UNIX03 #define REGISTER_FIELD(name) __r ## name #else #define REGISTER_FIELD(name) r ## name #endif // __DARWIN_UNIX03 #elif V8_HOST_ARCH_IA32 - thread_state_flavor_t flavor = i386_THREAD_STATE; - i386_thread_state_t state; - mach_msg_type_number_t count = i386_THREAD_STATE_COUNT; + thread_state_flavor_t flavor = i386_THREAD_STATE; + i386_thread_state_t state; + mach_msg_type_number_t count = i386_THREAD_STATE_COUNT; #if __DARWIN_UNIX03 #define REGISTER_FIELD(name) __e ## name #else @@ -597,24 +605,20 @@ class Sampler::PlatformData : public Malloced { #error Unsupported Mac OS X host architecture. #endif // V8_HOST_ARCH - if (thread_get_state(profiled_thread_, - flavor, - reinterpret_cast<natural_t*>(&state), - &count) == KERN_SUCCESS) { - sample->pc = reinterpret_cast<Address>(state.REGISTER_FIELD(ip)); - sample->sp = reinterpret_cast<Address>(state.REGISTER_FIELD(sp)); - sample->fp = reinterpret_cast<Address>(state.REGISTER_FIELD(bp)); - sampler_->SampleStack(sample); - } + if (thread_get_state(profiled_thread_, + flavor, + reinterpret_cast<natural_t*>(&state), + &count) == KERN_SUCCESS) { + sample->state = Top::current_vm_state(); + sample->pc = reinterpret_cast<Address>(state.REGISTER_FIELD(ip)); + sample->sp = reinterpret_cast<Address>(state.REGISTER_FIELD(sp)); + sample->fp = reinterpret_cast<Address>(state.REGISTER_FIELD(bp)); + sampler_->SampleStack(sample); + sampler_->Tick(sample); } - - // Invoke tick handler with program counter and stack pointer. - sampler_->Tick(sample); - - // If the sampler runs in sync with the JS thread, we have to - // remember to resume it. - if (sampler_->IsSynchronous()) thread_resume(profiled_thread_); + thread_resume(profiled_thread_); } + if (RuntimeProfiler::IsEnabled()) RuntimeProfiler::NotifyTick(); } }; @@ -630,10 +634,9 @@ static void* SamplerEntry(void* arg) { } -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(this); @@ -646,11 +649,9 @@ Sampler::~Sampler() { void Sampler::Start() { - // If we are starting a synchronous sampler, we need to be able to - // access the calling thread. - if (IsSynchronous()) { - data_->profiled_thread_ = mach_thread_self(); - } + // Do not start multiple threads for the same sampler. + ASSERT(!IsActive()); + data_->profiled_thread_ = mach_thread_self(); // Create sampler thread with high priority. // According to POSIX spec, when SCHED_FIFO policy is used, a thread @@ -663,7 +664,7 @@ void Sampler::Start() { fifo_param.sched_priority = sched_get_priority_max(SCHED_FIFO); pthread_attr_setschedparam(&sched_attr, &fifo_param); - active_ = true; + SetActive(true); pthread_create(&data_->sampler_thread_, &sched_attr, SamplerEntry, data_); } @@ -671,15 +672,14 @@ void Sampler::Start() { void Sampler::Stop() { // Seting active to false triggers termination of the sampler // thread. - active_ = false; + SetActive(false); // Wait for sampler thread to terminate. + Top::WakeUpRuntimeProfilerThreadBeforeShutdown(); pthread_join(data_->sampler_thread_, NULL); // Deallocate Mach port for thread. - if (IsSynchronous()) { - mach_port_deallocate(data_->task_self_, data_->profiled_thread_); - } + mach_port_deallocate(data_->task_self_, data_->profiled_thread_); } #endif // ENABLE_LOGGING_AND_PROFILING diff --git a/src/platform-nullos.cc b/src/platform-nullos.cc index b5caa5e1..72ea0e57 100644 --- a/src/platform-nullos.cc +++ b/src/platform-nullos.cc @@ -35,6 +35,7 @@ #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" namespace v8 { @@ -127,6 +128,19 @@ void OS::VPrint(const char* format, va_list args) { } +void OS::FPrint(FILE* out, const char* format, ...) { + va_list args; + va_start(args, format); + VFPrint(out, format, args); + va_end(args); +} + + +void OS::VFPrint(FILE* out, const char* format, va_list args) { + vfprintf(out, format, args); +} + + // Print error message to console. void OS::PrintError(const char* format, ...) { // Minimalistic implementation for bootstrapping. diff --git a/src/platform-openbsd.cc b/src/platform-openbsd.cc index 0751fc7e..b698d16b 100644 --- a/src/platform-openbsd.cc +++ b/src/platform-openbsd.cc @@ -52,6 +52,7 @@ #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" namespace v8 { @@ -571,10 +572,9 @@ class Sampler::PlatformData : public Malloced { }; -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(); diff --git a/src/platform-posix.cc b/src/platform-posix.cc index c50d396a..ab5c0a37 100644 --- a/src/platform-posix.cc +++ b/src/platform-posix.cc @@ -142,6 +142,23 @@ void OS::VPrint(const char* format, va_list args) { } +void OS::FPrint(FILE* out, const char* format, ...) { + va_list args; + va_start(args, format); + VFPrint(out, format, args); + va_end(args); +} + + +void OS::VFPrint(FILE* out, const char* format, va_list args) { +#if defined(ANDROID) + LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, format, args); +#else + vfprintf(out, format, args); +#endif +} + + void OS::PrintError(const char* format, ...) { va_list args; va_start(args, format); @@ -173,7 +190,9 @@ int OS::VSNPrintF(Vector<char> str, va_list args) { int n = vsnprintf(str.start(), str.length(), format, args); if (n < 0 || n >= str.length()) { - str[str.length() - 1] = '\0'; + // If the length is zero, the assignment fails. + if (str.length() > 0) + str[str.length() - 1] = '\0'; return -1; } else { return n; @@ -204,6 +223,14 @@ class POSIXSocket : public Socket { explicit POSIXSocket() { // Create the socket. socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (IsValid()) { + // Allow rapid reuse. + static const int kOn = 1; + int ret = setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, + &kOn, sizeof(kOn)); + ASSERT(ret == 0); + USE(ret); + } } explicit POSIXSocket(int socket): socket_(socket) { } virtual ~POSIXSocket() { Shutdown(); } diff --git a/src/platform-solaris.cc b/src/platform-solaris.cc index ff5d83b6..f84e80d1 100644 --- a/src/platform-solaris.cc +++ b/src/platform-solaris.cc @@ -52,6 +52,7 @@ #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" // It seems there is a bug in some Solaris distributions (experienced in @@ -601,10 +602,9 @@ class Sampler::PlatformData : public Malloced { }; -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(); diff --git a/src/platform-win32.cc b/src/platform-win32.cc index c50424e5..4438045e 100644 --- a/src/platform-win32.cc +++ b/src/platform-win32.cc @@ -26,70 +26,14 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Platform specific code for Win32. -#ifndef WIN32_LEAN_AND_MEAN -// WIN32_LEAN_AND_MEAN implies NOCRYPT and NOGDI. -#define WIN32_LEAN_AND_MEAN -#endif -#ifndef NOMINMAX -#define NOMINMAX -#endif -#ifndef NOKERNEL -#define NOKERNEL -#endif -#ifndef NOUSER -#define NOUSER -#endif -#ifndef NOSERVICE -#define NOSERVICE -#endif -#ifndef NOSOUND -#define NOSOUND -#endif -#ifndef NOMCX -#define NOMCX -#endif -// Require Windows XP or higher (this is required for the RtlCaptureContext -// function to be present). -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x501 -#endif -#include <windows.h> - -#include <time.h> // For LocalOffset() implementation. -#include <mmsystem.h> // For timeGetTime(). -#ifdef __MINGW32__ -// Require Windows XP or higher when compiling with MinGW. This is for MinGW -// header files to expose getaddrinfo. -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x501 -#endif // __MINGW32__ -#ifndef __MINGW32__ -#include <dbghelp.h> // For SymLoadModule64 and al. -#endif // __MINGW32__ -#include <limits.h> // For INT_MAX and al. -#include <tlhelp32.h> // For Module32First and al. - -// These additional WIN32 includes have to be right here as the #undef's below -// makes it impossible to have them elsewhere. -#include <winsock2.h> -#include <ws2tcpip.h> -#include <process.h> // for _beginthreadex() -#include <stdlib.h> - -#undef VOID -#undef DELETE -#undef IN -#undef THIS -#undef CONST -#undef NAN -#undef GetObject -#undef CreateMutex -#undef CreateSemaphore +#define V8_WIN32_HEADERS_FULL +#include "win32-headers.h" #include "v8.h" #include "platform.h" +#include "vm-state-inl.h" // Extra POSIX/ANSI routines for Win32 when when using Visual Studio C++. Please // refer to The Open Group Base Specification for specification of the correct @@ -207,6 +151,12 @@ int strncpy_s(char* strDest, size_t numberOfElements, return 0; } + +inline void MemoryBarrier() { + int barrier = 0; + __asm__ __volatile__("xchgl %%eax,%0 ":"=r" (barrier)); +} + #endif // __MINGW32__ // Generate a pseudo-random number in the range 0-2^31-1. Usually @@ -738,6 +688,19 @@ void OS::VPrint(const char* format, va_list args) { } +void OS::FPrint(FILE* out, const char* format, ...) { + va_list args; + va_start(args, format); + VFPrint(out, format, args); + va_end(args); +} + + +void OS::VFPrint(FILE* out, const char* format, va_list args) { + VPrintHelper(out, format, args); +} + + // Print error message to console. void OS::PrintError(const char* format, ...) { va_list args; @@ -766,7 +729,8 @@ int OS::VSNPrintF(Vector<char> str, const char* format, va_list args) { // Make sure to zero-terminate the string if the output was // truncated or if there was an error. if (n < 0 || n >= str.length()) { - str[str.length() - 1] = '\0'; + if (str.length() > 0) + str[str.length() - 1] = '\0'; return -1; } else { return n; @@ -858,13 +822,14 @@ void* OS::Allocate(const size_t requested, // VirtualAlloc rounds allocated size to page size automatically. size_t msize = RoundUp(requested, static_cast<int>(GetPageSize())); - intptr_t address = NULL; + intptr_t address = 0; // Windows XP SP2 allows Data Excution Prevention (DEP). int prot = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; // For exectutable pages try and randomize the allocation address - if (prot == PAGE_EXECUTE_READWRITE && msize >= Page::kPageSize) { + if (prot == PAGE_EXECUTE_READWRITE && + msize >= static_cast<size_t>(Page::kPageSize)) { address = (V8::RandomPrivate() << kPageSizeBits) | kAllocationRandomAddressMin; address &= kAllocationRandomAddressMax; @@ -874,7 +839,7 @@ void* OS::Allocate(const size_t requested, msize, MEM_COMMIT | MEM_RESERVE, prot); - if (mbase == NULL && address != NULL) + if (mbase == NULL && address != 0) mbase = VirtualAlloc(NULL, msize, MEM_COMMIT | MEM_RESERVE, prot); if (mbase == NULL) { @@ -1155,7 +1120,7 @@ static bool LoadSymbols(HANDLE process_handle) { // Initialize the symbol engine. ok = _SymInitialize(process_handle, // hProcess NULL, // UserSearchPath - FALSE); // fInvadeProcess + false); // fInvadeProcess if (!ok) return false; DWORD options = _SymGetOptions(); @@ -1347,6 +1312,7 @@ int OS::StackWalk(Vector<OS::StackFrame> frames) { #else // __MINGW32__ void OS::LogSharedLibraryAddresses() { } +void OS::SignalCodeMovingGC() { } int OS::StackWalk(Vector<OS::StackFrame> frames) { return 0; } #endif // __MINGW32__ @@ -1414,7 +1380,7 @@ bool VirtualMemory::Commit(void* address, size_t size, bool is_executable) { bool VirtualMemory::Uncommit(void* address, size_t size) { ASSERT(IsReserved()); - return VirtualFree(address, size, MEM_DECOMMIT) != FALSE; + return VirtualFree(address, size, MEM_DECOMMIT) != false; } @@ -1574,18 +1540,24 @@ class Win32Mutex : public Mutex { Win32Mutex() { InitializeCriticalSection(&cs_); } - ~Win32Mutex() { DeleteCriticalSection(&cs_); } + virtual ~Win32Mutex() { DeleteCriticalSection(&cs_); } - int Lock() { + virtual int Lock() { EnterCriticalSection(&cs_); return 0; } - int Unlock() { + virtual int Unlock() { LeaveCriticalSection(&cs_); return 0; } + + virtual bool TryLock() { + // Returns non-zero if critical section is entered successfully entered. + return TryEnterCriticalSection(&cs_); + } + private: CRITICAL_SECTION cs_; // Critical section used for mutex }; @@ -1768,7 +1740,7 @@ int Win32Socket::Receive(char* data, int len) const { bool Win32Socket::SetReuseAddress(bool reuse_address) { - BOOL on = reuse_address ? TRUE : FALSE; + BOOL on = reuse_address ? true : false; int status = setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&on), sizeof(on)); return status == SOCKET_ERROR; @@ -1838,53 +1810,48 @@ class Sampler::PlatformData : public Malloced { Sampler* sampler_; HANDLE sampler_thread_; HANDLE profiled_thread_; + RuntimeProfilerRateLimiter rate_limiter_; // Sampler thread handler. void Runner() { - // Context used for sampling the register state of the profiled thread. - CONTEXT context; - memset(&context, 0, sizeof(context)); - // Loop until the sampler is disengaged, keeping the specified - // sampling frequency. - for ( ; sampler_->IsActive(); Sleep(sampler_->interval_)) { + while (sampler_->IsActive()) { + if (rate_limiter_.SuspendIfNecessary()) continue; + Sample(); + Sleep(sampler_->interval_); + } + } + + void Sample() { + if (sampler_->IsProfiling()) { + // Context used for sampling the register state of the profiled thread. + CONTEXT context; + memset(&context, 0, sizeof(context)); + TickSample sample_obj; TickSample* sample = CpuProfiler::TickSampleEvent(); if (sample == NULL) sample = &sample_obj; - // If the sampler runs in sync with the JS thread, we try to - // suspend it. If we fail, we skip the current sample. - if (sampler_->IsSynchronous()) { - static const DWORD kSuspendFailed = static_cast<DWORD>(-1); - if (SuspendThread(profiled_thread_) == kSuspendFailed) continue; - } - - // We always sample the VM state. - sample->state = VMState::current_state(); + static const DWORD kSuspendFailed = static_cast<DWORD>(-1); + if (SuspendThread(profiled_thread_) == kSuspendFailed) return; + sample->state = Top::current_vm_state(); - // If profiling, we record the pc and sp of the profiled thread. - if (sampler_->IsProfiling()) { - context.ContextFlags = CONTEXT_FULL; - if (GetThreadContext(profiled_thread_, &context) != 0) { + context.ContextFlags = CONTEXT_FULL; + if (GetThreadContext(profiled_thread_, &context) != 0) { #if V8_HOST_ARCH_X64 - sample->pc = reinterpret_cast<Address>(context.Rip); - sample->sp = reinterpret_cast<Address>(context.Rsp); - sample->fp = reinterpret_cast<Address>(context.Rbp); + sample->pc = reinterpret_cast<Address>(context.Rip); + sample->sp = reinterpret_cast<Address>(context.Rsp); + sample->fp = reinterpret_cast<Address>(context.Rbp); #else - sample->pc = reinterpret_cast<Address>(context.Eip); - sample->sp = reinterpret_cast<Address>(context.Esp); - sample->fp = reinterpret_cast<Address>(context.Ebp); + sample->pc = reinterpret_cast<Address>(context.Eip); + sample->sp = reinterpret_cast<Address>(context.Esp); + sample->fp = reinterpret_cast<Address>(context.Ebp); #endif - sampler_->SampleStack(sample); - } + sampler_->SampleStack(sample); + sampler_->Tick(sample); } - - // Invoke tick handler with program counter and stack pointer. - sampler_->Tick(sample); - - // If the sampler runs in sync with the JS thread, we have to - // remember to resume it. - if (sampler_->IsSynchronous()) ResumeThread(profiled_thread_); + ResumeThread(profiled_thread_); } + if (RuntimeProfiler::IsEnabled()) RuntimeProfiler::NotifyTick(); } }; @@ -1899,10 +1866,9 @@ static unsigned int __stdcall SamplerEntry(void* arg) { // Initialize a profile sampler. -Sampler::Sampler(int interval, bool profiling) +Sampler::Sampler(int interval) : interval_(interval), - profiling_(profiling), - synchronous_(profiling), + profiling_(false), active_(false), samples_taken_(0) { data_ = new PlatformData(this); @@ -1916,26 +1882,25 @@ Sampler::~Sampler() { // Start profiling. void Sampler::Start() { - // If we are starting a synchronous sampler, we need to be able to - // access the calling thread. - if (IsSynchronous()) { - // Get a handle to the calling thread. This is the thread that we are - // going to profile. We need to make a copy of the handle because we are - // going to use it in the sampler thread. Using GetThreadHandle() will - // not work in this case. We're using OpenThread because DuplicateHandle - // for some reason doesn't work in Chrome's sandbox. - data_->profiled_thread_ = OpenThread(THREAD_GET_CONTEXT | - THREAD_SUSPEND_RESUME | - THREAD_QUERY_INFORMATION, - FALSE, - GetCurrentThreadId()); - BOOL ok = data_->profiled_thread_ != NULL; - if (!ok) return; - } + // Do not start multiple threads for the same sampler. + ASSERT(!IsActive()); + + // Get a handle to the calling thread. This is the thread that we are + // going to profile. We need to make a copy of the handle because we are + // going to use it in the sampler thread. Using GetThreadHandle() will + // not work in this case. We're using OpenThread because DuplicateHandle + // for some reason doesn't work in Chrome's sandbox. + data_->profiled_thread_ = OpenThread(THREAD_GET_CONTEXT | + THREAD_SUSPEND_RESUME | + THREAD_QUERY_INFORMATION, + false, + GetCurrentThreadId()); + BOOL ok = data_->profiled_thread_ != NULL; + if (!ok) return; // Start sampler thread. unsigned int tid; - active_ = true; + SetActive(true); data_->sampler_thread_ = reinterpret_cast<HANDLE>( _beginthreadex(NULL, 0, SamplerEntry, data_, 0, &tid)); // Set thread to high priority to increase sampling accuracy. @@ -1947,9 +1912,10 @@ void Sampler::Start() { void Sampler::Stop() { // Seting active to false triggers termination of the sampler // thread. - active_ = false; + SetActive(false); // Wait for sampler thread to terminate. + Top::WakeUpRuntimeProfilerThreadBeforeShutdown(); WaitForSingleObject(data_->sampler_thread_, INFINITE); // Release the thread handles diff --git a/src/platform.h b/src/platform.h index 49efc3c2..5a3e4a3f 100644 --- a/src/platform.h +++ b/src/platform.h @@ -113,6 +113,10 @@ int signbit(double x); #endif // __GNUC__ +#include "atomicops.h" +#include "utils.h" +#include "v8globals.h" + namespace v8 { namespace internal { @@ -121,6 +125,7 @@ namespace internal { typedef intptr_t AtomicWord; class Semaphore; +class Mutex; double ceiling(double x); double modulo(double x, double y); @@ -179,6 +184,10 @@ class OS { static void Print(const char* format, ...); static void VPrint(const char* format, va_list args); + // Print output to a file. This is mostly used for debugging output. + static void FPrint(FILE* out, const char* format, ...); + static void VFPrint(FILE* out, const char* format, va_list args); + // Print error output to console. This is mostly used for error message // output. On platforms that has standard terminal output, the output // should go to stderr. @@ -433,6 +442,10 @@ class Mutex { // Unlocks the given mutex. The mutex is assumed to be locked and owned by // the calling thread on entrance. virtual int Unlock() = 0; + + // Tries to lock the given mutex. Returns whether the mutex was + // successfully locked. + virtual bool TryLock() = 0; }; @@ -554,7 +567,7 @@ class TickSample { class Sampler { public: // Initialize sampler. - Sampler(int interval, bool profiling); + explicit Sampler(int interval); virtual ~Sampler(); // Performs stack sampling. @@ -572,16 +585,12 @@ class Sampler { void Stop(); // Is the sampler used for profiling? - bool IsProfiling() const { return profiling_; } - - // Is the sampler running in sync with the JS thread? On platforms - // where the sampler is implemented with a thread that wakes up - // every now and then, having a synchronous sampler implies - // suspending/resuming the JS thread. - bool IsSynchronous() const { return synchronous_; } + bool IsProfiling() const { return NoBarrier_Load(&profiling_) > 0; } + void IncreaseProfilingDepth() { NoBarrier_AtomicIncrement(&profiling_, 1); } + void DecreaseProfilingDepth() { NoBarrier_AtomicIncrement(&profiling_, -1); } // Whether the sampler is running (that is, consumes resources). - bool IsActive() const { return active_; } + bool IsActive() const { return NoBarrier_Load(&active_); } // Used in tests to make sure that stack sampling is performed. int samples_taken() const { return samples_taken_; } @@ -593,12 +602,12 @@ class Sampler { virtual void DoSampleStack(TickSample* sample) = 0; private: + void SetActive(bool value) { NoBarrier_Store(&active_, value); } void IncSamplesTaken() { if (++samples_taken_ < 0) samples_taken_ = 0; } const int interval_; - const bool profiling_; - const bool synchronous_; - bool active_; + Atomic32 profiling_; + Atomic32 active_; PlatformData* data_; // Platform specific data. int samples_taken_; // Counts stack samples taken. DISALLOW_IMPLICIT_CONSTRUCTORS(Sampler); diff --git a/src/preparser-api.cc b/src/preparser-api.cc new file mode 100644 index 00000000..cbec9b70 --- /dev/null +++ b/src/preparser-api.cc @@ -0,0 +1,209 @@ +// Copyright 2010 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. + +#include "../include/v8-preparser.h" +#include "globals.h" +#include "checks.h" +#include "allocation.h" +#include "utils.h" +#include "list.h" +#include "scanner-base.h" +#include "preparse-data.h" +#include "preparser.h" + +namespace v8 { +namespace internal { + +// UTF16Buffer based on a v8::UnicodeInputStream. +class InputStreamUTF16Buffer : public UC16CharacterStream { + public: + /* The InputStreamUTF16Buffer maintains an internal buffer + * that is filled in chunks from the UC16CharacterStream. + * It also maintains unlimited pushback capability, but optimized + * for small pushbacks. + * The pushback_buffer_ pointer points to the limit of pushbacks + * in the current buffer. There is room for a few pushback'ed chars before + * the buffer containing the most recently read chunk. If this is overflowed, + * an external buffer is allocated/reused to hold further pushbacks, and + * pushback_buffer_ and buffer_cursor_/buffer_end_ now points to the + * new buffer. When this buffer is read to the end again, the cursor is + * switched back to the internal buffer + */ + explicit InputStreamUTF16Buffer(v8::UnicodeInputStream* stream) + : UC16CharacterStream(), + stream_(stream), + pushback_buffer_(buffer_), + pushback_buffer_end_cache_(NULL), + pushback_buffer_backing_(NULL), + pushback_buffer_backing_size_(0) { + buffer_cursor_ = buffer_end_ = buffer_ + kPushBackSize; + } + + virtual ~InputStreamUTF16Buffer() { + if (pushback_buffer_backing_ != NULL) { + DeleteArray(pushback_buffer_backing_); + } + } + + virtual void PushBack(uc16 ch) { + ASSERT(pos_ > 0); + if (buffer_cursor_ <= pushback_buffer_) { + // No more room in the current buffer to do pushbacks. + if (pushback_buffer_end_cache_ == NULL) { + // We have overflowed the pushback space at the beginning of buffer_. + // Switch to using a separate allocated pushback buffer. + if (pushback_buffer_backing_ == NULL) { + // Allocate a buffer the first time we need it. + pushback_buffer_backing_ = NewArray<uc16>(kPushBackSize); + pushback_buffer_backing_size_ = kPushBackSize; + } + pushback_buffer_ = pushback_buffer_backing_; + pushback_buffer_end_cache_ = buffer_end_; + buffer_end_ = pushback_buffer_backing_ + pushback_buffer_backing_size_; + buffer_cursor_ = buffer_end_ - 1; + } else { + // Hit the bottom of the allocated pushback buffer. + // Double the buffer and continue. + uc16* new_buffer = NewArray<uc16>(pushback_buffer_backing_size_ * 2); + memcpy(new_buffer + pushback_buffer_backing_size_, + pushback_buffer_backing_, + pushback_buffer_backing_size_); + DeleteArray(pushback_buffer_backing_); + buffer_cursor_ = new_buffer + pushback_buffer_backing_size_; + pushback_buffer_backing_ = pushback_buffer_ = new_buffer; + buffer_end_ = pushback_buffer_backing_ + pushback_buffer_backing_size_; + } + } + pushback_buffer_[buffer_cursor_ - pushback_buffer_- 1] = ch; + pos_--; + } + + protected: + virtual bool ReadBlock() { + if (pushback_buffer_end_cache_ != NULL) { + buffer_cursor_ = buffer_; + buffer_end_ = pushback_buffer_end_cache_; + pushback_buffer_end_cache_ = NULL; + return buffer_end_ > buffer_cursor_; + } + // Copy the top of the buffer into the pushback area. + int32_t value; + uc16* buffer_start = buffer_ + kPushBackSize; + buffer_cursor_ = buffer_end_ = buffer_start; + while ((value = stream_->Next()) >= 0) { + if (value > static_cast<int32_t>(unibrow::Utf8::kMaxThreeByteChar)) { + value = unibrow::Utf8::kBadChar; + } + // buffer_end_ is a const pointer, but buffer_ is writable. + buffer_start[buffer_end_++ - buffer_start] = static_cast<uc16>(value); + if (buffer_end_ == buffer_ + kPushBackSize + kBufferSize) break; + } + return buffer_end_ > buffer_start; + } + + virtual unsigned SlowSeekForward(unsigned pos) { + // Seeking in the input is not used by preparsing. + // It's only used by the real parser based on preparser data. + UNIMPLEMENTED(); + return 0; + } + + private: + static const unsigned kBufferSize = 512; + static const unsigned kPushBackSize = 16; + v8::UnicodeInputStream* const stream_; + // Buffer holding first kPushBackSize characters of pushback buffer, + // then kBufferSize chars of read-ahead. + // The pushback buffer is only used if pushing back characters past + // the start of a block. + uc16 buffer_[kPushBackSize + kBufferSize]; + // Limit of pushbacks before new allocation is necessary. + uc16* pushback_buffer_; + // Only if that pushback buffer at the start of buffer_ isn't sufficient + // is the following used. + const uc16* pushback_buffer_end_cache_; + uc16* pushback_buffer_backing_; + unsigned pushback_buffer_backing_size_; +}; + + +class StandAloneJavaScriptScanner : public JavaScriptScanner { + public: + void Initialize(UC16CharacterStream* source) { + source_ = source; + literal_flags_ = kLiteralString | kLiteralIdentifier; + Init(); + // Skip initial whitespace allowing HTML comment ends just like + // after a newline and scan first token. + has_line_terminator_before_next_ = true; + SkipWhiteSpace(); + Scan(); + } +}; + + +// Functions declared by allocation.h + +void FatalProcessOutOfMemory(const char* reason) { + V8_Fatal(__FILE__, __LINE__, reason); +} + +bool EnableSlowAsserts() { return true; } + +} // namespace internal. + + +UnicodeInputStream::~UnicodeInputStream() { } + + +PreParserData Preparse(UnicodeInputStream* input, size_t max_stack) { + internal::InputStreamUTF16Buffer buffer(input); + uintptr_t stack_limit = reinterpret_cast<uintptr_t>(&buffer) - max_stack; + internal::StandAloneJavaScriptScanner scanner; + scanner.Initialize(&buffer); + internal::CompleteParserRecorder recorder; + preparser::PreParser::PreParseResult result = + preparser::PreParser::PreParseProgram(&scanner, + &recorder, + true, + stack_limit); + if (result == preparser::PreParser::kPreParseStackOverflow) { + return PreParserData::StackOverflow(); + } + internal::Vector<unsigned> pre_data = recorder.ExtractData(); + size_t size = pre_data.length() * sizeof(pre_data[0]); + unsigned char* data = reinterpret_cast<unsigned char*>(pre_data.start()); + return PreParserData(size, data); +} + +} // namespace v8. + + +// Used by ASSERT macros and other immediate exits. +extern "C" void V8_Fatal(const char* file, int line, const char* format, ...) { + exit(EXIT_FAILURE); +} diff --git a/src/preparser.cc b/src/preparser.cc index 90617312..7cce685e 100644 --- a/src/preparser.cc +++ b/src/preparser.cc @@ -65,7 +65,7 @@ void PreParser::ReportUnexpectedToken(i::Token::Value token) { // We don't report stack overflows here, to avoid increasing the // stack depth even further. Instead we report it after parsing is // over, in ParseProgram. - if (token == i::Token::ILLEGAL && scanner_->stack_overflow()) { + if (token == i::Token::ILLEGAL && stack_overflow_) { return; } i::JavaScriptScanner::Location source_location = scanner_->location(); @@ -92,8 +92,8 @@ void PreParser::ReportUnexpectedToken(i::Token::Value token) { } -SourceElements PreParser::ParseSourceElements(int end_token, - bool* ok) { +PreParser::SourceElements PreParser::ParseSourceElements(int end_token, + bool* ok) { // SourceElements :: // (Statement)* <end_token> @@ -104,7 +104,7 @@ SourceElements PreParser::ParseSourceElements(int end_token, } -Statement PreParser::ParseStatement(bool* ok) { +PreParser::Statement PreParser::ParseStatement(bool* ok) { // Statement :: // Block // VariableStatement @@ -190,7 +190,7 @@ Statement PreParser::ParseStatement(bool* ok) { } -Statement PreParser::ParseFunctionDeclaration(bool* ok) { +PreParser::Statement PreParser::ParseFunctionDeclaration(bool* ok) { // FunctionDeclaration :: // 'function' Identifier '(' FormalParameterListopt ')' '{' FunctionBody '}' Expect(i::Token::FUNCTION, CHECK_OK); @@ -204,7 +204,7 @@ Statement PreParser::ParseFunctionDeclaration(bool* ok) { // through the API's extension mechanism. A native function // declaration is resolved by looking up the function through a // callback provided by the extension. -Statement PreParser::ParseNativeDeclaration(bool* ok) { +PreParser::Statement PreParser::ParseNativeDeclaration(bool* ok) { Expect(i::Token::NATIVE, CHECK_OK); Expect(i::Token::FUNCTION, CHECK_OK); ParseIdentifier(CHECK_OK); @@ -223,7 +223,7 @@ Statement PreParser::ParseNativeDeclaration(bool* ok) { } -Statement PreParser::ParseBlock(bool* ok) { +PreParser::Statement PreParser::ParseBlock(bool* ok) { // Block :: // '{' Statement* '}' @@ -239,7 +239,7 @@ Statement PreParser::ParseBlock(bool* ok) { } -Statement PreParser::ParseVariableStatement(bool* ok) { +PreParser::Statement PreParser::ParseVariableStatement(bool* ok) { // VariableStatement :: // VariableDeclarations ';' @@ -254,9 +254,9 @@ Statement PreParser::ParseVariableStatement(bool* ok) { // *var is untouched; in particular, it is the caller's responsibility // to initialize it properly. This mechanism is also used for the parsing // of 'for-in' loops. -Statement PreParser::ParseVariableDeclarations(bool accept_IN, - int* num_decl, - bool* ok) { +PreParser::Statement PreParser::ParseVariableDeclarations(bool accept_IN, + int* num_decl, + bool* ok) { // VariableDeclarations :: // ('var' | 'const') (Identifier ('=' AssignmentExpression)?)+[','] @@ -288,7 +288,7 @@ Statement PreParser::ParseVariableDeclarations(bool accept_IN, } -Statement PreParser::ParseExpressionOrLabelledStatement( +PreParser::Statement PreParser::ParseExpressionOrLabelledStatement( bool* ok) { // ExpressionStatement | LabelledStatement :: // Expression ';' @@ -305,7 +305,7 @@ Statement PreParser::ParseExpressionOrLabelledStatement( } -Statement PreParser::ParseIfStatement(bool* ok) { +PreParser::Statement PreParser::ParseIfStatement(bool* ok) { // IfStatement :: // 'if' '(' Expression ')' Statement ('else' Statement)? @@ -322,7 +322,7 @@ Statement PreParser::ParseIfStatement(bool* ok) { } -Statement PreParser::ParseContinueStatement(bool* ok) { +PreParser::Statement PreParser::ParseContinueStatement(bool* ok) { // ContinueStatement :: // 'continue' [no line terminator] Identifier? ';' @@ -339,7 +339,7 @@ Statement PreParser::ParseContinueStatement(bool* ok) { } -Statement PreParser::ParseBreakStatement(bool* ok) { +PreParser::Statement PreParser::ParseBreakStatement(bool* ok) { // BreakStatement :: // 'break' [no line terminator] Identifier? ';' @@ -356,7 +356,7 @@ Statement PreParser::ParseBreakStatement(bool* ok) { } -Statement PreParser::ParseReturnStatement(bool* ok) { +PreParser::Statement PreParser::ParseReturnStatement(bool* ok) { // ReturnStatement :: // 'return' [no line terminator] Expression? ';' @@ -382,7 +382,7 @@ Statement PreParser::ParseReturnStatement(bool* ok) { } -Statement PreParser::ParseWithStatement(bool* ok) { +PreParser::Statement PreParser::ParseWithStatement(bool* ok) { // WithStatement :: // 'with' '(' Expression ')' Statement Expect(i::Token::WITH, CHECK_OK); @@ -397,7 +397,7 @@ Statement PreParser::ParseWithStatement(bool* ok) { } -Statement PreParser::ParseSwitchStatement(bool* ok) { +PreParser::Statement PreParser::ParseSwitchStatement(bool* ok) { // SwitchStatement :: // 'switch' '(' Expression ')' '{' CaseClause* '}' @@ -427,7 +427,7 @@ Statement PreParser::ParseSwitchStatement(bool* ok) { } -Statement PreParser::ParseDoWhileStatement(bool* ok) { +PreParser::Statement PreParser::ParseDoWhileStatement(bool* ok) { // DoStatement :: // 'do' Statement 'while' '(' Expression ')' ';' @@ -441,7 +441,7 @@ Statement PreParser::ParseDoWhileStatement(bool* ok) { } -Statement PreParser::ParseWhileStatement(bool* ok) { +PreParser::Statement PreParser::ParseWhileStatement(bool* ok) { // WhileStatement :: // 'while' '(' Expression ')' Statement @@ -454,7 +454,7 @@ Statement PreParser::ParseWhileStatement(bool* ok) { } -Statement PreParser::ParseForStatement(bool* ok) { +PreParser::Statement PreParser::ParseForStatement(bool* ok) { // ForStatement :: // 'for' '(' Expression? ';' Expression? ';' Expression? ')' Statement @@ -503,7 +503,7 @@ Statement PreParser::ParseForStatement(bool* ok) { } -Statement PreParser::ParseThrowStatement(bool* ok) { +PreParser::Statement PreParser::ParseThrowStatement(bool* ok) { // ThrowStatement :: // 'throw' [no line terminator] Expression ';' @@ -522,7 +522,7 @@ Statement PreParser::ParseThrowStatement(bool* ok) { } -Statement PreParser::ParseTryStatement(bool* ok) { +PreParser::Statement PreParser::ParseTryStatement(bool* ok) { // TryStatement :: // 'try' Block Catch // 'try' Block Finally @@ -565,7 +565,7 @@ Statement PreParser::ParseTryStatement(bool* ok) { } -Statement PreParser::ParseDebuggerStatement(bool* ok) { +PreParser::Statement PreParser::ParseDebuggerStatement(bool* ok) { // In ECMA-262 'debugger' is defined as a reserved keyword. In some browser // contexts this is used as a statement which invokes the debugger as if a // break point is present. @@ -579,7 +579,7 @@ Statement PreParser::ParseDebuggerStatement(bool* ok) { // Precedence = 1 -Expression PreParser::ParseExpression(bool accept_IN, bool* ok) { +PreParser::Expression PreParser::ParseExpression(bool accept_IN, bool* ok) { // Expression :: // AssignmentExpression // Expression ',' AssignmentExpression @@ -595,8 +595,8 @@ Expression PreParser::ParseExpression(bool accept_IN, bool* ok) { // Precedence = 2 -Expression PreParser::ParseAssignmentExpression(bool accept_IN, - bool* ok) { +PreParser::Expression PreParser::ParseAssignmentExpression(bool accept_IN, + bool* ok) { // AssignmentExpression :: // ConditionalExpression // LeftHandSideExpression AssignmentOperator AssignmentExpression @@ -620,8 +620,8 @@ Expression PreParser::ParseAssignmentExpression(bool accept_IN, // Precedence = 3 -Expression PreParser::ParseConditionalExpression(bool accept_IN, - bool* ok) { +PreParser::Expression PreParser::ParseConditionalExpression(bool accept_IN, + bool* ok) { // ConditionalExpression :: // LogicalOrExpression // LogicalOrExpression '?' AssignmentExpression ':' AssignmentExpression @@ -649,9 +649,9 @@ int PreParser::Precedence(i::Token::Value tok, bool accept_IN) { // Precedence >= 4 -Expression PreParser::ParseBinaryExpression(int prec, - bool accept_IN, - bool* ok) { +PreParser::Expression PreParser::ParseBinaryExpression(int prec, + bool accept_IN, + bool* ok) { Expression result = ParseUnaryExpression(CHECK_OK); for (int prec1 = Precedence(peek(), accept_IN); prec1 >= prec; prec1--) { // prec1 >= 4 @@ -665,7 +665,7 @@ Expression PreParser::ParseBinaryExpression(int prec, } -Expression PreParser::ParseUnaryExpression(bool* ok) { +PreParser::Expression PreParser::ParseUnaryExpression(bool* ok) { // UnaryExpression :: // PostfixExpression // 'delete' UnaryExpression @@ -689,7 +689,7 @@ Expression PreParser::ParseUnaryExpression(bool* ok) { } -Expression PreParser::ParsePostfixExpression(bool* ok) { +PreParser::Expression PreParser::ParsePostfixExpression(bool* ok) { // PostfixExpression :: // LeftHandSideExpression ('++' | '--')? @@ -703,7 +703,7 @@ Expression PreParser::ParsePostfixExpression(bool* ok) { } -Expression PreParser::ParseLeftHandSideExpression(bool* ok) { +PreParser::Expression PreParser::ParseLeftHandSideExpression(bool* ok) { // LeftHandSideExpression :: // (NewExpression | MemberExpression) ... @@ -752,7 +752,7 @@ Expression PreParser::ParseLeftHandSideExpression(bool* ok) { } -Expression PreParser::ParseNewExpression(bool* ok) { +PreParser::Expression PreParser::ParseNewExpression(bool* ok) { // NewExpression :: // ('new')+ MemberExpression @@ -774,12 +774,12 @@ Expression PreParser::ParseNewExpression(bool* ok) { } -Expression PreParser::ParseMemberExpression(bool* ok) { +PreParser::Expression PreParser::ParseMemberExpression(bool* ok) { return ParseMemberWithNewPrefixesExpression(0, ok); } -Expression PreParser::ParseMemberWithNewPrefixesExpression( +PreParser::Expression PreParser::ParseMemberWithNewPrefixesExpression( unsigned new_count, bool* ok) { // MemberExpression :: // (PrimaryExpression | FunctionLiteral) @@ -835,7 +835,7 @@ Expression PreParser::ParseMemberWithNewPrefixesExpression( } -Expression PreParser::ParsePrimaryExpression(bool* ok) { +PreParser::Expression PreParser::ParsePrimaryExpression(bool* ok) { // PrimaryExpression :: // 'this' // 'null' @@ -914,7 +914,7 @@ Expression PreParser::ParsePrimaryExpression(bool* ok) { } -Expression PreParser::ParseArrayLiteral(bool* ok) { +PreParser::Expression PreParser::ParseArrayLiteral(bool* ok) { // ArrayLiteral :: // '[' Expression? (',' Expression?)* ']' Expect(i::Token::LBRACK, CHECK_OK); @@ -933,7 +933,7 @@ Expression PreParser::ParseArrayLiteral(bool* ok) { } -Expression PreParser::ParseObjectLiteral(bool* ok) { +PreParser::Expression PreParser::ParseObjectLiteral(bool* ok) { // ObjectLiteral :: // '{' ( // ((IdentifierName | String | Number) ':' AssignmentExpression) @@ -995,8 +995,8 @@ Expression PreParser::ParseObjectLiteral(bool* ok) { } -Expression PreParser::ParseRegExpLiteral(bool seen_equal, - bool* ok) { +PreParser::Expression PreParser::ParseRegExpLiteral(bool seen_equal, + bool* ok) { if (!scanner_->ScanRegExpPattern(seen_equal)) { Next(); i::JavaScriptScanner::Location location = scanner_->location(); @@ -1021,7 +1021,7 @@ Expression PreParser::ParseRegExpLiteral(bool seen_equal, } -Arguments PreParser::ParseArguments(bool* ok) { +PreParser::Arguments PreParser::ParseArguments(bool* ok) { // Arguments :: // '(' (AssignmentExpression)*[','] ')' @@ -1039,7 +1039,7 @@ Arguments PreParser::ParseArguments(bool* ok) { } -Expression PreParser::ParseFunctionLiteral(bool* ok) { +PreParser::Expression PreParser::ParseFunctionLiteral(bool* ok) { // Function :: // '(' FormalParameterList? ')' '{' FunctionBody '}' @@ -1078,6 +1078,7 @@ Expression PreParser::ParseFunctionLiteral(bool* ok) { Expect(i::Token::RBRACE, CHECK_OK); + // Position right after terminal '}'. int end_pos = scanner_->location().end_pos; log_->LogFunction(function_block_pos, end_pos, function_scope.materialized_literal_count(), @@ -1090,7 +1091,7 @@ Expression PreParser::ParseFunctionLiteral(bool* ok) { } -Expression PreParser::ParseV8Intrinsic(bool* ok) { +PreParser::Expression PreParser::ParseV8Intrinsic(bool* ok) { // CallRuntime :: // '%' Identifier Arguments @@ -1119,7 +1120,7 @@ void PreParser::ExpectSemicolon(bool* ok) { } -Identifier PreParser::GetIdentifierSymbol() { +PreParser::Identifier PreParser::GetIdentifierSymbol() { const char* literal_chars = scanner_->literal_string(); int literal_length = scanner_->literal_length(); int identifier_pos = scanner_->location().beg_pos; @@ -1130,7 +1131,7 @@ Identifier PreParser::GetIdentifierSymbol() { } -Expression PreParser::GetStringSymbol() { +PreParser::Expression PreParser::GetStringSymbol() { const char* literal_chars = scanner_->literal_string(); int literal_length = scanner_->literal_length(); @@ -1141,14 +1142,14 @@ Expression PreParser::GetStringSymbol() { } -Identifier PreParser::ParseIdentifier(bool* ok) { +PreParser::Identifier PreParser::ParseIdentifier(bool* ok) { Expect(i::Token::IDENTIFIER, ok); if (!*ok) return kUnknownIdentifier; return GetIdentifierSymbol(); } -Identifier PreParser::ParseIdentifierName(bool* ok) { +PreParser::Identifier PreParser::ParseIdentifierName(bool* ok) { i::Token::Value next = Next(); if (i::Token::IsKeyword(next)) { int pos = scanner_->location().beg_pos; @@ -1168,9 +1169,9 @@ Identifier PreParser::ParseIdentifierName(bool* ok) { // is 'get' or 'set'. The reason for not using ParseIdentifier and // checking on the output is that this involves heap allocation which // we can't do during preparsing. -Identifier PreParser::ParseIdentifierOrGetOrSet(bool* is_get, - bool* is_set, - bool* ok) { +PreParser::Identifier PreParser::ParseIdentifierOrGetOrSet(bool* is_get, + bool* is_set, + bool* ok) { Expect(i::Token::IDENTIFIER, CHECK_OK); if (scanner_->literal_length() == 3) { const char* token = scanner_->literal_string(); diff --git a/src/preparser.h b/src/preparser.h index b783d65d..893b5751 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -46,56 +46,24 @@ namespace preparser { namespace i = v8::internal; -enum StatementType { - kUnknownStatement -}; - -enum ExpressionType { - kUnknownExpression, - kIdentifierExpression, // Used to detect labels. - kThisExpression, - kThisPropertyExpression -}; - -enum IdentifierType { - kUnknownIdentifier -}; - -enum SourceElementTypes { - kUnknownSourceElements -}; - - -typedef int SourceElements; -typedef int Expression; -typedef int Statement; -typedef int Identifier; -typedef int Arguments; - - class PreParser { public: - PreParser() : scope_(NULL), allow_lazy_(true) { } + enum PreParseResult { + kPreParseStackOverflow, + kPreParseSuccess + }; + ~PreParser() { } // Pre-parse the program from the character stream; returns true on // success (even if parsing failed, the pre-parse data successfully // captured the syntax error), and false if a stack-overflow happened // during parsing. - bool PreParseProgram(i::JavaScriptScanner* scanner, - i::ParserRecorder* log, - bool allow_lazy) { - allow_lazy_ = allow_lazy; - scanner_ = scanner; - log_ = log; - Scope top_scope(&scope_, kTopLevelScope); - bool ok = true; - ParseSourceElements(i::Token::EOS, &ok); - bool stack_overflow = scanner_->stack_overflow(); - if (!ok && !stack_overflow) { - ReportUnexpectedToken(scanner_->current_token()); - } - return !stack_overflow; + static PreParseResult PreParseProgram(i::JavaScriptScanner* scanner, + i::ParserRecorder* log, + bool allow_lazy, + uintptr_t stack_limit) { + return PreParser(scanner, log, stack_limit, allow_lazy).PreParse(); } private: @@ -104,6 +72,38 @@ class PreParser { kFunctionScope }; + // Types that allow us to recognize simple this-property assignments. + // A simple this-property assignment is a statement on the form + // "this.propertyName = {primitive constant or function parameter name);" + // where propertyName isn't "__proto__". + // The result is only relevant if the function body contains only + // simple this-property assignments. + + enum StatementType { + kUnknownStatement + }; + + enum ExpressionType { + kUnknownExpression, + kIdentifierExpression, // Used to detect labels. + kThisExpression, + kThisPropertyExpression + }; + + enum IdentifierType { + kUnknownIdentifier + }; + + enum SourceElementTypes { + kUnknownSourceElements + }; + + typedef int SourceElements; + typedef int Expression; + typedef int Statement; + typedef int Identifier; + typedef int Arguments; + class Scope { public: Scope(Scope** variable, ScopeType type) @@ -134,12 +134,30 @@ class PreParser { int with_nesting_count_; }; - // Types that allow us to recognize simple this-property assignments. - // A simple this-property assignment is a statement on the form - // "this.propertyName = {primitive constant or function parameter name);" - // where propertyName isn't "__proto__". - // The result is only relevant if the function body contains only - // simple this-property assignments. + // Private constructor only used in PreParseProgram. + PreParser(i::JavaScriptScanner* scanner, + i::ParserRecorder* log, + uintptr_t stack_limit, + bool allow_lazy) + : scanner_(scanner), + log_(log), + scope_(NULL), + stack_limit_(stack_limit), + stack_overflow_(false), + allow_lazy_(true) { } + + // Preparse the program. Only called in PreParseProgram after creating + // the instance. + PreParseResult PreParse() { + Scope top_scope(&scope_, kTopLevelScope); + bool ok = true; + ParseSourceElements(i::Token::EOS, &ok); + if (stack_overflow_) return kPreParseStackOverflow; + if (!ok) { + ReportUnexpectedToken(scanner_->current_token()); + } + return kPreParseSuccess; + } // Report syntax error void ReportUnexpectedToken(i::Token::Value token); @@ -202,16 +220,26 @@ class PreParser { unsigned int HexDigitValue(char digit); Expression GetStringSymbol(); + i::Token::Value peek() { + if (stack_overflow_) return i::Token::ILLEGAL; + return scanner_->peek(); + } - i::Token::Value peek() { return scanner_->peek(); } i::Token::Value Next() { - i::Token::Value next = scanner_->Next(); - return next; + if (stack_overflow_) return i::Token::ILLEGAL; + { + int marker; + if (reinterpret_cast<uintptr_t>(&marker) < stack_limit_) { + // Further calls to peek/Next will return illegal token. + // The current one will still be returned. It might already + // have been seen using peek. + stack_overflow_ = true; + } + } + return scanner_->Next(); } - void Consume(i::Token::Value token) { - Next(); - } + void Consume(i::Token::Value token) { Next(); } void Expect(i::Token::Value token, bool* ok) { if (Next() != token) { @@ -234,6 +262,8 @@ class PreParser { i::JavaScriptScanner* scanner_; i::ParserRecorder* log_; Scope* scope_; + uintptr_t stack_limit_; + bool stack_overflow_; bool allow_lazy_; }; } } // v8::preparser diff --git a/src/prettyprinter.h b/src/prettyprinter.h index dfff49a4..c83de345 100644 --- a/src/prettyprinter.h +++ b/src/prettyprinter.h @@ -51,6 +51,7 @@ class PrettyPrinter: public AstVisitor { // Print a node to stdout. static void PrintOut(AstNode* node); + virtual void VisitSlot(Slot* node); // Individual nodes #define DECLARE_VISIT(type) virtual void Visit##type(type* node); AST_NODE_LIST(DECLARE_VISIT) @@ -85,9 +86,11 @@ class AstPrinter: public PrettyPrinter { const char* PrintProgram(FunctionLiteral* program); // Individual nodes + virtual void VisitSlot(Slot* node); #define DECLARE_VISIT(type) virtual void Visit##type(type* node); AST_NODE_LIST(DECLARE_VISIT) #undef DECLARE_VISIT + private: friend class IndentedScope; void PrintIndented(const char* txt); @@ -160,6 +163,7 @@ class JsonAstBuilder: public PrettyPrinter { void AddAttribute(const char* name, bool value); // AST node visit functions. + virtual void VisitSlot(Slot* node); #define DECLARE_VISIT(type) virtual void Visit##type(type* node); AST_NODE_LIST(DECLARE_VISIT) #undef DECLARE_VISIT diff --git a/src/profile-generator-inl.h b/src/profile-generator-inl.h index 8b5c1e21..3df6af06 100644 --- a/src/profile-generator-inl.h +++ b/src/profile-generator-inl.h @@ -122,7 +122,7 @@ CodeEntry* ProfileGenerator::EntryForVMState(StateTag tag) { } -inline uint64_t HeapEntry::id() { +uint64_t HeapEntry::id() { union { Id stored_id; uint64_t returned_id; @@ -146,6 +146,18 @@ void HeapEntriesMap::UpdateEntries(Visitor* visitor) { } } + +bool HeapSnapshotGenerator::ReportProgress(bool force) { + const int kProgressReportGranularity = 10000; + if (control_ != NULL + && (force || progress_counter_ % kProgressReportGranularity == 0)) { + return + control_->ReportProgressValue(progress_counter_, progress_total_) == + v8::ActivityControl::kContinue; + } + return true; +} + } } // namespace v8::internal #endif // ENABLE_LOGGING_AND_PROFILING diff --git a/src/profile-generator.cc b/src/profile-generator.cc index 640f13cd..34d18771 100644 --- a/src/profile-generator.cc +++ b/src/profile-generator.cc @@ -603,8 +603,8 @@ CpuProfile* CpuProfilesCollection::GetProfile(int security_token_id, } List<CpuProfile*>* list = GetProfilesList(security_token_id); if (list->at(index) == NULL) { - list->at(index) = - unabridged_list->at(index)->FilteredClone(security_token_id); + (*list)[index] = + unabridged_list->at(index)->FilteredClone(security_token_id); } return list->at(index); } @@ -653,7 +653,7 @@ List<CpuProfile*>* CpuProfilesCollection::Profiles(int security_token_id) { const int current_count = unabridged_list->length(); for (int i = 0; i < current_count; ++i) { if (list->at(i) == NULL) { - list->at(i) = unabridged_list->at(i)->FilteredClone(security_token_id); + (*list)[i] = unabridged_list->at(i)->FilteredClone(security_token_id); } } return list; @@ -1382,86 +1382,6 @@ HeapEntry* HeapSnapshot::AddEntry(HeapEntry::Type type, } -void HeapSnapshot::FillReversePostorderIndexes(Vector<HeapEntry*>* entries) { - ClearPaint(); - int current_entry = 0; - List<HeapEntry*> nodes_to_visit; - nodes_to_visit.Add(root()); - root()->paint_reachable(); - while (!nodes_to_visit.is_empty()) { - HeapEntry* entry = nodes_to_visit.last(); - Vector<HeapGraphEdge> children = entry->children(); - bool has_new_edges = false; - for (int i = 0; i < children.length(); ++i) { - if (children[i].type() == HeapGraphEdge::kShortcut) continue; - HeapEntry* child = children[i].to(); - if (!child->painted_reachable()) { - nodes_to_visit.Add(child); - child->paint_reachable(); - has_new_edges = true; - } - } - if (!has_new_edges) { - entry->set_ordered_index(current_entry); - entries->at(current_entry++) = entry; - nodes_to_visit.RemoveLast(); - } - } - entries->Truncate(current_entry); -} - - -static int Intersect(int i1, int i2, const Vector<HeapEntry*>& dominators) { - int finger1 = i1, finger2 = i2; - while (finger1 != finger2) { - while (finger1 < finger2) finger1 = dominators[finger1]->ordered_index(); - while (finger2 < finger1) finger2 = dominators[finger2]->ordered_index(); - } - return finger1; -} - -// The algorithm is based on the article: -// K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" -// Softw. Pract. Exper. 4 (2001), pp. 1–10. -void HeapSnapshot::BuildDominatorTree(const Vector<HeapEntry*>& entries, - Vector<HeapEntry*>* dominators) { - if (entries.length() == 0) return; - const int root_index = entries.length() - 1; - for (int i = 0; i < root_index; ++i) dominators->at(i) = NULL; - dominators->at(root_index) = entries[root_index]; - bool changed = true; - while (changed) { - changed = false; - for (int i = root_index - 1; i >= 0; --i) { - HeapEntry* new_idom = NULL; - Vector<HeapGraphEdge*> rets = entries[i]->retainers(); - int j = 0; - for (; j < rets.length(); ++j) { - if (rets[j]->type() == HeapGraphEdge::kShortcut) continue; - HeapEntry* ret = rets[j]->From(); - if (dominators->at(ret->ordered_index()) != NULL) { - new_idom = ret; - break; - } - } - for (++j; j < rets.length(); ++j) { - if (rets[j]->type() == HeapGraphEdge::kShortcut) continue; - HeapEntry* ret = rets[j]->From(); - if (dominators->at(ret->ordered_index()) != NULL) { - new_idom = entries[Intersect(ret->ordered_index(), - new_idom->ordered_index(), - *dominators)]; - } - } - if (new_idom != NULL && dominators->at(i) != new_idom) { - dominators->at(i) = new_idom; - changed = true; - } - } - } -} - - void HeapSnapshot::SetDominatorsToSelf() { for (int i = 0; i < entries_.length(); ++i) { HeapEntry* entry = entries_[i]; @@ -1470,61 +1390,6 @@ void HeapSnapshot::SetDominatorsToSelf() { } -void HeapSnapshot::SetEntriesDominators() { - // This array is used for maintaining reverse postorder of nodes. - ScopedVector<HeapEntry*> ordered_entries(entries_.length()); - FillReversePostorderIndexes(&ordered_entries); - ScopedVector<HeapEntry*> dominators(ordered_entries.length()); - BuildDominatorTree(ordered_entries, &dominators); - for (int i = 0; i < ordered_entries.length(); ++i) { - ASSERT(dominators[i] != NULL); - ordered_entries[i]->set_dominator(dominators[i]); - } - // For nodes unreachable from root, set dominator to itself. - SetDominatorsToSelf(); -} - - -void HeapSnapshot::ApproximateRetainedSizes() { - SetEntriesDominators(); - // As for the dominators tree we only know parent nodes, not - // children, to sum up total sizes we traverse the tree level by - // level upwards, starting from leaves. - for (int i = 0; i < entries_.length(); ++i) { - HeapEntry* entry = entries_[i]; - entry->set_retained_size(entry->self_size()); - entry->set_leaf(); - } - while (true) { - bool onlyLeaves = true; - for (int i = 0; i < entries_.length(); ++i) { - HeapEntry *entry = entries_[i], *dominator = entry->dominator(); - if (!entry->is_processed() && dominator != entry) { - dominator->set_non_leaf(); - onlyLeaves = false; - } - } - if (onlyLeaves) break; - - for (int i = 0; i < entries_.length(); ++i) { - HeapEntry *entry = entries_[i], *dominator = entry->dominator(); - if (entry->is_leaf() && dominator != entry) { - dominator->add_retained_size(entry->retained_size()); - } - } - - // Mark all current leaves as processed, reset non-leaves back to leaves. - for (int i = 0; i < entries_.length(); ++i) { - HeapEntry* entry = entries_[i]; - if (entry->is_leaf()) - entry->set_processed(); - else if (entry->is_non_leaf()) - entry->set_leaf(); - } - } -} - - HeapEntry* HeapSnapshot::GetNextEntryToInit() { if (entries_.length() > 0) { HeapEntry* last_entry = entries_.last(); @@ -1544,6 +1409,29 @@ HeapSnapshotsDiff* HeapSnapshot::CompareWith(HeapSnapshot* snapshot) { } +HeapEntry* HeapSnapshot::GetEntryById(uint64_t id) { + // GetSortedEntriesList is used in diff algorithm and sorts + // entries by their id. + List<HeapEntry*>* entries_by_id = GetSortedEntriesList(); + + // Perform a binary search by id. + int low = 0; + int high = entries_by_id->length() - 1; + while (low <= high) { + int mid = + (static_cast<unsigned int>(low) + static_cast<unsigned int>(high)) >> 1; + uint64_t mid_id = entries_by_id->at(mid)->id(); + if (mid_id > id) + high = mid - 1; + else if (mid_id < id) + low = mid + 1; + else + return entries_by_id->at(mid); + } + return NULL; +} + + List<HeapGraphPath*>* HeapSnapshot::GetRetainingPaths(HeapEntry* entry) { HashMap::Entry* p = retaining_paths_.Lookup(entry, HeapEntry::Hash(entry), true); @@ -1693,15 +1581,22 @@ HeapSnapshot* HeapSnapshotsCollection::NewSnapshot(HeapSnapshot::Type type, const char* name, unsigned uid) { is_tracking_objects_ = true; // Start watching for heap objects moves. - HeapSnapshot* snapshot = new HeapSnapshot(this, type, name, uid); - snapshots_.Add(snapshot); - HashMap::Entry* entry = - snapshots_uids_.Lookup(reinterpret_cast<void*>(snapshot->uid()), - static_cast<uint32_t>(snapshot->uid()), - true); - ASSERT(entry->value == NULL); - entry->value = snapshot; - return snapshot; + return new HeapSnapshot(this, type, name, uid); +} + + +void HeapSnapshotsCollection::SnapshotGenerationFinished( + HeapSnapshot* snapshot) { + ids_.SnapshotGenerationFinished(); + if (snapshot != NULL) { + snapshots_.Add(snapshot); + HashMap::Entry* entry = + snapshots_uids_.Lookup(reinterpret_cast<void*>(snapshot->uid()), + static_cast<uint32_t>(snapshot->uid()), + true); + ASSERT(entry->value == NULL); + entry->value = snapshot; + } } @@ -1809,8 +1704,10 @@ void HeapObjectsSet::Insert(Object* obj) { } -HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot) +HeapSnapshotGenerator::HeapSnapshotGenerator(HeapSnapshot* snapshot, + v8::ActivityControl* control) : snapshot_(snapshot), + control_(control), collection_(snapshot->collection()), filler_(NULL) { } @@ -1967,21 +1864,13 @@ class RootsReferencesExtractor : public ObjectVisitor { }; -void HeapSnapshotGenerator::GenerateSnapshot() { +bool HeapSnapshotGenerator::GenerateSnapshot() { AssertNoAllocation no_alloc; + SetProgressTotal(4); // 2 passes + dominators + sizes. + // Pass 1. Iterate heap contents to count entries and references. - SnapshotCounter counter(&entries_); - filler_ = &counter; - filler_->AddEntry(HeapSnapshot::kInternalRootObject); - filler_->AddEntry(HeapSnapshot::kGcRootsObject); - HeapIterator iterator(HeapIterator::kPreciseFiltering); - for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { - ExtractReferences(obj); - } - SetRootGcRootsReference(); - RootsReferencesExtractor extractor(this); - Heap::IterateRoots(&extractor, VISIT_ONLY_STRONG); + if (!CountEntriesAndReferences()) return false; // Allocate and fill entries in the snapshot, allocate references. snapshot_->AllocateEntries(entries_.entries_count(), @@ -1991,16 +1880,14 @@ void HeapSnapshotGenerator::GenerateSnapshot() { entries_.UpdateEntries(&allocator); // Pass 2. Fill references. - SnapshotFiller filler(snapshot_, &entries_); - filler_ = &filler; - iterator.reset(); - for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { - ExtractReferences(obj); - } - SetRootGcRootsReference(); - Heap::IterateRoots(&extractor, VISIT_ONLY_STRONG); + if (!FillReferences()) return false; - snapshot_->ApproximateRetainedSizes(); + if (!SetEntriesDominators()) return false; + if (!ApproximateRetainedSizes()) return false; + + progress_counter_ = progress_total_; + if (!ReportProgress(true)) return false; + return true; } @@ -2328,6 +2215,181 @@ void HeapSnapshotGenerator::SetGcRootsReference(Object* child_obj) { } +void HeapSnapshotGenerator::SetProgressTotal(int iterations_count) { + if (control_ == NULL) return; + + HeapIterator iterator(HeapIterator::kFilterUnreachable); + int objects_count = 0; + for (HeapObject* obj = iterator.next(); + obj != NULL; + obj = iterator.next(), ++objects_count) {} + progress_total_ = objects_count * iterations_count; + progress_counter_ = 0; +} + + +bool HeapSnapshotGenerator::CountEntriesAndReferences() { + SnapshotCounter counter(&entries_); + filler_ = &counter; + filler_->AddEntry(HeapSnapshot::kInternalRootObject); + filler_->AddEntry(HeapSnapshot::kGcRootsObject); + return IterateAndExtractReferences(); +} + + +bool HeapSnapshotGenerator::FillReferences() { + SnapshotFiller filler(snapshot_, &entries_); + filler_ = &filler; + return IterateAndExtractReferences(); +} + + +void HeapSnapshotGenerator::FillReversePostorderIndexes( + Vector<HeapEntry*>* entries) { + snapshot_->ClearPaint(); + int current_entry = 0; + List<HeapEntry*> nodes_to_visit; + nodes_to_visit.Add(snapshot_->root()); + snapshot_->root()->paint_reachable(); + while (!nodes_to_visit.is_empty()) { + HeapEntry* entry = nodes_to_visit.last(); + Vector<HeapGraphEdge> children = entry->children(); + bool has_new_edges = false; + for (int i = 0; i < children.length(); ++i) { + if (children[i].type() == HeapGraphEdge::kShortcut) continue; + HeapEntry* child = children[i].to(); + if (!child->painted_reachable()) { + nodes_to_visit.Add(child); + child->paint_reachable(); + has_new_edges = true; + } + } + if (!has_new_edges) { + entry->set_ordered_index(current_entry); + (*entries)[current_entry++] = entry; + nodes_to_visit.RemoveLast(); + } + } + entries->Truncate(current_entry); +} + + +static int Intersect(int i1, int i2, const Vector<HeapEntry*>& dominators) { + int finger1 = i1, finger2 = i2; + while (finger1 != finger2) { + while (finger1 < finger2) finger1 = dominators[finger1]->ordered_index(); + while (finger2 < finger1) finger2 = dominators[finger2]->ordered_index(); + } + return finger1; +} + +// The algorithm is based on the article: +// K. Cooper, T. Harvey and K. Kennedy "A Simple, Fast Dominance Algorithm" +// Softw. Pract. Exper. 4 (2001), pp. 1–10. +bool HeapSnapshotGenerator::BuildDominatorTree( + const Vector<HeapEntry*>& entries, + Vector<HeapEntry*>* dominators) { + if (entries.length() == 0) return true; + const int entries_length = entries.length(), root_index = entries_length - 1; + for (int i = 0; i < root_index; ++i) (*dominators)[i] = NULL; + (*dominators)[root_index] = entries[root_index]; + int changed = 1; + const int base_progress_counter = progress_counter_; + while (changed != 0) { + changed = 0; + for (int i = root_index - 1; i >= 0; --i) { + HeapEntry* new_idom = NULL; + Vector<HeapGraphEdge*> rets = entries[i]->retainers(); + int j = 0; + for (; j < rets.length(); ++j) { + if (rets[j]->type() == HeapGraphEdge::kShortcut) continue; + HeapEntry* ret = rets[j]->From(); + if (dominators->at(ret->ordered_index()) != NULL) { + new_idom = ret; + break; + } + } + for (++j; j < rets.length(); ++j) { + if (rets[j]->type() == HeapGraphEdge::kShortcut) continue; + HeapEntry* ret = rets[j]->From(); + if (dominators->at(ret->ordered_index()) != NULL) { + new_idom = entries[Intersect(ret->ordered_index(), + new_idom->ordered_index(), + *dominators)]; + } + } + if (new_idom != NULL && dominators->at(i) != new_idom) { + (*dominators)[i] = new_idom; + ++changed; + } + } + int remaining = entries_length - changed; + if (remaining < 0) remaining = 0; + progress_counter_ = base_progress_counter + remaining; + if (!ReportProgress(true)) return false; + } + return true; +} + + +bool HeapSnapshotGenerator::SetEntriesDominators() { + // This array is used for maintaining reverse postorder of nodes. + ScopedVector<HeapEntry*> ordered_entries(snapshot_->entries()->length()); + FillReversePostorderIndexes(&ordered_entries); + ScopedVector<HeapEntry*> dominators(ordered_entries.length()); + if (!BuildDominatorTree(ordered_entries, &dominators)) return false; + for (int i = 0; i < ordered_entries.length(); ++i) { + ASSERT(dominators[i] != NULL); + ordered_entries[i]->set_dominator(dominators[i]); + } + return true; +} + + +bool HeapSnapshotGenerator::ApproximateRetainedSizes() { + // As for the dominators tree we only know parent nodes, not + // children, to sum up total sizes we "bubble" node's self size + // adding it to all of its parents. + for (int i = 0; i < snapshot_->entries()->length(); ++i) { + HeapEntry* entry = snapshot_->entries()->at(i); + entry->set_retained_size(entry->self_size()); + } + for (int i = 0; + i < snapshot_->entries()->length(); + ++i, IncProgressCounter()) { + HeapEntry* entry = snapshot_->entries()->at(i); + int entry_size = entry->self_size(); + for (HeapEntry* dominator = entry->dominator(); + dominator != entry; + entry = dominator, dominator = entry->dominator()) { + dominator->add_retained_size(entry_size); + } + if (!ReportProgress()) return false; + } + return true; +} + + +bool HeapSnapshotGenerator::IterateAndExtractReferences() { + HeapIterator iterator(HeapIterator::kFilterUnreachable); + bool interrupted = false; + // Heap iteration with filtering must be finished in any case. + for (HeapObject* obj = iterator.next(); + obj != NULL; + obj = iterator.next(), IncProgressCounter()) { + if (!interrupted) { + ExtractReferences(obj); + if (!ReportProgress()) interrupted = true; + } + } + if (interrupted) return false; + SetRootGcRootsReference(); + RootsReferencesExtractor extractor(this); + Heap::IterateRoots(&extractor, VISIT_ONLY_STRONG); + return ReportProgress(); +} + + void HeapSnapshotsDiff::CreateRoots(int additions_count, int deletions_count) { raw_additions_root_ = NewArray<char>(HeapEntry::EntriesSize(1, additions_count, 0)); diff --git a/src/profile-generator.h b/src/profile-generator.h index 55c57fd5..cacd27ea 100644 --- a/src/profile-generator.h +++ b/src/profile-generator.h @@ -526,7 +526,7 @@ class HeapEntry BASE_EMBEDDED { HeapSnapshot* snapshot() { return snapshot_; } Type type() { return static_cast<Type>(type_); } const char* name() { return name_; } - uint64_t id(); + inline uint64_t id(); int self_size() { return self_size_; } int retained_size() { return retained_size_; } void add_retained_size(int size) { retained_size_ += size; } @@ -558,13 +558,6 @@ class HeapEntry BASE_EMBEDDED { void ApplyAndPaintAllReachable(Visitor* visitor); void PaintAllReachable(); - bool is_leaf() { return painted_ == kLeaf; } - void set_leaf() { painted_ = kLeaf; } - bool is_non_leaf() { return painted_ == kNonLeaf; } - void set_non_leaf() { painted_ = kNonLeaf; } - bool is_processed() { return painted_ == kProcessed; } - void set_processed() { painted_ = kProcessed; } - void SetIndexedReference(HeapGraphEdge::Type type, int child_index, int index, @@ -625,10 +618,6 @@ class HeapEntry BASE_EMBEDDED { static const unsigned kUnpainted = 0; static const unsigned kPainted = 1; static const unsigned kPaintedReachableFromOthers = 2; - // Paints used for approximate retained sizes calculation. - static const unsigned kLeaf = 0; - static const unsigned kNonLeaf = 1; - static const unsigned kProcessed = 2; static const int kExactRetainedSizeTag = 1; @@ -682,6 +671,7 @@ class HeapSnapshot { unsigned uid() { return uid_; } HeapEntry* root() { return root_entry_; } HeapEntry* gc_roots() { return gc_roots_entry_; } + List<HeapEntry*>* entries() { return &entries_; } void AllocateEntries( int entries_count, int children_count, int retainers_count); @@ -693,9 +683,9 @@ class HeapSnapshot { int size, int children_count, int retainers_count); - void ApproximateRetainedSizes(); void ClearPaint(); HeapSnapshotsDiff* CompareWith(HeapSnapshot* snapshot); + HeapEntry* GetEntryById(uint64_t id); List<HeapGraphPath*>* GetRetainingPaths(HeapEntry* entry); List<HeapEntry*>* GetSortedEntriesList(); template<class Visitor> @@ -715,10 +705,6 @@ class HeapSnapshot { int children_count, int retainers_count); HeapEntry* GetNextEntryToInit(); - void BuildDominatorTree(const Vector<HeapEntry*>& entries, - Vector<HeapEntry*>* dominators); - void FillReversePostorderIndexes(Vector<HeapEntry*>* entries); - void SetEntriesDominators(); HeapSnapshotsCollection* collection_; Type type_; @@ -844,7 +830,7 @@ class HeapSnapshotsCollection { HeapSnapshot* NewSnapshot( HeapSnapshot::Type type, const char* name, unsigned uid); - void SnapshotGenerationFinished() { ids_.SnapshotGenerationFinished(); } + void SnapshotGenerationFinished(HeapSnapshot* snapshot); List<HeapSnapshot*>* snapshots() { return &snapshots_; } HeapSnapshot* GetSnapshot(unsigned uid); @@ -967,16 +953,27 @@ class HeapSnapshotGenerator { HeapEntry* child_entry) = 0; }; - explicit HeapSnapshotGenerator(HeapSnapshot* snapshot); - void GenerateSnapshot(); + HeapSnapshotGenerator(HeapSnapshot* snapshot, + v8::ActivityControl* control); + bool GenerateSnapshot(); private: + bool ApproximateRetainedSizes(); + bool BuildDominatorTree(const Vector<HeapEntry*>& entries, + Vector<HeapEntry*>* dominators); + bool CountEntriesAndReferences(); HeapEntry* GetEntry(Object* obj); + void IncProgressCounter() { ++progress_counter_; } void ExtractReferences(HeapObject* obj); void ExtractClosureReferences(JSObject* js_obj, HeapEntry* entry); void ExtractPropertyReferences(JSObject* js_obj, HeapEntry* entry); void ExtractElementReferences(JSObject* js_obj, HeapEntry* entry); void ExtractInternalReferences(JSObject* js_obj, HeapEntry* entry); + bool FillReferences(); + void FillReversePostorderIndexes(Vector<HeapEntry*>* entries); + bool IterateAndExtractReferences(); + inline bool ReportProgress(bool force = false); + bool SetEntriesDominators(); void SetClosureReference(HeapObject* parent_obj, HeapEntry* parent, String* reference_name, @@ -1008,8 +1005,10 @@ class HeapSnapshotGenerator { void SetRootShortcutReference(Object* child); void SetRootGcRootsReference(); void SetGcRootsReference(Object* child); + void SetProgressTotal(int iterations_count); HeapSnapshot* snapshot_; + v8::ActivityControl* control_; HeapSnapshotsCollection* collection_; // Mapping from HeapObject* pointers to HeapEntry* pointers. HeapEntriesMap entries_; @@ -1017,6 +1016,9 @@ class HeapSnapshotGenerator { // Used during references extraction to mark heap objects that // are references via non-hidden properties. HeapObjectsSet known_references_; + // Used during snapshot generation. + int progress_counter_; + int progress_total_; friend class IndexedReferencesExtractor; friend class RootsReferencesExtractor; diff --git a/src/property.cc b/src/property.cc index b579b687..96774333 100644 --- a/src/property.cc +++ b/src/property.cc @@ -31,62 +31,62 @@ namespace v8 { namespace internal { -#ifdef DEBUG -void LookupResult::Print() { +#ifdef OBJECT_PRINT +void LookupResult::Print(FILE* out) { if (!IsFound()) { - PrintF("Not Found\n"); + PrintF(out, "Not Found\n"); return; } - PrintF("LookupResult:\n"); - PrintF(" -cacheable = %s\n", IsCacheable() ? "true" : "false"); - PrintF(" -attributes = %x\n", GetAttributes()); + PrintF(out, "LookupResult:\n"); + PrintF(out, " -cacheable = %s\n", IsCacheable() ? "true" : "false"); + PrintF(out, " -attributes = %x\n", GetAttributes()); switch (type()) { case NORMAL: - PrintF(" -type = normal\n"); - PrintF(" -entry = %d", GetDictionaryEntry()); + PrintF(out, " -type = normal\n"); + PrintF(out, " -entry = %d", GetDictionaryEntry()); break; case MAP_TRANSITION: - PrintF(" -type = map transition\n"); - PrintF(" -map:\n"); - GetTransitionMap()->Print(); - PrintF("\n"); + PrintF(out, " -type = map transition\n"); + PrintF(out, " -map:\n"); + GetTransitionMap()->Print(out); + PrintF(out, "\n"); break; case CONSTANT_FUNCTION: - PrintF(" -type = constant function\n"); - PrintF(" -function:\n"); - GetConstantFunction()->Print(); - PrintF("\n"); + PrintF(out, " -type = constant function\n"); + PrintF(out, " -function:\n"); + GetConstantFunction()->Print(out); + PrintF(out, "\n"); break; case FIELD: - PrintF(" -type = field\n"); - PrintF(" -index = %d", GetFieldIndex()); - PrintF("\n"); + PrintF(out, " -type = field\n"); + PrintF(out, " -index = %d", GetFieldIndex()); + PrintF(out, "\n"); break; case CALLBACKS: - PrintF(" -type = call backs\n"); - PrintF(" -callback object:\n"); - GetCallbackObject()->Print(); + PrintF(out, " -type = call backs\n"); + PrintF(out, " -callback object:\n"); + GetCallbackObject()->Print(out); break; case INTERCEPTOR: - PrintF(" -type = lookup interceptor\n"); + PrintF(out, " -type = lookup interceptor\n"); break; case CONSTANT_TRANSITION: - PrintF(" -type = constant property transition\n"); + PrintF(out, " -type = constant property transition\n"); break; case NULL_DESCRIPTOR: - PrintF(" =type = null descriptor\n"); + PrintF(out, " =type = null descriptor\n"); break; } } -void Descriptor::Print() { - PrintF("Descriptor "); - GetKey()->ShortPrint(); - PrintF(" @ "); - GetValue()->ShortPrint(); - PrintF(" %d\n", GetDetails().index()); +void Descriptor::Print(FILE* out) { + PrintF(out, "Descriptor "); + GetKey()->ShortPrint(out); + PrintF(out, " @ "); + GetValue()->ShortPrint(out); + PrintF(out, " %d\n", GetDetails().index()); } diff --git a/src/property.h b/src/property.h index 4715a725..c39fe41e 100644 --- a/src/property.h +++ b/src/property.h @@ -60,8 +60,8 @@ class Descriptor BASE_EMBEDDED { Object* GetValue() { return value_; } PropertyDetails GetDetails() { return details_; } -#ifdef DEBUG - void Print(); +#ifdef OBJECT_PRINT + void Print(FILE* out); #endif void SetEnumerationIndex(int index) { @@ -266,12 +266,26 @@ class LookupResult BASE_EMBEDDED { return Map::cast(GetValue()); } + Map* GetTransitionMapFromMap(Map* map) { + ASSERT(lookup_type_ == DESCRIPTOR_TYPE); + ASSERT(type() == MAP_TRANSITION); + return Map::cast(map->instance_descriptors()->GetValue(number_)); + } + int GetFieldIndex() { ASSERT(lookup_type_ == DESCRIPTOR_TYPE); ASSERT(type() == FIELD); return Descriptor::IndexFromValue(GetValue()); } + int GetLocalFieldIndexFromMap(Map* map) { + ASSERT(lookup_type_ == DESCRIPTOR_TYPE); + ASSERT(type() == FIELD); + return Descriptor::IndexFromValue( + map->instance_descriptors()->GetValue(number_)) - + map->inobject_properties(); + } + int GetDictionaryEntry() { ASSERT(lookup_type_ == DICTIONARY_TYPE); return number_; @@ -282,6 +296,12 @@ class LookupResult BASE_EMBEDDED { return JSFunction::cast(GetValue()); } + JSFunction* GetConstantFunctionFromMap(Map* map) { + ASSERT(lookup_type_ == DESCRIPTOR_TYPE); + ASSERT(type() == CONSTANT_FUNCTION); + return JSFunction::cast(map->instance_descriptors()->GetValue(number_)); + } + Object* GetCallbackObject() { if (lookup_type_ == CONSTANT_TYPE) { // For now we only have the __proto__ as constant type. @@ -290,8 +310,8 @@ class LookupResult BASE_EMBEDDED { return GetValue(); } -#ifdef DEBUG - void Print(); +#ifdef OBJECT_PRINT + void Print(FILE* out); #endif Object* GetValue() { diff --git a/src/regexp.js b/src/regexp.js index d01d04f2..0de66c64 100644 --- a/src/regexp.js +++ b/src/regexp.js @@ -32,7 +32,7 @@ const $RegExp = global.RegExp; // A recursive descent parser for Patterns according to the grammar of // ECMA-262 15.10.1, with deviations noted below. -function DoConstructRegExp(object, pattern, flags, isConstructorCall) { +function DoConstructRegExp(object, pattern, flags) { // RegExp : Called as constructor; see ECMA-262, section 15.10.4. if (IS_REGEXP(pattern)) { if (!IS_UNDEFINED(flags)) { @@ -80,7 +80,7 @@ function DoConstructRegExp(object, pattern, flags, isConstructorCall) { function RegExpConstructor(pattern, flags) { if (%_IsConstructCall()) { - DoConstructRegExp(this, pattern, flags, true); + DoConstructRegExp(this, pattern, flags); } else { // RegExp : Called as function; see ECMA-262, section 15.10.3.1. if (IS_REGEXP(pattern) && IS_UNDEFINED(flags)) { @@ -104,9 +104,9 @@ function CompileRegExp(pattern, flags) { // the empty string. For compatibility with JSC, we match their // behavior. if (IS_UNDEFINED(pattern) && %_ArgumentsLength() != 0) { - DoConstructRegExp(this, 'undefined', flags, false); + DoConstructRegExp(this, 'undefined', flags); } else { - DoConstructRegExp(this, pattern, flags, false); + DoConstructRegExp(this, pattern, flags); } } @@ -120,22 +120,28 @@ function DoRegExpExec(regexp, string, index) { function BuildResultFromMatchInfo(lastMatchInfo, s) { var numResults = NUMBER_OF_CAPTURES(lastMatchInfo) >> 1; - var result = %_RegExpConstructResult(numResults, lastMatchInfo[CAPTURE0], s); - if (numResults === 1) { - var matchStart = lastMatchInfo[CAPTURE(0)]; - var matchEnd = lastMatchInfo[CAPTURE(1)]; - result[0] = SubString(s, matchStart, matchEnd); + var start = lastMatchInfo[CAPTURE0]; + var end = lastMatchInfo[CAPTURE1]; + var result = %_RegExpConstructResult(numResults, start, s); + if (start + 1 == end) { + result[0] = %_StringCharAt(s, start); } else { - for (var i = 0; i < numResults; i++) { - var matchStart = lastMatchInfo[CAPTURE(i << 1)]; - var matchEnd = lastMatchInfo[CAPTURE((i << 1) + 1)]; - if (matchStart != -1 && matchEnd != -1) { - result[i] = SubString(s, matchStart, matchEnd); + result[0] = %_SubString(s, start, end); + } + var j = REGEXP_FIRST_CAPTURE + 2; + for (var i = 1; i < numResults; i++) { + start = lastMatchInfo[j++]; + end = lastMatchInfo[j++]; + if (end != -1) { + if (start + 1 == end) { + result[i] = %_StringCharAt(s, start); } else { - // Make sure the element is present. Avoid reading the undefined - // property from the global object since this may change. - result[i] = void 0; + result[i] = %_SubString(s, start, end); } + } else { + // Make sure the element is present. Avoid reading the undefined + // property from the global object since this may change. + result[i] = void 0; } } return result; @@ -144,12 +150,12 @@ function BuildResultFromMatchInfo(lastMatchInfo, s) { function RegExpExecNoTests(regexp, string, start) { // Must be called with RegExp, string and positive integer as arguments. - var matchInfo = DoRegExpExec(regexp, string, start); - var result = null; + var matchInfo = %_RegExpExec(regexp, string, start, lastMatchInfo); if (matchInfo !== null) { - result = BuildResultFromMatchInfo(matchInfo, string); + lastMatchInfoOverride = null; + return BuildResultFromMatchInfo(matchInfo, string); } - return result; + return null; } @@ -166,12 +172,7 @@ function RegExpExec(string) { } string = regExpInput; } - var s; - if (IS_STRING(string)) { - s = string; - } else { - s = ToString(string); - } + string = TO_STRING_INLINE(string); var lastIndex = this.lastIndex; // Conversion is required by the ES5 specification (RegExp.prototype.exec @@ -180,7 +181,7 @@ function RegExpExec(string) { var global = this.global; if (global) { - if (i < 0 || i > s.length) { + if (i < 0 || i > string.length) { this.lastIndex = 0; return null; } @@ -188,9 +189,9 @@ function RegExpExec(string) { i = 0; } - %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); + %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, string, lastIndex]); // matchIndices is either null or the lastMatchInfo array. - var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); + var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); if (matchIndices === null) { if (global) this.lastIndex = 0; @@ -202,7 +203,7 @@ function RegExpExec(string) { if (global) { this.lastIndex = lastMatchInfo[CAPTURE1]; } - return BuildResultFromMatchInfo(matchIndices, s); + return BuildResultFromMatchInfo(matchIndices, string); } @@ -227,12 +228,7 @@ function RegExpTest(string) { string = regExpInput; } - var s; - if (IS_STRING(string)) { - s = string; - } else { - s = ToString(string); - } + string = TO_STRING_INLINE(string); var lastIndex = this.lastIndex; @@ -241,13 +237,13 @@ function RegExpTest(string) { var i = TO_INTEGER(lastIndex); if (this.global) { - if (i < 0 || i > s.length) { + if (i < 0 || i > string.length) { this.lastIndex = 0; return false; } - %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); + %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, string, lastIndex]); // matchIndices is either null or the lastMatchInfo array. - var matchIndices = %_RegExpExec(this, s, i, lastMatchInfo); + var matchIndices = %_RegExpExec(this, string, i, lastMatchInfo); if (matchIndices === null) { this.lastIndex = 0; return false; @@ -265,15 +261,18 @@ function RegExpTest(string) { %_StringCharCodeAt(this.source, 2) != 63) { // '?' if (!%_ObjectEquals(regexp_key, this)) { regexp_key = this; - regexp_val = new $RegExp(this.source.substring(2, this.source.length), - (this.ignoreCase ? 'i' : '') - + (this.multiline ? 'm' : '')); + regexp_val = new $RegExp(SubString(this.source, 2, this.source.length), + (!this.ignoreCase + ? !this.multiline ? "" : "m" + : !this.multiline ? "i" : "im")); + } + if (%_RegExpExec(regexp_val, string, 0, lastMatchInfo) === null) { + return false; } - if (!regexp_val.test(s)) return false; } - %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, s, lastIndex]); + %_Log('regexp', 'regexp-exec,%0r,%1S,%2i', [this, string, lastIndex]); // matchIndices is either null or the lastMatchInfo array. - var matchIndices = %_RegExpExec(this, s, 0, lastMatchInfo); + var matchIndices = %_RegExpExec(this, string, 0, lastMatchInfo); if (matchIndices === null) return false; lastMatchInfoOverride = null; return true; diff --git a/src/rewriter.cc b/src/rewriter.cc index b6f82406..3d737a49 100644 --- a/src/rewriter.cc +++ b/src/rewriter.cc @@ -222,11 +222,6 @@ void AstOptimizer::VisitConditional(Conditional* node) { } -void AstOptimizer::VisitSlot(Slot* node) { - USE(node); -} - - void AstOptimizer::VisitVariableProxy(VariableProxy* node) { Variable* var = node->AsVariable(); if (var != NULL) { @@ -686,7 +681,7 @@ void AstOptimizer::VisitThisFunction(ThisFunction* node) { class Processor: public AstVisitor { public: - explicit Processor(VariableProxy* result) + explicit Processor(Variable* result) : result_(result), result_assigned_(false), is_set_(false), @@ -697,7 +692,7 @@ class Processor: public AstVisitor { bool result_assigned() const { return result_assigned_; } private: - VariableProxy* result_; + Variable* result_; // We are not tracking result usage via the result_'s use // counts (we leave the accurate computation to the @@ -714,7 +709,8 @@ class Processor: public AstVisitor { Expression* SetResult(Expression* value) { result_assigned_ = true; - return new Assignment(Token::ASSIGN, result_, value, + VariableProxy* result_proxy = new VariableProxy(result_); + return new Assignment(Token::ASSIGN, result_proxy, value, RelocInfo::kNoPosition); } @@ -869,12 +865,6 @@ void Processor::VisitConditional(Conditional* node) { } -void Processor::VisitSlot(Slot* node) { - USE(node); - UNREACHABLE(); -} - - void Processor::VisitVariableProxy(VariableProxy* node) { USE(node); UNREACHABLE(); @@ -999,12 +989,15 @@ bool Rewriter::Rewrite(CompilationInfo* info) { ZoneList<Statement*>* body = function->body(); if (!body->is_empty()) { - VariableProxy* result = scope->NewTemporary(Factory::result_symbol()); + Variable* result = scope->NewTemporary(Factory::result_symbol()); Processor processor(result); processor.Process(body); if (processor.HasStackOverflow()) return false; - if (processor.result_assigned()) body->Add(new ReturnStatement(result)); + if (processor.result_assigned()) { + VariableProxy* result_proxy = new VariableProxy(result); + body->Add(new ReturnStatement(result_proxy)); + } } return true; diff --git a/src/runtime-profiler.cc b/src/runtime-profiler.cc new file mode 100644 index 00000000..c53ddd2b --- /dev/null +++ b/src/runtime-profiler.cc @@ -0,0 +1,458 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "runtime-profiler.h" + +#include "assembler.h" +#include "code-stubs.h" +#include "compilation-cache.h" +#include "deoptimizer.h" +#include "execution.h" +#include "global-handles.h" +#include "scopeinfo.h" +#include "top.h" + +namespace v8 { +namespace internal { + + +class PendingListNode : public Malloced { + public: + explicit PendingListNode(JSFunction* function); + ~PendingListNode() { Destroy(); } + + PendingListNode* next() const { return next_; } + void set_next(PendingListNode* node) { next_ = node; } + Handle<JSFunction> function() { return Handle<JSFunction>::cast(function_); } + + // If the function is garbage collected before we've had the chance + // to optimize it the weak handle will be null. + bool IsValid() { return !function_.is_null(); } + + // Returns the number of microseconds this node has been pending. + int Delay() const { return static_cast<int>(OS::Ticks() - start_); } + + private: + void Destroy(); + static void WeakCallback(v8::Persistent<v8::Value> object, void* data); + + PendingListNode* next_; + Handle<Object> function_; // Weak handle. + int64_t start_; +}; + + +enum SamplerState { + IN_NON_JS_STATE = 0, + IN_JS_STATE = 1 +}; + + +// Optimization sampler constants. +static const int kSamplerFrameCount = 2; +static const int kSamplerFrameWeight[kSamplerFrameCount] = { 2, 1 }; +static const int kSamplerWindowSize = 16; + +static const int kSamplerTicksBetweenThresholdAdjustment = 32; + +static const int kSamplerThresholdInit = 3; +static const int kSamplerThresholdMin = 1; +static const int kSamplerThresholdDelta = 1; + +static const int kSamplerThresholdSizeFactorInit = 3; +static const int kSamplerThresholdSizeFactorMin = 1; +static const int kSamplerThresholdSizeFactorDelta = 1; + +static const int kSizeLimit = 1500; + +static int sampler_threshold = kSamplerThresholdInit; +static int sampler_threshold_size_factor = kSamplerThresholdSizeFactorInit; + +static int sampler_ticks_until_threshold_adjustment = + kSamplerTicksBetweenThresholdAdjustment; + +// The ratio of ticks spent in JS code in percent. +static Atomic32 js_ratio; + +// The JSFunctions in the sampler window are not GC safe. Old-space +// pointers are not cleared during mark-sweep collection and therefore +// the window might contain stale pointers. The window is updated on +// scavenges and (parts of it) cleared on mark-sweep and +// mark-sweep-compact. +static Object* sampler_window[kSamplerWindowSize] = { NULL, }; +static int sampler_window_position = 0; +static int sampler_window_weight[kSamplerWindowSize] = { 0, }; + + +// Support for pending 'optimize soon' requests. +static PendingListNode* optimize_soon_list = NULL; + + +PendingListNode::PendingListNode(JSFunction* function) : next_(NULL) { + function_ = GlobalHandles::Create(function); + start_ = OS::Ticks(); + GlobalHandles::MakeWeak(function_.location(), this, &WeakCallback); +} + + +void PendingListNode::Destroy() { + if (!IsValid()) return; + GlobalHandles::Destroy(function_.location()); + function_= Handle<Object>::null(); +} + + +void PendingListNode::WeakCallback(v8::Persistent<v8::Value>, void* data) { + reinterpret_cast<PendingListNode*>(data)->Destroy(); +} + + +static bool IsOptimizable(JSFunction* function) { + Code* code = function->code(); + return code->kind() == Code::FUNCTION && code->optimizable(); +} + + +static void Optimize(JSFunction* function, bool eager, int delay) { + ASSERT(IsOptimizable(function)); + if (FLAG_trace_opt) { + PrintF("[marking (%s) ", eager ? "eagerly" : "lazily"); + function->PrintName(); + PrintF(" for recompilation"); + if (delay > 0) { + PrintF(" (delayed %0.3f ms)", static_cast<double>(delay) / 1000); + } + PrintF("]\n"); + } + + // The next call to the function will trigger optimization. + function->MarkForLazyRecompilation(); +} + + +static void AttemptOnStackReplacement(JSFunction* function) { + // See AlwaysFullCompiler (in compiler.cc) comment on why we need + // Debug::has_break_points(). + ASSERT(function->IsMarkedForLazyRecompilation()); + if (!FLAG_use_osr || Debug::has_break_points() || function->IsBuiltin()) { + return; + } + + SharedFunctionInfo* shared = function->shared(); + // If the code is not optimizable, don't try OSR. + if (!shared->code()->optimizable()) return; + + // We are not prepared to do OSR for a function that already has an + // allocated arguments object. The optimized code would bypass it for + // arguments accesses, which is unsound. Don't try OSR. + if (shared->scope_info()->HasArgumentsShadow()) return; + + // We're using on-stack replacement: patch the unoptimized code so that + // any back edge in any unoptimized frame will trigger on-stack + // replacement for that frame. + if (FLAG_trace_osr) { + PrintF("[patching stack checks in "); + function->PrintName(); + PrintF(" for on-stack replacement]\n"); + } + + // Get the stack check stub code object to match against. We aren't + // prepared to generate it, but we don't expect to have to. + StackCheckStub check_stub; + Object* check_code; + MaybeObject* maybe_check_code = check_stub.TryGetCode(); + if (maybe_check_code->ToObject(&check_code)) { + Code* replacement_code = Builtins::builtin(Builtins::OnStackReplacement); + Code* unoptimized_code = shared->code(); + // Iterate the unoptimized code and patch every stack check except at + // the function entry. This code assumes the function entry stack + // check appears first i.e., is not deferred or otherwise reordered. + bool first = true; + for (RelocIterator it(unoptimized_code, RelocInfo::kCodeTargetMask); + !it.done(); + it.next()) { + RelocInfo* rinfo = it.rinfo(); + if (rinfo->target_address() == Code::cast(check_code)->entry()) { + if (first) { + first = false; + } else { + Deoptimizer::PatchStackCheckCode(rinfo, replacement_code); + } + } + } + } +} + + +static void ClearSampleBuffer() { + for (int i = 0; i < kSamplerWindowSize; i++) { + sampler_window[i] = NULL; + sampler_window_weight[i] = 0; + } +} + + +static void ClearSampleBufferNewSpaceEntries() { + for (int i = 0; i < kSamplerWindowSize; i++) { + if (Heap::InNewSpace(sampler_window[i])) { + sampler_window[i] = NULL; + sampler_window_weight[i] = 0; + } + } +} + + +static int LookupSample(JSFunction* function) { + int weight = 0; + for (int i = 0; i < kSamplerWindowSize; i++) { + Object* sample = sampler_window[i]; + if (sample != NULL) { + if (function == sample) { + weight += sampler_window_weight[i]; + } + } + } + return weight; +} + + +static void AddSample(JSFunction* function, int weight) { + ASSERT(IsPowerOf2(kSamplerWindowSize)); + sampler_window[sampler_window_position] = function; + sampler_window_weight[sampler_window_position] = weight; + sampler_window_position = (sampler_window_position + 1) & + (kSamplerWindowSize - 1); +} + + +void RuntimeProfiler::OptimizeNow() { + HandleScope scope; + PendingListNode* current = optimize_soon_list; + while (current != NULL) { + PendingListNode* next = current->next(); + if (current->IsValid()) { + Handle<JSFunction> function = current->function(); + int delay = current->Delay(); + if (IsOptimizable(*function)) { + Optimize(*function, true, delay); + } + } + delete current; + current = next; + } + optimize_soon_list = NULL; + + // Run through the JavaScript frames and collect them. If we already + // have a sample of the function, we mark it for optimizations + // (eagerly or lazily). + JSFunction* samples[kSamplerFrameCount]; + int sample_count = 0; + int frame_count = 0; + for (JavaScriptFrameIterator it; + frame_count++ < kSamplerFrameCount && !it.done(); + it.Advance()) { + JavaScriptFrame* frame = it.frame(); + JSFunction* function = JSFunction::cast(frame->function()); + + // Adjust threshold each time we have processed + // a certain number of ticks. + if (sampler_ticks_until_threshold_adjustment > 0) { + sampler_ticks_until_threshold_adjustment--; + if (sampler_ticks_until_threshold_adjustment <= 0) { + // If the threshold is not already at the minimum + // modify and reset the ticks until next adjustment. + if (sampler_threshold > kSamplerThresholdMin) { + sampler_threshold -= kSamplerThresholdDelta; + sampler_ticks_until_threshold_adjustment = + kSamplerTicksBetweenThresholdAdjustment; + } + } + } + + if (function->IsMarkedForLazyRecompilation()) { + Code* unoptimized = function->shared()->code(); + int nesting = unoptimized->allow_osr_at_loop_nesting_level(); + if (nesting == 0) AttemptOnStackReplacement(function); + int new_nesting = Min(nesting + 1, Code::kMaxLoopNestingMarker); + unoptimized->set_allow_osr_at_loop_nesting_level(new_nesting); + } + + // Do not record non-optimizable functions. + if (!IsOptimizable(function)) continue; + samples[sample_count++] = function; + + int function_size = function->shared()->SourceSize(); + int threshold_size_factor = (function_size > kSizeLimit) + ? sampler_threshold_size_factor + : 1; + + int threshold = sampler_threshold * threshold_size_factor; + int current_js_ratio = NoBarrier_Load(&js_ratio); + + // Adjust threshold depending on the ratio of time spent + // in JS code. + if (current_js_ratio < 20) { + // If we spend less than 20% of the time in JS code, + // do not optimize. + continue; + } else if (current_js_ratio < 75) { + // Below 75% of time spent in JS code, only optimize very + // frequently used functions. + threshold *= 3; + } + + if (LookupSample(function) >= threshold) { + Optimize(function, false, 0); + CompilationCache::MarkForEagerOptimizing(Handle<JSFunction>(function)); + } + } + + // Add the collected functions as samples. It's important not to do + // this as part of collecting them because this will interfere with + // the sample lookup in case of recursive functions. + for (int i = 0; i < sample_count; i++) { + AddSample(samples[i], kSamplerFrameWeight[i]); + } +} + + +void RuntimeProfiler::OptimizeSoon(JSFunction* function) { + if (!IsOptimizable(function)) return; + PendingListNode* node = new PendingListNode(function); + node->set_next(optimize_soon_list); + optimize_soon_list = node; +} + + +#ifdef ENABLE_LOGGING_AND_PROFILING +static void UpdateStateRatio(SamplerState current_state) { + static const int kStateWindowSize = 128; + static SamplerState state_window[kStateWindowSize]; + static int state_window_position = 0; + static int state_counts[2] = { kStateWindowSize, 0 }; + + SamplerState old_state = state_window[state_window_position]; + state_counts[old_state]--; + state_window[state_window_position] = current_state; + state_counts[current_state]++; + ASSERT(IsPowerOf2(kStateWindowSize)); + state_window_position = (state_window_position + 1) & + (kStateWindowSize - 1); + NoBarrier_Store(&js_ratio, state_counts[IN_JS_STATE] * 100 / + kStateWindowSize); +} +#endif + + +void RuntimeProfiler::NotifyTick() { +#ifdef ENABLE_LOGGING_AND_PROFILING + // Record state sample. + SamplerState state = Top::IsInJSState() + ? IN_JS_STATE + : IN_NON_JS_STATE; + UpdateStateRatio(state); + StackGuard::RequestRuntimeProfilerTick(); +#endif +} + + +void RuntimeProfiler::MarkCompactPrologue(bool is_compacting) { + if (is_compacting) { + // Clear all samples before mark-sweep-compact because every + // function might move. + ClearSampleBuffer(); + } else { + // Clear only new space entries on mark-sweep since none of the + // old-space functions will move. + ClearSampleBufferNewSpaceEntries(); + } +} + + +bool IsEqual(void* first, void* second) { + return first == second; +} + + +void RuntimeProfiler::Setup() { + ClearSampleBuffer(); + // If the ticker hasn't already started, make sure to do so to get + // the ticks for the runtime profiler. + if (IsEnabled()) Logger::EnsureTickerStarted(); +} + + +void RuntimeProfiler::Reset() { + sampler_threshold = kSamplerThresholdInit; + sampler_ticks_until_threshold_adjustment = + kSamplerTicksBetweenThresholdAdjustment; + sampler_threshold_size_factor = kSamplerThresholdSizeFactorInit; +} + + +void RuntimeProfiler::TearDown() { + // Nothing to do. +} + + +Object** RuntimeProfiler::SamplerWindowAddress() { + return sampler_window; +} + + +int RuntimeProfiler::SamplerWindowSize() { + return kSamplerWindowSize; +} + + +bool RuntimeProfilerRateLimiter::SuspendIfNecessary() { +#ifdef ENABLE_LOGGING_AND_PROFILING + static const int kNonJSTicksThreshold = 100; + // We suspend the runtime profiler thread when not running + // JavaScript. If the CPU profiler is active we must not do this + // because it samples both JavaScript and C++ code. + if (RuntimeProfiler::IsEnabled() && + !CpuProfiler::is_profiling() && + !(FLAG_prof && FLAG_prof_auto)) { + if (Top::IsInJSState()) { + non_js_ticks_ = 0; + } else { + if (non_js_ticks_ < kNonJSTicksThreshold) { + ++non_js_ticks_; + } else { + if (Top::WaitForJSState()) return true; + } + } + } +#endif + return false; +} + + +} } // namespace v8::internal diff --git a/src/runtime-profiler.h b/src/runtime-profiler.h new file mode 100644 index 00000000..e041c059 --- /dev/null +++ b/src/runtime-profiler.h @@ -0,0 +1,76 @@ +// Copyright 2010 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. + +#ifndef V8_RUNTIME_PROFILER_H_ +#define V8_RUNTIME_PROFILER_H_ + +#include "v8.h" +#include "allocation.h" + +namespace v8 { +namespace internal { + +class RuntimeProfiler : public AllStatic { + public: + static bool IsEnabled() { return V8::UseCrankshaft() && FLAG_opt; } + + static void OptimizeNow(); + static void OptimizeSoon(JSFunction* function); + + static void NotifyTick(); + + static void Setup(); + static void Reset(); + static void TearDown(); + + static void MarkCompactPrologue(bool is_compacting); + static Object** SamplerWindowAddress(); + static int SamplerWindowSize(); +}; + + +// Rate limiter intended to be used in the profiler thread. +class RuntimeProfilerRateLimiter BASE_EMBEDDED { + public: + RuntimeProfilerRateLimiter() : non_js_ticks_(0) { } + + // Suspends the current thread when not executing JavaScript to + // minimize CPU usage. Returns whether this thread was suspended + // (and so might have to check whether profiling is still active.) + // + // Does nothing when runtime profiling is not enabled. + bool SuspendIfNecessary(); + + private: + int non_js_ticks_; + + DISALLOW_COPY_AND_ASSIGN(RuntimeProfilerRateLimiter); +}; + +} } // namespace v8::internal + +#endif // V8_RUNTIME_PROFILER_H_ diff --git a/src/runtime.cc b/src/runtime.cc index 2324e62d..724a4363 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -33,16 +33,19 @@ #include "api.h" #include "arguments.h" #include "codegen.h" +#include "compilation-cache.h" #include "compiler.h" #include "cpu.h" #include "dateparser-inl.h" #include "debug.h" +#include "deoptimizer.h" #include "execution.h" #include "jsregexp.h" #include "liveedit.h" #include "parser.h" #include "platform.h" #include "runtime.h" +#include "runtime-profiler.h" #include "scopeinfo.h" #include "smart-pointer.h" #include "stub-cache.h" @@ -635,90 +638,6 @@ static void GetOwnPropertyImplementation(JSObject* obj, } -static bool CheckAccessException(LookupResult* result, - v8::AccessType access_type) { - if (result->type() == CALLBACKS) { - Object* callback = result->GetCallbackObject(); - if (callback->IsAccessorInfo()) { - AccessorInfo* info = AccessorInfo::cast(callback); - bool can_access = - (access_type == v8::ACCESS_HAS && - (info->all_can_read() || info->all_can_write())) || - (access_type == v8::ACCESS_GET && info->all_can_read()) || - (access_type == v8::ACCESS_SET && info->all_can_write()); - return can_access; - } - } - - return false; -} - - -static bool CheckAccess(JSObject* obj, - String* name, - LookupResult* result, - v8::AccessType access_type) { - ASSERT(result->IsProperty()); - - JSObject* holder = result->holder(); - JSObject* current = obj; - while (true) { - if (current->IsAccessCheckNeeded() && - !Top::MayNamedAccess(current, name, access_type)) { - // Access check callback denied the access, but some properties - // can have a special permissions which override callbacks descision - // (currently see v8::AccessControl). - break; - } - - if (current == holder) { - return true; - } - - current = JSObject::cast(current->GetPrototype()); - } - - // API callbacks can have per callback access exceptions. - switch (result->type()) { - case CALLBACKS: { - if (CheckAccessException(result, access_type)) { - return true; - } - break; - } - case INTERCEPTOR: { - // If the object has an interceptor, try real named properties. - // Overwrite the result to fetch the correct property later. - holder->LookupRealNamedProperty(name, result); - if (result->IsProperty()) { - if (CheckAccessException(result, access_type)) { - return true; - } - } - break; - } - default: - break; - } - - Top::ReportFailedAccessCheck(current, access_type); - return false; -} - - -// TODO(1095): we should traverse hidden prototype hierachy as well. -static bool CheckElementAccess(JSObject* obj, - uint32_t index, - v8::AccessType access_type) { - if (obj->IsAccessCheckNeeded() && - !Top::MayIndexedAccess(obj, index, access_type)) { - return false; - } - - return true; -} - - // Enumerator used as indices into the array returned from GetOwnProperty enum PropertyDescriptorIndices { IS_ACCESSOR_INDEX, @@ -761,7 +680,7 @@ static MaybeObject* Runtime_GetOwnProperty(Arguments args) { // subsequent cases. Handle<JSValue> js_value = Handle<JSValue>::cast(obj); Handle<String> str(String::cast(js_value->value())); - Handle<String> substr = SubString(str, index, index + 1, NOT_TENURED); + Handle<String> substr = SubString(str, index, index+1, NOT_TENURED); elms->set(IS_ACCESSOR_INDEX, Heap::false_value()); elms->set(VALUE_INDEX, *substr); @@ -774,7 +693,8 @@ static MaybeObject* Runtime_GetOwnProperty(Arguments args) { case JSObject::INTERCEPTED_ELEMENT: case JSObject::FAST_ELEMENT: { elms->set(IS_ACCESSOR_INDEX, Heap::false_value()); - elms->set(VALUE_INDEX, *GetElement(obj, index)); + Handle<Object> element = GetElement(Handle<Object>(obj), index); + elms->set(VALUE_INDEX, *element); elms->set(WRITABLE_INDEX, Heap::true_value()); elms->set(ENUMERABLE_INDEX, Heap::true_value()); elms->set(CONFIGURABLE_INDEX, Heap::true_value()); @@ -782,14 +702,7 @@ static MaybeObject* Runtime_GetOwnProperty(Arguments args) { } case JSObject::DICTIONARY_ELEMENT: { - Handle<JSObject> holder = obj; - if (obj->IsJSGlobalProxy()) { - Object* proto = obj->GetPrototype(); - if (proto->IsNull()) return Heap::undefined_value(); - ASSERT(proto->IsJSGlobalObject()); - holder = Handle<JSObject>(JSObject::cast(proto)); - } - NumberDictionary* dictionary = holder->element_dictionary(); + NumberDictionary* dictionary = obj->element_dictionary(); int entry = dictionary->FindEntry(index); ASSERT(entry != NumberDictionary::kNotFound); PropertyDetails details = dictionary->DetailsAt(entry); @@ -799,18 +712,14 @@ static MaybeObject* Runtime_GetOwnProperty(Arguments args) { FixedArray* callbacks = FixedArray::cast(dictionary->ValueAt(entry)); elms->set(IS_ACCESSOR_INDEX, Heap::true_value()); - if (CheckElementAccess(*obj, index, v8::ACCESS_GET)) { - elms->set(GETTER_INDEX, callbacks->get(0)); - } - if (CheckElementAccess(*obj, index, v8::ACCESS_SET)) { - elms->set(SETTER_INDEX, callbacks->get(1)); - } + elms->set(GETTER_INDEX, callbacks->get(0)); + elms->set(SETTER_INDEX, callbacks->get(1)); break; } case NORMAL: // This is a data property. elms->set(IS_ACCESSOR_INDEX, Heap::false_value()); - elms->set(VALUE_INDEX, *GetElement(obj, index)); + elms->set(VALUE_INDEX, dictionary->ValueAt(entry)); elms->set(WRITABLE_INDEX, Heap::ToBoolean(!details.IsReadOnly())); break; default: @@ -830,41 +739,35 @@ static MaybeObject* Runtime_GetOwnProperty(Arguments args) { if (!result.IsProperty()) { return Heap::undefined_value(); } - - if (!CheckAccess(*obj, *name, &result, v8::ACCESS_HAS)) { - return Heap::false_value(); - } - - elms->set(ENUMERABLE_INDEX, Heap::ToBoolean(!result.IsDontEnum())); - elms->set(CONFIGURABLE_INDEX, Heap::ToBoolean(!result.IsDontDelete())); - - bool is_js_accessor = (result.type() == CALLBACKS) && - (result.GetCallbackObject()->IsFixedArray()); - - if (is_js_accessor) { - // __defineGetter__/__defineSetter__ callback. - elms->set(IS_ACCESSOR_INDEX, Heap::true_value()); - - FixedArray* structure = FixedArray::cast(result.GetCallbackObject()); - if (CheckAccess(*obj, *name, &result, v8::ACCESS_GET)) { - elms->set(GETTER_INDEX, structure->get(0)); - } - if (CheckAccess(*obj, *name, &result, v8::ACCESS_SET)) { - elms->set(SETTER_INDEX, structure->get(1)); + if (result.type() == CALLBACKS) { + Object* structure = result.GetCallbackObject(); + if (structure->IsProxy() || structure->IsAccessorInfo()) { + // Property that is internally implemented as a callback or + // an API defined callback. + Object* value; + { MaybeObject* maybe_value = obj->GetPropertyWithCallback( + *obj, structure, *name, result.holder()); + if (!maybe_value->ToObject(&value)) return maybe_value; + } + elms->set(IS_ACCESSOR_INDEX, Heap::false_value()); + elms->set(VALUE_INDEX, value); + elms->set(WRITABLE_INDEX, Heap::ToBoolean(!result.IsReadOnly())); + } else if (structure->IsFixedArray()) { + // __defineGetter__/__defineSetter__ callback. + elms->set(IS_ACCESSOR_INDEX, Heap::true_value()); + elms->set(GETTER_INDEX, FixedArray::cast(structure)->get(0)); + elms->set(SETTER_INDEX, FixedArray::cast(structure)->get(1)); + } else { + return Heap::undefined_value(); } } else { elms->set(IS_ACCESSOR_INDEX, Heap::false_value()); + elms->set(VALUE_INDEX, result.GetLazyValue()); elms->set(WRITABLE_INDEX, Heap::ToBoolean(!result.IsReadOnly())); - - PropertyAttributes attrs; - Object* value; - // GetProperty will check access and report any violations. - { MaybeObject* maybe_value = obj->GetProperty(*obj, &result, *name, &attrs); - if (!maybe_value->ToObject(&value)) return maybe_value; - } - elms->set(VALUE_INDEX, value); } + elms->set(ENUMERABLE_INDEX, Heap::ToBoolean(!result.IsDontEnum())); + elms->set(CONFIGURABLE_INDEX, Heap::ToBoolean(!result.IsDontDelete())); return *desc; } @@ -1740,14 +1643,13 @@ static MaybeObject* Runtime_FunctionGetScriptSourcePosition(Arguments args) { static MaybeObject* Runtime_FunctionGetPositionForOffset(Arguments args) { ASSERT(args.length() == 2); - CONVERT_CHECKED(JSFunction, fun, args[0]); + CONVERT_CHECKED(Code, code, args[0]); CONVERT_NUMBER_CHECKED(int, offset, Int32, args[1]); - Code* code = fun->code(); RUNTIME_ASSERT(0 <= offset && offset < code->Size()); Address pc = code->address() + offset; - return Smi::FromInt(fun->code()->SourcePosition(pc)); + return Smi::FromInt(code->SourcePosition(pc)); } @@ -1824,10 +1726,14 @@ static MaybeObject* Runtime_SetCode(Arguments args) { if (!EnsureCompiled(shared, KEEP_EXCEPTION)) { return Failure::Exception(); } + // Since we don't store the source for this we should never + // optimize this. + shared->code()->set_optimizable(false); + // Set the code, scope info, formal parameter count, // and the length of the target function. target->shared()->set_code(shared->code()); - target->set_code(shared->code()); + target->ReplaceCode(shared->code()); target->shared()->set_scope_info(shared->scope_info()); target->shared()->set_length(shared->length()); target->shared()->set_formal_parameter_count( @@ -1857,6 +1763,7 @@ static MaybeObject* Runtime_SetCode(Arguments args) { // It's okay to skip the write barrier here because the literals // are guaranteed to be in old space. target->set_literals(*literals, SKIP_WRITE_BARRIER); + target->set_next_function_link(Heap::undefined_value()); } target->set_context(*context); @@ -2119,10 +2026,7 @@ class ReplacementStringBuilder { } Handle<JSArray> GetParts() { - Handle<JSArray> result = - Factory::NewJSArrayWithElements(array_builder_.array()); - result->set_length(Smi::FromInt(array_builder_.length())); - return result; + return array_builder_.ToJSArray(); } private: @@ -2697,7 +2601,7 @@ static MaybeObject* Runtime_StringReplaceRegExpWithString(Arguments args) { // Perform string match of pattern on subject, starting at start index. // Caller must ensure that 0 <= start_index <= sub->length(), -// and should check that pat->length() + start_index <= sub->length() +// and should check that pat->length() + start_index <= sub->length(). int Runtime::StringMatch(Handle<String> sub, Handle<String> pat, int start_index) { @@ -3296,7 +3200,7 @@ static MaybeObject* Runtime_RegExpExecMultiple(Arguments args) { if (regexp->TypeTag() == JSRegExp::ATOM) { Handle<String> pattern( String::cast(regexp->DataAt(JSRegExp::kAtomPatternIndex))); - if (!pattern->IsFlat()) FlattenString(pattern); + ASSERT(pattern->IsFlat()); if (SearchStringMultiple(subject, pattern, last_match_info, &builder)) { return *builder.ToJSArray(result_array); } @@ -3596,7 +3500,8 @@ static MaybeObject* Runtime_DefineOrRedefineAccessorProperty(Arguments args) { CONVERT_ARG_CHECKED(JSObject, obj, 0); CONVERT_CHECKED(String, name, args[1]); CONVERT_CHECKED(Smi, flag_setter, args[2]); - CONVERT_CHECKED(JSFunction, fun, args[3]); + Object* fun = args[3]; + RUNTIME_ASSERT(fun->IsJSFunction() || fun->IsUndefined()); CONVERT_CHECKED(Smi, flag_attr, args[4]); int unchecked = flag_attr->value(); RUNTIME_ASSERT((unchecked & ~(READ_ONLY | DONT_ENUM | DONT_DELETE)) == 0); @@ -3652,7 +3557,7 @@ static MaybeObject* Runtime_DefineOrRedefineDataProperty(Arguments args) { } LookupResult result; - js_object->LocalLookupRealNamedProperty(*name, &result); + js_object->LookupRealNamedProperty(*name, &result); // Take special care when attributes are different and there is already // a property. For simplicity we normalize the property which enables us @@ -3660,7 +3565,8 @@ static MaybeObject* Runtime_DefineOrRedefineDataProperty(Arguments args) { // map. The current version of SetObjectProperty does not handle attributes // correctly in the case where a property is a field and is reset with // new attributes. - if (result.IsProperty() && attr != result.GetAttributes()) { + if (result.IsProperty() && + (attr != result.GetAttributes() || result.type() == CALLBACKS)) { // New attributes - normalize to avoid writing to instance descriptor NormalizeProperties(js_object, CLEAR_INOBJECT_PROPERTIES, 0); // Use IgnoreAttributes version since a readonly property may be @@ -4624,6 +4530,222 @@ static MaybeObject* Runtime_URIUnescape(Arguments args) { } +static const unsigned int kQuoteTableLength = 128u; + +static const int kJsonQuotesCharactersPerEntry = 8; +static const char* const JsonQuotes = + "\\u0000 \\u0001 \\u0002 \\u0003 " + "\\u0004 \\u0005 \\u0006 \\u0007 " + "\\b \\t \\n \\u000b " + "\\f \\r \\u000e \\u000f " + "\\u0010 \\u0011 \\u0012 \\u0013 " + "\\u0014 \\u0015 \\u0016 \\u0017 " + "\\u0018 \\u0019 \\u001a \\u001b " + "\\u001c \\u001d \\u001e \\u001f " + " ! \\\" # " + "$ % & ' " + "( ) * + " + ", - . / " + "0 1 2 3 " + "4 5 6 7 " + "8 9 : ; " + "< = > ? " + "@ A B C " + "D E F G " + "H I J K " + "L M N O " + "P Q R S " + "T U V W " + "X Y Z [ " + "\\\\ ] ^ _ " + "` a b c " + "d e f g " + "h i j k " + "l m n o " + "p q r s " + "t u v w " + "x y z { " + "| } ~ \177 "; + + +// For a string that is less than 32k characters it should always be +// possible to allocate it in new space. +static const int kMaxGuaranteedNewSpaceString = 32 * 1024; + + +// Doing JSON quoting cannot make the string more than this many times larger. +static const int kJsonQuoteWorstCaseBlowup = 6; + + +// Covers the entire ASCII range (all other characters are unchanged by JSON +// quoting). +static const byte JsonQuoteLengths[kQuoteTableLength] = { + 6, 6, 6, 6, 6, 6, 6, 6, + 2, 2, 2, 6, 2, 2, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 1, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + + +template <typename StringType> +MaybeObject* AllocateRawString(int length); + + +template <> +MaybeObject* AllocateRawString<SeqTwoByteString>(int length) { + return Heap::AllocateRawTwoByteString(length); +} + + +template <> +MaybeObject* AllocateRawString<SeqAsciiString>(int length) { + return Heap::AllocateRawAsciiString(length); +} + + +template <typename Char, typename StringType> +static MaybeObject* SlowQuoteJsonString(Vector<const Char> characters) { + int length = characters.length(); + const Char* read_cursor = characters.start(); + const Char* end = read_cursor + length; + const int kSpaceForQuotes = 2; + int quoted_length = kSpaceForQuotes; + while (read_cursor < end) { + Char c = *(read_cursor++); + if (sizeof(Char) > 1u && static_cast<unsigned>(c) >= kQuoteTableLength) { + quoted_length++; + } else { + quoted_length += JsonQuoteLengths[static_cast<unsigned>(c)]; + } + } + MaybeObject* new_alloc = AllocateRawString<StringType>(quoted_length); + Object* new_object; + if (!new_alloc->ToObject(&new_object)) { + return new_alloc; + } + StringType* new_string = StringType::cast(new_object); + + Char* write_cursor = reinterpret_cast<Char*>( + new_string->address() + SeqAsciiString::kHeaderSize); + *(write_cursor++) = '"'; + + read_cursor = characters.start(); + while (read_cursor < end) { + Char c = *(read_cursor++); + if (sizeof(Char) > 1u && static_cast<unsigned>(c) >= kQuoteTableLength) { + *(write_cursor++) = c; + } else { + int len = JsonQuoteLengths[static_cast<unsigned>(c)]; + const char* replacement = JsonQuotes + + static_cast<unsigned>(c) * kJsonQuotesCharactersPerEntry; + for (int i = 0; i < len; i++) { + *write_cursor++ = *replacement++; + } + } + } + *(write_cursor++) = '"'; + return new_string; +} + + +template <typename Char, typename StringType> +static MaybeObject* QuoteJsonString(Vector<const Char> characters) { + int length = characters.length(); + Counters::quote_json_char_count.Increment(length); + const int kSpaceForQuotes = 2; + int worst_case_length = length * kJsonQuoteWorstCaseBlowup + kSpaceForQuotes; + if (worst_case_length > kMaxGuaranteedNewSpaceString) { + return SlowQuoteJsonString<Char, StringType>(characters); + } + + MaybeObject* new_alloc = AllocateRawString<StringType>(worst_case_length); + Object* new_object; + if (!new_alloc->ToObject(&new_object)) { + return new_alloc; + } + if (!Heap::new_space()->Contains(new_object)) { + // Even if our string is small enough to fit in new space we still have to + // handle it being allocated in old space as may happen in the third + // attempt. See CALL_AND_RETRY in heap-inl.h and similar code in + // CEntryStub::GenerateCore. + return SlowQuoteJsonString<Char, StringType>(characters); + } + StringType* new_string = StringType::cast(new_object); + ASSERT(Heap::new_space()->Contains(new_string)); + + STATIC_ASSERT(SeqTwoByteString::kHeaderSize == SeqAsciiString::kHeaderSize); + Char* write_cursor = reinterpret_cast<Char*>( + new_string->address() + SeqAsciiString::kHeaderSize); + *(write_cursor++) = '"'; + + const Char* read_cursor = characters.start(); + const Char* end = read_cursor + length; + while (read_cursor < end) { + Char c = *(read_cursor++); + if (sizeof(Char) > 1u && static_cast<unsigned>(c) >= kQuoteTableLength) { + *(write_cursor++) = c; + } else { + int len = JsonQuoteLengths[static_cast<unsigned>(c)]; + const char* replacement = JsonQuotes + + static_cast<unsigned>(c) * kJsonQuotesCharactersPerEntry; + write_cursor[0] = replacement[0]; + if (len > 1) { + write_cursor[1] = replacement[1]; + if (len > 2) { + ASSERT(len == 6); + write_cursor[2] = replacement[2]; + write_cursor[3] = replacement[3]; + write_cursor[4] = replacement[4]; + write_cursor[5] = replacement[5]; + } + } + write_cursor += len; + } + } + *(write_cursor++) = '"'; + + int final_length = static_cast<int>( + write_cursor - reinterpret_cast<Char*>( + new_string->address() + SeqAsciiString::kHeaderSize)); + Heap::new_space()->ShrinkStringAtAllocationBoundary<StringType>(new_string, + final_length); + return new_string; +} + + +static MaybeObject* Runtime_QuoteJSONString(Arguments args) { + NoHandleAllocation ha; + CONVERT_CHECKED(String, str, args[0]); + if (!str->IsFlat()) { + MaybeObject* try_flatten = str->TryFlatten(); + Object* flat; + if (!try_flatten->ToObject(&flat)) { + return try_flatten; + } + str = String::cast(flat); + ASSERT(str->IsFlat()); + } + if (str->IsTwoByteRepresentation()) { + return QuoteJsonString<uc16, SeqTwoByteString>(str->ToUC16Vector()); + } else { + return QuoteJsonString<char, SeqAsciiString>(str->ToAsciiVector()); + } +} + + + static MaybeObject* Runtime_StringParseInt(Arguments args) { NoHandleAllocation ha; @@ -5278,6 +5400,13 @@ static MaybeObject* Runtime_NumberToSmi(Arguments args) { } +static MaybeObject* Runtime_AllocateHeapNumber(Arguments args) { + NoHandleAllocation ha; + ASSERT(args.length() == 0); + return Heap::AllocateHeapNumber(0); +} + + static MaybeObject* Runtime_NumberAdd(Arguments args) { NoHandleAllocation ha; ASSERT(args.length() == 2); @@ -5886,37 +6015,6 @@ static MaybeObject* Runtime_Math_log(Arguments args) { } -// Helper function to compute x^y, where y is known to be an -// integer. Uses binary decomposition to limit the number of -// multiplications; see the discussion in "Hacker's Delight" by Henry -// S. Warren, Jr., figure 11-6, page 213. -static double powi(double x, int y) { - ASSERT(y != kMinInt); - unsigned n = (y < 0) ? -y : y; - double m = x; - double p = 1; - while (true) { - if ((n & 1) != 0) p *= m; - n >>= 1; - if (n == 0) { - if (y < 0) { - // Unfortunately, we have to be careful when p has reached - // infinity in the computation, because sometimes the higher - // internal precision in the pow() implementation would have - // given us a finite p. This happens very rarely. - double result = 1.0 / p; - return (result == 0 && isinf(p)) - ? pow(x, static_cast<double>(y)) // Avoid pow(double, int). - : result; - } else { - return p; - } - } - m *= m; - } -} - - static MaybeObject* Runtime_Math_pow(Arguments args) { NoHandleAllocation ha; ASSERT(args.length() == 2); @@ -5928,31 +6026,11 @@ static MaybeObject* Runtime_Math_pow(Arguments args) { // custom powi() function than the generic pow(). if (args[1]->IsSmi()) { int y = Smi::cast(args[1])->value(); - return Heap::NumberFromDouble(powi(x, y)); + return Heap::NumberFromDouble(power_double_int(x, y)); } CONVERT_DOUBLE_CHECKED(y, args[1]); - - if (!isinf(x)) { - if (y == 0.5) { - // It's not uncommon to use Math.pow(x, 0.5) to compute the - // square root of a number. To speed up such computations, we - // explictly check for this case and use the sqrt() function - // which is faster than pow(). - return Heap::AllocateHeapNumber(sqrt(x)); - } else if (y == -0.5) { - // Optimized using Math.pow(x, -0.5) == 1 / Math.pow(x, 0.5). - return Heap::AllocateHeapNumber(1.0 / sqrt(x)); - } - } - - if (y == 0) { - return Smi::FromInt(1); - } else if (isnan(y) || ((x == 1 || x == -1) && isinf(y))) { - return Heap::nan_value(); - } else { - return Heap::AllocateHeapNumber(pow(x, y)); - } + return Heap::AllocateHeapNumber(power_double_double(x, y)); } // Fast version of Math.pow if we know that y is not an integer and @@ -5963,11 +6041,11 @@ static MaybeObject* Runtime_Math_pow_cfunction(Arguments args) { CONVERT_DOUBLE_CHECKED(x, args[0]); CONVERT_DOUBLE_CHECKED(y, args[1]); if (y == 0) { - return Smi::FromInt(1); + return Smi::FromInt(1); } else if (isnan(y) || ((x == 1 || x == -1) && isinf(y))) { - return Heap::nan_value(); + return Heap::nan_value(); } else { - return Heap::AllocateHeapNumber(pow(x, y)); + return Heap::AllocateHeapNumber(pow(x, y)); } } @@ -6550,9 +6628,12 @@ static MaybeObject* Runtime_NewObject(Arguments args) { } } - // The function should be compiled for the optimization hints to be available. + // The function should be compiled for the optimization hints to be + // available. We cannot use EnsureCompiled because that forces a + // compilation through the shared function info which makes it + // impossible for us to optimize. Handle<SharedFunctionInfo> shared(function->shared()); - EnsureCompiled(shared, CLEAR_EXCEPTION); + if (!function->is_compiled()) CompileLazy(function, CLEAR_EXCEPTION); if (!function->has_initial_map() && shared->IsInobjectSlackTrackingInProgress()) { @@ -6596,7 +6677,7 @@ static MaybeObject* Runtime_LazyCompile(Arguments args) { #ifdef DEBUG if (FLAG_trace_lazy && !function->shared()->is_compiled()) { PrintF("[lazy: "); - function->shared()->name()->Print(); + function->PrintName(); PrintF("]\n"); } #endif @@ -6613,10 +6694,241 @@ static MaybeObject* Runtime_LazyCompile(Arguments args) { return Failure::Exception(); } + // All done. Return the compiled code. + ASSERT(function->is_compiled()); return function->code(); } +static MaybeObject* Runtime_LazyRecompile(Arguments args) { + HandleScope scope; + ASSERT(args.length() == 1); + Handle<JSFunction> function = args.at<JSFunction>(0); + // If the function is not optimizable or debugger is active continue using the + // code from the full compiler. + if (!function->shared()->code()->optimizable() || + Debug::has_break_points()) { + function->ReplaceCode(function->shared()->code()); + return function->code(); + } + if (CompileOptimized(function, AstNode::kNoNumber)) { + return function->code(); + } + function->ReplaceCode(function->shared()->code()); + return Failure::Exception(); +} + + +static MaybeObject* Runtime_NotifyDeoptimized(Arguments args) { + HandleScope scope; + ASSERT(args.length() == 1); + RUNTIME_ASSERT(args[0]->IsSmi()); + Deoptimizer::BailoutType type = + static_cast<Deoptimizer::BailoutType>(Smi::cast(args[0])->value()); + Deoptimizer* deoptimizer = Deoptimizer::Grab(); + ASSERT(Heap::IsAllocationAllowed()); + int frames = deoptimizer->output_count(); + + JavaScriptFrameIterator it; + JavaScriptFrame* frame = NULL; + for (int i = 0; i < frames; i++) { + if (i != 0) it.Advance(); + frame = it.frame(); + deoptimizer->InsertHeapNumberValues(frames - i - 1, frame); + } + delete deoptimizer; + + RUNTIME_ASSERT(frame->function()->IsJSFunction()); + Handle<JSFunction> function(JSFunction::cast(frame->function())); + Handle<Object> arguments; + for (int i = frame->ComputeExpressionsCount() - 1; i >= 0; --i) { + if (frame->GetExpression(i) == Heap::the_hole_value()) { + if (arguments.is_null()) { + // FunctionGetArguments can't throw an exception, so cast away the + // doubt with an assert. + arguments = Handle<Object>( + Accessors::FunctionGetArguments(*function, + NULL)->ToObjectUnchecked()); + ASSERT(*arguments != Heap::null_value()); + ASSERT(*arguments != Heap::undefined_value()); + } + frame->SetExpression(i, *arguments); + } + } + + CompilationCache::MarkForLazyOptimizing(function); + if (type == Deoptimizer::EAGER) { + RUNTIME_ASSERT(function->IsOptimized()); + } else { + RUNTIME_ASSERT(!function->IsOptimized()); + } + + // Avoid doing too much work when running with --always-opt and keep + // the optimized code around. + if (FLAG_always_opt || type == Deoptimizer::LAZY) { + return Heap::undefined_value(); + } + + // Count the number of optimized activations of the function. + int activations = 0; + while (!it.done()) { + JavaScriptFrame* frame = it.frame(); + if (frame->is_optimized() && frame->function() == *function) { + activations++; + } + it.Advance(); + } + + // TODO(kasperl): For now, we cannot support removing the optimized + // code when we have recursive invocations of the same function. + if (activations == 0) { + if (FLAG_trace_deopt) { + PrintF("[removing optimized code for: "); + function->PrintName(); + PrintF("]\n"); + } + function->ReplaceCode(function->shared()->code()); + } + return Heap::undefined_value(); +} + + +static MaybeObject* Runtime_NotifyOSR(Arguments args) { + Deoptimizer* deoptimizer = Deoptimizer::Grab(); + delete deoptimizer; + return Heap::undefined_value(); +} + + +static MaybeObject* Runtime_DeoptimizeFunction(Arguments args) { + HandleScope scope; + ASSERT(args.length() == 1); + CONVERT_ARG_CHECKED(JSFunction, function, 0); + if (!function->IsOptimized()) return Heap::undefined_value(); + + Deoptimizer::DeoptimizeFunction(*function); + + return Heap::undefined_value(); +} + + +static MaybeObject* Runtime_CompileForOnStackReplacement(Arguments args) { + HandleScope scope; + ASSERT(args.length() == 1); + CONVERT_ARG_CHECKED(JSFunction, function, 0); + + // We're not prepared to handle a function with arguments object. + ASSERT(!function->shared()->scope_info()->HasArgumentsShadow()); + + // We have hit a back edge in an unoptimized frame for a function that was + // selected for on-stack replacement. Find the unoptimized code object. + Handle<Code> unoptimized(function->shared()->code()); + // Keep track of whether we've succeeded in optimizing. + bool succeeded = unoptimized->optimizable(); + if (succeeded) { + // If we are trying to do OSR when there are already optimized + // activations of the function, it means (a) the function is directly or + // indirectly recursive and (b) an optimized invocation has been + // deoptimized so that we are currently in an unoptimized activation. + // Check for optimized activations of this function. + JavaScriptFrameIterator it; + while (succeeded && !it.done()) { + JavaScriptFrame* frame = it.frame(); + succeeded = !frame->is_optimized() || frame->function() != *function; + it.Advance(); + } + } + + int ast_id = AstNode::kNoNumber; + if (succeeded) { + // The top JS function is this one, the PC is somewhere in the + // unoptimized code. + JavaScriptFrameIterator it; + JavaScriptFrame* frame = it.frame(); + ASSERT(frame->function() == *function); + ASSERT(frame->code() == *unoptimized); + ASSERT(unoptimized->contains(frame->pc())); + + // Use linear search of the unoptimized code's stack check table to find + // the AST id matching the PC. + Address start = unoptimized->instruction_start(); + unsigned target_pc_offset = static_cast<unsigned>(frame->pc() - start); + Address table_cursor = start + unoptimized->stack_check_table_start(); + uint32_t table_length = Memory::uint32_at(table_cursor); + table_cursor += kIntSize; + for (unsigned i = 0; i < table_length; ++i) { + // Table entries are (AST id, pc offset) pairs. + uint32_t pc_offset = Memory::uint32_at(table_cursor + kIntSize); + if (pc_offset == target_pc_offset) { + ast_id = static_cast<int>(Memory::uint32_at(table_cursor)); + break; + } + table_cursor += 2 * kIntSize; + } + ASSERT(ast_id != AstNode::kNoNumber); + if (FLAG_trace_osr) { + PrintF("[replacing on-stack at AST id %d in ", ast_id); + function->PrintName(); + PrintF("]\n"); + } + + // Try to compile the optimized code. A true return value from + // CompileOptimized means that compilation succeeded, not necessarily + // that optimization succeeded. + if (CompileOptimized(function, ast_id) && function->IsOptimized()) { + DeoptimizationInputData* data = DeoptimizationInputData::cast( + function->code()->deoptimization_data()); + if (data->OsrPcOffset()->value() >= 0) { + if (FLAG_trace_osr) { + PrintF("[on-stack replacement offset %d in optimized code]\n", + data->OsrPcOffset()->value()); + } + ASSERT(data->OsrAstId()->value() == ast_id); + } else { + // We may never generate the desired OSR entry if we emit an + // early deoptimize. + succeeded = false; + } + } else { + succeeded = false; + } + } + + // Revert to the original stack checks in the original unoptimized code. + if (FLAG_trace_osr) { + PrintF("[restoring original stack checks in "); + function->PrintName(); + PrintF("]\n"); + } + StackCheckStub check_stub; + Handle<Code> check_code = check_stub.GetCode(); + Handle<Code> replacement_code( + Builtins::builtin(Builtins::OnStackReplacement)); + // Iterate the unoptimized code and revert all the patched stack checks. + for (RelocIterator it(*unoptimized, RelocInfo::kCodeTargetMask); + !it.done(); + it.next()) { + RelocInfo* rinfo = it.rinfo(); + if (rinfo->target_address() == replacement_code->entry()) { + Deoptimizer::RevertStackCheckCode(rinfo, *check_code); + } + } + + // Allow OSR only at nesting level zero again. + unoptimized->set_allow_osr_at_loop_nesting_level(0); + + // If the optimization attempt succeeded, return the AST id tagged as a + // smi. This tells the builtin that we need to translate the unoptimized + // frame to an optimized one. + if (succeeded) { + ASSERT(function->code()->kind() == Code::OPTIMIZED_FUNCTION); + return Smi::FromInt(ast_id); + } else { + return Smi::FromInt(-1); + } +} + + static MaybeObject* Runtime_GetFunctionDelegate(Arguments args) { HandleScope scope; ASSERT(args.length() == 1); @@ -7351,13 +7663,13 @@ static MaybeObject* Runtime_AllocateInNewSpace(Arguments args) { } -// Push an array unto an array of arrays if it is not already in the +// Push an object unto an array of objects if it is not already in the // array. Returns true if the element was pushed on the stack and // false otherwise. static MaybeObject* Runtime_PushIfAbsent(Arguments args) { ASSERT(args.length() == 2); CONVERT_CHECKED(JSArray, array, args[0]); - CONVERT_CHECKED(JSArray, element, args[1]); + CONVERT_CHECKED(JSObject, element, args[1]); RUNTIME_ASSERT(array->HasFastElements()); int length = Smi::cast(array->length())->value(); FixedArray* elements = FixedArray::cast(array->elements()); @@ -7894,7 +8206,7 @@ static MaybeObject* Runtime_GetArrayKeys(Arguments args) { int keys_length = keys->length(); for (int i = 0; i < keys_length; i++) { Object* key = keys->get(i); - uint32_t index; + uint32_t index = 0; if (!key->ToArrayIndex(&index) || index >= length) { // Zap invalid keys. keys->set_undefined(i); @@ -8021,6 +8333,7 @@ static MaybeObject* DebugLookupResultValue(Object* receiver, String* name, MaybeObject* maybe_value = receiver->GetPropertyWithCallback( receiver, structure, name, result->holder()); if (!maybe_value->ToObject(&value)) { + if (maybe_value->IsRetryAfterGC()) return maybe_value; ASSERT(maybe_value->IsException()); maybe_value = Top::pending_exception(); Top::clear_pending_exception(); @@ -8321,6 +8634,9 @@ static MaybeObject* Runtime_GetFrameDetails(Arguments args) { } if (it.done()) return Heap::undefined_value(); + bool is_optimized_frame = + it.frame()->code()->kind() == Code::OPTIMIZED_FUNCTION; + // Traverse the saved contexts chain to find the active context for the // selected frame. SaveContext* save = Top::save_context(); @@ -8352,18 +8668,28 @@ static MaybeObject* Runtime_GetFrameDetails(Arguments args) { // (e.g. .result)? For users of the debugger, they will probably be // confusing. Handle<FixedArray> locals = Factory::NewFixedArray(info.NumberOfLocals() * 2); + + // Fill in the names of the locals. for (int i = 0; i < info.NumberOfLocals(); i++) { - // Name of the local. locals->set(i * 2, *info.LocalName(i)); + } - // Fetch the value of the local - either from the stack or from a - // heap-allocated context. - if (i < info.number_of_stack_slots()) { + // Fill in the values of the locals. + for (int i = 0; i < info.NumberOfLocals(); i++) { + if (is_optimized_frame) { + // If we are inspecting an optimized frame use undefined as the + // value for all locals. + // + // TODO(3141533): We should be able to get the correct values + // for locals in optimized frames. + locals->set(i * 2 + 1, Heap::undefined_value()); + } else if (i < info.number_of_stack_slots()) { + // Get the value from the stack. locals->set(i * 2 + 1, it.frame()->GetExpression(i)); } else { - Handle<String> name = info.LocalName(i); // Traverse the context chain to the function context as all local // variables stored in the context will be on the function context. + Handle<String> name = info.LocalName(i); while (!context->is_function_context()) { context = Handle<Context>(context->previous()); } @@ -8373,8 +8699,12 @@ static MaybeObject* Runtime_GetFrameDetails(Arguments args) { } } - // Check whether this frame is positioned at return. - int at_return = (index == 0) ? Debug::IsBreakAtReturn(it.frame()) : false; + // Check whether this frame is positioned at return. If not top + // frame or if the frame is optimized it cannot be at a return. + bool at_return = false; + if (!is_optimized_frame && index == 0) { + at_return = Debug::IsBreakAtReturn(it.frame()); + } // If positioned just before return find the value to be returned and add it // to the frame information. @@ -8468,8 +8798,13 @@ static MaybeObject* Runtime_GetFrameDetails(Arguments args) { details->set(details_index++, Heap::undefined_value()); } - // Parameter value. - if (i < it.frame()->GetProvidedParametersCount()) { + // Parameter value. If we are inspecting an optimized frame, use + // undefined as the value. + // + // TODO(3141533): We should be able to get the actual parameter + // value for optimized frames. + if (!is_optimized_frame && + (i < it.frame()->GetProvidedParametersCount())) { details->set(details_index++, it.frame()->GetParameter(i)); } else { details->set(details_index++, Heap::undefined_value()); @@ -9063,7 +9398,7 @@ Object* Runtime::FindSharedFunctionInfoInScript(Handle<Script> script, // Iterate the heap looking for SharedFunctionInfo generated from the // script. The inner most SharedFunctionInfo containing the source position // for the requested break point is found. - // NOTE: This might reqire several heap iterations. If the SharedFunctionInfo + // NOTE: This might require several heap iterations. If the SharedFunctionInfo // which is found is not compiled it is compiled and the heap is iterated // again as the compilation might create inner functions from the newly // compiled function and the actual requested break point might be in one of @@ -9345,7 +9680,7 @@ static MaybeObject* Runtime_DebugEvaluate(Arguments args) { // Check the execution state and decode arguments frame and source to be // evaluated. - ASSERT(args.length() == 4); + ASSERT(args.length() == 5); Object* check_result; { MaybeObject* maybe_check_result = Runtime_CheckExecutionState(args); if (!maybe_check_result->ToObject(&check_result)) { @@ -9355,6 +9690,7 @@ static MaybeObject* Runtime_DebugEvaluate(Arguments args) { CONVERT_CHECKED(Smi, wrapped_id, args[1]); CONVERT_ARG_CHECKED(String, source, 2); CONVERT_BOOLEAN_CHECKED(disable_break, args[3]); + Handle<Object> additional_context(args[4]); // Handle the processing of break. DisableBreak disable_break_save(disable_break); @@ -9405,6 +9741,11 @@ static MaybeObject* Runtime_DebugEvaluate(Arguments args) { Handle<Context> function_context(frame_context->fcontext()); context = CopyWithContextChain(frame_context, context); + if (additional_context->IsJSObject()) { + context = Factory::NewWithContext(context, + Handle<JSObject>::cast(additional_context), false); + } + // Wrap the evaluation statement in a new function compiled in the newly // created context. The function has one parameter which has to be called // 'arguments'. This it to have access to what would have been 'arguments' in @@ -9459,7 +9800,7 @@ static MaybeObject* Runtime_DebugEvaluateGlobal(Arguments args) { // Check the execution state and decode arguments frame and source to be // evaluated. - ASSERT(args.length() == 3); + ASSERT(args.length() == 4); Object* check_result; { MaybeObject* maybe_check_result = Runtime_CheckExecutionState(args); if (!maybe_check_result->ToObject(&check_result)) { @@ -9468,6 +9809,7 @@ static MaybeObject* Runtime_DebugEvaluateGlobal(Arguments args) { } CONVERT_ARG_CHECKED(String, source, 1); CONVERT_BOOLEAN_CHECKED(disable_break, args[2]); + Handle<Object> additional_context(args[3]); // Handle the processing of break. DisableBreak disable_break_save(disable_break); @@ -9486,11 +9828,24 @@ static MaybeObject* Runtime_DebugEvaluateGlobal(Arguments args) { // debugger was invoked. Handle<Context> context = Top::global_context(); + bool is_global = true; + + if (additional_context->IsJSObject()) { + // Create a function context first, than put 'with' context on top of it. + Handle<JSFunction> go_between = Factory::NewFunction( + Factory::empty_string(), Factory::undefined_value()); + go_between->set_context(*context); + context = + Factory::NewFunctionContext(Context::MIN_CONTEXT_SLOTS, go_between); + context->set_extension(JSObject::cast(*additional_context)); + is_global = false; + } + // Compile the source to be evaluated. Handle<SharedFunctionInfo> shared = Compiler::CompileEval(source, context, - true); + is_global); if (shared.is_null()) return Failure::Exception(); Handle<JSFunction> compiled_function = Handle<JSFunction>(Factory::NewFunctionFromSharedFunctionInfo(shared, @@ -9885,6 +10240,15 @@ static MaybeObject* Runtime_LiveEditReplaceScript(Arguments args) { } } + +static MaybeObject* Runtime_LiveEditFunctionSourceUpdated(Arguments args) { + ASSERT(args.length() == 1); + HandleScope scope; + CONVERT_ARG_CHECKED(JSArray, shared_info, 0); + return LiveEdit::FunctionSourceUpdated(shared_info); +} + + // Replaces code of SharedFunctionInfo with a new one. static MaybeObject* Runtime_LiveEditReplaceFunctionCode(Arguments args) { ASSERT(args.length() == 2); @@ -9987,7 +10351,12 @@ static MaybeObject* Runtime_GetFunctionCodePositionFromSource(Arguments args) { Handle<Code> code(function->code()); - RelocIterator it(*code, 1 << RelocInfo::STATEMENT_POSITION); + if (code->kind() != Code::FUNCTION && + code->kind() != Code::OPTIMIZED_FUNCTION) { + return Heap::undefined_value(); + } + + RelocIterator it(*code, RelocInfo::ModeMask(RelocInfo::STATEMENT_POSITION)); int closest_pc = 0; int distance = kMaxInt; while (!it.done()) { @@ -10141,9 +10510,9 @@ static bool ShowFrameInStackTrace(StackFrame* raw_frame, Object* caller, } -// Collect the raw data for a stack trace. Returns an array of three -// element segments each containing a receiver, function and native -// code offset. +// Collect the raw data for a stack trace. Returns an array of 4 +// element segments each containing a receiver, function, code and +// native code offset. static MaybeObject* Runtime_CollectStackTrace(Arguments args) { ASSERT_EQ(args.length(), 2); Handle<Object> caller = args.at<Object>(0); @@ -10153,7 +10522,7 @@ static MaybeObject* Runtime_CollectStackTrace(Arguments args) { limit = Max(limit, 0); // Ensure that limit is not negative. int initial_size = Min(limit, 10); - Handle<JSArray> result = Factory::NewJSArray(initial_size * 3); + Handle<JSArray> result = Factory::NewJSArray(initial_size * 4); StackFrameIterator iter; // If the caller parameter is a function we skip frames until we're @@ -10166,23 +10535,25 @@ static MaybeObject* Runtime_CollectStackTrace(Arguments args) { if (ShowFrameInStackTrace(raw_frame, *caller, &seen_caller)) { frames_seen++; JavaScriptFrame* frame = JavaScriptFrame::cast(raw_frame); - Object* recv = frame->receiver(); - Object* fun = frame->function(); - Address pc = frame->pc(); - Address start = frame->code()->address(); - Smi* offset = Smi::FromInt(static_cast<int>(pc - start)); - FixedArray* elements = FixedArray::cast(result->elements()); - if (cursor + 2 < elements->length()) { - elements->set(cursor++, recv); - elements->set(cursor++, fun); - elements->set(cursor++, offset); - } else { - HandleScope scope; - Handle<Object> recv_handle(recv); - Handle<Object> fun_handle(fun); - SetElement(result, cursor++, recv_handle); - SetElement(result, cursor++, fun_handle); - SetElement(result, cursor++, Handle<Smi>(offset)); + List<FrameSummary> frames(3); // Max 2 levels of inlining. + frame->Summarize(&frames); + for (int i = frames.length() - 1; i >= 0; i--) { + Handle<Object> recv = frames[i].receiver(); + Handle<JSFunction> fun = frames[i].function(); + Handle<Code> code = frames[i].code(); + Handle<Smi> offset(Smi::FromInt(frames[i].offset())); + FixedArray* elements = FixedArray::cast(result->elements()); + if (cursor + 3 < elements->length()) { + elements->set(cursor++, *recv); + elements->set(cursor++, *fun); + elements->set(cursor++, *code); + elements->set(cursor++, *offset); + } else { + SetElement(result, cursor++, recv); + SetElement(result, cursor++, fun); + SetElement(result, cursor++, code); + SetElement(result, cursor++, offset); + } } } iter.Advance(); diff --git a/src/runtime.h b/src/runtime.h index f9ebbc42..5ecae7e9 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -79,6 +79,11 @@ namespace internal { F(GetConstructorDelegate, 1, 1) \ F(NewArgumentsFast, 3, 1) \ F(LazyCompile, 1, 1) \ + F(LazyRecompile, 1, 1) \ + F(NotifyDeoptimized, 1, 1) \ + F(NotifyOSR, 0, 1) \ + F(DeoptimizeFunction, 1, 1) \ + F(CompileForOnStackReplacement, 1, 1) \ F(SetNewFunctionAttributes, 1, 1) \ F(AllocateInNewSpace, 1, 1) \ \ @@ -100,6 +105,7 @@ namespace internal { F(CharFromCode, 1, 1) \ F(URIEscape, 1, 1) \ F(URIUnescape, 1, 1) \ + F(QuoteJSONString, 1, 1) \ \ F(NumberToString, 1, 1) \ F(NumberToStringSkipCache, 1, 1) \ @@ -108,6 +114,7 @@ namespace internal { F(NumberToJSUint32, 1, 1) \ F(NumberToJSInt32, 1, 1) \ F(NumberToSmi, 1, 1) \ + F(AllocateHeapNumber, 0, 1) \ \ /* Arithmetic operations */ \ F(NumberAdd, 2, 1) \ @@ -335,8 +342,8 @@ namespace internal { F(IsBreakOnException, 1, 1) \ F(PrepareStep, 3, 1) \ F(ClearStepping, 0, 1) \ - F(DebugEvaluate, 4, 1) \ - F(DebugEvaluateGlobal, 3, 1) \ + F(DebugEvaluate, 5, 1) \ + F(DebugEvaluateGlobal, 4, 1) \ F(DebugGetLoadedScripts, 0, 1) \ F(DebugReferencedBy, 3, 1) \ F(DebugConstructedBy, 2, 1) \ @@ -349,6 +356,7 @@ namespace internal { F(LiveEditGatherCompileInfo, 2, 1) \ F(LiveEditReplaceScript, 3, 1) \ F(LiveEditReplaceFunctionCode, 2, 1) \ + F(LiveEditFunctionSourceUpdated, 1, 1) \ F(LiveEditFunctionSetScript, 2, 1) \ F(LiveEditReplaceRefToNestedFunction, 3, 1) \ F(LiveEditPatchFunctionPositions, 2, 1) \ @@ -416,6 +424,7 @@ namespace internal { F(MathSin, 1, 1) \ F(MathCos, 1, 1) \ F(MathSqrt, 1, 1) \ + F(MathLog, 1, 1) \ F(IsRegExpEquivalent, 2, 1) \ F(HasCachedArrayIndex, 1, 1) \ F(GetCachedArrayIndex, 1, 1) \ diff --git a/src/runtime.js b/src/runtime.js index f2c8d6b8..28a38ca8 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -594,13 +594,15 @@ function IsPrimitive(x) { // ECMA-262, section 8.6.2.6, page 28. function DefaultNumber(x) { - if (IS_FUNCTION(x.valueOf)) { - var v = x.valueOf(); + var valueOf = x.valueOf; + if (IS_FUNCTION(valueOf)) { + var v = %_CallFunction(x, valueOf); if (%IsPrimitive(v)) return v; } - if (IS_FUNCTION(x.toString)) { - var s = x.toString(); + var toString = x.toString; + if (IS_FUNCTION(toString)) { + var s = %_CallFunction(x, toString); if (%IsPrimitive(s)) return s; } @@ -610,13 +612,15 @@ function DefaultNumber(x) { // ECMA-262, section 8.6.2.6, page 28. function DefaultString(x) { - if (IS_FUNCTION(x.toString)) { - var s = x.toString(); + var toString = x.toString; + if (IS_FUNCTION(toString)) { + var s = %_CallFunction(x, toString); if (%IsPrimitive(s)) return s; } - if (IS_FUNCTION(x.valueOf)) { - var v = x.valueOf(); + var valueOf = x.valueOf; + if (IS_FUNCTION(valueOf)) { + var v = %_CallFunction(x, valueOf); if (%IsPrimitive(v)) return v; } diff --git a/src/safepoint-table.cc b/src/safepoint-table.cc new file mode 100644 index 00000000..b9468a50 --- /dev/null +++ b/src/safepoint-table.cc @@ -0,0 +1,210 @@ +// Copyright 2010 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. + +#include "safepoint-table.h" +#include "disasm.h" + +namespace v8 { +namespace internal { + +SafepointTable::SafepointTable(Code* code) { + ASSERT(code->kind() == Code::OPTIMIZED_FUNCTION); + code_ = code; + Address header = code->instruction_start() + code->safepoint_table_start(); + length_ = Memory::uint32_at(header + kLengthOffset); + entry_size_ = Memory::uint32_at(header + kEntrySizeOffset); + pc_and_deoptimization_indexes_ = header + kHeaderSize; + entries_ = pc_and_deoptimization_indexes_ + + (length_ * kPcAndDeoptimizationIndexSize); + ASSERT(entry_size_ > 0); + ASSERT_EQ(DeoptimizationIndexField::max(), Safepoint::kNoDeoptimizationIndex); +} + + +bool SafepointTable::HasRegisters(uint8_t* entry) { + ASSERT(IsAligned(kNumSafepointRegisters, kBitsPerByte)); + const int num_reg_bytes = kNumSafepointRegisters >> kBitsPerByteLog2; + for (int i = 0; i < num_reg_bytes; i++) { + if (entry[i] != kNoRegisters) return true; + } + return false; +} + + +bool SafepointTable::HasRegisterAt(uint8_t* entry, int reg_index) { + ASSERT(reg_index >= 0 && reg_index < kNumSafepointRegisters); + int byte_index = reg_index >> kBitsPerByteLog2; + int bit_index = reg_index & (kBitsPerByte - 1); + return (entry[byte_index] & (1 << bit_index)) != 0; +} + + +void SafepointTable::PrintEntry(unsigned index) const { + disasm::NameConverter converter; + uint8_t* entry = GetEntry(index); + + // Print the stack slot bits. + if (entry_size_ > 0) { + ASSERT(IsAligned(kNumSafepointRegisters, kBitsPerByte)); + const int first = kNumSafepointRegisters >> kBitsPerByteLog2; + int last = entry_size_ - 1; + for (int i = first; i < last; i++) PrintBits(entry[i], kBitsPerByte); + int last_bits = code_->stack_slots() - ((last - first) * kBitsPerByte); + PrintBits(entry[last], last_bits); + + // Print the registers (if any). + if (!HasRegisters(entry)) return; + for (int j = 0; j < kNumSafepointRegisters; j++) { + if (HasRegisterAt(entry, j)) { + PrintF(" | %s", converter.NameOfCPURegister(j)); + } + } + } +} + + +void SafepointTable::PrintBits(uint8_t byte, int digits) { + ASSERT(digits >= 0 && digits <= kBitsPerByte); + for (int i = 0; i < digits; i++) { + PrintF("%c", ((byte & (1 << i)) == 0) ? '0' : '1'); + } +} + + +Safepoint SafepointTableBuilder::DefineSafepoint(Assembler* assembler, + int deoptimization_index) { + ASSERT(deoptimization_index != -1); + DeoptimizationInfo pc_and_deoptimization_index; + pc_and_deoptimization_index.pc = assembler->pc_offset(); + pc_and_deoptimization_index.deoptimization_index = deoptimization_index; + pc_and_deoptimization_index.pc_after_gap = assembler->pc_offset(); + deoptimization_info_.Add(pc_and_deoptimization_index); + indexes_.Add(new ZoneList<int>(8)); + registers_.Add(NULL); + return Safepoint(indexes_.last(), registers_.last()); +} + + +Safepoint SafepointTableBuilder::DefineSafepointWithRegisters( + Assembler* assembler, int arguments, int deoptimization_index) { + ASSERT(deoptimization_index != -1); + ASSERT(arguments == 0); // Only case that works for now. + DeoptimizationInfo pc_and_deoptimization_index; + pc_and_deoptimization_index.pc = assembler->pc_offset(); + pc_and_deoptimization_index.deoptimization_index = deoptimization_index; + pc_and_deoptimization_index.pc_after_gap = assembler->pc_offset(); + deoptimization_info_.Add(pc_and_deoptimization_index); + indexes_.Add(new ZoneList<int>(8)); + registers_.Add(new ZoneList<int>(4)); + return Safepoint(indexes_.last(), registers_.last()); +} + + +unsigned SafepointTableBuilder::GetCodeOffset() const { + ASSERT(emitted_); + return offset_; +} + + +void SafepointTableBuilder::Emit(Assembler* assembler, int bits_per_entry) { + // Make sure the safepoint table is properly aligned. Pad with nops. + assembler->Align(kIntSize); + assembler->RecordComment(";;; Safepoint table."); + offset_ = assembler->pc_offset(); + + // Take the register bits into account. + bits_per_entry += kNumSafepointRegisters; + + // Compute the number of bytes per safepoint entry. + int bytes_per_entry = + RoundUp(bits_per_entry, kBitsPerByte) >> kBitsPerByteLog2; + + // Emit the table header. + int length = deoptimization_info_.length(); + assembler->dd(length); + assembler->dd(bytes_per_entry); + + // Emit sorted table of pc offsets together with deoptimization indexes and + // pc after gap information. + for (int i = 0; i < length; i++) { + assembler->dd(deoptimization_info_[i].pc); + assembler->dd(EncodeDeoptimizationIndexAndGap(deoptimization_info_[i])); + } + + // Emit table of bitmaps. + ZoneList<uint8_t> bits(bytes_per_entry); + for (int i = 0; i < length; i++) { + ZoneList<int>* indexes = indexes_[i]; + ZoneList<int>* registers = registers_[i]; + bits.Clear(); + bits.AddBlock(0, bytes_per_entry); + + // Run through the registers (if any). + ASSERT(IsAligned(kNumSafepointRegisters, kBitsPerByte)); + if (registers == NULL) { + const int num_reg_bytes = kNumSafepointRegisters >> kBitsPerByteLog2; + for (int j = 0; j < num_reg_bytes; j++) { + bits[j] = SafepointTable::kNoRegisters; + } + } else { + for (int j = 0; j < registers->length(); j++) { + int index = registers->at(j); + ASSERT(index >= 0 && index < kNumSafepointRegisters); + int byte_index = index >> kBitsPerByteLog2; + int bit_index = index & (kBitsPerByte - 1); + bits[byte_index] |= (1 << bit_index); + } + } + + // Run through the indexes and build a bitmap. + for (int j = 0; j < indexes->length(); j++) { + int index = bits_per_entry - 1 - indexes->at(j); + int byte_index = index >> kBitsPerByteLog2; + int bit_index = index & (kBitsPerByte - 1); + bits[byte_index] |= (1U << bit_index); + } + + // Emit the bitmap for the current entry. + for (int k = 0; k < bytes_per_entry; k++) { + assembler->db(bits[k]); + } + } + emitted_ = true; +} + + +uint32_t SafepointTableBuilder::EncodeDeoptimizationIndexAndGap( + DeoptimizationInfo info) { + unsigned index = info.deoptimization_index; + unsigned gap_size = info.pc_after_gap - info.pc; + uint32_t encoding = SafepointTable::DeoptimizationIndexField::encode(index); + encoding |= SafepointTable::GapCodeSizeField::encode(gap_size); + return encoding; +} + + +} } // namespace v8::internal diff --git a/src/safepoint-table.h b/src/safepoint-table.h new file mode 100644 index 00000000..010ac575 --- /dev/null +++ b/src/safepoint-table.h @@ -0,0 +1,189 @@ +// Copyright 2010 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. + +#ifndef V8_SAFEPOINT_TABLE_H_ +#define V8_SAFEPOINT_TABLE_H_ + +#include "v8.h" + +#include "macro-assembler.h" +#include "zone.h" +#include "zone-inl.h" + +namespace v8 { +namespace internal { + +class SafepointTable BASE_EMBEDDED { + public: + explicit SafepointTable(Code* code); + + int size() const { + return kHeaderSize + + (length_ * (kPcAndDeoptimizationIndexSize + entry_size_)); } + unsigned length() const { return length_; } + unsigned entry_size() const { return entry_size_; } + + unsigned GetPcOffset(unsigned index) const { + ASSERT(index < length_); + return Memory::uint32_at(GetPcOffsetLocation(index)); + } + + int GetDeoptimizationIndex(unsigned index) const { + ASSERT(index < length_); + unsigned value = Memory::uint32_at(GetDeoptimizationLocation(index)); + return DeoptimizationIndexField::decode(value); + } + + unsigned GetGapCodeSize(unsigned index) const { + ASSERT(index < length_); + unsigned value = Memory::uint32_at(GetDeoptimizationLocation(index)); + return GapCodeSizeField::decode(value); + } + + uint8_t* GetEntry(unsigned index) const { + ASSERT(index < length_); + return &Memory::uint8_at(entries_ + (index * entry_size_)); + } + + class GapCodeSizeField: public BitField<unsigned, 0, 8> {}; + class DeoptimizationIndexField: public BitField<int, 8, 24> {}; + + static bool HasRegisters(uint8_t* entry); + static bool HasRegisterAt(uint8_t* entry, int reg_index); + + void PrintEntry(unsigned index) const; + + private: + static const uint8_t kNoRegisters = 0xFF; + + static const int kLengthOffset = 0; + static const int kEntrySizeOffset = kLengthOffset + kIntSize; + static const int kHeaderSize = kEntrySizeOffset + kIntSize; + + static const int kPcSize = kIntSize; + static const int kDeoptimizationIndexSize = kIntSize; + static const int kPcAndDeoptimizationIndexSize = + kPcSize + kDeoptimizationIndexSize; + + Address GetPcOffsetLocation(unsigned index) const { + return pc_and_deoptimization_indexes_ + + (index * kPcAndDeoptimizationIndexSize); + } + + Address GetDeoptimizationLocation(unsigned index) const { + return GetPcOffsetLocation(index) + kPcSize; + } + + static void PrintBits(uint8_t byte, int digits); + + AssertNoAllocation no_allocation_; + Code* code_; + unsigned length_; + unsigned entry_size_; + + Address pc_and_deoptimization_indexes_; + Address entries_; + + friend class SafepointTableBuilder; +}; + + +class Safepoint BASE_EMBEDDED { + public: + static const int kNoDeoptimizationIndex = 0x00ffffff; + + void DefinePointerSlot(int index) { indexes_->Add(index); } + void DefinePointerRegister(Register reg) { registers_->Add(reg.code()); } + + private: + Safepoint(ZoneList<int>* indexes, ZoneList<int>* registers) : + indexes_(indexes), registers_(registers) { } + ZoneList<int>* indexes_; + ZoneList<int>* registers_; + + friend class SafepointTableBuilder; +}; + + +class SafepointTableBuilder BASE_EMBEDDED { + public: + SafepointTableBuilder() + : deoptimization_info_(32), + indexes_(32), + registers_(32), + emitted_(false) { } + + // Get the offset of the emitted safepoint table in the code. + unsigned GetCodeOffset() const; + + // Define a new safepoint for the current position in the body. + Safepoint DefineSafepoint( + Assembler* assembler, + int deoptimization_index = Safepoint::kNoDeoptimizationIndex); + + // Define a new safepoint with registers on the stack for the + // current position in the body and take the number of arguments on + // top of the registers into account. + Safepoint DefineSafepointWithRegisters( + Assembler* assembler, + int arguments, + int deoptimization_index = Safepoint::kNoDeoptimizationIndex); + + // Update the last safepoint with the size of the code generated for the gap + // following it. + void SetPcAfterGap(int pc) { + ASSERT(!deoptimization_info_.is_empty()); + int index = deoptimization_info_.length() - 1; + deoptimization_info_[index].pc_after_gap = pc; + } + + // Emit the safepoint table after the body. The number of bits per + // entry must be enough to hold all the pointer indexes. + void Emit(Assembler* assembler, int bits_per_entry); + + private: + struct DeoptimizationInfo { + unsigned pc; + unsigned deoptimization_index; + unsigned pc_after_gap; + }; + + uint32_t EncodeDeoptimizationIndexAndGap(DeoptimizationInfo info); + + ZoneList<DeoptimizationInfo> deoptimization_info_; + ZoneList<ZoneList<int>*> indexes_; + ZoneList<ZoneList<int>*> registers_; + + bool emitted_; + unsigned offset_; + + DISALLOW_COPY_AND_ASSIGN(SafepointTableBuilder); +}; + +} } // namespace v8::internal + +#endif // V8_SAFEPOINT_TABLE_H_ diff --git a/src/scanner-base.cc b/src/scanner-base.cc index 8242f81c..b26fee01 100644 --- a/src/scanner-base.cc +++ b/src/scanner-base.cc @@ -35,12 +35,6 @@ namespace v8 { namespace internal { // ---------------------------------------------------------------------------- -// UTF16Buffer - -UTF16Buffer::UTF16Buffer() - : pos_(0), end_(kNoEndPosition) { } - -// ---------------------------------------------------------------------------- // LiteralCollector LiteralCollector::LiteralCollector() @@ -92,7 +86,7 @@ bool ScannerConstants::IsIdentifier(unibrow::CharacterStream* buffer) { // ---------------------------------------------------------------------------- // Scanner -Scanner::Scanner() : source_(NULL), stack_overflow_(false) {} +Scanner::Scanner() { } uc32 Scanner::ScanHexEscape(uc32 c, int length) { @@ -142,8 +136,7 @@ uc32 Scanner::ScanOctalEscape(uc32 c, int length) { // ---------------------------------------------------------------------------- // JavaScriptScanner -JavaScriptScanner::JavaScriptScanner() - : has_line_terminator_before_next_(false) {} +JavaScriptScanner::JavaScriptScanner() : Scanner() {} Token::Value JavaScriptScanner::Next() { @@ -503,12 +496,21 @@ void JavaScriptScanner::Scan() { void JavaScriptScanner::SeekForward(int pos) { - source_->SeekForward(pos - 1); - Advance(); - // This function is only called to seek to the location - // of the end of a function (at the "}" token). It doesn't matter - // whether there was a line terminator in the part we skip. - has_line_terminator_before_next_ = false; + // After this call, we will have the token at the given position as + // the "next" token. The "current" token will be invalid. + if (pos == next_.location.beg_pos) return; + int current_pos = source_pos(); + ASSERT_EQ(next_.location.end_pos, current_pos); + // Positions inside the lookahead token aren't supported. + ASSERT(pos >= current_pos); + if (pos != current_pos) { + source_->SeekForward(pos - source_->pos()); + Advance(); + // This function is only called to seek to the location + // of the end of a function (at the "}" token). It doesn't matter + // whether there was a line terminator in the part we skip. + has_line_terminator_before_next_ = false; + } Scan(); } diff --git a/src/scanner-base.h b/src/scanner-base.h index 3714ae2d..c50b8f3e 100644 --- a/src/scanner-base.h +++ b/src/scanner-base.h @@ -52,31 +52,75 @@ inline int HexValue(uc32 c) { return -1; } -// ---------------------------------------------------------------------------- -// UTF16Buffer - scanner input source with pushback. -class UTF16Buffer { +// --------------------------------------------------------------------- +// Buffered stream of characters, using an internal UC16 buffer. + +class UC16CharacterStream { public: - UTF16Buffer(); - virtual ~UTF16Buffer() {} + UC16CharacterStream() : pos_(0) { } + virtual ~UC16CharacterStream() { } + + // Returns and advances past the next UC16 character in the input + // stream. If there are no more characters, it returns a negative + // value. + inline int32_t Advance() { + if (buffer_cursor_ < buffer_end_ || ReadBlock()) { + pos_++; + return *(buffer_cursor_++); + } + // Note: currently the following increment is necessary to avoid a + // parser problem! The scanner treats the final kEndOfInput as + // a character with a position, and does math relative to that + // position. + pos_++; - virtual void PushBack(uc32 ch) = 0; - // Returns a value < 0 when the buffer end is reached. - virtual uc32 Advance() = 0; - virtual void SeekForward(int pos) = 0; + return kEndOfInput; + } - int pos() const { return pos_; } + // Return the current position in the character stream. + // Starts at zero. + inline unsigned pos() const { return pos_; } + + // Skips forward past the next character_count UC16 characters + // in the input, or until the end of input if that comes sooner. + // Returns the number of characters actually skipped. If less + // than character_count, + inline unsigned SeekForward(unsigned character_count) { + unsigned buffered_chars = + static_cast<unsigned>(buffer_end_ - buffer_cursor_); + if (character_count <= buffered_chars) { + buffer_cursor_ += character_count; + pos_ += character_count; + return character_count; + } + return SlowSeekForward(character_count); + } - static const int kNoEndPosition = 1; + // Pushes back the most recently read UC16 character, i.e., + // the value returned by the most recent call to Advance. + // Must not be used right after calling SeekForward. + virtual void PushBack(uc16 character) = 0; protected: - // Initial value of end_ before the input stream is initialized. - - int pos_; // Current position in the buffer. - int end_; // Position where scanning should stop (EOF). + static const int32_t kEndOfInput = -1; + + // Ensures that the buffer_cursor_ points to the character at + // position pos_ of the input, if possible. If the position + // is at or after the end of the input, return false. If there + // are more characters available, return true. + virtual bool ReadBlock() = 0; + virtual unsigned SlowSeekForward(unsigned character_count) = 0; + + const uc16* buffer_cursor_; + const uc16* buffer_end_; + unsigned pos_; }; +// --------------------------------------------------------------------- +// Constants used by scanners. + class ScannerConstants : AllStatic { public: typedef unibrow::Utf8InputBuffer<1024> Utf8Decoder; @@ -228,8 +272,6 @@ class Scanner { return Vector<const char>(next_literal_string(), next_literal_length()); } - bool stack_overflow() { return stack_overflow_; } - static const int kCharacterLookaheadBufferSize = 1; protected: @@ -279,7 +321,7 @@ class Scanner { // Low-level scanning support. void Advance() { c0_ = source_->Advance(); } void PushBack(uc32 ch) { - source_->PushBack(ch); + source_->PushBack(c0_); c0_ = ch; } @@ -309,15 +351,13 @@ class Scanner { TokenDesc current_; // desc for current token (as returned by Next()) TokenDesc next_; // desc for next token (one token look-ahead) - // Input stream. Must be initialized to an UTF16Buffer. - UTF16Buffer* source_; + // Input stream. Must be initialized to an UC16CharacterStream. + UC16CharacterStream* source_; // Buffer to hold literal values (identifiers, strings, numbers) // using '\x00'-terminated UTF-8 encoding. Handles allocation internally. LiteralCollector literal_buffer_; - bool stack_overflow_; - // One Unicode character look-ahead; c0_ < 0 at the end of the input. uc32 c0_; }; diff --git a/src/scanner.cc b/src/scanner.cc index 63b2fd80..47e9895c 100755 --- a/src/scanner.cc +++ b/src/scanner.cc @@ -36,63 +36,265 @@ namespace v8 { namespace internal { // ---------------------------------------------------------------------------- -// UTF16Buffer - -// CharacterStreamUTF16Buffer -CharacterStreamUTF16Buffer::CharacterStreamUTF16Buffer() - : pushback_buffer_(0), last_(0), stream_(NULL) { } +// BufferedUC16CharacterStreams + +BufferedUC16CharacterStream::BufferedUC16CharacterStream() + : UC16CharacterStream(), + pushback_limit_(NULL) { + // Initialize buffer as being empty. First read will fill the buffer. + buffer_cursor_ = buffer_; + buffer_end_ = buffer_; +} +BufferedUC16CharacterStream::~BufferedUC16CharacterStream() { } -void CharacterStreamUTF16Buffer::Initialize(Handle<String> data, - unibrow::CharacterStream* input, - int start_position, - int end_position) { - stream_ = input; - if (start_position > 0) { - SeekForward(start_position); +void BufferedUC16CharacterStream::PushBack(uc16 character) { + if (pushback_limit_ == NULL && buffer_cursor_ > buffer_) { + // buffer_ is writable, buffer_cursor_ is const pointer. + buffer_[--buffer_cursor_ - buffer_] = character; + pos_--; + return; } - end_ = end_position != kNoEndPosition ? end_position : kMaxInt; + SlowPushBack(character); } -void CharacterStreamUTF16Buffer::PushBack(uc32 ch) { - pushback_buffer()->Add(last_); - last_ = ch; +void BufferedUC16CharacterStream::SlowPushBack(uc16 character) { + // In pushback mode, the end of the buffer contains pushback, + // and the start of the buffer (from buffer start to pushback_limit_) + // contains valid data that comes just after the pushback. + // We NULL the pushback_limit_ if pushing all the way back to the + // start of the buffer. + + if (pushback_limit_ == NULL) { + // Enter pushback mode. + pushback_limit_ = buffer_end_; + buffer_end_ = buffer_ + kBufferSize; + buffer_cursor_ = buffer_end_; + } + ASSERT(pushback_limit_ > buffer_); + ASSERT(pos_ > 0); + buffer_[--buffer_cursor_ - buffer_] = character; + if (buffer_cursor_ == buffer_) { + pushback_limit_ = NULL; + } else if (buffer_cursor_ < pushback_limit_) { + pushback_limit_ = buffer_cursor_; + } pos_--; } -uc32 CharacterStreamUTF16Buffer::Advance() { - ASSERT(end_ != kNoEndPosition); - ASSERT(end_ >= 0); - // NOTE: It is of importance to Persian / Farsi resources that we do - // *not* strip format control characters in the scanner; see - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=274152 - // - // So, even though ECMA-262, section 7.1, page 11, dictates that we - // must remove Unicode format-control characters, we do not. This is - // in line with how IE and SpiderMonkey handles it. - if (!pushback_buffer()->is_empty()) { - pos_++; - return last_ = pushback_buffer()->RemoveLast(); - } else if (stream_->has_more() && pos_ < end_) { - pos_++; - uc32 next = stream_->GetNext(); - return last_ = next; - } else { - // Note: currently the following increment is necessary to avoid a - // test-parser problem! - pos_++; - return last_ = static_cast<uc32>(-1); +bool BufferedUC16CharacterStream::ReadBlock() { + if (pushback_limit_ != NULL) { + buffer_cursor_ = buffer_; + buffer_end_ = pushback_limit_; + pushback_limit_ = NULL; + ASSERT(buffer_cursor_ != buffer_end_); + return true; + } + unsigned length = FillBuffer(pos_, kBufferSize); + buffer_cursor_ = buffer_; + buffer_end_ = buffer_ + length; + return length > 0; +} + + +unsigned BufferedUC16CharacterStream::SlowSeekForward(unsigned delta) { + // Leave pushback mode (i.e., ignore that there might be valid data + // in the buffer before the pushback_limit_ point). + pushback_limit_ = NULL; + return BufferSeekForward(delta); +} + +// ---------------------------------------------------------------------------- +// GenericStringUC16CharacterStream + + +GenericStringUC16CharacterStream::GenericStringUC16CharacterStream( + Handle<String> data, + unsigned start_position, + unsigned end_position) + : string_(data), + length_(end_position) { + ASSERT(end_position >= start_position); + buffer_cursor_ = buffer_; + buffer_end_ = buffer_; + pos_ = start_position; +} + + +GenericStringUC16CharacterStream::~GenericStringUC16CharacterStream() { } + + +unsigned GenericStringUC16CharacterStream::BufferSeekForward(unsigned delta) { + unsigned old_pos = pos_; + pos_ = Min(pos_ + delta, length_); + ReadBlock(); + return pos_ - old_pos; +} + + +unsigned GenericStringUC16CharacterStream::FillBuffer(unsigned from_pos, + unsigned length) { + if (from_pos >= length_) return 0; + if (from_pos + length > length_) { + length = length_ - from_pos; + } + String::WriteToFlat<uc16>(*string_, buffer_, from_pos, from_pos + length); + return length; +} + + +// ---------------------------------------------------------------------------- +// Utf8ToUC16CharacterStream +Utf8ToUC16CharacterStream::Utf8ToUC16CharacterStream(const byte* data, + unsigned length) + : BufferedUC16CharacterStream(), + raw_data_(data), + raw_data_length_(length), + raw_data_pos_(0), + raw_character_position_(0) { + ReadBlock(); +} + + +Utf8ToUC16CharacterStream::~Utf8ToUC16CharacterStream() { } + + +unsigned Utf8ToUC16CharacterStream::BufferSeekForward(unsigned delta) { + unsigned old_pos = pos_; + unsigned target_pos = pos_ + delta; + SetRawPosition(target_pos); + pos_ = raw_character_position_; + ReadBlock(); + return pos_ - old_pos; +} + + +unsigned Utf8ToUC16CharacterStream::FillBuffer(unsigned char_position, + unsigned length) { + static const unibrow::uchar kMaxUC16Character = 0xffff; + SetRawPosition(char_position); + if (raw_character_position_ != char_position) { + // char_position was not a valid position in the stream (hit the end + // while spooling to it). + return 0u; + } + unsigned i = 0; + while (i < length) { + if (raw_data_pos_ == raw_data_length_) break; + unibrow::uchar c = raw_data_[raw_data_pos_]; + if (c <= unibrow::Utf8::kMaxOneByteChar) { + raw_data_pos_++; + } else { + c = unibrow::Utf8::CalculateValue(raw_data_ + raw_data_pos_, + raw_data_length_ - raw_data_pos_, + &raw_data_pos_); + // Don't allow characters outside of the BMP. + if (c > kMaxUC16Character) { + c = unibrow::Utf8::kBadChar; + } + } + buffer_[i++] = static_cast<uc16>(c); + } + raw_character_position_ = char_position + i; + return i; +} + + +static const byte kUtf8MultiByteMask = 0xC0; +static const byte kUtf8MultiByteCharStart = 0xC0; +static const byte kUtf8MultiByteCharFollower = 0x80; + + +#ifdef DEBUG +static bool IsUtf8MultiCharacterStart(byte first_byte) { + return (first_byte & kUtf8MultiByteMask) == kUtf8MultiByteCharStart; +} +#endif + + +static bool IsUtf8MultiCharacterFollower(byte later_byte) { + return (later_byte & kUtf8MultiByteMask) == kUtf8MultiByteCharFollower; +} + + +// Move the cursor back to point at the preceding UTF-8 character start +// in the buffer. +static inline void Utf8CharacterBack(const byte* buffer, unsigned* cursor) { + byte character = buffer[--*cursor]; + if (character > unibrow::Utf8::kMaxOneByteChar) { + ASSERT(IsUtf8MultiCharacterFollower(character)); + // Last byte of a multi-byte character encoding. Step backwards until + // pointing to the first byte of the encoding, recognized by having the + // top two bits set. + while (IsUtf8MultiCharacterFollower(buffer[--*cursor])) { } + ASSERT(IsUtf8MultiCharacterStart(buffer[*cursor])); + } +} + + +// Move the cursor forward to point at the next following UTF-8 character start +// in the buffer. +static inline void Utf8CharacterForward(const byte* buffer, unsigned* cursor) { + byte character = buffer[(*cursor)++]; + if (character > unibrow::Utf8::kMaxOneByteChar) { + // First character of a multi-byte character encoding. + // The number of most-significant one-bits determines the length of the + // encoding: + // 110..... - (0xCx, 0xDx) one additional byte (minimum). + // 1110.... - (0xEx) two additional bytes. + // 11110... - (0xFx) three additional bytes (maximum). + ASSERT(IsUtf8MultiCharacterStart(character)); + // Additional bytes is: + // 1 if value in range 0xC0 .. 0xDF. + // 2 if value in range 0xE0 .. 0xEF. + // 3 if value in range 0xF0 .. 0xF7. + // Encode that in a single value. + unsigned additional_bytes = + ((0x3211u) >> (((character - 0xC0) >> 2) & 0xC)) & 0x03; + *cursor += additional_bytes; + ASSERT(!IsUtf8MultiCharacterFollower(buffer[1 + additional_bytes])); } } -void CharacterStreamUTF16Buffer::SeekForward(int pos) { - pos_ = pos; - ASSERT(pushback_buffer()->is_empty()); - stream_->Seek(pos); +void Utf8ToUC16CharacterStream::SetRawPosition(unsigned target_position) { + if (raw_character_position_ > target_position) { + // Spool backwards in utf8 buffer. + do { + Utf8CharacterBack(raw_data_, &raw_data_pos_); + raw_character_position_--; + } while (raw_character_position_ > target_position); + return; + } + // Spool forwards in the utf8 buffer. + while (raw_character_position_ < target_position) { + if (raw_data_pos_ == raw_data_length_) return; + Utf8CharacterForward(raw_data_, &raw_data_pos_); + raw_character_position_++; + } +} + + +// ---------------------------------------------------------------------------- +// ExternalTwoByteStringUC16CharacterStream + +ExternalTwoByteStringUC16CharacterStream:: + ~ExternalTwoByteStringUC16CharacterStream() { } + + +ExternalTwoByteStringUC16CharacterStream + ::ExternalTwoByteStringUC16CharacterStream( + Handle<ExternalTwoByteString> data, + int start_position, + int end_position) + : UC16CharacterStream(), + source_(data), + raw_data_(data->GetTwoByteData(start_position)) { + buffer_cursor_ = raw_data_, + buffer_end_ = raw_data_ + (end_position - start_position); + pos_ = start_position; } @@ -115,46 +317,19 @@ void Scanner::LiteralScope::Complete() { complete_ = true; } + // ---------------------------------------------------------------------------- // V8JavaScriptScanner -void V8JavaScriptScanner::Initialize(Handle<String> source, - int literal_flags) { - source_ = stream_initializer_.Init(source, NULL, 0, source->length()); - // Need to capture identifiers in order to recognize "get" and "set" - // in object literals. - literal_flags_ = literal_flags | kLiteralIdentifier; - Init(); - // Skip initial whitespace allowing HTML comment ends just like - // after a newline and scan first token. - has_line_terminator_before_next_ = true; - SkipWhiteSpace(); - Scan(); -} +V8JavaScriptScanner::V8JavaScriptScanner() : JavaScriptScanner() { } -void V8JavaScriptScanner::Initialize(Handle<String> source, - unibrow::CharacterStream* stream, +void V8JavaScriptScanner::Initialize(UC16CharacterStream* source, int literal_flags) { - source_ = stream_initializer_.Init(source, stream, - 0, UTF16Buffer::kNoEndPosition); - literal_flags_ = literal_flags | kLiteralIdentifier; - Init(); - // Skip initial whitespace allowing HTML comment ends just like - // after a newline and scan first token. - has_line_terminator_before_next_ = true; - SkipWhiteSpace(); - Scan(); -} - - -void V8JavaScriptScanner::Initialize(Handle<String> source, - int start_position, - int end_position, - int literal_flags) { - source_ = stream_initializer_.Init(source, NULL, - start_position, end_position); + source_ = source; literal_flags_ = literal_flags | kLiteralIdentifier; + // Need to capture identifiers in order to recognize "get" and "set" + // in object literals. Init(); // Skip initial whitespace allowing HTML comment ends just like // after a newline and scan first token. @@ -164,64 +339,14 @@ void V8JavaScriptScanner::Initialize(Handle<String> source, } -Token::Value V8JavaScriptScanner::NextCheckStack() { - // BUG 1215673: Find a thread safe way to set a stack limit in - // pre-parse mode. Otherwise, we cannot safely pre-parse from other - // threads. - StackLimitCheck check; - if (check.HasOverflowed()) { - stack_overflow_ = true; - current_ = next_; - next_.token = Token::ILLEGAL; - return current_.token; - } else { - return Next(); - } -} - - -UTF16Buffer* StreamInitializer::Init(Handle<String> source, - unibrow::CharacterStream* stream, - int start_position, - int end_position) { - // Either initialize the scanner from a character stream or from a - // string. - ASSERT(source.is_null() || stream == NULL); - - // Initialize the source buffer. - if (!source.is_null() && StringShape(*source).IsExternalTwoByte()) { - two_byte_string_buffer_.Initialize( - Handle<ExternalTwoByteString>::cast(source), - start_position, - end_position); - return &two_byte_string_buffer_; - } else if (!source.is_null() && StringShape(*source).IsExternalAscii()) { - ascii_string_buffer_.Initialize( - Handle<ExternalAsciiString>::cast(source), - start_position, - end_position); - return &ascii_string_buffer_; - } else { - if (!source.is_null()) { - safe_string_input_buffer_.Reset(source.location()); - stream = &safe_string_input_buffer_; - } - char_stream_buffer_.Initialize(source, - stream, - start_position, - end_position); - return &char_stream_buffer_; - } -} - // ---------------------------------------------------------------------------- // JsonScanner -JsonScanner::JsonScanner() {} +JsonScanner::JsonScanner() : Scanner() { } -void JsonScanner::Initialize(Handle<String> source) { - source_ = stream_initializer_.Init(source, NULL, 0, source->length()); +void JsonScanner::Initialize(UC16CharacterStream* source) { + source_ = source; Init(); // Skip initial whitespace. SkipJsonWhiteSpace(); @@ -236,13 +361,7 @@ Token::Value JsonScanner::Next() { // threads. current_ = next_; // Check for stack-overflow before returning any tokens. - StackLimitCheck check; - if (check.HasOverflowed()) { - stack_overflow_ = true; - next_.token = Token::ILLEGAL; - } else { - ScanJson(); - } + ScanJson(); return current_.token; } diff --git a/src/scanner.h b/src/scanner.h index acb9b47b..572778f8 100644 --- a/src/scanner.h +++ b/src/scanner.h @@ -35,67 +35,97 @@ namespace v8 { namespace internal { -// UTF16 buffer to read characters from a character stream. -class CharacterStreamUTF16Buffer: public UTF16Buffer { +// A buffered character stream based on a random access character +// source (ReadBlock can be called with pos_ pointing to any position, +// even positions before the current). +class BufferedUC16CharacterStream: public UC16CharacterStream { public: - CharacterStreamUTF16Buffer(); - virtual ~CharacterStreamUTF16Buffer() {} - void Initialize(Handle<String> data, - unibrow::CharacterStream* stream, - int start_position, - int end_position); - virtual void PushBack(uc32 ch); - virtual uc32 Advance(); - virtual void SeekForward(int pos); - - private: - List<uc32> pushback_buffer_; - uc32 last_; - unibrow::CharacterStream* stream_; - - List<uc32>* pushback_buffer() { return &pushback_buffer_; } + BufferedUC16CharacterStream(); + virtual ~BufferedUC16CharacterStream(); + + virtual void PushBack(uc16 character); + + protected: + static const unsigned kBufferSize = 512; + static const unsigned kPushBackStepSize = 16; + + virtual unsigned SlowSeekForward(unsigned delta); + virtual bool ReadBlock(); + virtual void SlowPushBack(uc16 character); + + virtual unsigned BufferSeekForward(unsigned delta) = 0; + virtual unsigned FillBuffer(unsigned position, unsigned length) = 0; + + const uc16* pushback_limit_; + uc16 buffer_[kBufferSize]; }; -// UTF16 buffer to read characters from an external string. -template <typename StringType, typename CharType> -class ExternalStringUTF16Buffer: public UTF16Buffer { +// Generic string stream. +class GenericStringUC16CharacterStream: public BufferedUC16CharacterStream { public: - ExternalStringUTF16Buffer(); - virtual ~ExternalStringUTF16Buffer() {} - void Initialize(Handle<StringType> data, - int start_position, - int end_position); - virtual void PushBack(uc32 ch); - virtual uc32 Advance(); - virtual void SeekForward(int pos); - - private: - const CharType* raw_data_; // Pointer to the actual array of characters. + GenericStringUC16CharacterStream(Handle<String> data, + unsigned start_position, + unsigned end_position); + virtual ~GenericStringUC16CharacterStream(); + + protected: + virtual unsigned BufferSeekForward(unsigned delta); + virtual unsigned FillBuffer(unsigned position, unsigned length); + + Handle<String> string_; + unsigned start_position_; + unsigned length_; }; -// Initializes a UTF16Buffer as input stream, using one of a number -// of strategies depending on the available character sources. -class StreamInitializer { +// UC16 stream based on a literal UTF-8 string. +class Utf8ToUC16CharacterStream: public BufferedUC16CharacterStream { public: - UTF16Buffer* Init(Handle<String> source, - unibrow::CharacterStream* stream, - int start_position, - int end_position); - private: - // Different UTF16 buffers used to pull characters from. Based on input one of - // these will be initialized as the actual data source. - CharacterStreamUTF16Buffer char_stream_buffer_; - ExternalStringUTF16Buffer<ExternalTwoByteString, uint16_t> - two_byte_string_buffer_; - ExternalStringUTF16Buffer<ExternalAsciiString, char> ascii_string_buffer_; - - // Used to convert the source string into a character stream when a stream - // is not passed to the scanner. - SafeStringInputBuffer safe_string_input_buffer_; + Utf8ToUC16CharacterStream(const byte* data, unsigned length); + virtual ~Utf8ToUC16CharacterStream(); + + protected: + virtual unsigned BufferSeekForward(unsigned delta); + virtual unsigned FillBuffer(unsigned char_position, unsigned length); + void SetRawPosition(unsigned char_position); + + const byte* raw_data_; + unsigned raw_data_length_; // Measured in bytes, not characters. + unsigned raw_data_pos_; + // The character position of the character at raw_data[raw_data_pos_]. + // Not necessarily the same as pos_. + unsigned raw_character_position_; }; + +// UTF16 buffer to read characters from an external string. +class ExternalTwoByteStringUC16CharacterStream: public UC16CharacterStream { + public: + ExternalTwoByteStringUC16CharacterStream(Handle<ExternalTwoByteString> data, + int start_position, + int end_position); + virtual ~ExternalTwoByteStringUC16CharacterStream(); + + virtual void PushBack(uc16 character) { + ASSERT(buffer_cursor_ > raw_data_); + buffer_cursor_--; + pos_--; + } + protected: + virtual unsigned SlowSeekForward(unsigned delta) { + // Fast case always handles seeking. + return 0; + } + virtual bool ReadBlock() { + // Entire string is read at start. + return false; + } + Handle<ExternalTwoByteString> source_; + const uc16* raw_data_; // Pointer to the actual array of characters. +}; + + // ---------------------------------------------------------------------------- // V8JavaScriptScanner // JavaScript scanner getting its input from either a V8 String or a unicode @@ -103,21 +133,9 @@ class StreamInitializer { class V8JavaScriptScanner : public JavaScriptScanner { public: - V8JavaScriptScanner() {} - - Token::Value NextCheckStack(); - - // Initialize the Scanner to scan source. - void Initialize(Handle<String> source, int literal_flags = kAllLiterals); - void Initialize(Handle<String> source, - unibrow::CharacterStream* stream, - int literal_flags = kAllLiterals); - void Initialize(Handle<String> source, - int start_position, int end_position, + V8JavaScriptScanner(); + void Initialize(UC16CharacterStream* source, int literal_flags = kAllLiterals); - - protected: - StreamInitializer stream_initializer_; }; @@ -125,8 +143,7 @@ class JsonScanner : public Scanner { public: JsonScanner(); - // Initialize the Scanner to scan source. - void Initialize(Handle<String> source); + void Initialize(UC16CharacterStream* source); // Returns the next token. Token::Value Next(); @@ -140,7 +157,7 @@ class JsonScanner : public Scanner { // Recognizes all of the single-character tokens directly, or calls a function // to scan a number, string or identifier literal. // The only allowed whitespace characters between tokens are tab, - // carrige-return, newline and space. + // carriage-return, newline and space. void ScanJson(); // A JSON number (production JSONNumber) is a subset of the valid JavaScript @@ -161,60 +178,8 @@ class JsonScanner : public Scanner { // are the only valid JSON identifiers (productions JSONBooleanLiteral, // JSONNullLiteral). Token::Value ScanJsonIdentifier(const char* text, Token::Value token); - - StreamInitializer stream_initializer_; }; - -// ExternalStringUTF16Buffer -template <typename StringType, typename CharType> -ExternalStringUTF16Buffer<StringType, CharType>::ExternalStringUTF16Buffer() - : raw_data_(NULL) { } - - -template <typename StringType, typename CharType> -void ExternalStringUTF16Buffer<StringType, CharType>::Initialize( - Handle<StringType> data, - int start_position, - int end_position) { - ASSERT(!data.is_null()); - raw_data_ = data->resource()->data(); - - ASSERT(end_position <= data->length()); - if (start_position > 0) { - SeekForward(start_position); - } - end_ = - end_position != kNoEndPosition ? end_position : data->length(); -} - - -template <typename StringType, typename CharType> -uc32 ExternalStringUTF16Buffer<StringType, CharType>::Advance() { - if (pos_ < end_) { - return raw_data_[pos_++]; - } else { - // note: currently the following increment is necessary to avoid a - // test-parser problem! - pos_++; - return static_cast<uc32>(-1); - } -} - - -template <typename StringType, typename CharType> -void ExternalStringUTF16Buffer<StringType, CharType>::PushBack(uc32 ch) { - pos_--; - ASSERT(pos_ >= Scanner::kCharacterLookaheadBufferSize); - ASSERT(raw_data_[pos_ - Scanner::kCharacterLookaheadBufferSize] == ch); -} - - -template <typename StringType, typename CharType> -void ExternalStringUTF16Buffer<StringType, CharType>::SeekForward(int pos) { - pos_ = pos; -} - } } // namespace v8::internal #endif // V8_SCANNER_H_ diff --git a/src/scopeinfo.h b/src/scopeinfo.h index b210ae76..dd49a4e0 100644 --- a/src/scopeinfo.h +++ b/src/scopeinfo.h @@ -109,9 +109,14 @@ class SerializedScopeInfo : public FixedArray { return reinterpret_cast<SerializedScopeInfo*>(object); } - // Does this scope call eval. + // Does this scope call eval? bool CallsEval(); + // Does this scope have an arguments shadow? + bool HasArgumentsShadow() { + return StackSlotIndex(Heap::arguments_shadow_symbol()) >= 0; + } + // Return the number of stack slots for code. int NumberOfStackSlots(); diff --git a/src/scopes.cc b/src/scopes.cc index 5ff250ff..3565e11b 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -291,13 +291,11 @@ void Scope::RemoveUnresolved(VariableProxy* var) { } -VariableProxy* Scope::NewTemporary(Handle<String> name) { - Variable* var = new Variable(this, name, Variable::TEMPORARY, true, - Variable::NORMAL); - VariableProxy* tmp = new VariableProxy(name, false, false); - tmp->BindTo(var); +Variable* Scope::NewTemporary(Handle<String> name) { + Variable* var = + new Variable(this, name, Variable::TEMPORARY, true, Variable::NORMAL); temps_.Add(var); - return tmp; + return var; } @@ -861,11 +859,13 @@ void Scope::AllocateParameterLocals() { // allocated. arguments_shadow_->is_accessed_from_inner_scope_ = true; } - var->rewrite_ = + Property* rewrite = new Property(new VariableProxy(arguments_shadow_), new Literal(Handle<Object>(Smi::FromInt(i))), RelocInfo::kNoPosition, Property::SYNTHETIC); + rewrite->set_is_arguments_access(true); + var->rewrite_ = rewrite; } } diff --git a/src/scopes.h b/src/scopes.h index 526c3d34..d909b81f 100644 --- a/src/scopes.h +++ b/src/scopes.h @@ -105,7 +105,7 @@ class Scope: public ZoneObject { static bool Analyze(CompilationInfo* info); // The scope name is only used for printing/debugging. - void SetScopeName(Handle<String> scope_name) { scope_name_ = scope_name; } + void SetScopeName(Handle<String> scope_name) { scope_name_ = scope_name; } virtual void Initialize(bool inside_with); @@ -156,11 +156,11 @@ class Scope: public ZoneObject { // such a variable again if it was added; otherwise this is a no-op. void RemoveUnresolved(VariableProxy* var); - // Creates a new temporary variable in this scope and binds a proxy to it. - // The name is only used for printing and cannot be used to find the variable. - // In particular, the only way to get hold of the temporary is by keeping the - // VariableProxy* around. - virtual VariableProxy* NewTemporary(Handle<String> name); + // Creates a new temporary variable in this scope. The name is only used + // for printing and cannot be used to find the variable. In particular, + // the only way to get hold of the temporary is by keeping the Variable* + // around. + virtual Variable* NewTemporary(Handle<String> name); // Adds the specific declaration node to the list of declarations in // this scope. The declarations are processed as part of entering @@ -188,10 +188,10 @@ class Scope: public ZoneObject { // Scope-specific info. // Inform the scope that the corresponding code contains a with statement. - void RecordWithStatement() { scope_contains_with_ = true; } + void RecordWithStatement() { scope_contains_with_ = true; } // Inform the scope that the corresponding code contains an eval call. - void RecordEvalCall() { scope_calls_eval_ = true; } + void RecordEvalCall() { scope_calls_eval_ = true; } // --------------------------------------------------------------------------- @@ -423,7 +423,7 @@ class DummyScope : public Scope { return NULL; } - virtual VariableProxy* NewTemporary(Handle<String> name) { return NULL; } + virtual Variable* NewTemporary(Handle<String> name) { return NULL; } virtual bool HasTrivialOuterContext() const { return (nesting_level_ == 0 || inside_with_level_ <= 0); diff --git a/src/serialize.cc b/src/serialize.cc index 15fed442..00a601ff 100644 --- a/src/serialize.cc +++ b/src/serialize.cc @@ -470,6 +470,34 @@ void ExternalReferenceTable::PopulateTable() { UNCLASSIFIED, 32, "HandleScope::level"); + Add(ExternalReference::new_deoptimizer_function().address(), + UNCLASSIFIED, + 33, + "Deoptimizer::New()"); + Add(ExternalReference::compute_output_frames_function().address(), + UNCLASSIFIED, + 34, + "Deoptimizer::ComputeOutputFrames()"); + Add(ExternalReference::address_of_min_int().address(), + UNCLASSIFIED, + 35, + "LDoubleConstant::min_int"); + Add(ExternalReference::address_of_one_half().address(), + UNCLASSIFIED, + 36, + "LDoubleConstant::one_half"); + Add(ExternalReference::address_of_negative_infinity().address(), + UNCLASSIFIED, + 37, + "LDoubleConstant::negative_infinity"); + Add(ExternalReference::power_double_double_function().address(), + UNCLASSIFIED, + 38, + "power_double_double_function"); + Add(ExternalReference::power_double_int_function().address(), + UNCLASSIFIED, + 39, + "power_double_int_function"); } @@ -1370,6 +1398,13 @@ void Serializer::ObjectSerializer::VisitCodeEntry(Address entry_address) { } +void Serializer::ObjectSerializer::VisitGlobalPropertyCell(RelocInfo* rinfo) { + // We shouldn't have any global property cell references in code + // objects in the snapshot. + UNREACHABLE(); +} + + void Serializer::ObjectSerializer::VisitExternalAsciiString( v8::String::ExternalAsciiStringResource** resource_pointer) { Address references_start = reinterpret_cast<Address>(resource_pointer); diff --git a/src/serialize.h b/src/serialize.h index 92a51497..e80c302d 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -449,6 +449,7 @@ class Serializer : public SerializerDeserializer { void VisitExternalReferences(Address* start, Address* end); void VisitCodeTarget(RelocInfo* target); void VisitCodeEntry(Address entry_address); + void VisitGlobalPropertyCell(RelocInfo* rinfo); void VisitRuntimeEntry(RelocInfo* reloc); // Used for seralizing the external strings that hold the natives source. void VisitExternalAsciiString( diff --git a/src/spaces-inl.h b/src/spaces-inl.h index 78062232..b5ee1e40 100644 --- a/src/spaces-inl.h +++ b/src/spaces-inl.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2006-2010 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: @@ -412,6 +412,14 @@ bool PagedSpace::Contains(Address addr) { } +bool PagedSpace::SafeContains(Address addr) { + if (!MemoryAllocator::SafeIsInAPageChunk(addr)) return false; + Page* p = Page::FromAddress(addr); + if (!p->is_valid()) return false; + return MemoryAllocator::IsPageInSpace(p, this); +} + + // Try linear allocation in the page of alloc_info's allocation top. Does // not contain slow case logic (eg, move to the next page or try free list // allocation) so it can be used by all the allocation functions and for all @@ -460,16 +468,20 @@ MaybeObject* PagedSpace::MCAllocateRaw(int size_in_bytes) { // ----------------------------------------------------------------------------- // LargeObjectChunk -HeapObject* LargeObjectChunk::GetObject() { +Address LargeObjectChunk::GetStartAddress() { // Round the chunk address up to the nearest page-aligned address // and return the heap object in that page. Page* page = Page::FromAddress(RoundUp(address(), Page::kPageSize)); - return HeapObject::FromAddress(page->ObjectAreaStart()); + return page->ObjectAreaStart(); } +void LargeObjectChunk::Free(Executability executable) { + MemoryAllocator::FreeRawMemory(address(), size(), executable); +} + // ----------------------------------------------------------------------------- -// LargeObjectSpace +// NewSpace MaybeObject* NewSpace::AllocateRawInternal(int size_in_bytes, AllocationInfo* alloc_info) { @@ -489,6 +501,18 @@ MaybeObject* NewSpace::AllocateRawInternal(int size_in_bytes, } +template <typename StringType> +void NewSpace::ShrinkStringAtAllocationBoundary(String* string, int length) { + ASSERT(length <= string->length()); + ASSERT(string->IsSeqString()); + ASSERT(string->address() + StringType::SizeFor(string->length()) == + allocation_info_.top); + allocation_info_.top = + string->address() + StringType::SizeFor(length); + string->set_length(length); +} + + bool FreeListNode::IsFreeListNode(HeapObject* object) { return object->map() == Heap::raw_unchecked_byte_array_map() || object->map() == Heap::raw_unchecked_one_pointer_filler_map() diff --git a/src/spaces.cc b/src/spaces.cc index 239c9cd6..fca10329 100644 --- a/src/spaces.cc +++ b/src/spaces.cc @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2006-2010 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: @@ -333,6 +333,11 @@ bool MemoryAllocator::Setup(intptr_t capacity, intptr_t capacity_executable) { } +bool MemoryAllocator::SafeIsInAPageChunk(Address addr) { + return InInitialChunk(addr) || InAllocatedChunks(addr); +} + + void MemoryAllocator::TearDown() { for (int i = 0; i < max_nof_chunks_; i++) { if (chunks_[i].address() != NULL) DeleteChunk(i); @@ -346,6 +351,10 @@ void MemoryAllocator::TearDown() { initial_chunk_ = NULL; } + FreeChunkTables(&chunk_table_[0], + kChunkTableTopLevelEntries, + kChunkTableLevels); + ASSERT(top_ == max_nof_chunks_); // all chunks are free top_ = 0; capacity_ = 0; @@ -355,6 +364,22 @@ void MemoryAllocator::TearDown() { } +void MemoryAllocator::FreeChunkTables(uintptr_t* array, int len, int level) { + for (int i = 0; i < len; i++) { + if (array[i] != kUnusedChunkTableEntry) { + uintptr_t* subarray = reinterpret_cast<uintptr_t*>(array[i]); + if (level > 1) { + array[i] = kUnusedChunkTableEntry; + FreeChunkTables(subarray, 1 << kChunkTableBitsPerLevel, level - 1); + } else { + array[i] = kUnusedChunkTableEntry; + } + delete[] subarray; + } + } +} + + void* MemoryAllocator::AllocateRawMemory(const size_t requested, size_t* allocated, Executability executable) { @@ -488,25 +513,19 @@ static int PagesInChunk(Address start, size_t size) { } -Page* MemoryAllocator::AllocatePages(int requested_pages, int* allocated_pages, +Page* MemoryAllocator::AllocatePages(int requested_pages, + int* allocated_pages, PagedSpace* owner) { if (requested_pages <= 0) return Page::FromAddress(NULL); size_t chunk_size = requested_pages * Page::kPageSize; - // There is not enough space to guarantee the desired number pages can be - // allocated. - if (size_ + static_cast<int>(chunk_size) > capacity_) { - // Request as many pages as we can. - chunk_size = capacity_ - size_; - requested_pages = static_cast<int>(chunk_size >> kPageSizeBits); - - if (requested_pages <= 0) return Page::FromAddress(NULL); - } void* chunk = AllocateRawMemory(chunk_size, &chunk_size, owner->executable()); if (chunk == NULL) return Page::FromAddress(NULL); LOG(NewEvent("PagedChunk", chunk, chunk_size)); *allocated_pages = PagesInChunk(static_cast<Address>(chunk), chunk_size); + // We may 'lose' a page due to alignment. + ASSERT(*allocated_pages >= kPagesPerChunk - 1); if (*allocated_pages == 0) { FreeRawMemory(chunk, chunk_size, owner->executable()); LOG(DeleteEvent("PagedChunk", chunk)); @@ -518,7 +537,11 @@ Page* MemoryAllocator::AllocatePages(int requested_pages, int* allocated_pages, ObjectSpace space = static_cast<ObjectSpace>(1 << owner->identity()); PerformAllocationCallback(space, kAllocationActionAllocate, chunk_size); - return InitializePagesInChunk(chunk_id, *allocated_pages, owner); + Page* new_pages = InitializePagesInChunk(chunk_id, *allocated_pages, owner); + + AddToAllocatedChunks(static_cast<Address>(chunk), chunk_size); + + return new_pages; } @@ -675,6 +698,7 @@ void MemoryAllocator::DeleteChunk(int chunk_id) { initial_chunk_->Uncommit(c.address(), c.size()); Counters::memory_allocated.Decrement(static_cast<int>(c.size())); } else { + RemoveFromAllocatedChunks(c.address(), c.size()); LOG(DeleteEvent("PagedChunk", c.address())); ObjectSpace space = static_cast<ObjectSpace>(1 << c.owner()->identity()); size_t size = c.size(); @@ -788,6 +812,123 @@ Page* MemoryAllocator::RelinkPagesInChunk(int chunk_id, } +void MemoryAllocator::AddToAllocatedChunks(Address addr, intptr_t size) { + ASSERT(size == kChunkSize); + uintptr_t int_address = reinterpret_cast<uintptr_t>(addr); + AddChunkUsingAddress(int_address, int_address); + AddChunkUsingAddress(int_address, int_address + size - 1); +} + + +void MemoryAllocator::AddChunkUsingAddress(uintptr_t chunk_start, + uintptr_t chunk_index_base) { + uintptr_t* fine_grained = AllocatedChunksFinder( + chunk_table_, + chunk_index_base, + kChunkSizeLog2 + (kChunkTableLevels - 1) * kChunkTableBitsPerLevel, + kCreateTablesAsNeeded); + int index = FineGrainedIndexForAddress(chunk_index_base); + if (fine_grained[index] != kUnusedChunkTableEntry) index++; + ASSERT(fine_grained[index] == kUnusedChunkTableEntry); + fine_grained[index] = chunk_start; +} + + +void MemoryAllocator::RemoveFromAllocatedChunks(Address addr, intptr_t size) { + ASSERT(size == kChunkSize); + uintptr_t int_address = reinterpret_cast<uintptr_t>(addr); + RemoveChunkFoundUsingAddress(int_address, int_address); + RemoveChunkFoundUsingAddress(int_address, int_address + size - 1); +} + + +void MemoryAllocator::RemoveChunkFoundUsingAddress( + uintptr_t chunk_start, + uintptr_t chunk_index_base) { + uintptr_t* fine_grained = AllocatedChunksFinder( + chunk_table_, + chunk_index_base, + kChunkSizeLog2 + (kChunkTableLevels - 1) * kChunkTableBitsPerLevel, + kDontCreateTables); + // Can't remove an entry that's not there. + ASSERT(fine_grained != kUnusedChunkTableEntry); + int index = FineGrainedIndexForAddress(chunk_index_base); + ASSERT(fine_grained[index] != kUnusedChunkTableEntry); + if (fine_grained[index] != chunk_start) { + index++; + ASSERT(fine_grained[index] == chunk_start); + fine_grained[index] = kUnusedChunkTableEntry; + } else { + // If only one of the entries is used it must be the first, since + // InAllocatedChunks relies on that. Move things around so that this is + // the case. + fine_grained[index] = fine_grained[index + 1]; + fine_grained[index + 1] = kUnusedChunkTableEntry; + } +} + + +bool MemoryAllocator::InAllocatedChunks(Address addr) { + uintptr_t int_address = reinterpret_cast<uintptr_t>(addr); + uintptr_t* fine_grained = AllocatedChunksFinder( + chunk_table_, + int_address, + kChunkSizeLog2 + (kChunkTableLevels - 1) * kChunkTableBitsPerLevel, + kDontCreateTables); + if (fine_grained == NULL) return false; + int index = FineGrainedIndexForAddress(int_address); + if (fine_grained[index] == kUnusedChunkTableEntry) return false; + uintptr_t entry = fine_grained[index]; + if (entry <= int_address && entry + kChunkSize > int_address) return true; + index++; + if (fine_grained[index] == kUnusedChunkTableEntry) return false; + entry = fine_grained[index]; + if (entry <= int_address && entry + kChunkSize > int_address) return true; + return false; +} + + +uintptr_t* MemoryAllocator::AllocatedChunksFinder( + uintptr_t* table, + uintptr_t address, + int bit_position, + CreateTables create_as_needed) { + if (bit_position == kChunkSizeLog2) { + return table; + } + ASSERT(bit_position >= kChunkSizeLog2 + kChunkTableBitsPerLevel); + int index = + ((address >> bit_position) & + ((V8_INTPTR_C(1) << kChunkTableBitsPerLevel) - 1)); + uintptr_t more_fine_grained_address = + address & ((V8_INTPTR_C(1) << bit_position) - 1); + ASSERT((table == chunk_table_ && index < kChunkTableTopLevelEntries) || + (table != chunk_table_ && index < 1 << kChunkTableBitsPerLevel)); + uintptr_t* more_fine_grained_table = + reinterpret_cast<uintptr_t*>(table[index]); + if (more_fine_grained_table == kUnusedChunkTableEntry) { + if (create_as_needed == kDontCreateTables) return NULL; + int words_needed = 1 << kChunkTableBitsPerLevel; + if (bit_position == kChunkTableBitsPerLevel + kChunkSizeLog2) { + words_needed = + (1 << kChunkTableBitsPerLevel) * kChunkTableFineGrainedWordsPerEntry; + } + more_fine_grained_table = new uintptr_t[words_needed]; + for (int i = 0; i < words_needed; i++) { + more_fine_grained_table[i] = kUnusedChunkTableEntry; + } + table[index] = reinterpret_cast<uintptr_t>(more_fine_grained_table); + } + return AllocatedChunksFinder( + more_fine_grained_table, + more_fine_grained_address, + bit_position - kChunkTableBitsPerLevel, + create_as_needed); +} + + +uintptr_t MemoryAllocator::chunk_table_[kChunkTableTopLevelEntries]; + // ----------------------------------------------------------------------------- // PagedSpace implementation @@ -1010,7 +1151,10 @@ bool PagedSpace::Expand(Page* last_page) { int available_pages = static_cast<int>((max_capacity_ - Capacity()) / Page::kObjectAreaSize); - if (available_pages <= 0) return false; + // We don't want to have to handle small chunks near the end so if there are + // not kPagesPerChunk pages available without exceeding the max capacity then + // act as if memory has run out. + if (available_pages < MemoryAllocator::kPagesPerChunk) return false; int desired_pages = Min(available_pages, MemoryAllocator::kPagesPerChunk); Page* p = MemoryAllocator::AllocatePages(desired_pages, &desired_pages, this); @@ -1544,6 +1688,7 @@ static void ReportCodeKindStatistics() { for (int i = 0; i < Code::NUMBER_OF_KINDS; i++) { switch (static_cast<Code::Kind>(i)) { CASE(FUNCTION); + CASE(OPTIMIZED_FUNCTION); CASE(STUB); CASE(BUILTIN); CASE(LOAD_IC); @@ -1553,6 +1698,8 @@ static void ReportCodeKindStatistics() { CASE(CALL_IC); CASE(KEYED_CALL_IC); CASE(BINARY_OP_IC); + CASE(TYPE_RECORDING_BINARY_OP_IC); + CASE(COMPARE_IC); } } @@ -2697,32 +2844,40 @@ HeapObject* LargeObjectIterator::next() { // LargeObjectChunk LargeObjectChunk* LargeObjectChunk::New(int size_in_bytes, - size_t* chunk_size, Executability executable) { size_t requested = ChunkSizeFor(size_in_bytes); - void* mem = MemoryAllocator::AllocateRawMemory(requested, - chunk_size, - executable); + size_t size; + void* mem = MemoryAllocator::AllocateRawMemory(requested, &size, executable); if (mem == NULL) return NULL; - LOG(NewEvent("LargeObjectChunk", mem, *chunk_size)); - if (*chunk_size < requested) { - MemoryAllocator::FreeRawMemory(mem, *chunk_size, executable); + + // The start of the chunk may be overlayed with a page so we have to + // make sure that the page flags fit in the size field. + ASSERT((size & Page::kPageFlagMask) == 0); + + LOG(NewEvent("LargeObjectChunk", mem, size)); + if (size < requested) { + MemoryAllocator::FreeRawMemory(mem, size, executable); LOG(DeleteEvent("LargeObjectChunk", mem)); return NULL; } - ObjectSpace space = - (executable == EXECUTABLE) ? kObjectSpaceCodeSpace : kObjectSpaceLoSpace; - MemoryAllocator::PerformAllocationCallback(space, - kAllocationActionAllocate, - *chunk_size); - return reinterpret_cast<LargeObjectChunk*>(mem); + + ObjectSpace space = (executable == EXECUTABLE) + ? kObjectSpaceCodeSpace + : kObjectSpaceLoSpace; + MemoryAllocator::PerformAllocationCallback( + space, kAllocationActionAllocate, size); + + LargeObjectChunk* chunk = reinterpret_cast<LargeObjectChunk*>(mem); + chunk->size_ = size; + return chunk; } int LargeObjectChunk::ChunkSizeFor(int size_in_bytes) { int os_alignment = static_cast<int>(OS::AllocateAlignment()); - if (os_alignment < Page::kPageSize) + if (os_alignment < Page::kPageSize) { size_in_bytes += (Page::kPageSize - os_alignment); + } return size_in_bytes + Page::kObjectStartOffset; } @@ -2803,27 +2958,24 @@ MaybeObject* LargeObjectSpace::AllocateRawInternal(int requested_size, return Failure::RetryAfterGC(identity()); } - size_t chunk_size; - LargeObjectChunk* chunk = - LargeObjectChunk::New(requested_size, &chunk_size, executable); + LargeObjectChunk* chunk = LargeObjectChunk::New(requested_size, executable); if (chunk == NULL) { return Failure::RetryAfterGC(identity()); } - size_ += static_cast<int>(chunk_size); + size_ += static_cast<int>(chunk->size()); objects_size_ += requested_size; page_count_++; chunk->set_next(first_chunk_); - chunk->set_size(chunk_size); first_chunk_ = chunk; // Initialize page header. Page* page = Page::FromAddress(RoundUp(chunk->address(), Page::kPageSize)); Address object_address = page->ObjectAreaStart(); + // Clear the low order bit of the second word in the page to flag it as a // large object page. If the chunk_size happened to be written there, its // low order bit should already be clear. - ASSERT((chunk_size & 0x1) == 0); page->SetIsLargeObjectPage(true); page->SetIsPageExecutable(executable); page->SetRegionMarks(Page::kAllRegionsCleanMarks); diff --git a/src/spaces.h b/src/spaces.h index 1b99c562..4f2d07b0 100644 --- a/src/spaces.h +++ b/src/spaces.h @@ -1,4 +1,4 @@ -// Copyright 2006-2008 the V8 project authors. All rights reserved. +// Copyright 2006-2010 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: @@ -609,6 +609,9 @@ class MemoryAllocator : public AllStatic { return (Available() / Page::kPageSize) * Page::kObjectAreaSize; } + // Sanity check on a pointer. + static bool SafeIsInAPageChunk(Address addr); + // Links two pages. static inline void SetNextPage(Page* prev, Page* next); @@ -650,23 +653,50 @@ class MemoryAllocator : public AllStatic { static void ReportStatistics(); #endif + static void AddToAllocatedChunks(Address addr, intptr_t size); + static void RemoveFromAllocatedChunks(Address addr, intptr_t size); + // Note: This only checks the regular chunks, not the odd-sized initial + // chunk. + static bool InAllocatedChunks(Address addr); + // Due to encoding limitation, we can only have 8K chunks. static const int kMaxNofChunks = 1 << kPageSizeBits; // If a chunk has at least 16 pages, the maximum heap size is about // 8K * 8K * 16 = 1G bytes. #ifdef V8_TARGET_ARCH_X64 static const int kPagesPerChunk = 32; + // On 64 bit the chunk table consists of 4 levels of 4096-entry tables. + static const int kPagesPerChunkLog2 = 5; + static const int kChunkTableLevels = 4; + static const int kChunkTableBitsPerLevel = 12; #else static const int kPagesPerChunk = 16; + // On 32 bit the chunk table consists of 2 levels of 256-entry tables. + static const int kPagesPerChunkLog2 = 4; + static const int kChunkTableLevels = 2; + static const int kChunkTableBitsPerLevel = 8; #endif - static const int kChunkSize = kPagesPerChunk * Page::kPageSize; private: + static const int kChunkSize = kPagesPerChunk * Page::kPageSize; + static const int kChunkSizeLog2 = kPagesPerChunkLog2 + kPageSizeBits; + static const int kChunkTableTopLevelEntries = + 1 << (sizeof(intptr_t) * kBitsPerByte - kChunkSizeLog2 - + (kChunkTableLevels - 1) * kChunkTableBitsPerLevel); + + // The chunks are not chunk-size aligned so for a given chunk-sized area of + // memory there can be two chunks that cover it. + static const int kChunkTableFineGrainedWordsPerEntry = 2; + static const uintptr_t kUnusedChunkTableEntry = 0; + // Maximum space size in bytes. static intptr_t capacity_; // Maximum subset of capacity_ that can be executable static intptr_t capacity_executable_; + // Top level table to track whether memory is part of a chunk or not. + static uintptr_t chunk_table_[kChunkTableTopLevelEntries]; + // Allocated space size in bytes. static intptr_t size_; // Allocated executable space size in bytes. @@ -725,6 +755,28 @@ class MemoryAllocator : public AllStatic { // Frees a chunk. static void DeleteChunk(int chunk_id); + // Helpers to maintain and query the chunk tables. + static void AddChunkUsingAddress( + uintptr_t chunk_start, // Where the chunk starts. + uintptr_t chunk_index_base); // Used to place the chunk in the tables. + static void RemoveChunkFoundUsingAddress( + uintptr_t chunk_start, // Where the chunk starts. + uintptr_t chunk_index_base); // Used to locate the entry in the tables. + // Controls whether the lookup creates intermediate levels of tables as + // needed. + enum CreateTables { kDontCreateTables, kCreateTablesAsNeeded }; + static uintptr_t* AllocatedChunksFinder(uintptr_t* table, + uintptr_t address, + int bit_position, + CreateTables create_as_needed); + static void FreeChunkTables(uintptr_t* array, int length, int level); + static int FineGrainedIndexForAddress(uintptr_t address) { + int index = ((address >> kChunkSizeLog2) & + ((1 << kChunkTableBitsPerLevel) - 1)); + return index * kChunkTableFineGrainedWordsPerEntry; + } + + // Basic check whether a chunk id is in the valid range. static inline bool IsValidChunkId(int chunk_id); @@ -1019,6 +1071,8 @@ class PagedSpace : public Space { // Checks whether an object/address is in this space. inline bool Contains(Address a); bool Contains(HeapObject* o) { return Contains(o->address()); } + // Never crashes even if a is not a valid pointer. + inline bool SafeContains(Address a); // Given an address occupied by a live object, return that object if it is // in this space, or Failure::Exception() if it is not. The implementation @@ -1588,6 +1642,11 @@ class NewSpace : public Space { virtual bool ReserveSpace(int bytes); + // Resizes a sequential string which must be the most recent thing that was + // allocated in new space. + template <typename StringType> + inline void ShrinkStringAtAllocationBoundary(String* string, int len); + #ifdef ENABLE_HEAP_PROTECTION // Protect/unprotect the space by marking it read-only/writable. virtual void Protect(); @@ -2062,12 +2121,6 @@ class MapSpace : public FixedSpace { accounting_stats_.DeallocateBytes(accounting_stats_.Size()); accounting_stats_.AllocateBytes(new_size); - // Flush allocation watermarks. - for (Page* p = first_page_; p != top_page; p = p->next_page()) { - p->SetAllocationWatermark(p->AllocationTop()); - } - top_page->SetAllocationWatermark(new_top); - #ifdef DEBUG if (FLAG_enable_slow_asserts) { intptr_t actual_size = 0; @@ -2138,10 +2191,10 @@ class LargeObjectChunk { // Allocates a new LargeObjectChunk that contains a large object page // (Page::kPageSize aligned) that has at least size_in_bytes (for a large // object) bytes after the object area start of that page. - // The allocated chunk size is set in the output parameter chunk_size. - static LargeObjectChunk* New(int size_in_bytes, - size_t* chunk_size, - Executability executable); + static LargeObjectChunk* New(int size_in_bytes, Executability executable); + + // Free the memory associated with the chunk. + inline void Free(Executability executable); // Interpret a raw address as a large object chunk. static LargeObjectChunk* FromAddress(Address address) { @@ -2154,12 +2207,13 @@ class LargeObjectChunk { // Accessors for the fields of the chunk. LargeObjectChunk* next() { return next_; } void set_next(LargeObjectChunk* chunk) { next_ = chunk; } - size_t size() { return size_ & ~Page::kPageFlagMask; } - void set_size(size_t size_in_bytes) { size_ = size_in_bytes; } + + // Compute the start address in the chunk. + inline Address GetStartAddress(); // Returns the object in this chunk. - inline HeapObject* GetObject(); + HeapObject* GetObject() { return HeapObject::FromAddress(GetStartAddress()); } // Given a requested size returns the physical size of a chunk to be // allocated. @@ -2176,7 +2230,7 @@ class LargeObjectChunk { // A pointer to the next large object chunk in the space or NULL. LargeObjectChunk* next_; - // The size of this chunk. + // The total size of this chunk. size_t size_; public: diff --git a/src/string-stream.cc b/src/string-stream.cc index d1859a20..7abd1bbe 100644 --- a/src/string-stream.cc +++ b/src/string-stream.cc @@ -264,7 +264,7 @@ void StringStream::Log() { } -void StringStream::OutputToStdOut() { +void StringStream::OutputToFile(FILE* out) { // Dump the output to stdout, but make sure to break it up into // manageable chunks to avoid losing parts of the output in the OS // printing code. This is a problem on Windows in particular; see @@ -273,10 +273,10 @@ void StringStream::OutputToStdOut() { for (unsigned next; (next = position + 2048) < length_; position = next) { char save = buffer_[next]; buffer_[next] = '\0'; - internal::PrintF("%s", &buffer_[position]); + internal::PrintF(out, "%s", &buffer_[position]); buffer_[next] = save; } - internal::PrintF("%s", &buffer_[position]); + internal::PrintF(out, "%s", &buffer_[position]); } diff --git a/src/string-stream.h b/src/string-stream.h index 323a6d66..b3f2e0d7 100644 --- a/src/string-stream.h +++ b/src/string-stream.h @@ -138,10 +138,12 @@ class StringStream { FmtElm arg3); // Getting the message out. - void OutputToStdOut(); + void OutputToFile(FILE* out); + void OutputToStdOut() { OutputToFile(stdout); } void Log(); Handle<String> ToString(); SmartPointer<const char> ToCString() const; + int length() const { return length_; } // Object printing support. void PrintName(Object* o); diff --git a/src/string.js b/src/string.js index 3b3c82bb..95275995 100644 --- a/src/string.js +++ b/src/string.js @@ -101,28 +101,28 @@ function StringConcat() { // ECMA-262 section 15.5.4.7 -function StringIndexOf(searchString /* position */) { // length == 1 - var subject_str = TO_STRING_INLINE(this); - var pattern_str = TO_STRING_INLINE(searchString); - var subject_str_len = subject_str.length; - var pattern_str_len = pattern_str.length; +function StringIndexOf(pattern /* position */) { // length == 1 + var subject = TO_STRING_INLINE(this); + var pattern = TO_STRING_INLINE(pattern); + var subject_len = subject.length; + var pattern_len = pattern.length; var index = 0; if (%_ArgumentsLength() > 1) { var arg1 = %_Arguments(1); // position index = TO_INTEGER(arg1); } if (index < 0) index = 0; - if (index > subject_str_len) index = subject_str_len; - if (pattern_str_len + index > subject_str_len) return -1; - return %StringIndexOf(subject_str, pattern_str, index); + if (index > subject_len) index = subject_len; + if (pattern_len + index > subject_len) return -1; + return %StringIndexOf(subject, pattern, index); } // ECMA-262 section 15.5.4.8 -function StringLastIndexOf(searchString /* position */) { // length == 1 +function StringLastIndexOf(pat /* position */) { // length == 1 var sub = TO_STRING_INLINE(this); var subLength = sub.length; - var pat = TO_STRING_INLINE(searchString); + var pat = TO_STRING_INLINE(pat); var patLength = pat.length; var index = subLength - patLength; if (%_ArgumentsLength() > 1) { @@ -150,10 +150,8 @@ function StringLastIndexOf(searchString /* position */) { // length == 1 // do anything locale specific. function StringLocaleCompare(other) { if (%_ArgumentsLength() === 0) return 0; - - var this_str = TO_STRING_INLINE(this); - var other_str = TO_STRING_INLINE(other); - return %StringLocaleCompare(this_str, other_str); + return %StringLocaleCompare(TO_STRING_INLINE(this), + TO_STRING_INLINE(other)); } @@ -161,7 +159,7 @@ function StringLocaleCompare(other) { function StringMatch(regexp) { var subject = TO_STRING_INLINE(this); if (IS_REGEXP(regexp)) { - if (!regexp.global) return regexp.exec(subject); + if (!regexp.global) return RegExpExecNoTests(regexp, subject, 0); %_Log('regexp', 'regexp-match,%0S,%1r', [subject, regexp]); // lastMatchInfo is defined in regexp.js. return %StringMatch(subject, regexp, lastMatchInfo); @@ -177,9 +175,7 @@ function StringMatch(regexp) { // otherwise we call the runtime system. function SubString(string, start, end) { // Use the one character string cache. - if (start + 1 == end) { - return %_StringCharAt(string, start); - } + if (start + 1 == end) return %_StringCharAt(string, start); return %_SubString(string, start, end); } @@ -208,7 +204,10 @@ function StringReplace(search, replace) { replace); } } else { - return StringReplaceRegExp(subject, search, replace); + return %StringReplaceRegExpWithString(subject, + search, + TO_STRING_INLINE(replace), + lastMatchInfo); } } @@ -224,7 +223,11 @@ function StringReplace(search, replace) { // Compute the string to replace with. if (IS_FUNCTION(replace)) { - builder.add(replace.call(null, search, start, subject)); + builder.add(%_CallFunction(%GetGlobalReceiver(), + search, + start, + subject, + replace)); } else { reusableMatchInfo[CAPTURE0] = start; reusableMatchInfo[CAPTURE1] = end; @@ -239,29 +242,21 @@ function StringReplace(search, replace) { } -// Helper function for regular expressions in String.prototype.replace. -function StringReplaceRegExp(subject, regexp, replace) { - return %StringReplaceRegExpWithString(subject, - regexp, - TO_STRING_INLINE(replace), - lastMatchInfo); -} - - // Expand the $-expressions in the string and return a new string with // the result. function ExpandReplacement(string, subject, matchInfo, builder) { + var length = string.length; + var builder_elements = builder.elements; var next = %StringIndexOf(string, '$', 0); if (next < 0) { - builder.add(string); + if (length > 0) builder_elements.push(string); return; } // Compute the number of captures; see ECMA-262, 15.5.4.11, p. 102. var m = NUMBER_OF_CAPTURES(matchInfo) >> 1; // Includes the match. - if (next > 0) builder.add(SubString(string, 0, next)); - var length = string.length; + if (next > 0) builder_elements.push(SubString(string, 0, next)); while (true) { var expansion = '$'; @@ -270,7 +265,7 @@ function ExpandReplacement(string, subject, matchInfo, builder) { var peek = %_StringCharCodeAt(string, position); if (peek == 36) { // $$ ++position; - builder.add('$'); + builder_elements.push('$'); } else if (peek == 38) { // $& - match ++position; builder.addSpecialSlice(matchInfo[CAPTURE0], @@ -307,14 +302,14 @@ function ExpandReplacement(string, subject, matchInfo, builder) { // digit capture references, we can only enter here when a // single digit capture reference is outside the range of // captures. - builder.add('$'); + builder_elements.push('$'); --position; } } else { - builder.add('$'); + builder_elements.push('$'); } } else { - builder.add('$'); + builder_elements.push('$'); } // Go the the next $ in the string. @@ -324,13 +319,15 @@ function ExpandReplacement(string, subject, matchInfo, builder) { // haven't reached the end, we need to append the suffix. if (next < 0) { if (position < length) { - builder.add(SubString(string, position, length)); + builder_elements.push(SubString(string, position, length)); } return; } // Append substring between the previous and the next $ character. - builder.add(SubString(string, position, next)); + if (next > position) { + builder_elements.push(SubString(string, position, next)); + } } }; @@ -408,9 +405,7 @@ function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { lastMatchInfoOverride = override; var func_result = %_CallFunction(receiver, elem, match_start, subject, replace); - if (!IS_STRING(func_result)) { - func_result = NonStringToString(func_result); - } + func_result = TO_STRING_INLINE(func_result); res[i] = func_result; match_start += elem.length; } @@ -424,9 +419,7 @@ function StringReplaceGlobalRegExpWithFunction(subject, regexp, replace) { // Use the apply argument as backing for global RegExp properties. lastMatchInfoOverride = elem; var func_result = replace.apply(null, elem); - if (!IS_STRING(func_result)) { - func_result = NonStringToString(func_result); - } + func_result = TO_STRING_INLINE(func_result); res[i] = func_result; } i++; @@ -487,8 +480,7 @@ function StringSearch(re) { } else { regexp = new $RegExp(re); } - var s = TO_STRING_INLINE(this); - var match = DoRegExpExec(regexp, s, 0); + var match = DoRegExpExec(regexp, TO_STRING_INLINE(this), 0); if (match) { return match[CAPTURE0]; } @@ -570,23 +562,22 @@ function StringSplit(separator, limit) { var currentIndex = 0; var startIndex = 0; + var startMatch = 0; var result = []; outer_loop: while (true) { if (startIndex === length) { - result[result.length] = subject.slice(currentIndex, length); + result.push(SubString(subject, currentIndex, length)); break; } - var matchInfo = splitMatch(separator, subject, currentIndex, startIndex); - - if (IS_NULL(matchInfo)) { - result[result.length] = subject.slice(currentIndex, length); + var matchInfo = DoRegExpExec(separator, subject, startIndex); + if (matchInfo == null || length === (startMatch = matchInfo[CAPTURE0])) { + result.push(SubString(subject, currentIndex, length)); break; } - var endIndex = matchInfo[CAPTURE1]; // We ignore a zero-length match at the currentIndex. @@ -595,17 +586,26 @@ function StringSplit(separator, limit) { continue; } - result[result.length] = SubString(subject, currentIndex, matchInfo[CAPTURE0]); + if (currentIndex + 1 == startMatch) { + result.push(%_StringCharAt(subject, currentIndex)); + } else { + result.push(%_SubString(subject, currentIndex, startMatch)); + } + if (result.length === limit) break; - var num_captures = NUMBER_OF_CAPTURES(matchInfo); - for (var i = 2; i < num_captures; i += 2) { - var start = matchInfo[CAPTURE(i)]; - var end = matchInfo[CAPTURE(i + 1)]; - if (start != -1 && end != -1) { - result[result.length] = SubString(subject, start, end); + var matchinfo_len = NUMBER_OF_CAPTURES(matchInfo) + REGEXP_FIRST_CAPTURE; + for (var i = REGEXP_FIRST_CAPTURE + 2; i < matchinfo_len; ) { + var start = matchInfo[i++]; + var end = matchInfo[i++]; + if (end != -1) { + if (start + 1 == end) { + result.push(%_StringCharAt(subject, start)); + } else { + result.push(%_SubString(subject, start, end)); + } } else { - result[result.length] = void 0; + result.push(void 0); } if (result.length === limit) break outer_loop; } @@ -616,19 +616,6 @@ function StringSplit(separator, limit) { } -// ECMA-262 section 15.5.4.14 -// Helper function used by split. This version returns the matchInfo -// instead of allocating a new array with basically the same information. -function splitMatch(separator, subject, current_index, start_index) { - var matchInfo = DoRegExpExec(separator, subject, start_index); - if (matchInfo == null) return null; - // Section 15.5.4.14 paragraph two says that we do not allow zero length - // matches at the end of the string. - if (matchInfo[CAPTURE0] === subject.length) return null; - return matchInfo; -} - - // ECMA-262 section 15.5.4.15 function StringSubstring(start, end) { var s = TO_STRING_INLINE(this); @@ -656,7 +643,9 @@ function StringSubstring(start, end) { } } - return SubString(s, start_i, end_i); + return (start_i + 1 == end_i + ? %_StringCharAt(s, start_i) + : %_SubString(s, start_i, end_i)); } @@ -694,7 +683,9 @@ function StringSubstr(start, n) { var end = start + len; if (end > s.length) end = s.length; - return SubString(s, start, end); + return (start + 1 == end + ? %_StringCharAt(s, start) + : %_SubString(s, start, end)); } @@ -847,24 +838,21 @@ function ReplaceResultBuilder(str) { ReplaceResultBuilder.prototype.add = function(str) { str = TO_STRING_INLINE(str); - if (str.length > 0) { - var elements = this.elements; - elements[elements.length] = str; - } + if (str.length > 0) this.elements.push(str); } ReplaceResultBuilder.prototype.addSpecialSlice = function(start, end) { var len = end - start; if (start < 0 || len <= 0) return; - var elements = this.elements; if (start < 0x80000 && len < 0x800) { - elements[elements.length] = (start << 11) | len; + this.elements.push((start << 11) | len); } else { // 0 < len <= String::kMaxLength and Smi::kMaxValue >= String::kMaxLength, // so -len is a smi. - elements[elements.length] = -len; - elements[elements.length] = start; + var elements = this.elements; + elements.push(-len); + elements.push(start); } } @@ -875,11 +863,6 @@ ReplaceResultBuilder.prototype.generate = function() { } -function StringToJSON(key) { - return CheckJSONPrimitive(this.valueOf()); -} - - // ------------------------------------------------------------------- function SetupString() { @@ -929,8 +912,7 @@ function SetupString() { "small", StringSmall, "strike", StringStrike, "sub", StringSub, - "sup", StringSup, - "toJSON", StringToJSON + "sup", StringSup )); } diff --git a/src/stub-cache.cc b/src/stub-cache.cc index 5cc009f7..86e72012 100644 --- a/src/stub-cache.cc +++ b/src/stub-cache.cc @@ -31,6 +31,7 @@ #include "arguments.h" #include "ic-inl.h" #include "stub-cache.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -425,6 +426,27 @@ MaybeObject* StubCache::ComputeKeyedLoadFunctionPrototype( } +MaybeObject* StubCache::ComputeKeyedLoadSpecialized(JSObject* receiver) { + Code::Flags flags = + Code::ComputeMonomorphicFlags(Code::KEYED_LOAD_IC, NORMAL); + String* name = Heap::KeyedLoadSpecialized_symbol(); + Object* code = receiver->map()->FindInCodeCache(name, flags); + if (code->IsUndefined()) { + KeyedLoadStubCompiler compiler; + { MaybeObject* maybe_code = compiler.CompileLoadSpecialized(receiver); + if (!maybe_code->ToObject(&code)) return maybe_code; + } + PROFILE(CodeCreateEvent(Logger::KEYED_LOAD_IC_TAG, Code::cast(code), 0)); + Object* result; + { MaybeObject* maybe_result = + receiver->UpdateMapCodeCache(name, Code::cast(code)); + if (!maybe_result->ToObject(&result)) return maybe_result; + } + } + return code; +} + + MaybeObject* StubCache::ComputeStoreField(String* name, JSObject* receiver, int field_index, @@ -449,6 +471,27 @@ MaybeObject* StubCache::ComputeStoreField(String* name, } +MaybeObject* StubCache::ComputeKeyedStoreSpecialized(JSObject* receiver) { + Code::Flags flags = + Code::ComputeMonomorphicFlags(Code::KEYED_STORE_IC, NORMAL); + String* name = Heap::KeyedStoreSpecialized_symbol(); + Object* code = receiver->map()->FindInCodeCache(name, flags); + if (code->IsUndefined()) { + KeyedStoreStubCompiler compiler; + { MaybeObject* maybe_code = compiler.CompileStoreSpecialized(receiver); + if (!maybe_code->ToObject(&code)) return maybe_code; + } + PROFILE(CodeCreateEvent(Logger::KEYED_STORE_IC_TAG, Code::cast(code), 0)); + Object* result; + { MaybeObject* maybe_result = + receiver->UpdateMapCodeCache(name, Code::cast(code)); + if (!maybe_result->ToObject(&result)) return maybe_result; + } + } + return code; +} + + MaybeObject* StubCache::ComputeStoreNormal() { return Builtins::builtin(Builtins::StoreIC_Normal); } @@ -561,13 +604,13 @@ MaybeObject* StubCache::ComputeCallConstant(int argc, JSObject* map_holder = IC::GetCodeCacheHolder(object, cache_holder); // Compute check type based on receiver/holder. - StubCompiler::CheckType check = StubCompiler::RECEIVER_MAP_CHECK; + CheckType check = RECEIVER_MAP_CHECK; if (object->IsString()) { - check = StubCompiler::STRING_CHECK; + check = STRING_CHECK; } else if (object->IsNumber()) { - check = StubCompiler::NUMBER_CHECK; + check = NUMBER_CHECK; } else if (object->IsBoolean()) { - check = StubCompiler::BOOLEAN_CHECK; + check = BOOLEAN_CHECK; } Code::Flags flags = @@ -589,6 +632,7 @@ MaybeObject* StubCache::ComputeCallConstant(int argc, compiler.CompileCallConstant(object, holder, function, name, check); if (!maybe_code->ToObject(&code)) return maybe_code; } + Code::cast(code)->set_check_type(check); ASSERT_EQ(flags, Code::cast(code)->flags()); PROFILE(CodeCreateEvent(CALL_LOGGER_TAG(kind, CALL_IC_TAG), Code::cast(code), name)); @@ -953,6 +997,48 @@ void StubCache::Clear() { } +void StubCache::CollectMatchingMaps(ZoneMapList* types, + String* name, + Code::Flags flags) { + for (int i = 0; i < kPrimaryTableSize; i++) { + if (primary_[i].key == name) { + Map* map = primary_[i].value->FindFirstMap(); + // Map can be NULL, if the stub is constant function call + // with a primitive receiver. + if (map == NULL) continue; + + int offset = PrimaryOffset(name, flags, map); + if (entry(primary_, offset) == &primary_[i]) { + types->Add(Handle<Map>(map)); + } + } + } + + for (int i = 0; i < kSecondaryTableSize; i++) { + if (secondary_[i].key == name) { + Map* map = secondary_[i].value->FindFirstMap(); + // Map can be NULL, if the stub is constant function call + // with a primitive receiver. + if (map == NULL) continue; + + // Lookup in primary table and skip duplicates. + int primary_offset = PrimaryOffset(name, flags, map); + Entry* primary_entry = entry(primary_, primary_offset); + if (primary_entry->key == name) { + Map* primary_map = primary_entry->value->FindFirstMap(); + if (map == primary_map) continue; + } + + // Lookup in secondary table and add matches. + int offset = SecondaryOffset(name, flags, primary_offset); + if (entry(secondary_, offset) == &secondary_[i]) { + types->Add(Handle<Map>(map)); + } + } + } +} + + // ------------------------------------------------------------------------ // StubCompiler implementation. @@ -970,9 +1056,7 @@ MaybeObject* LoadCallbackProperty(Arguments args) { { // Leaving JavaScript. VMState state(EXTERNAL); -#ifdef ENABLE_LOGGING_AND_PROFILING - state.set_external_callback(getter_address); -#endif + ExternalCallbackScope call_scope(getter_address); result = fun(v8::Utils::ToLocal(args.at<String>(4)), info); } RETURN_IF_SCHEDULED_EXCEPTION(); @@ -996,9 +1080,7 @@ MaybeObject* StoreCallbackProperty(Arguments args) { { // Leaving JavaScript. VMState state(EXTERNAL); -#ifdef ENABLE_LOGGING_AND_PROFILING - state.set_external_callback(setter_address); -#endif + ExternalCallbackScope call_scope(setter_address); fun(v8::Utils::ToLocal(name), v8::Utils::ToLocal(value), info); } RETURN_IF_SCHEDULED_EXCEPTION(); @@ -1419,25 +1501,31 @@ CallStubCompiler::CallStubCompiler(int argc, } -MaybeObject* CallStubCompiler::CompileCustomCall(int generator_id, +bool CallStubCompiler::HasCustomCallGenerator(BuiltinFunctionId id) { +#define CALL_GENERATOR_CASE(name) if (id == k##name) return true; + CUSTOM_CALL_IC_GENERATORS(CALL_GENERATOR_CASE) +#undef CALL_GENERATOR_CASE + return false; +} + + +MaybeObject* CallStubCompiler::CompileCustomCall(BuiltinFunctionId id, Object* object, JSObject* holder, JSGlobalPropertyCell* cell, JSFunction* function, String* fname) { - ASSERT(generator_id >= 0 && generator_id < kNumCallGenerators); - switch (generator_id) { -#define CALL_GENERATOR_CASE(ignored1, ignored2, name) \ - case k##name##CallGenerator: \ - return CallStubCompiler::Compile##name##Call(object, \ - holder, \ - cell, \ - function, \ - fname); - CUSTOM_CALL_IC_GENERATORS(CALL_GENERATOR_CASE) +#define CALL_GENERATOR_CASE(name) \ + if (id == k##name) { \ + return CallStubCompiler::Compile##name##Call(object, \ + holder, \ + cell, \ + function, \ + fname); \ + } + CUSTOM_CALL_IC_GENERATORS(CALL_GENERATOR_CASE) #undef CALL_GENERATOR_CASE - } - UNREACHABLE(); + ASSERT(!HasCustomCallGenerator(id)); return Heap::undefined_value(); } diff --git a/src/stub-cache.h b/src/stub-cache.h index cef5481c..a7829a60 100644 --- a/src/stub-cache.h +++ b/src/stub-cache.h @@ -29,6 +29,7 @@ #define V8_STUB_CACHE_H_ #include "macro-assembler.h" +#include "zone-inl.h" namespace v8 { namespace internal { @@ -44,6 +45,7 @@ namespace internal { class SCTableReference; + class StubCache : public AllStatic { public: struct Entry { @@ -76,9 +78,10 @@ class StubCache : public AllStatic { JSObject* holder, Object* value); - MUST_USE_RESULT static MaybeObject* ComputeLoadInterceptor(String* name, - JSObject* receiver, - JSObject* holder); + MUST_USE_RESULT static MaybeObject* ComputeLoadInterceptor( + String* name, + JSObject* receiver, + JSObject* holder); MUST_USE_RESULT static MaybeObject* ComputeLoadNormal(); @@ -127,6 +130,9 @@ class StubCache : public AllStatic { String* name, JSFunction* receiver); + MUST_USE_RESULT static MaybeObject* ComputeKeyedLoadSpecialized( + JSObject* receiver); + // --- MUST_USE_RESULT static MaybeObject* ComputeStoreField(String* name, @@ -158,6 +164,9 @@ class StubCache : public AllStatic { int field_index, Map* transition = NULL); + MUST_USE_RESULT static MaybeObject* ComputeKeyedStoreSpecialized( + JSObject* receiver); + // --- MUST_USE_RESULT static MaybeObject* ComputeCallField(int argc, @@ -244,6 +253,11 @@ class StubCache : public AllStatic { // Clear the lookup table (@ mark compact collection). static void Clear(); + // Collect all maps that match the name and flags. + static void CollectMatchingMaps(ZoneMapList* types, + String* name, + Code::Flags flags); + // Generate code for probing the stub cache table. // Arguments extra and extra2 may be used to pass additional scratch // registers. Set to no_reg if not needed. @@ -366,13 +380,6 @@ MaybeObject* KeyedLoadPropertyWithInterceptor(Arguments args); // The stub compiler compiles stubs for the stub cache. class StubCompiler BASE_EMBEDDED { public: - enum CheckType { - RECEIVER_MAP_CHECK, - STRING_CHECK, - NUMBER_CHECK, - BOOLEAN_CHECK - }; - StubCompiler() : scope_(), masm_(NULL, 256), failure_(NULL) { } MUST_USE_RESULT MaybeObject* CompileCallInitialize(Code::Flags flags); @@ -564,7 +571,7 @@ class LoadStubCompiler: public StubCompiler { bool is_dont_delete); private: - MaybeObject* GetCode(PropertyType type, String* name); + MUST_USE_RESULT MaybeObject* GetCode(PropertyType type, String* name); }; @@ -593,6 +600,8 @@ class KeyedLoadStubCompiler: public StubCompiler { MUST_USE_RESULT MaybeObject* CompileLoadStringLength(String* name); MUST_USE_RESULT MaybeObject* CompileLoadFunctionPrototype(String* name); + MUST_USE_RESULT MaybeObject* CompileLoadSpecialized(JSObject* receiver); + private: MaybeObject* GetCode(PropertyType type, String* name); }; @@ -604,6 +613,7 @@ class StoreStubCompiler: public StubCompiler { int index, Map* transition, String* name); + MUST_USE_RESULT MaybeObject* CompileStoreCallback(JSObject* object, AccessorInfo* callbacks, String* name); @@ -615,53 +625,38 @@ class StoreStubCompiler: public StubCompiler { private: - MUST_USE_RESULT MaybeObject* GetCode(PropertyType type, String* name); + MaybeObject* GetCode(PropertyType type, String* name); }; class KeyedStoreStubCompiler: public StubCompiler { public: - MaybeObject* CompileStoreField(JSObject* object, - int index, - Map* transition, - String* name); + MUST_USE_RESULT MaybeObject* CompileStoreField(JSObject* object, + int index, + Map* transition, + String* name); + + MUST_USE_RESULT MaybeObject* CompileStoreSpecialized(JSObject* receiver); private: MaybeObject* GetCode(PropertyType type, String* name); }; -// List of functions with custom constant call IC stubs. -// -// Installation of custom call generators for the selected builtins is -// handled by the bootstrapper. -// -// Each entry has a name of a global object property holding an object -// optionally followed by ".prototype" (this controls whether the -// generator is set on the object itself or, in case it's a function, -// on the its instance prototype), a name of a builtin function on the -// object (the one the generator is set for), and a name of the -// generator (used to build ids and generator function names). -#define CUSTOM_CALL_IC_GENERATORS(V) \ - V(Array.prototype, push, ArrayPush) \ - V(Array.prototype, pop, ArrayPop) \ - V(String.prototype, charCodeAt, StringCharCodeAt) \ - V(String.prototype, charAt, StringCharAt) \ - V(String, fromCharCode, StringFromCharCode) \ - V(Math, floor, MathFloor) \ - V(Math, abs, MathAbs) +// Subset of FUNCTIONS_WITH_ID_LIST with custom constant/global call +// IC stubs. +#define CUSTOM_CALL_IC_GENERATORS(V) \ + V(ArrayPush) \ + V(ArrayPop) \ + V(StringCharCodeAt) \ + V(StringCharAt) \ + V(StringFromCharCode) \ + V(MathFloor) \ + V(MathAbs) class CallStubCompiler: public StubCompiler { public: - enum { -#define DECLARE_CALL_GENERATOR_ID(ignored1, ignore2, name) \ - k##name##CallGenerator, - CUSTOM_CALL_IC_GENERATORS(DECLARE_CALL_GENERATOR_ID) -#undef DECLARE_CALL_GENERATOR_ID - kNumCallGenerators - }; - CallStubCompiler(int argc, InLoopFlag in_loop, Code::Kind kind, @@ -685,16 +680,20 @@ class CallStubCompiler: public StubCompiler { JSFunction* function, String* name); - // Compiles a custom call constant/global IC using the generator - // with given id. For constant calls cell is NULL. - MUST_USE_RESULT MaybeObject* CompileCustomCall(int generator_id, + static bool HasCustomCallGenerator(BuiltinFunctionId id); + + private: + // Compiles a custom call constant/global IC. For constant calls + // cell is NULL. Returns undefined if there is no custom call code + // for the given function or it can't be generated. + MUST_USE_RESULT MaybeObject* CompileCustomCall(BuiltinFunctionId id, Object* object, JSObject* holder, JSGlobalPropertyCell* cell, JSFunction* function, String* name); -#define DECLARE_CALL_GENERATOR(ignored1, ignored2, name) \ +#define DECLARE_CALL_GENERATOR(name) \ MUST_USE_RESULT MaybeObject* Compile##name##Call(Object* object, \ JSObject* holder, \ JSGlobalPropertyCell* cell, \ @@ -703,7 +702,6 @@ class CallStubCompiler: public StubCompiler { CUSTOM_CALL_IC_GENERATORS(DECLARE_CALL_GENERATOR) #undef DECLARE_CALL_GENERATOR - private: const ParameterCount arguments_; const InLoopFlag in_loop_; const Code::Kind kind_; diff --git a/src/token.h b/src/token.h index 74d9539f..2f5ca1b5 100644 --- a/src/token.h +++ b/src/token.h @@ -238,6 +238,40 @@ class Token { return EQ <= op && op <= IN; } + static bool IsOrderedCompareOp(Value op) { + return op == LT || op == LTE || op == GT || op == GTE; + } + + static Value NegateCompareOp(Value op) { + ASSERT(IsCompareOp(op)); + switch (op) { + case EQ: return NE; + case NE: return EQ; + case EQ_STRICT: return NE_STRICT; + case LT: return GTE; + case GT: return LTE; + case LTE: return GT; + case GTE: return LT; + default: + return op; + } + } + + static Value InvertCompareOp(Value op) { + ASSERT(IsCompareOp(op)); + switch (op) { + case EQ: return NE; + case NE: return EQ; + case EQ_STRICT: return NE_STRICT; + case LT: return GT; + case GT: return LT; + case LTE: return GTE; + case GTE: return LTE; + default: + return op; + } + } + static bool IsBitOp(Value op) { return (BIT_OR <= op && op <= SHR) || op == BIT_NOT; } @@ -35,10 +35,14 @@ #include "platform.h" #include "simulator.h" #include "string-stream.h" +#include "vm-state-inl.h" namespace v8 { namespace internal { +#ifdef ENABLE_LOGGING_AND_PROFILING +Semaphore* Top::runtime_profiler_semaphore_ = NULL; +#endif ThreadLocalTop Top::thread_local_; Mutex* Top::break_access_ = OS::CreateMutex(); @@ -74,10 +78,12 @@ void ThreadLocalTop::Initialize() { #endif #endif #ifdef ENABLE_LOGGING_AND_PROFILING - js_entry_sp_ = 0; + js_entry_sp_ = NULL; + external_callback_ = NULL; #endif #ifdef ENABLE_VMSTATE_TRACKING - current_vm_state_ = NULL; + current_vm_state_ = EXTERNAL; + runtime_profiler_state_ = Top::PROF_NOT_IN_JS; #endif try_catch_handler_address_ = NULL; context_ = NULL; @@ -273,6 +279,11 @@ static bool initialized = false; void Top::Initialize() { CHECK(!initialized); +#ifdef ENABLE_LOGGING_AND_PROFILING + ASSERT(runtime_profiler_semaphore_ == NULL); + runtime_profiler_semaphore_ = OS::CreateSemaphore(0); +#endif + InitializeThreadLocal(); // Only preallocate on the first initialization. @@ -290,6 +301,11 @@ void Top::Initialize() { void Top::TearDown() { if (initialized) { +#ifdef ENABLE_LOGGING_AND_PROFILING + delete runtime_profiler_semaphore_; + runtime_profiler_semaphore_ = NULL; +#endif + // Remove the external reference to the preallocated stack memory. if (preallocated_message_space != NULL) { delete preallocated_message_space; @@ -376,79 +392,85 @@ Handle<JSArray> Top::CaptureCurrentStackTrace( StackTraceFrameIterator it; int frames_seen = 0; while (!it.done() && (frames_seen < limit)) { - // Create a JSObject to hold the information for the StackFrame. - Handle<JSObject> stackFrame = Factory::NewJSObject(object_function()); - JavaScriptFrame* frame = it.frame(); - Handle<JSFunction> fun(JSFunction::cast(frame->function())); - Handle<Script> script(Script::cast(fun->shared()->script())); - - if (options & StackTrace::kLineNumber) { - int script_line_offset = script->line_offset()->value(); - int position = frame->code()->SourcePosition(frame->pc()); - int line_number = GetScriptLineNumber(script, position); - // line_number is already shifted by the script_line_offset. - int relative_line_number = line_number - script_line_offset; - if (options & StackTrace::kColumnOffset && relative_line_number >= 0) { - Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends())); - int start = (relative_line_number == 0) ? 0 : - Smi::cast(line_ends->get(relative_line_number - 1))->value() + 1; - int column_offset = position - start; - if (relative_line_number == 0) { - // For the case where the code is on the same line as the script tag. - column_offset += script->column_offset()->value(); + + List<FrameSummary> frames(3); // Max 2 levels of inlining. + frame->Summarize(&frames); + for (int i = frames.length() - 1; i >= 0 && frames_seen < limit; i--) { + // Create a JSObject to hold the information for the StackFrame. + Handle<JSObject> stackFrame = Factory::NewJSObject(object_function()); + + Handle<JSFunction> fun = frames[i].function(); + Handle<Script> script(Script::cast(fun->shared()->script())); + + if (options & StackTrace::kLineNumber) { + int script_line_offset = script->line_offset()->value(); + int position = frames[i].code()->SourcePosition(frames[i].pc()); + int line_number = GetScriptLineNumber(script, position); + // line_number is already shifted by the script_line_offset. + int relative_line_number = line_number - script_line_offset; + if (options & StackTrace::kColumnOffset && relative_line_number >= 0) { + Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends())); + int start = (relative_line_number == 0) ? 0 : + Smi::cast(line_ends->get(relative_line_number - 1))->value() + 1; + int column_offset = position - start; + if (relative_line_number == 0) { + // For the case where the code is on the same line as the script + // tag. + column_offset += script->column_offset()->value(); + } + SetProperty(stackFrame, column_key, + Handle<Smi>(Smi::FromInt(column_offset + 1)), NONE); } - SetProperty(stackFrame, column_key, - Handle<Smi>(Smi::FromInt(column_offset + 1)), NONE); + SetProperty(stackFrame, line_key, + Handle<Smi>(Smi::FromInt(line_number + 1)), NONE); } - SetProperty(stackFrame, line_key, - Handle<Smi>(Smi::FromInt(line_number + 1)), NONE); - } - if (options & StackTrace::kScriptName) { - Handle<Object> script_name(script->name()); - SetProperty(stackFrame, script_key, script_name, NONE); - } + if (options & StackTrace::kScriptName) { + Handle<Object> script_name(script->name()); + SetProperty(stackFrame, script_key, script_name, NONE); + } + + if (options & StackTrace::kScriptNameOrSourceURL) { + Handle<Object> script_name(script->name()); + Handle<JSValue> script_wrapper = GetScriptWrapper(script); + Handle<Object> property = GetProperty(script_wrapper, + name_or_source_url_key); + ASSERT(property->IsJSFunction()); + Handle<JSFunction> method = Handle<JSFunction>::cast(property); + bool caught_exception; + Handle<Object> result = Execution::TryCall(method, script_wrapper, 0, + NULL, &caught_exception); + if (caught_exception) { + result = Factory::undefined_value(); + } + SetProperty(stackFrame, script_name_or_source_url_key, result, NONE); + } - if (options & StackTrace::kScriptNameOrSourceURL) { - Handle<Object> script_name(script->name()); - Handle<JSValue> script_wrapper = GetScriptWrapper(script); - Handle<Object> property = GetProperty(script_wrapper, - name_or_source_url_key); - ASSERT(property->IsJSFunction()); - Handle<JSFunction> method = Handle<JSFunction>::cast(property); - bool caught_exception; - Handle<Object> result = Execution::TryCall(method, script_wrapper, 0, - NULL, &caught_exception); - if (caught_exception) { - result = Factory::undefined_value(); + if (options & StackTrace::kFunctionName) { + Handle<Object> fun_name(fun->shared()->name()); + if (fun_name->ToBoolean()->IsFalse()) { + fun_name = Handle<Object>(fun->shared()->inferred_name()); + } + SetProperty(stackFrame, function_key, fun_name, NONE); } - SetProperty(stackFrame, script_name_or_source_url_key, result, NONE); - } - if (options & StackTrace::kFunctionName) { - Handle<Object> fun_name(fun->shared()->name()); - if (fun_name->ToBoolean()->IsFalse()) { - fun_name = Handle<Object>(fun->shared()->inferred_name()); + if (options & StackTrace::kIsEval) { + int type = Smi::cast(script->compilation_type())->value(); + Handle<Object> is_eval = (type == Script::COMPILATION_TYPE_EVAL) ? + Factory::true_value() : Factory::false_value(); + SetProperty(stackFrame, eval_key, is_eval, NONE); } - SetProperty(stackFrame, function_key, fun_name, NONE); - } - if (options & StackTrace::kIsEval) { - int type = Smi::cast(script->compilation_type())->value(); - Handle<Object> is_eval = (type == Script::COMPILATION_TYPE_EVAL) ? - Factory::true_value() : Factory::false_value(); - SetProperty(stackFrame, eval_key, is_eval, NONE); - } + if (options & StackTrace::kIsConstructor) { + Handle<Object> is_constructor = (frames[i].is_constructor()) ? + Factory::true_value() : Factory::false_value(); + SetProperty(stackFrame, constructor_key, is_constructor, NONE); + } - if (options & StackTrace::kIsConstructor) { - Handle<Object> is_constructor = (frame->IsConstructor()) ? - Factory::true_value() : Factory::false_value(); - SetProperty(stackFrame, constructor_key, is_constructor, NONE); + FixedArray::cast(stack_trace->elements())->set(frames_seen, *stackFrame); + frames_seen++; } - - FixedArray::cast(stack_trace->elements())->set(frames_seen, *stackFrame); - frames_seen++; it.Advance(); } @@ -1079,15 +1101,4 @@ char* Top::RestoreThread(char* from) { return from + sizeof(thread_local_); } - -ExecutionAccess::ExecutionAccess() { - Top::break_access_->Lock(); -} - - -ExecutionAccess::~ExecutionAccess() { - Top::break_access_->Unlock(); -} - - } } // namespace v8::internal @@ -28,7 +28,10 @@ #ifndef V8_TOP_H_ #define V8_TOP_H_ +#include "atomicops.h" +#include "compilation-cache.h" #include "frames-inl.h" +#include "runtime-profiler.h" #include "simulator.h" namespace v8 { @@ -114,10 +117,15 @@ class ThreadLocalTop BASE_EMBEDDED { #ifdef ENABLE_LOGGING_AND_PROFILING Address js_entry_sp_; // the stack pointer of the bottom js entry frame + Address external_callback_; // the external callback we're currently in #endif #ifdef ENABLE_VMSTATE_TRACKING - VMState* current_vm_state_; + StateTag current_vm_state_; + + // Used for communication with the runtime profiler thread. + // Possible values are specified in RuntimeProfilerState. + Atomic32 runtime_profiler_state_; #endif // Generated code scratch locations. @@ -267,16 +275,72 @@ class Top { static inline Address* js_entry_sp_address() { return &thread_local_.js_entry_sp_; } + + static Address external_callback() { + return thread_local_.external_callback_; + } + static void set_external_callback(Address callback) { + thread_local_.external_callback_ = callback; + } #endif #ifdef ENABLE_VMSTATE_TRACKING - static VMState* current_vm_state() { + static StateTag current_vm_state() { return thread_local_.current_vm_state_; } - static void set_current_vm_state(VMState* state) { + static void SetCurrentVMState(StateTag state) { + if (RuntimeProfiler::IsEnabled()) { + if (state == JS) { + // JS or non-JS -> JS transition. + RuntimeProfilerState old_state = SwapRuntimeProfilerState(PROF_IN_JS); + if (old_state == PROF_NOT_IN_JS_WAITING_FOR_JS) { + // If the runtime profiler was waiting, we reset the eager + // optimizing data in the compilation cache to get a fresh + // start after not running JavaScript code for a while and + // signal the runtime profiler so it can resume. + CompilationCache::ResetEagerOptimizingData(); + runtime_profiler_semaphore_->Signal(); + } + } else if (thread_local_.current_vm_state_ == JS) { + // JS -> non-JS transition. Update the runtime profiler state. + ASSERT(IsInJSState()); + SetRuntimeProfilerState(PROF_NOT_IN_JS); + } + } thread_local_.current_vm_state_ = state; } + + // Called in the runtime profiler thread. + // Returns whether the current VM state is set to JS. + static bool IsInJSState() { + ASSERT(RuntimeProfiler::IsEnabled()); + return static_cast<RuntimeProfilerState>( + NoBarrier_Load(&thread_local_.runtime_profiler_state_)) == PROF_IN_JS; + } + + // Called in the runtime profiler thread. + // Waits for the VM state to transtion from non-JS to JS. Returns + // true when notified of the transition, false when the current + // state is not the expected non-JS state. + static bool WaitForJSState() { + ASSERT(RuntimeProfiler::IsEnabled()); + // Try to switch to waiting state. + RuntimeProfilerState old_state = CompareAndSwapRuntimeProfilerState( + PROF_NOT_IN_JS, PROF_NOT_IN_JS_WAITING_FOR_JS); + if (old_state == PROF_NOT_IN_JS) { + runtime_profiler_semaphore_->Wait(); + return true; + } + return false; + } + + // When shutting down we join the profiler thread. Doing so while + // it's waiting on a semaphore will cause a deadlock, so we have to + // wake it up first. + static void WakeUpRuntimeProfilerThreadBeforeShutdown() { + runtime_profiler_semaphore_->Signal(); + } #endif // Generated code scratch locations. @@ -386,6 +450,51 @@ class Top { static const char* kStackOverflowMessage; private: +#ifdef ENABLE_VMSTATE_TRACKING + // Set of states used when communicating with the runtime profiler. + // + // The set of possible transitions is divided between the VM and the + // profiler threads. + // + // The VM thread can perform these transitions: + // o IN_JS -> NOT_IN_JS + // o NOT_IN_JS -> IN_JS + // o NOT_IN_JS_WAITING_FOR_JS -> IN_JS notifying the profiler thread + // using the semaphore. + // All the above transitions are caused by VM state changes. + // + // The profiler thread can only perform a single transition + // NOT_IN_JS -> NOT_IN_JS_WAITING_FOR_JS before it starts waiting on + // the semaphore. + enum RuntimeProfilerState { + PROF_NOT_IN_JS, + PROF_NOT_IN_JS_WAITING_FOR_JS, + PROF_IN_JS + }; + + static void SetRuntimeProfilerState(RuntimeProfilerState state) { + NoBarrier_Store(&thread_local_.runtime_profiler_state_, state); + } + + static RuntimeProfilerState SwapRuntimeProfilerState( + RuntimeProfilerState state) { + return static_cast<RuntimeProfilerState>( + NoBarrier_AtomicExchange(&thread_local_.runtime_profiler_state_, + state)); + } + + static RuntimeProfilerState CompareAndSwapRuntimeProfilerState( + RuntimeProfilerState old_state, + RuntimeProfilerState state) { + return static_cast<RuntimeProfilerState>( + NoBarrier_CompareAndSwap(&thread_local_.runtime_profiler_state_, + old_state, + state)); + } + + static Semaphore* runtime_profiler_semaphore_; +#endif // ENABLE_VMSTATE_TRACKING + // The context that initiated this JS execution. static ThreadLocalTop thread_local_; static void InitializeThreadLocal(); @@ -402,6 +511,7 @@ class Top { friend class SaveContext; friend class AssertNoContextChange; friend class ExecutionAccess; + friend class ThreadLocalTop; static void FillCache(); }; @@ -471,8 +581,15 @@ class AssertNoContextChange BASE_EMBEDDED { class ExecutionAccess BASE_EMBEDDED { public: - ExecutionAccess(); - ~ExecutionAccess(); + ExecutionAccess() { Lock(); } + ~ExecutionAccess() { Unlock(); } + + static void Lock() { Top::break_access_->Lock(); } + static void Unlock() { Top::break_access_->Unlock(); } + + static bool TryLock() { + return Top::break_access_->TryLock(); + } }; } } // namespace v8::internal diff --git a/src/type-info.cc b/src/type-info.cc index 3fc929db..8719439a 100644 --- a/src/type-info.cc +++ b/src/type-info.cc @@ -26,7 +26,15 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "v8.h" + +#include "ast.h" +#include "compiler.h" +#include "ic.h" +#include "macro-assembler.h" +#include "stub-cache.h" #include "type-info.h" + +#include "ic-inl.h" #include "objects-inl.h" namespace v8 { @@ -50,4 +58,303 @@ TypeInfo TypeInfo::TypeFromValue(Handle<Object> value) { } +TypeFeedbackOracle::TypeFeedbackOracle(Handle<Code> code) { + Initialize(code); +} + + +void TypeFeedbackOracle::Initialize(Handle<Code> code) { + ASSERT(map_.is_null()); // Only initialize once. + map_ = Factory::NewJSObject(Top::object_function()); + PopulateMap(code); +} + + +bool TypeFeedbackOracle::LoadIsMonomorphic(Property* expr) { + return IsMonomorphic(expr->position()); +} + + +bool TypeFeedbackOracle:: StoreIsMonomorphic(Assignment* expr) { + return IsMonomorphic(expr->position()); +} + + +bool TypeFeedbackOracle::CallIsMonomorphic(Call* expr) { + return IsMonomorphic(expr->position()); +} + + +Handle<Map> TypeFeedbackOracle::LoadMonomorphicReceiverType(Property* expr) { + ASSERT(LoadIsMonomorphic(expr)); + return Handle<Map>::cast(GetElement(map_, expr->position())); +} + + +Handle<Map> TypeFeedbackOracle::StoreMonomorphicReceiverType(Assignment* expr) { + ASSERT(StoreIsMonomorphic(expr)); + return Handle<Map>::cast(GetElement(map_, expr->position())); +} + + +Handle<Map> TypeFeedbackOracle::CallMonomorphicReceiverType(Call* expr) { + ASSERT(CallIsMonomorphic(expr)); + return Handle<Map>::cast(GetElement(map_, expr->position())); +} + + +ZoneMapList* TypeFeedbackOracle::LoadReceiverTypes(Property* expr, + Handle<String> name) { + Code::Flags flags = Code::ComputeMonomorphicFlags(Code::LOAD_IC, NORMAL); + return CollectReceiverTypes(expr->position(), name, flags); +} + + +ZoneMapList* TypeFeedbackOracle::StoreReceiverTypes(Assignment* expr, + Handle<String> name) { + Code::Flags flags = Code::ComputeMonomorphicFlags(Code::STORE_IC, NORMAL); + return CollectReceiverTypes(expr->position(), name, flags); +} + + +ZoneMapList* TypeFeedbackOracle::CallReceiverTypes(Call* expr, + Handle<String> name) { + int arity = expr->arguments()->length(); + Code::Flags flags = Code::ComputeMonomorphicFlags( + Code::CALL_IC, NORMAL, OWN_MAP, NOT_IN_LOOP, arity); + return CollectReceiverTypes(expr->position(), name, flags); +} + + +bool TypeFeedbackOracle::LoadIsBuiltin(Property* expr, Builtins::Name id) { + Handle<Object> object = GetElement(map_, expr->position()); + return *object == Builtins::builtin(id); +} + + +TypeInfo TypeFeedbackOracle::CompareType(CompareOperation* expr, Side side) { + Handle<Object> object = GetElement(map_, expr->position()); + TypeInfo unknown = TypeInfo::Unknown(); + if (!object->IsCode()) return unknown; + Handle<Code> code = Handle<Code>::cast(object); + if (!code->is_compare_ic_stub()) return unknown; + + CompareIC::State state = static_cast<CompareIC::State>(code->compare_state()); + switch (state) { + case CompareIC::UNINITIALIZED: + // Uninitialized means never executed. + // TODO(fschneider): Introduce a separate value for never-executed ICs. + return unknown; + case CompareIC::SMIS: + return TypeInfo::Smi(); + case CompareIC::HEAP_NUMBERS: + return TypeInfo::Number(); + case CompareIC::OBJECTS: + // TODO(kasperl): We really need a type for JS objects here. + return TypeInfo::NonPrimitive(); + case CompareIC::GENERIC: + default: + return unknown; + } +} + + +TypeInfo TypeFeedbackOracle::BinaryType(BinaryOperation* expr, Side side) { + Handle<Object> object = GetElement(map_, expr->position()); + TypeInfo unknown = TypeInfo::Unknown(); + if (!object->IsCode()) return unknown; + Handle<Code> code = Handle<Code>::cast(object); + if (code->is_binary_op_stub()) { + BinaryOpIC::TypeInfo type = static_cast<BinaryOpIC::TypeInfo>( + code->binary_op_type()); + switch (type) { + case BinaryOpIC::UNINIT_OR_SMI: + return TypeInfo::Smi(); + case BinaryOpIC::DEFAULT: + return (expr->op() == Token::DIV || expr->op() == Token::MUL) + ? TypeInfo::Double() + : TypeInfo::Integer32(); + case BinaryOpIC::HEAP_NUMBERS: + return TypeInfo::Double(); + default: + return unknown; + } + } else if (code->is_type_recording_binary_op_stub()) { + TRBinaryOpIC::TypeInfo type = static_cast<TRBinaryOpIC::TypeInfo>( + code->type_recording_binary_op_type()); + TRBinaryOpIC::TypeInfo result_type = static_cast<TRBinaryOpIC::TypeInfo>( + code->type_recording_binary_op_result_type()); + + switch (type) { + case TRBinaryOpIC::UNINITIALIZED: + // Uninitialized means never executed. + // TODO(fschneider): Introduce a separate value for never-executed ICs + return unknown; + case TRBinaryOpIC::SMI: + switch (result_type) { + case TRBinaryOpIC::UNINITIALIZED: + case TRBinaryOpIC::SMI: + return TypeInfo::Smi(); + case TRBinaryOpIC::INT32: + return TypeInfo::Integer32(); + case TRBinaryOpIC::HEAP_NUMBER: + return TypeInfo::Double(); + default: + return unknown; + } + case TRBinaryOpIC::INT32: + if (expr->op() == Token::DIV || + result_type == TRBinaryOpIC::HEAP_NUMBER) { + return TypeInfo::Double(); + } + return TypeInfo::Integer32(); + case TRBinaryOpIC::HEAP_NUMBER: + return TypeInfo::Double(); + case TRBinaryOpIC::STRING: + case TRBinaryOpIC::GENERIC: + return unknown; + default: + return unknown; + } + } + return unknown; +} + +TypeInfo TypeFeedbackOracle::SwitchType(CaseClause* clause) { + Handle<Object> object = GetElement(map_, clause->position()); + TypeInfo unknown = TypeInfo::Unknown(); + if (!object->IsCode()) return unknown; + Handle<Code> code = Handle<Code>::cast(object); + if (!code->is_compare_ic_stub()) return unknown; + + CompareIC::State state = static_cast<CompareIC::State>(code->compare_state()); + switch (state) { + case CompareIC::UNINITIALIZED: + // Uninitialized means never executed. + // TODO(fschneider): Introduce a separate value for never-executed ICs. + return unknown; + case CompareIC::SMIS: + return TypeInfo::Smi(); + case CompareIC::HEAP_NUMBERS: + return TypeInfo::Number(); + case CompareIC::OBJECTS: + // TODO(kasperl): We really need a type for JS objects here. + return TypeInfo::NonPrimitive(); + case CompareIC::GENERIC: + default: + return unknown; + } +} + + + +ZoneMapList* TypeFeedbackOracle::CollectReceiverTypes(int position, + Handle<String> name, + Code::Flags flags) { + Handle<Object> object = GetElement(map_, position); + if (object->IsUndefined()) return NULL; + + if (*object == Builtins::builtin(Builtins::StoreIC_GlobalProxy)) { + // TODO(fschneider): We could collect the maps and signal that + // we need a generic store (or load) here. + ASSERT(Handle<Code>::cast(object)->ic_state() == MEGAMORPHIC); + return NULL; + } else if (object->IsMap()) { + ZoneMapList* types = new ZoneMapList(1); + types->Add(Handle<Map>::cast(object)); + return types; + } else if (Handle<Code>::cast(object)->ic_state() == MEGAMORPHIC) { + ZoneMapList* types = new ZoneMapList(4); + ASSERT(object->IsCode()); + StubCache::CollectMatchingMaps(types, *name, flags); + return types->length() > 0 ? types : NULL; + } else { + return NULL; + } +} + + +void TypeFeedbackOracle::PopulateMap(Handle<Code> code) { + HandleScope scope; + + const int kInitialCapacity = 16; + List<int> code_positions(kInitialCapacity); + List<int> source_positions(kInitialCapacity); + CollectPositions(*code, &code_positions, &source_positions); + + int length = code_positions.length(); + ASSERT(source_positions.length() == length); + for (int i = 0; i < length; i++) { + RelocInfo info(code->instruction_start() + code_positions[i], + RelocInfo::CODE_TARGET, 0); + Handle<Code> target(Code::GetCodeFromTargetAddress(info.target_address())); + int position = source_positions[i]; + InlineCacheState state = target->ic_state(); + Code::Kind kind = target->kind(); + if (kind == Code::BINARY_OP_IC || + kind == Code::TYPE_RECORDING_BINARY_OP_IC || + kind == Code::COMPARE_IC) { + // TODO(kasperl): Avoid having multiple ICs with the same + // position by making sure that we have position information + // recorded for all binary ICs. + if (GetElement(map_, position)->IsUndefined()) { + SetElement(map_, position, target); + } + } else if (state == MONOMORPHIC) { + Handle<Map> map = Handle<Map>(target->FindFirstMap()); + if (*map == NULL) { + SetElement(map_, position, target); + } else { + SetElement(map_, position, map); + } + } else if (state == MEGAMORPHIC) { + SetElement(map_, position, target); + } + } +} + + +void TypeFeedbackOracle::CollectPositions(Code* code, + List<int>* code_positions, + List<int>* source_positions) { + AssertNoAllocation no_allocation; + int position = 0; + // Because the ICs we use for global variables access in the full + // code generator do not have any meaningful positions, we avoid + // collecting those by filtering out contextual code targets. + int mask = RelocInfo::ModeMask(RelocInfo::CODE_TARGET) | + RelocInfo::kPositionMask; + for (RelocIterator it(code, mask); !it.done(); it.next()) { + RelocInfo* info = it.rinfo(); + RelocInfo::Mode mode = info->rmode(); + if (RelocInfo::IsCodeTarget(mode)) { + Code* target = Code::GetCodeFromTargetAddress(info->target_address()); + if (target->is_inline_cache_stub()) { + InlineCacheState state = target->ic_state(); + Code::Kind kind = target->kind(); + if (kind == Code::BINARY_OP_IC) { + if (target->binary_op_type() == BinaryOpIC::GENERIC) continue; + } else if (kind == Code::TYPE_RECORDING_BINARY_OP_IC) { + if (target->type_recording_binary_op_type() == + TRBinaryOpIC::GENERIC) { + continue; + } + } else if (kind == Code::COMPARE_IC) { + if (target->compare_state() == CompareIC::GENERIC) continue; + } else { + if (kind == Code::CALL_IC && state == MONOMORPHIC && + target->check_type() != RECEIVER_MAP_CHECK) continue; + if (state != MONOMORPHIC && state != MEGAMORPHIC) continue; + } + code_positions->Add( + static_cast<int>(info->pc() - code->instruction_start())); + source_positions->Add(position); + } + } else { + ASSERT(RelocInfo::IsPosition(mode)); + position = static_cast<int>(info->data()); + } + } +} + } } // namespace v8::internal diff --git a/src/type-info.h b/src/type-info.h index f588e561..cb3e75d8 100644 --- a/src/type-info.h +++ b/src/type-info.h @@ -29,47 +29,53 @@ #define V8_TYPE_INFO_H_ #include "globals.h" +#include "zone.h" +#include "zone-inl.h" namespace v8 { namespace internal { -// Unknown -// | -// PrimitiveType -// | \--------| -// Number String -// / | | -// Double Integer32 | -// | | / -// | Smi / -// | / / -// Uninitialized. +// Unknown +// | | +// | \--------------| +// Primitive Non-primitive +// | \--------| | +// Number String | +// / | | | +// Double Integer32 | / +// | | / / +// | Smi / / +// | | / / +// | | / / +// Uninitialized.--/ class TypeInfo { public: - TypeInfo() : type_(kUnknownType) { } + TypeInfo() : type_(kUninitialized) { } - static inline TypeInfo Unknown(); + static TypeInfo Unknown() { return TypeInfo(kUnknown); } // We know it's a primitive type. - static inline TypeInfo Primitive(); + static TypeInfo Primitive() { return TypeInfo(kPrimitive); } // We know it's a number of some sort. - static inline TypeInfo Number(); - // We know it's signed 32 bit integer. - static inline TypeInfo Integer32(); + static TypeInfo Number() { return TypeInfo(kNumber); } + // We know it's a signed 32 bit integer. + static TypeInfo Integer32() { return TypeInfo(kInteger32); } // We know it's a Smi. - static inline TypeInfo Smi(); + static TypeInfo Smi() { return TypeInfo(kSmi); } // We know it's a heap number. - static inline TypeInfo Double(); + static TypeInfo Double() { return TypeInfo(kDouble); } // We know it's a string. - static inline TypeInfo String(); + static TypeInfo String() { return TypeInfo(kString); } + // We know it's a non-primitive (object) type. + static TypeInfo NonPrimitive() { return TypeInfo(kNonPrimitive); } // We haven't started collecting info yet. - static inline TypeInfo Uninitialized(); + static TypeInfo Uninitialized() { return TypeInfo(kUninitialized); } // Return compact representation. Very sensitive to enum values below! - // Compacting drops information about primtive types and strings types. + // Compacting drops information about primitive types and strings types. // We use the compact representation when we only care about number types. int ThreeBitRepresentation() { - ASSERT(type_ != kUninitializedType); + ASSERT(type_ != kUninitialized); int answer = type_ & 0xf; answer = answer > 6 ? answer - 2 : answer; ASSERT(answer >= 0); @@ -82,12 +88,12 @@ class TypeInfo { Type t = static_cast<Type>(three_bit_representation > 4 ? three_bit_representation + 2 : three_bit_representation); - t = (t == kUnknownType) ? t : static_cast<Type>(t | kPrimitiveType); - ASSERT(t == kUnknownType || - t == kNumberType || - t == kInteger32Type || - t == kSmiType || - t == kDoubleType); + t = (t == kUnknown) ? t : static_cast<Type>(t | kPrimitive); + ASSERT(t == kUnknown || + t == kNumber || + t == kInteger32 || + t == kSmi || + t == kDouble); return TypeInfo(t); } @@ -97,13 +103,14 @@ class TypeInfo { static TypeInfo FromInt(int bit_representation) { Type t = static_cast<Type>(bit_representation); - ASSERT(t == kUnknownType || - t == kPrimitiveType || - t == kNumberType || - t == kInteger32Type || - t == kSmiType || - t == kDoubleType || - t == kStringType); + ASSERT(t == kUnknown || + t == kPrimitive || + t == kNumber || + t == kInteger32 || + t == kSmi || + t == kDouble || + t == kString || + t == kNonPrimitive); return TypeInfo(t); } @@ -113,82 +120,98 @@ class TypeInfo { } - // Integer32 is an integer that can be represented as a signed - // 32-bit integer. It has to be in the range [-2^31, 2^31 - 1]. - // We also have to check for negative 0 as it is not an Integer32. + // Integer32 is an integer that can be represented as either a signed + // 32-bit integer or as an unsigned 32-bit integer. It has to be + // in the range [-2^31, 2^32 - 1]. We also have to check for negative 0 + // as it is not an Integer32. static inline bool IsInt32Double(double value) { const DoubleRepresentation minus_zero(-0.0); DoubleRepresentation rep(value); if (rep.bits == minus_zero.bits) return false; - if (value >= kMinInt && value <= kMaxInt) { - if (value == static_cast<int32_t>(value)) return true; + if (value >= kMinInt && value <= kMaxInt && + value == static_cast<int32_t>(value)) { + return true; } return false; } static TypeInfo TypeFromValue(Handle<Object> value); + bool Equals(const TypeInfo& other) { + return type_ == other.type_; + } + inline bool IsUnknown() { - return type_ == kUnknownType; + ASSERT(type_ != kUninitialized); + return type_ == kUnknown; + } + + inline bool IsPrimitive() { + ASSERT(type_ != kUninitialized); + return ((type_ & kPrimitive) == kPrimitive); } inline bool IsNumber() { - ASSERT(type_ != kUninitializedType); - return ((type_ & kNumberType) == kNumberType); + ASSERT(type_ != kUninitialized); + return ((type_ & kNumber) == kNumber); } inline bool IsSmi() { - ASSERT(type_ != kUninitializedType); - return ((type_ & kSmiType) == kSmiType); + ASSERT(type_ != kUninitialized); + return ((type_ & kSmi) == kSmi); } inline bool IsInteger32() { - ASSERT(type_ != kUninitializedType); - return ((type_ & kInteger32Type) == kInteger32Type); + ASSERT(type_ != kUninitialized); + return ((type_ & kInteger32) == kInteger32); } inline bool IsDouble() { - ASSERT(type_ != kUninitializedType); - return ((type_ & kDoubleType) == kDoubleType); + ASSERT(type_ != kUninitialized); + return ((type_ & kDouble) == kDouble); } inline bool IsString() { - ASSERT(type_ != kUninitializedType); - return ((type_ & kStringType) == kStringType); + ASSERT(type_ != kUninitialized); + return ((type_ & kString) == kString); + } + + inline bool IsNonPrimitive() { + ASSERT(type_ != kUninitialized); + return ((type_ & kNonPrimitive) == kNonPrimitive); } inline bool IsUninitialized() { - return type_ == kUninitializedType; + return type_ == kUninitialized; } const char* ToString() { switch (type_) { - case kUnknownType: return "UnknownType"; - case kPrimitiveType: return "PrimitiveType"; - case kNumberType: return "NumberType"; - case kInteger32Type: return "Integer32Type"; - case kSmiType: return "SmiType"; - case kDoubleType: return "DoubleType"; - case kStringType: return "StringType"; - case kUninitializedType: - UNREACHABLE(); - return "UninitializedType"; + case kUnknown: return "Unknown"; + case kPrimitive: return "Primitive"; + case kNumber: return "Number"; + case kInteger32: return "Integer32"; + case kSmi: return "Smi"; + case kDouble: return "Double"; + case kString: return "String"; + case kNonPrimitive: return "Object"; + case kUninitialized: return "Uninitialized"; } UNREACHABLE(); return "Unreachable code"; } private: - // We use 6 bits to represent the types. enum Type { - kUnknownType = 0, // 000000 - kPrimitiveType = 0x10, // 010000 - kNumberType = 0x11, // 010001 - kInteger32Type = 0x13, // 010011 - kSmiType = 0x17, // 010111 - kDoubleType = 0x19, // 011001 - kStringType = 0x30, // 110000 - kUninitializedType = 0x3f // 111111 + kUnknown = 0, // 0000000 + kPrimitive = 0x10, // 0010000 + kNumber = 0x11, // 0010001 + kInteger32 = 0x13, // 0010011 + kSmi = 0x17, // 0010111 + kDouble = 0x19, // 0011001 + kString = 0x30, // 0110000 + kNonPrimitive = 0x40, // 1000000 + kUninitialized = 0x7f // 1111111 }; explicit inline TypeInfo(Type t) : type_(t) { } @@ -196,44 +219,63 @@ class TypeInfo { }; -TypeInfo TypeInfo::Unknown() { - return TypeInfo(kUnknownType); -} +// Forward declarations. +class Assignment; +class BinaryOperation; +class Call; +class CompareOperation; +class CompilationInfo; +class Property; +class CaseClause; +class TypeFeedbackOracle BASE_EMBEDDED { + public: + enum Side { + LEFT, + RIGHT, + RESULT + }; -TypeInfo TypeInfo::Primitive() { - return TypeInfo(kPrimitiveType); -} + explicit TypeFeedbackOracle(Handle<Code> code); + bool LoadIsMonomorphic(Property* expr); + bool StoreIsMonomorphic(Assignment* expr); + bool CallIsMonomorphic(Call* expr); -TypeInfo TypeInfo::Number() { - return TypeInfo(kNumberType); -} + Handle<Map> LoadMonomorphicReceiverType(Property* expr); + Handle<Map> StoreMonomorphicReceiverType(Assignment* expr); + Handle<Map> CallMonomorphicReceiverType(Call* expr); + ZoneMapList* LoadReceiverTypes(Property* expr, Handle<String> name); + ZoneMapList* StoreReceiverTypes(Assignment* expr, Handle<String> name); + ZoneMapList* CallReceiverTypes(Call* expr, Handle<String> name); -TypeInfo TypeInfo::Integer32() { - return TypeInfo(kInteger32Type); -} + bool LoadIsBuiltin(Property* expr, Builtins::Name id); + // Get type information for arithmetic operations and compares. + TypeInfo BinaryType(BinaryOperation* expr, Side side); + TypeInfo CompareType(CompareOperation* expr, Side side); + TypeInfo SwitchType(CaseClause* clause); -TypeInfo TypeInfo::Smi() { - return TypeInfo(kSmiType); -} + private: + void Initialize(Handle<Code> code); + bool IsMonomorphic(int pos) { return GetElement(map_, pos)->IsMap(); } -TypeInfo TypeInfo::Double() { - return TypeInfo(kDoubleType); -} + ZoneMapList* CollectReceiverTypes(int position, + Handle<String> name, + Code::Flags flags); + void PopulateMap(Handle<Code> code); -TypeInfo TypeInfo::String() { - return TypeInfo(kStringType); -} + void CollectPositions(Code* code, + List<int>* code_positions, + List<int>* source_positions); + Handle<JSObject> map_; -TypeInfo TypeInfo::Uninitialized() { - return TypeInfo(kUninitializedType); -} + DISALLOW_COPY_AND_ASSIGN(TypeFeedbackOracle); +}; } } // namespace v8::internal diff --git a/src/utils.cc b/src/utils.cc index 7096ba35..d0ec4ef5 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -45,8 +45,16 @@ void PrintF(const char* format, ...) { } -void Flush() { - fflush(stdout); +void PrintF(FILE* out, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + OS::VFPrint(out, format, arguments); + va_end(arguments); +} + + +void Flush(FILE* out) { + fflush(out); } @@ -168,6 +176,23 @@ int WriteCharsToFile(const char* str, int size, FILE* f) { } +int AppendChars(const char* filename, + const char* str, + int size, + bool verbose) { + FILE* f = OS::FOpen(filename, "ab"); + if (f == NULL) { + if (verbose) { + OS::PrintError("Cannot open file %s for writing.\n", filename); + } + return 0; + } + int written = WriteCharsToFile(str, size, f); + fclose(f); + return written; +} + + int WriteChars(const char* filename, const char* str, int size, @@ -214,11 +239,16 @@ void StringBuilder::AddSubstring(const char* s, int n) { void StringBuilder::AddFormatted(const char* format, ...) { + va_list arguments; + va_start(arguments, format); + AddFormattedList(format, arguments); + va_end(arguments); +} + + +void StringBuilder::AddFormattedList(const char* format, va_list list) { ASSERT(!is_finalized() && position_ < buffer_.length()); - va_list args; - va_start(args, format); - int n = OS::VSNPrintF(buffer_ + position_, format, args); - va_end(args); + int n = OS::VSNPrintF(buffer_ + position_, format, list); if (n < 0 || n >= (buffer_.length() - position_)) { position_ = buffer_.length(); } else { diff --git a/src/utils.h b/src/utils.h index 69c062fb..62b8726b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -226,6 +226,11 @@ class BitField { static T decode(uint32_t value) { return static_cast<T>((value & mask()) >> shift); } + + // Value for the field with all bits set. + static T max() { + return decode(mask()); + } }; @@ -326,7 +331,7 @@ class Vector { return start_[index]; } - T& at(int i) const { return operator[](i); } + const T& at(int index) const { return operator[](index); } T& first() { return start_[0]; } @@ -387,11 +392,40 @@ class Vector { }; +// A pointer that can only be set once and doesn't allow NULL values. +template<typename T> +class SetOncePointer { + public: + SetOncePointer() : pointer_(NULL) { } + + bool is_set() const { return pointer_ != NULL; } + + T* get() const { + ASSERT(pointer_ != NULL); + return pointer_; + } + + void set(T* value) { + ASSERT(pointer_ == NULL && value != NULL); + pointer_ = value; + } + + private: + T* pointer_; +}; + + template <typename T, int kSize> class EmbeddedVector : public Vector<T> { public: EmbeddedVector() : Vector<T>(buffer_, kSize) { } + explicit EmbeddedVector(T initial_value) : Vector<T>(buffer_, kSize) { + for (int i = 0; i < kSize; ++i) { + buffer_[i] = initial_value; + } + } + // When copying, make underlying Vector to reference our buffer. EmbeddedVector(const EmbeddedVector& rhs) : Vector<T>(rhs) { diff --git a/src/v8-counters.h b/src/v8-counters.h index 60e8741d..fa5d581e 100644 --- a/src/v8-counters.h +++ b/src/v8-counters.h @@ -28,7 +28,9 @@ #ifndef V8_V8_COUNTERS_H_ #define V8_V8_COUNTERS_H_ +#include "allocation.h" #include "counters.h" +#include "v8globals.h" namespace v8 { namespace internal { @@ -159,7 +161,20 @@ namespace internal { SC(named_load_global_stub, V8.NamedLoadGlobalStub) \ SC(named_load_global_stub_miss, V8.NamedLoadGlobalStubMiss) \ SC(keyed_store_field, V8.KeyedStoreField) \ + SC(named_store_inline_field, V8.NamedStoreInlineField) \ SC(keyed_store_inline, V8.KeyedStoreInline) \ + SC(named_load_inline_generic, V8.NamedLoadInlineGeneric) \ + SC(named_load_inline_field, V8.NamedLoadInlineFast) \ + SC(keyed_load_inline_generic, V8.KeyedLoadInlineGeneric) \ + SC(keyed_load_inline_fast, V8.KeyedLoadInlineFast) \ + SC(named_load_full, V8.NamedLoadFull) \ + SC(keyed_load_full, V8.KeyedLoadFull) \ + SC(keyed_store_inline_generic, V8.KeyedStoreInlineGeneric) \ + SC(keyed_store_inline_fast, V8.KeyedStoreInlineFast) \ + SC(named_store_inline_generic, V8.NamedStoreInlineGeneric) \ + SC(named_store_inline_fast, V8.NamedStoreInlineFast) \ + SC(keyed_store_full, V8.KeyedStoreFull) \ + SC(named_store_full, V8.NamedStoreFull) \ SC(keyed_store_inline_miss, V8.KeyedStoreInlineMiss) \ SC(named_store_global_inline, V8.NamedStoreGlobalInline) \ SC(named_store_global_inline_miss, V8.NamedStoreGlobalInlineMiss) \ @@ -224,7 +239,25 @@ namespace internal { SC(math_sqrt, V8.MathSqrt) \ SC(math_tan, V8.MathTan) \ SC(transcendental_cache_hit, V8.TranscendentalCacheHit) \ - SC(transcendental_cache_miss, V8.TranscendentalCacheMiss) + SC(transcendental_cache_miss, V8.TranscendentalCacheMiss) \ + SC(stack_interrupts, V8.StackInterrupts) \ + SC(runtime_profiler_ticks, V8.RuntimeProfilerTicks) \ + SC(other_ticks, V8.OtherTicks) \ + SC(js_opt_ticks, V8.JsOptTicks) \ + SC(js_non_opt_ticks, V8.JsNonoptTicks) \ + SC(js_other_ticks, V8.JsOtherTicks) \ + SC(smi_checks_removed, V8.SmiChecksRemoved) \ + SC(map_checks_removed, V8.MapChecksRemoved) \ + SC(quote_json_char_count, V8.QuoteJsonCharacterCount) \ + SC(quote_json_char_recount, V8.QuoteJsonCharacterReCount) \ + SC(instance_of, V8.InstanceOf) \ + SC(instance_of_cache, V8.InstanceOfCache) \ + SC(instance_of_stub_true, V8.InstanceOfStubTrue) \ + SC(instance_of_stub_false, V8.InstanceOfStubFalse) \ + SC(instance_of_stub_false_null, V8.InstanceOfStubFalseNull) \ + SC(instance_of_stub_false_string, V8.InstanceOfStubFalseString) \ + SC(instance_of_full, V8.InstanceOfFull) \ + SC(instance_of_slow, V8.InstanceOfSlow) // This file contains all the v8 counters that are in use. @@ -29,12 +29,16 @@ #include "bootstrapper.h" #include "debug.h" +#include "deoptimizer.h" +#include "heap-profiler.h" +#include "hydrogen.h" +#include "lithium-allocator.h" +#include "log.h" +#include "oprofile-agent.h" +#include "runtime-profiler.h" #include "serialize.h" #include "simulator.h" #include "stub-cache.h" -#include "heap-profiler.h" -#include "oprofile-agent.h" -#include "log.h" namespace v8 { namespace internal { @@ -43,6 +47,7 @@ bool V8::is_running_ = false; bool V8::has_been_setup_ = false; bool V8::has_been_disposed_ = false; bool V8::has_fatal_error_ = false; +bool V8::use_crankshaft_ = true; bool V8::Initialize(Deserializer* des) { @@ -50,6 +55,9 @@ bool V8::Initialize(Deserializer* des) { if (has_been_disposed_ || has_fatal_error_) return false; if (IsRunning()) return true; + use_crankshaft_ = FLAG_crankshaft; + // Peephole optimization might interfere with deoptimization. + FLAG_peephole_optimization = !use_crankshaft_; is_running_ = true; has_been_setup_ = true; has_fatal_error_ = false; @@ -122,6 +130,9 @@ bool V8::Initialize(Deserializer* des) { CPU::Setup(); OProfileAgent::Initialize(); + Deoptimizer::Setup(); + LAllocator::Setup(); + RuntimeProfiler::Setup(); // If we are deserializing, log non-function code objects and compiled // functions found in the snapshot. @@ -144,6 +155,12 @@ void V8::SetFatalError() { void V8::TearDown() { if (!has_been_setup_ || has_been_disposed_) return; + if (FLAG_time_hydrogen) HStatistics::Instance()->Print(); + + // We must stop the logger before we tear down other components. + Logger::EnsureTickerStopped(); + + Deoptimizer::TearDown(); OProfileAgent::TearDown(); if (FLAG_preemption) { @@ -157,12 +174,11 @@ void V8::TearDown() { Top::TearDown(); HeapProfiler::TearDown(); - CpuProfiler::TearDown(); - - Heap::TearDown(); + RuntimeProfiler::TearDown(); Logger::TearDown(); + Heap::TearDown(); is_running_ = false; has_been_disposed_ = true; @@ -66,7 +66,6 @@ #include "log-inl.h" #include "cpu-profiler-inl.h" #include "handles-inl.h" -#include "vm-state-inl.h" namespace v8 { namespace internal { @@ -84,6 +83,8 @@ class V8 : public AllStatic { static bool Initialize(Deserializer* des); static void TearDown(); static bool IsRunning() { return is_running_; } + static bool UseCrankshaft() { return use_crankshaft_; } + static void DisableCrankshaft() { use_crankshaft_ = false; } // To be dead you have to have lived static bool IsDead() { return has_fatal_error_ || has_been_disposed_; } static void SetFatalError(); @@ -115,6 +116,8 @@ class V8 : public AllStatic { // True if engine has been shut down // (reset if engine is restarted) static bool has_been_disposed_; + // True if we are using the crankshaft optimizing compiler. + static bool use_crankshaft_; }; } } // namespace v8::internal diff --git a/src/v8globals.h b/src/v8globals.h index 2815771a..65bbf6ab 100644 --- a/src/v8globals.h +++ b/src/v8globals.h @@ -82,6 +82,7 @@ const uint64_t kDebugZapValue = 0xbadbaddbbadbaddb; const Address kZapValue = reinterpret_cast<Address>(0xdeadbeed); const Address kHandleZapValue = reinterpret_cast<Address>(0xbaddead); const Address kFromSpaceZapValue = reinterpret_cast<Address>(0xbeefdad); +const uint32_t kSlotsZapValue = 0xbeefdeed; const uint32_t kDebugZapValue = 0xbadbaddb; #endif @@ -285,6 +286,14 @@ enum InlineCacheState { }; +enum CheckType { + RECEIVER_MAP_CHECK, + STRING_CHECK, + NUMBER_CHECK, + BOOLEAN_CHECK +}; + + enum InLoopFlag { NOT_IN_LOOP, IN_LOOP diff --git a/src/v8natives.js b/src/v8natives.js index 09b296d4..9fd21626 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -491,29 +491,28 @@ PropertyDescriptor.prototype.hasSetter = function() { } -// Converts an array returned from Runtime_GetOwnProperty to an actual -// property descriptor. For a description of the array layout please -// see the runtime.cc file. -function ConvertDescriptorArrayToDescriptor(desc_array) { - if (desc_array == false) { - throw 'Internal error: invalid desc_array'; - } - - if (IS_UNDEFINED(desc_array)) { - return void 0; - } +// ES5 section 8.12.1. +function GetOwnProperty(obj, p) { var desc = new PropertyDescriptor(); - // This is an accessor. - if (desc_array[IS_ACCESSOR_INDEX]) { - desc.setGet(desc_array[GETTER_INDEX]); - desc.setSet(desc_array[SETTER_INDEX]); + + // GetOwnProperty returns an array indexed by the constants + // defined in macros.py. + // If p is not a property on obj undefined is returned. + var props = %GetOwnProperty(ToObject(obj), ToString(p)); + + if (IS_UNDEFINED(props)) return void 0; + + // This is an accessor + if (props[IS_ACCESSOR_INDEX]) { + desc.setGet(props[GETTER_INDEX]); + desc.setSet(props[SETTER_INDEX]); } else { - desc.setValue(desc_array[VALUE_INDEX]); - desc.setWritable(desc_array[WRITABLE_INDEX]); + desc.setValue(props[VALUE_INDEX]); + desc.setWritable(props[WRITABLE_INDEX]); } - desc.setEnumerable(desc_array[ENUMERABLE_INDEX]); - desc.setConfigurable(desc_array[CONFIGURABLE_INDEX]); + desc.setEnumerable(props[ENUMERABLE_INDEX]); + desc.setConfigurable(props[CONFIGURABLE_INDEX]); return desc; } @@ -536,27 +535,9 @@ function HasProperty(obj, p) { } -// ES5 section 8.12.1. -function GetOwnProperty(obj, p) { - // GetOwnProperty returns an array indexed by the constants - // defined in macros.py. - // If p is not a property on obj undefined is returned. - var props = %GetOwnProperty(ToObject(obj), ToString(p)); - - // A false value here means that access checks failed. - if (props == false) return void 0; - - return ConvertDescriptorArrayToDescriptor(props); -} - - // ES5 8.12.9. function DefineOwnProperty(obj, p, desc, should_throw) { - var current_or_access = %GetOwnProperty(ToObject(obj), ToString(p)); - // A false value here means that access checks failed. - if (current_or_access == false) return void 0; - - var current = ConvertDescriptorArrayToDescriptor(current_or_access); + var current = GetOwnProperty(obj, p); var extensible = %IsExtensible(ToObject(obj)); // Error handling according to spec. @@ -582,7 +563,7 @@ function DefineOwnProperty(obj, p, desc, should_throw) { } // Step 7 - if (desc.isConfigurable() || desc.isEnumerable() != current.isEnumerable()) + if (desc.isConfigurable() || desc.isEnumerable() != current.isEnumerable()) throw MakeTypeError("redefine_disallowed", ["defineProperty"]); // Step 9 if (IsDataDescriptor(current) != IsDataDescriptor(desc)) @@ -634,12 +615,20 @@ function DefineOwnProperty(obj, p, desc, should_throw) { } else { flag |= READ_ONLY; } - %DefineOrRedefineDataProperty(obj, p, desc.getValue(), flag); + var value = void 0; // Default value is undefined. + if (desc.hasValue()) { + value = desc.getValue(); + } else if (!IS_UNDEFINED(current)) { + value = current.getValue(); + } + %DefineOrRedefineDataProperty(obj, p, value, flag); } else { - if (desc.hasGetter() && IS_FUNCTION(desc.getGet())) { + if (desc.hasGetter() && + (IS_FUNCTION(desc.getGet()) || IS_UNDEFINED(desc.getGet()))) { %DefineOrRedefineAccessorProperty(obj, p, GETTER, desc.getGet(), flag); } - if (desc.hasSetter() && IS_FUNCTION(desc.getSet())) { + if (desc.hasSetter() && + (IS_FUNCTION(desc.getSet()) || IS_UNDEFINED(desc.getSet()))) { %DefineOrRedefineAccessorProperty(obj, p, SETTER, desc.getSet(), flag); } } @@ -922,19 +911,13 @@ function BooleanValueOf() { } -function BooleanToJSON(key) { - return CheckJSONPrimitive(this.valueOf()); -} - - // ---------------------------------------------------------------------------- function SetupBoolean() { InstallFunctions($Boolean.prototype, DONT_ENUM, $Array( "toString", BooleanToString, - "valueOf", BooleanValueOf, - "toJSON", BooleanToJSON + "valueOf", BooleanValueOf )); } @@ -1034,18 +1017,6 @@ function NumberToPrecision(precision) { } -function CheckJSONPrimitive(val) { - if (!IsPrimitive(val)) - throw MakeTypeError('result_not_primitive', ['toJSON', val]); - return val; -} - - -function NumberToJSON(key) { - return CheckJSONPrimitive(this.valueOf()); -} - - // ---------------------------------------------------------------------------- function SetupNumber() { @@ -1086,15 +1057,13 @@ function SetupNumber() { "valueOf", NumberValueOf, "toFixed", NumberToFixed, "toExponential", NumberToExponential, - "toPrecision", NumberToPrecision, - "toJSON", NumberToJSON + "toPrecision", NumberToPrecision )); } SetupNumber(); - // ---------------------------------------------------------------------------- // Function diff --git a/src/v8preparserdll-main.cc b/src/v8preparserdll-main.cc new file mode 100644 index 00000000..c0344d34 --- /dev/null +++ b/src/v8preparserdll-main.cc @@ -0,0 +1,39 @@ +// Copyright 2010 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. + +#include <windows.h> + +#include "../include/v8-preparser.h" + +extern "C" { +BOOL WINAPI DllMain(HANDLE hinstDLL, + DWORD dwReason, + LPVOID lpvReserved) { + // Do nothing. + return TRUE; +} +} diff --git a/src/v8utils.h b/src/v8utils.h index a907c9f5..87efbcfe 100644 --- a/src/v8utils.h +++ b/src/v8utils.h @@ -29,6 +29,10 @@ #define V8_V8UTILS_H_ #include "utils.h" +#ifdef ANDROID +// Cherry pick from r6346 to build on Android. +#include "platform.h" +#endif namespace v8 { namespace internal { @@ -42,18 +46,26 @@ namespace internal { // so it works on MacOSX. #if defined(__MACH__) && defined(__APPLE__) #define PRINTF_CHECKING +#define FPRINTF_CHECKING #else // MacOsX. #define PRINTF_CHECKING __attribute__ ((format (printf, 1, 2))) +#define FPRINTF_CHECKING __attribute__ ((format (printf, 2, 3))) #endif #else #define PRINTF_CHECKING +#define FPRINTF_CHECKING #endif // Our version of printf(). void PRINTF_CHECKING PrintF(const char* format, ...); +void FPRINTF_CHECKING PrintF(FILE* out, const char* format, ...); // Our version of fflush. -void Flush(); +void Flush(FILE* out); + +inline void Flush() { + Flush(stdout); +} // Read a line of characters after printing the prompt to stdout. The resulting @@ -67,6 +79,14 @@ char* ReadLine(const char* prompt); byte* ReadBytes(const char* filename, int* size, bool verbose = true); +// Append size chars from str to the file given by filename. +// The file is overwritten. Returns the number of chars written. +int AppendChars(const char* filename, + const char* str, + int size, + bool verbose = true); + + // Write size chars from str to the file given by filename. // The file is overwritten. Returns the number of chars written. int WriteChars(const char* filename, @@ -217,6 +237,9 @@ class StringBuilder { // Add formatted contents to the builder just like printf(). void AddFormatted(const char* format, ...); + // Add formatted contents like printf based on a va_list. + void AddFormattedList(const char* format, va_list list); + // Add character padding to the builder. If count is non-positive, // nothing is added to the builder. void AddPadding(char c, int count); diff --git a/src/variables.cc b/src/variables.cc index 504e2244..c1440b7f 100644 --- a/src/variables.cc +++ b/src/variables.cc @@ -86,6 +86,18 @@ bool Variable::IsStackAllocated() const { } +bool Variable::IsParameter() const { + Slot* s = AsSlot(); + return s != NULL && s->type() == Slot::PARAMETER; +} + + +bool Variable::IsStackLocal() const { + Slot* s = AsSlot(); + return s != NULL && s->type() == Slot::LOCAL; +} + + Variable::Variable(Scope* scope, Handle<String> name, Mode mode, diff --git a/src/variables.h b/src/variables.h index ec76fee4..9e460f76 100644 --- a/src/variables.h +++ b/src/variables.h @@ -146,6 +146,8 @@ class Variable: public ZoneObject { } bool IsStackAllocated() const; + bool IsParameter() const; // Includes 'this'. + bool IsStackLocal() const; bool is_dynamic() const { return (mode_ == DYNAMIC || diff --git a/src/version.cc b/src/version.cc index 3806e68e..d2c09605 100644 --- a/src/version.cc +++ b/src/version.cc @@ -32,10 +32,10 @@ // These macros define the version number for the current version. // NOTE these macros are used by the SCons build script so their names // cannot be changed without changing the SCons build script. -#define MAJOR_VERSION 2 -#define MINOR_VERSION 5 -#define BUILD_NUMBER 9 -#define PATCH_LEVEL 20 +#define MAJOR_VERSION 3 +#define MINOR_VERSION 0 +#define BUILD_NUMBER 4 +#define PATCH_LEVEL 1 #define CANDIDATE_VERSION false // Define SONAME to have the SCons build the put a specific SONAME into the @@ -57,12 +57,19 @@ const char* Version::soname_ = SONAME; // Calculate the V8 version string. void Version::GetString(Vector<char> str) { const char* candidate = IsCandidate() ? " (candidate)" : ""; +#ifdef USE_SIMULATOR + const char* is_simulator = " SIMULATOR"; +#else + const char* is_simulator = ""; +#endif // USE_SIMULATOR if (GetPatch() > 0) { - OS::SNPrintF(str, "%d.%d.%d.%d%s", - GetMajor(), GetMinor(), GetBuild(), GetPatch(), candidate); + OS::SNPrintF(str, "%d.%d.%d.%d%s%s", + GetMajor(), GetMinor(), GetBuild(), GetPatch(), candidate, + is_simulator); } else { - OS::SNPrintF(str, "%d.%d.%d%s", - GetMajor(), GetMinor(), GetBuild(), candidate); + OS::SNPrintF(str, "%d.%d.%d%s%s", + GetMajor(), GetMinor(), GetBuild(), candidate, + is_simulator); } } diff --git a/src/vm-state-inl.h b/src/vm-state-inl.h index 74f4a6a7..da912b74 100644 --- a/src/vm-state-inl.h +++ b/src/vm-state-inl.h @@ -29,6 +29,7 @@ #define V8_VM_STATE_INL_H_ #include "vm-state.h" +#include "runtime-profiler.h" namespace v8 { namespace internal { @@ -49,52 +50,31 @@ inline const char* StateToString(StateTag state) { return "COMPILER"; case OTHER: return "OTHER"; + case EXTERNAL: + return "EXTERNAL"; default: UNREACHABLE(); return NULL; } } -VMState::VMState(StateTag state) - : disabled_(true), - state_(OTHER), - external_callback_(NULL) { -#ifdef ENABLE_LOGGING_AND_PROFILING - if (!Logger::is_logging() && !CpuProfiler::is_profiling()) { - return; - } -#endif - - disabled_ = false; -#if !defined(ENABLE_HEAP_PROTECTION) - // When not protecting the heap, there is no difference between - // EXTERNAL and OTHER. As an optimization in that case, we will not - // perform EXTERNAL->OTHER transitions through the API. We thus - // compress the two states into one. - if (state == EXTERNAL) state = OTHER; -#endif - state_ = state; - // Save the previous state. - previous_ = Top::current_vm_state(); - // Install the new state. - Top::set_current_vm_state(this); - +VMState::VMState(StateTag tag) : previous_tag_(Top::current_vm_state()) { #ifdef ENABLE_LOGGING_AND_PROFILING if (FLAG_log_state_changes) { - LOG(UncheckedStringEvent("Entering", StateToString(state_))); - if (previous_ != NULL) { - LOG(UncheckedStringEvent("From", StateToString(previous_->state_))); - } + LOG(UncheckedStringEvent("Entering", StateToString(tag))); + LOG(UncheckedStringEvent("From", StateToString(previous_tag_))); } #endif + Top::SetCurrentVMState(tag); + #ifdef ENABLE_HEAP_PROTECTION if (FLAG_protect_heap) { - if (state_ == EXTERNAL) { + if (tag == EXTERNAL) { // We are leaving V8. - ASSERT((previous_ != NULL) && (previous_->state_ != EXTERNAL)); + ASSERT(previous_tag_ != EXTERNAL); Heap::Protect(); - } else if ((previous_ == NULL) || (previous_->state_ == EXTERNAL)) { + } else if (previous_tag_ = EXTERNAL) { // We are entering V8. Heap::Unprotect(); } @@ -104,34 +84,51 @@ VMState::VMState(StateTag state) VMState::~VMState() { - if (disabled_) return; - // Return to the previous state. - Top::set_current_vm_state(previous_); - #ifdef ENABLE_LOGGING_AND_PROFILING if (FLAG_log_state_changes) { - LOG(UncheckedStringEvent("Leaving", StateToString(state_))); - if (previous_ != NULL) { - LOG(UncheckedStringEvent("To", StateToString(previous_->state_))); - } + LOG(UncheckedStringEvent("Leaving", + StateToString(Top::current_vm_state()))); + LOG(UncheckedStringEvent("To", StateToString(previous_tag_))); } #endif // ENABLE_LOGGING_AND_PROFILING #ifdef ENABLE_HEAP_PROTECTION + StateTag tag = Top::current_vm_state(); +#endif + + Top::SetCurrentVMState(previous_tag_); + +#ifdef ENABLE_HEAP_PROTECTION if (FLAG_protect_heap) { - if (state_ == EXTERNAL) { + if (tag == EXTERNAL) { // We are reentering V8. - ASSERT((previous_ != NULL) && (previous_->state_ != EXTERNAL)); + ASSERT(previous_tag_ != EXTERNAL); Heap::Unprotect(); - } else if ((previous_ == NULL) || (previous_->state_ == EXTERNAL)) { + } else if (previous_tag_ == EXTERNAL) { // We are leaving V8. Heap::Protect(); } } #endif // ENABLE_HEAP_PROTECTION } + #endif // ENABLE_VMSTATE_TRACKING + +#ifdef ENABLE_LOGGING_AND_PROFILING + +ExternalCallbackScope::ExternalCallbackScope(Address callback) + : previous_callback_(Top::external_callback()) { + Top::set_external_callback(callback); +} + +ExternalCallbackScope::~ExternalCallbackScope() { + Top::set_external_callback(previous_callback_); +} + +#endif // ENABLE_LOGGING_AND_PROFILING + + } } // namespace v8::internal #endif // V8_VM_STATE_INL_H_ diff --git a/src/vm-state.h b/src/vm-state.h index cc91e837..df7fb30a 100644 --- a/src/vm-state.h +++ b/src/vm-state.h @@ -36,38 +36,29 @@ namespace internal { class VMState BASE_EMBEDDED { #ifdef ENABLE_VMSTATE_TRACKING public: - inline VMState(StateTag state); + inline explicit VMState(StateTag tag); inline ~VMState(); - StateTag state() { return state_; } - void set_external_callback(Address external_callback) { - external_callback_ = external_callback; - } - - // Used for debug asserts. - static bool is_outermost_external() { - return Top::current_vm_state() == 0; - } + private: + StateTag previous_tag_; - static StateTag current_state() { - VMState* state = Top::current_vm_state(); - return state ? state->state() : EXTERNAL; - } +#else + public: + explicit VMState(StateTag state) {} +#endif +}; - static Address external_callback() { - VMState* state = Top::current_vm_state(); - return state ? state->external_callback_ : NULL; - } +class ExternalCallbackScope BASE_EMBEDDED { +#ifdef ENABLE_LOGGING_AND_PROFILING + public: + inline explicit ExternalCallbackScope(Address callback); + inline ~ExternalCallbackScope(); private: - bool disabled_; - StateTag state_; - VMState* previous_; - Address external_callback_; - + Address previous_callback_; #else public: - explicit VMState(StateTag state) {} + explicit ExternalCallbackScope(Address callback) {} #endif }; diff --git a/src/win32-headers.h b/src/win32-headers.h new file mode 100644 index 00000000..b51a38a1 --- /dev/null +++ b/src/win32-headers.h @@ -0,0 +1,95 @@ +// Copyright 2010 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. + +#ifndef WIN32_LEAN_AND_MEAN +// WIN32_LEAN_AND_MEAN implies NOCRYPT and NOGDI. +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef NOKERNEL +#define NOKERNEL +#endif +#ifndef NOUSER +#define NOUSER +#endif +#ifndef NOSERVICE +#define NOSERVICE +#endif +#ifndef NOSOUND +#define NOSOUND +#endif +#ifndef NOMCX +#define NOMCX +#endif +// Require Windows XP or higher (this is required for the RtlCaptureContext +// function to be present). +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#endif + +#include <windows.h> + +#ifdef V8_WIN32_HEADERS_FULL +#include <time.h> // For LocalOffset() implementation. +#include <mmsystem.h> // For timeGetTime(). +#ifdef __MINGW32__ +// Require Windows XP or higher when compiling with MinGW. This is for MinGW +// header files to expose getaddrinfo. +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x501 +#endif // __MINGW32__ +#ifndef __MINGW32__ +#include <dbghelp.h> // For SymLoadModule64 and al. +#endif // __MINGW32__ +#include <limits.h> // For INT_MAX and al. +#include <tlhelp32.h> // For Module32First and al. + +// These additional WIN32 includes have to be right here as the #undef's below +// makes it impossible to have them elsewhere. +#include <winsock2.h> +#include <ws2tcpip.h> +#include <process.h> // for _beginthreadex() +#include <stdlib.h> +#endif // V8_WIN32_HEADERS_FULL + +#undef VOID +#undef DELETE +#undef IN +#undef THIS +#undef CONST +#undef NAN +#undef TRUE +#undef FALSE +#undef UNKNOWN +#undef NONE +#undef ANY +#undef IGNORE +#undef GetObject +#undef CreateMutex +#undef CreateSemaphore diff --git a/src/x64/assembler-x64-inl.h b/src/x64/assembler-x64-inl.h index 44159e06..1fe9eed4 100644 --- a/src/x64/assembler-x64-inl.h +++ b/src/x64/assembler-x64-inl.h @@ -274,6 +274,30 @@ void RelocInfo::set_target_object(Object* target) { } +Handle<JSGlobalPropertyCell> RelocInfo::target_cell_handle() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + return Handle<JSGlobalPropertyCell>( + reinterpret_cast<JSGlobalPropertyCell**>(address)); +} + + +JSGlobalPropertyCell* RelocInfo::target_cell() { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = Memory::Address_at(pc_); + Object* object = HeapObject::FromAddress( + address - JSGlobalPropertyCell::kValueOffset); + return reinterpret_cast<JSGlobalPropertyCell*>(object); +} + + +void RelocInfo::set_target_cell(JSGlobalPropertyCell* cell) { + ASSERT(rmode_ == RelocInfo::GLOBAL_PROPERTY_CELL); + Address address = cell->address() + JSGlobalPropertyCell::kValueOffset; + Memory::Address_at(pc_) = address; +} + + bool RelocInfo::IsPatchedReturnSequence() { // The recognized call sequence is: // movq(kScratchRegister, immediate64); call(kScratchRegister); diff --git a/src/x64/assembler-x64.cc b/src/x64/assembler-x64.cc index caed7c8a..8f15f23b 100644 --- a/src/x64/assembler-x64.cc +++ b/src/x64/assembler-x64.cc @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -44,10 +44,10 @@ uint64_t CpuFeatures::supported_ = kDefaultCpuFeatures; uint64_t CpuFeatures::enabled_ = 0; uint64_t CpuFeatures::found_by_runtime_probing_ = 0; -void CpuFeatures::Probe() { +void CpuFeatures::Probe(bool portable) { ASSERT(Heap::HasBeenSetup()); - ASSERT(supported_ == kDefaultCpuFeatures); - if (Serializer::enabled()) { + supported_ = kDefaultCpuFeatures; + if (portable && Serializer::enabled()) { supported_ |= OS::CpuFeaturesImpliedByPlatform(); return; // No features if we might serialize. } @@ -133,7 +133,7 @@ void CpuFeatures::Probe() { found_by_runtime_probing_ &= ~kDefaultCpuFeatures; uint64_t os_guarantees = OS::CpuFeaturesImpliedByPlatform(); supported_ |= os_guarantees; - found_by_runtime_probing_ &= ~os_guarantees; + found_by_runtime_probing_ &= portable ? ~os_guarantees : 0; // SSE2 and CMOV must be available on an X64 CPU. ASSERT(IsSupported(CPUID)); ASSERT(IsSupported(SSE2)); @@ -821,6 +821,7 @@ void Assembler::bts(const Operand& dst, Register src) { void Assembler::call(Label* L) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; // 1110 1000 #32-bit disp. @@ -852,6 +853,7 @@ void Assembler::call(Handle<Code> target, RelocInfo::Mode rmode) { void Assembler::call(Register adr) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; // Opcode: FF /2 r64. @@ -862,6 +864,7 @@ void Assembler::call(Register adr) { void Assembler::call(const Operand& op) { + positions_recorder()->WriteRecordedPositions(); EnsureSpace ensure_space(this); last_pc_ = pc_; // Opcode: FF /2 m64. @@ -2217,6 +2220,14 @@ void Assembler::fldpi() { } +void Assembler::fldln2() { + EnsureSpace ensure_space(this); + last_pc_ = pc_; + emit(0xD9); + emit(0xED); +} + + void Assembler::fld_s(const Operand& adr) { EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -2358,6 +2369,14 @@ void Assembler::fsin() { } +void Assembler::fyl2x() { + EnsureSpace ensure_space(this); + last_pc_ = pc_; + emit(0xD9); + emit(0xF1); +} + + void Assembler::fadd(int i) { EnsureSpace ensure_space(this); last_pc_ = pc_; @@ -2917,6 +2936,12 @@ void Assembler::emit_sse_operand(Register dst, XMMRegister src) { } +void Assembler::dd(uint32_t data) { + EnsureSpace ensure_space(this); + emitl(data); +} + + // Relocation information implementations. void Assembler::RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data) { @@ -2946,7 +2971,7 @@ void Assembler::RecordDebugBreakSlot() { void Assembler::RecordComment(const char* msg) { - if (FLAG_debug_code) { + if (FLAG_code_comments) { EnsureSpace ensure_space(this); RecordRelocInfo(RelocInfo::COMMENT, reinterpret_cast<intptr_t>(msg)); } diff --git a/src/x64/assembler-x64.h b/src/x64/assembler-x64.h index c7f76322..fde88df7 100644 --- a/src/x64/assembler-x64.h +++ b/src/x64/assembler-x64.h @@ -30,7 +30,7 @@ // The original source code covered by the above license above has been // modified significantly by Google Inc. -// Copyright 2006-2009 the V8 project authors. All rights reserved. +// Copyright 2010 the V8 project authors. All rights reserved. // A lightweight X64 Assembler. @@ -88,11 +88,38 @@ static inline bool is_uint32(uint64_t x) { // struct Register { + // The non-allocatable registers are: + // rsp - stack pointer + // rbp - frame pointer + // rsi - context register + // r10 - fixed scratch register + // r13 - root register + // r15 - smi constant register + static const int kNumRegisters = 16; + static const int kNumAllocatableRegisters = 10; + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "rax", + "rcx", + "rdx", + "rbx", + "rdi", + "r8", + "r9", + "r11", + "r12", + "r14" + }; + return names[index]; + } + static Register toRegister(int code) { Register r = { code }; return r; } - bool is_valid() const { return 0 <= code_ && code_ < 16; } + bool is_valid() const { return 0 <= code_ && code_ < kNumRegisters; } bool is(Register reg) const { return code_ == reg.code_; } int code() const { ASSERT(is_valid()); @@ -138,7 +165,37 @@ const Register no_reg = { -1 }; struct XMMRegister { - bool is_valid() const { return 0 <= code_ && code_ < 16; } + static const int kNumRegisters = 16; + static const int kNumAllocatableRegisters = 15; + + static int ToAllocationIndex(XMMRegister reg) { + ASSERT(reg.code() != 0); + return reg.code() - 1; + } + + static const char* AllocationIndexToString(int index) { + ASSERT(index >= 0 && index < kNumAllocatableRegisters); + const char* const names[] = { + "xmm1", + "xmm2", + "xmm3", + "xmm4", + "xmm5", + "xmm6", + "xmm7", + "xmm8", + "xmm9", + "xmm10", + "xmm11", + "xmm12", + "xmm13", + "xmm14", + "xmm15" + }; + return names[index]; + } + + bool is_valid() const { return 0 <= code_ && code_ < kNumRegisters; } int code() const { ASSERT(is_valid()); return code_; @@ -175,6 +232,10 @@ const XMMRegister xmm13 = { 13 }; const XMMRegister xmm14 = { 14 }; const XMMRegister xmm15 = { 15 }; + +typedef XMMRegister DoubleRegister; + + enum Condition { // any value < 0 is considered no_condition no_condition = -1, @@ -345,7 +406,7 @@ class CpuFeatures : public AllStatic { public: // Detect features of the target CPU. Set safe defaults if the serializer // is enabled (snapshots must be portable). - static void Probe(); + static void Probe(bool portable); // Check whether a feature is supported by the target CPU. static bool IsSupported(CpuFeature f) { if (f == SSE2 && !FLAG_enable_sse2) return false; @@ -1046,6 +1107,7 @@ class Assembler : public Malloced { void fld1(); void fldz(); void fldpi(); + void fldln2(); void fld_s(const Operand& adr); void fld_d(const Operand& adr); @@ -1100,6 +1162,7 @@ class Assembler : public Malloced { void fsin(); void fcos(); + void fyl2x(); void frndint(); @@ -1171,9 +1234,14 @@ class Assembler : public Malloced { void RecordDebugBreakSlot(); // Record a comment relocation entry that can be used by a disassembler. - // Use --debug_code to enable. + // Use --code-comments to enable. void RecordComment(const char* msg); + // Writes a single word of data in the code stream. + // Used for inline tables, e.g., jump-tables. + void db(uint8_t data) { UNIMPLEMENTED(); } + void dd(uint32_t data); + int pc_offset() const { return static_cast<int>(pc_ - buffer_); } PositionsRecorder* positions_recorder() { return &positions_recorder_; } diff --git a/src/x64/builtins-x64.cc b/src/x64/builtins-x64.cc index 0dead6b7..456d0765 100644 --- a/src/x64/builtins-x64.cc +++ b/src/x64/builtins-x64.cc @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -30,11 +30,13 @@ #if defined(V8_TARGET_ARCH_X64) #include "codegen-inl.h" -#include "macro-assembler.h" +#include "deoptimizer.h" +#include "full-codegen.h" namespace v8 { namespace internal { + #define __ ACCESS_MASM(masm) @@ -71,118 +73,509 @@ void Builtins::Generate_Adaptor(MacroAssembler* masm, } -static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) { - __ push(rbp); - __ movq(rbp, rsp); +void Builtins::Generate_JSConstructCall(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- rax: number of arguments + // -- rdi: constructor function + // ----------------------------------- - // Store the arguments adaptor context sentinel. - __ Push(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); + Label non_function_call; + // Check that function is not a smi. + __ JumpIfSmi(rdi, &non_function_call); + // Check that function is a JSFunction. + __ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx); + __ j(not_equal, &non_function_call); - // Push the function on the stack. - __ push(rdi); + // Jump to the function-specific construct stub. + __ movq(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset)); + __ movq(rbx, FieldOperand(rbx, SharedFunctionInfo::kConstructStubOffset)); + __ lea(rbx, FieldOperand(rbx, Code::kHeaderSize)); + __ jmp(rbx); - // Preserve the number of arguments on the stack. Must preserve both - // rax and rbx because these registers are used when copying the - // arguments and the receiver. - __ Integer32ToSmi(rcx, rax); - __ push(rcx); + // rdi: called object + // rax: number of arguments + __ bind(&non_function_call); + // Set expected number of arguments to zero (not changing rax). + __ movq(rbx, Immediate(0)); + __ GetBuiltinEntry(rdx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR); + __ Jump(Handle<Code>(builtin(ArgumentsAdaptorTrampoline)), + RelocInfo::CODE_TARGET); } -static void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) { - // Retrieve the number of arguments from the stack. Number is a Smi. - __ movq(rbx, Operand(rbp, ArgumentsAdaptorFrameConstants::kLengthOffset)); +static void Generate_JSConstructStubHelper(MacroAssembler* masm, + bool is_api_function, + bool count_constructions) { + // Should never count constructions for api objects. + ASSERT(!is_api_function || !count_constructions); - // Leave the frame. - __ movq(rsp, rbp); - __ pop(rbp); + // Enter a construct frame. + __ EnterConstructFrame(); - // Remove caller arguments from the stack. + // Store a smi-tagged arguments count on the stack. + __ Integer32ToSmi(rax, rax); + __ push(rax); + + // Push the function to invoke on the stack. + __ push(rdi); + + // Try to allocate the object without transitioning into C code. If any of the + // preconditions is not met, the code bails out to the runtime call. + Label rt_call, allocated; + if (FLAG_inline_new) { + Label undo_allocation; + +#ifdef ENABLE_DEBUGGER_SUPPORT + ExternalReference debug_step_in_fp = + ExternalReference::debug_step_in_fp_address(); + __ movq(kScratchRegister, debug_step_in_fp); + __ cmpq(Operand(kScratchRegister, 0), Immediate(0)); + __ j(not_equal, &rt_call); +#endif + + // Verified that the constructor is a JSFunction. + // Load the initial map and verify that it is in fact a map. + // rdi: constructor + __ movq(rax, FieldOperand(rdi, JSFunction::kPrototypeOrInitialMapOffset)); + // Will both indicate a NULL and a Smi + ASSERT(kSmiTag == 0); + __ JumpIfSmi(rax, &rt_call); + // rdi: constructor + // rax: initial map (if proven valid below) + __ CmpObjectType(rax, MAP_TYPE, rbx); + __ j(not_equal, &rt_call); + + // Check that the constructor is not constructing a JSFunction (see comments + // in Runtime_NewObject in runtime.cc). In which case the initial map's + // instance type would be JS_FUNCTION_TYPE. + // rdi: constructor + // rax: initial map + __ CmpInstanceType(rax, JS_FUNCTION_TYPE); + __ j(equal, &rt_call); + + if (count_constructions) { + Label allocate; + // Decrease generous allocation count. + __ movq(rcx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset)); + __ decb(FieldOperand(rcx, SharedFunctionInfo::kConstructionCountOffset)); + __ j(not_zero, &allocate); + + __ push(rax); + __ push(rdi); + + __ push(rdi); // constructor + // The call will replace the stub, so the countdown is only done once. + __ CallRuntime(Runtime::kFinalizeInstanceSize, 1); + + __ pop(rdi); + __ pop(rax); + + __ bind(&allocate); + } + + // Now allocate the JSObject on the heap. + __ movzxbq(rdi, FieldOperand(rax, Map::kInstanceSizeOffset)); + __ shl(rdi, Immediate(kPointerSizeLog2)); + // rdi: size of new object + __ AllocateInNewSpace(rdi, + rbx, + rdi, + no_reg, + &rt_call, + NO_ALLOCATION_FLAGS); + // Allocated the JSObject, now initialize the fields. + // rax: initial map + // rbx: JSObject (not HeapObject tagged - the actual address). + // rdi: start of next object + __ movq(Operand(rbx, JSObject::kMapOffset), rax); + __ LoadRoot(rcx, Heap::kEmptyFixedArrayRootIndex); + __ movq(Operand(rbx, JSObject::kPropertiesOffset), rcx); + __ movq(Operand(rbx, JSObject::kElementsOffset), rcx); + // Set extra fields in the newly allocated object. + // rax: initial map + // rbx: JSObject + // rdi: start of next object + { Label loop, entry; + // To allow for truncation. + if (count_constructions) { + __ LoadRoot(rdx, Heap::kOnePointerFillerMapRootIndex); + } else { + __ LoadRoot(rdx, Heap::kUndefinedValueRootIndex); + } + __ lea(rcx, Operand(rbx, JSObject::kHeaderSize)); + __ jmp(&entry); + __ bind(&loop); + __ movq(Operand(rcx, 0), rdx); + __ addq(rcx, Immediate(kPointerSize)); + __ bind(&entry); + __ cmpq(rcx, rdi); + __ j(less, &loop); + } + + // Add the object tag to make the JSObject real, so that we can continue and + // jump into the continuation code at any time from now on. Any failures + // need to undo the allocation, so that the heap is in a consistent state + // and verifiable. + // rax: initial map + // rbx: JSObject + // rdi: start of next object + __ or_(rbx, Immediate(kHeapObjectTag)); + + // Check if a non-empty properties array is needed. + // Allocate and initialize a FixedArray if it is. + // rax: initial map + // rbx: JSObject + // rdi: start of next object + // Calculate total properties described map. + __ movzxbq(rdx, FieldOperand(rax, Map::kUnusedPropertyFieldsOffset)); + __ movzxbq(rcx, FieldOperand(rax, Map::kPreAllocatedPropertyFieldsOffset)); + __ addq(rdx, rcx); + // Calculate unused properties past the end of the in-object properties. + __ movzxbq(rcx, FieldOperand(rax, Map::kInObjectPropertiesOffset)); + __ subq(rdx, rcx); + // Done if no extra properties are to be allocated. + __ j(zero, &allocated); + __ Assert(positive, "Property allocation count failed."); + + // Scale the number of elements by pointer size and add the header for + // FixedArrays to the start of the next object calculation from above. + // rbx: JSObject + // rdi: start of next object (will be start of FixedArray) + // rdx: number of elements in properties array + __ AllocateInNewSpace(FixedArray::kHeaderSize, + times_pointer_size, + rdx, + rdi, + rax, + no_reg, + &undo_allocation, + RESULT_CONTAINS_TOP); + + // Initialize the FixedArray. + // rbx: JSObject + // rdi: FixedArray + // rdx: number of elements + // rax: start of next object + __ LoadRoot(rcx, Heap::kFixedArrayMapRootIndex); + __ movq(Operand(rdi, HeapObject::kMapOffset), rcx); // setup the map + __ Integer32ToSmi(rdx, rdx); + __ movq(Operand(rdi, FixedArray::kLengthOffset), rdx); // and length + + // Initialize the fields to undefined. + // rbx: JSObject + // rdi: FixedArray + // rax: start of next object + // rdx: number of elements + { Label loop, entry; + __ LoadRoot(rdx, Heap::kUndefinedValueRootIndex); + __ lea(rcx, Operand(rdi, FixedArray::kHeaderSize)); + __ jmp(&entry); + __ bind(&loop); + __ movq(Operand(rcx, 0), rdx); + __ addq(rcx, Immediate(kPointerSize)); + __ bind(&entry); + __ cmpq(rcx, rax); + __ j(below, &loop); + } + + // Store the initialized FixedArray into the properties field of + // the JSObject + // rbx: JSObject + // rdi: FixedArray + __ or_(rdi, Immediate(kHeapObjectTag)); // add the heap tag + __ movq(FieldOperand(rbx, JSObject::kPropertiesOffset), rdi); + + + // Continue with JSObject being successfully allocated + // rbx: JSObject + __ jmp(&allocated); + + // Undo the setting of the new top so that the heap is verifiable. For + // example, the map's unused properties potentially do not match the + // allocated objects unused properties. + // rbx: JSObject (previous new top) + __ bind(&undo_allocation); + __ UndoAllocationInNewSpace(rbx); + } + + // Allocate the new receiver object using the runtime call. + // rdi: function (constructor) + __ bind(&rt_call); + // Must restore rdi (constructor) before calling runtime. + __ movq(rdi, Operand(rsp, 0)); + __ push(rdi); + __ CallRuntime(Runtime::kNewObject, 1); + __ movq(rbx, rax); // store result in rbx + + // New object allocated. + // rbx: newly allocated object + __ bind(&allocated); + // Retrieve the function from the stack. + __ pop(rdi); + + // Retrieve smi-tagged arguments count from the stack. + __ movq(rax, Operand(rsp, 0)); + __ SmiToInteger32(rax, rax); + + // Push the allocated receiver to the stack. We need two copies + // because we may have to return the original one and the calling + // conventions dictate that the called function pops the receiver. + __ push(rbx); + __ push(rbx); + + // Setup pointer to last argument. + __ lea(rbx, Operand(rbp, StandardFrameConstants::kCallerSPOffset)); + + // Copy arguments and receiver to the expression stack. + Label loop, entry; + __ movq(rcx, rax); + __ jmp(&entry); + __ bind(&loop); + __ push(Operand(rbx, rcx, times_pointer_size, 0)); + __ bind(&entry); + __ decq(rcx); + __ j(greater_equal, &loop); + + // Call the function. + if (is_api_function) { + __ movq(rsi, FieldOperand(rdi, JSFunction::kContextOffset)); + Handle<Code> code = Handle<Code>( + Builtins::builtin(Builtins::HandleApiCallConstruct)); + ParameterCount expected(0); + __ InvokeCode(code, expected, expected, + RelocInfo::CODE_TARGET, CALL_FUNCTION); + } else { + ParameterCount actual(rax); + __ InvokeFunction(rdi, actual, CALL_FUNCTION); + } + + // Restore context from the frame. + __ movq(rsi, Operand(rbp, StandardFrameConstants::kContextOffset)); + + // If the result is an object (in the ECMA sense), we should get rid + // of the receiver and use the result; see ECMA-262 section 13.2.2-7 + // on page 74. + Label use_receiver, exit; + // If the result is a smi, it is *not* an object in the ECMA sense. + __ JumpIfSmi(rax, &use_receiver); + + // If the type of the result (stored in its map) is less than + // FIRST_JS_OBJECT_TYPE, it is not an object in the ECMA sense. + __ CmpObjectType(rax, FIRST_JS_OBJECT_TYPE, rcx); + __ j(above_equal, &exit); + + // Throw away the result of the constructor invocation and use the + // on-stack receiver as the result. + __ bind(&use_receiver); + __ movq(rax, Operand(rsp, 0)); + + // Restore the arguments count and leave the construct frame. + __ bind(&exit); + __ movq(rbx, Operand(rsp, kPointerSize)); // get arguments count + __ LeaveConstructFrame(); + + // Remove caller arguments from the stack and return. __ pop(rcx); SmiIndex index = masm->SmiToIndex(rbx, rbx, kPointerSizeLog2); __ lea(rsp, Operand(rsp, index.reg, index.scale, 1 * kPointerSize)); __ push(rcx); + __ IncrementCounter(&Counters::constructed_objects, 1); + __ ret(0); } -void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { - // ----------- S t a t e ------------- - // -- rax : actual number of arguments - // -- rbx : expected number of arguments - // -- rdx : code entry to call - // ----------------------------------- +void Builtins::Generate_JSConstructStubCountdown(MacroAssembler* masm) { + Generate_JSConstructStubHelper(masm, false, true); +} - Label invoke, dont_adapt_arguments; - __ IncrementCounter(&Counters::arguments_adaptors, 1); - Label enough, too_few; - __ cmpq(rax, rbx); - __ j(less, &too_few); - __ cmpq(rbx, Immediate(SharedFunctionInfo::kDontAdaptArgumentsSentinel)); - __ j(equal, &dont_adapt_arguments); +void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) { + Generate_JSConstructStubHelper(masm, false, false); +} - { // Enough parameters: Actual >= expected. - __ bind(&enough); - EnterArgumentsAdaptorFrame(masm); - // Copy receiver and all expected arguments. - const int offset = StandardFrameConstants::kCallerSPOffset; - __ lea(rax, Operand(rbp, rax, times_pointer_size, offset)); - __ movq(rcx, Immediate(-1)); // account for receiver +void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) { + Generate_JSConstructStubHelper(masm, true, false); +} - Label copy; - __ bind(©); - __ incq(rcx); - __ push(Operand(rax, 0)); - __ subq(rax, Immediate(kPointerSize)); - __ cmpq(rcx, rbx); - __ j(less, ©); - __ jmp(&invoke); - } - { // Too few parameters: Actual < expected. - __ bind(&too_few); - EnterArgumentsAdaptorFrame(masm); +static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, + bool is_construct) { + // Expects five C++ function parameters. + // - Address entry (ignored) + // - JSFunction* function ( + // - Object* receiver + // - int argc + // - Object*** argv + // (see Handle::Invoke in execution.cc). - // Copy receiver and all actual arguments. - const int offset = StandardFrameConstants::kCallerSPOffset; - __ lea(rdi, Operand(rbp, rax, times_pointer_size, offset)); - __ movq(rcx, Immediate(-1)); // account for receiver + // Platform specific argument handling. After this, the stack contains + // an internal frame and the pushed function and receiver, and + // register rax and rbx holds the argument count and argument array, + // while rdi holds the function pointer and rsi the context. +#ifdef _WIN64 + // MSVC parameters in: + // rcx : entry (ignored) + // rdx : function + // r8 : receiver + // r9 : argc + // [rsp+0x20] : argv - Label copy; - __ bind(©); - __ incq(rcx); - __ push(Operand(rdi, 0)); - __ subq(rdi, Immediate(kPointerSize)); - __ cmpq(rcx, rax); - __ j(less, ©); + // Clear the context before we push it when entering the JS frame. + __ xor_(rsi, rsi); + __ EnterInternalFrame(); - // Fill remaining expected arguments with undefined values. - Label fill; - __ LoadRoot(kScratchRegister, Heap::kUndefinedValueRootIndex); - __ bind(&fill); - __ incq(rcx); - __ push(kScratchRegister); - __ cmpq(rcx, rbx); - __ j(less, &fill); + // Load the function context into rsi. + __ movq(rsi, FieldOperand(rdx, JSFunction::kContextOffset)); - // Restore function pointer. - __ movq(rdi, Operand(rbp, JavaScriptFrameConstants::kFunctionOffset)); + // Push the function and the receiver onto the stack. + __ push(rdx); + __ push(r8); + + // Load the number of arguments and setup pointer to the arguments. + __ movq(rax, r9); + // Load the previous frame pointer to access C argument on stack + __ movq(kScratchRegister, Operand(rbp, 0)); + __ movq(rbx, Operand(kScratchRegister, EntryFrameConstants::kArgvOffset)); + // Load the function pointer into rdi. + __ movq(rdi, rdx); +#else // _WIN64 + // GCC parameters in: + // rdi : entry (ignored) + // rsi : function + // rdx : receiver + // rcx : argc + // r8 : argv + + __ movq(rdi, rsi); + // rdi : function + + // Clear the context before we push it when entering the JS frame. + __ xor_(rsi, rsi); + // Enter an internal frame. + __ EnterInternalFrame(); + + // Push the function and receiver and setup the context. + __ push(rdi); + __ push(rdx); + __ movq(rsi, FieldOperand(rdi, JSFunction::kContextOffset)); + + // Load the number of arguments and setup pointer to the arguments. + __ movq(rax, rcx); + __ movq(rbx, r8); +#endif // _WIN64 + + // Current stack contents: + // [rsp + 2 * kPointerSize ... ]: Internal frame + // [rsp + kPointerSize] : function + // [rsp] : receiver + // Current register contents: + // rax : argc + // rbx : argv + // rsi : context + // rdi : function + + // Copy arguments to the stack in a loop. + // Register rbx points to array of pointers to handle locations. + // Push the values of these handles. + Label loop, entry; + __ xor_(rcx, rcx); // Set loop variable to 0. + __ jmp(&entry); + __ bind(&loop); + __ movq(kScratchRegister, Operand(rbx, rcx, times_pointer_size, 0)); + __ push(Operand(kScratchRegister, 0)); // dereference handle + __ addq(rcx, Immediate(1)); + __ bind(&entry); + __ cmpq(rcx, rax); + __ j(not_equal, &loop); + + // Invoke the code. + if (is_construct) { + // Expects rdi to hold function pointer. + __ Call(Handle<Code>(Builtins::builtin(Builtins::JSConstructCall)), + RelocInfo::CODE_TARGET); + } else { + ParameterCount actual(rax); + // Function must be in rdi. + __ InvokeFunction(rdi, actual, CALL_FUNCTION); } - // Call the entry point. - __ bind(&invoke); - __ call(rdx); + // Exit the JS frame. Notice that this also removes the empty + // context and the function left on the stack by the code + // invocation. + __ LeaveInternalFrame(); + // TODO(X64): Is argument correct? Is there a receiver to remove? + __ ret(1 * kPointerSize); // remove receiver +} - // Leave frame and return. - LeaveArgumentsAdaptorFrame(masm); - __ ret(0); - // ------------------------------------------- - // Dont adapt arguments. - // ------------------------------------------- - __ bind(&dont_adapt_arguments); - __ jmp(rdx); +void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { + Generate_JSEntryTrampolineHelper(masm, false); +} + + +void Builtins::Generate_JSConstructEntryTrampoline(MacroAssembler* masm) { + Generate_JSEntryTrampolineHelper(masm, true); +} + + +void Builtins::Generate_LazyCompile(MacroAssembler* masm) { + // Enter an internal frame. + __ EnterInternalFrame(); + + // Push a copy of the function onto the stack. + __ push(rdi); + + __ push(rdi); // Function is also the parameter to the runtime call. + __ CallRuntime(Runtime::kLazyCompile, 1); + __ pop(rdi); + + // Tear down temporary frame. + __ LeaveInternalFrame(); + + // Do a tail-call of the compiled function. + __ lea(rcx, FieldOperand(rax, Code::kHeaderSize)); + __ jmp(rcx); +} + + +void Builtins::Generate_LazyRecompile(MacroAssembler* masm) { + // Enter an internal frame. + __ EnterInternalFrame(); + + // Push a copy of the function onto the stack. + __ push(rdi); + + __ push(rdi); // Function is also the parameter to the runtime call. + __ CallRuntime(Runtime::kLazyRecompile, 1); + + // Restore function and tear down temporary frame. + __ pop(rdi); + __ LeaveInternalFrame(); + + // Do a tail-call of the compiled function. + __ lea(rcx, FieldOperand(rax, Code::kHeaderSize)); + __ jmp(rcx); +} + + +static void Generate_NotifyDeoptimizedHelper(MacroAssembler* masm, + Deoptimizer::BailoutType type) { + __ int3(); +} + +void Builtins::Generate_NotifyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::EAGER); +} + + +void Builtins::Generate_NotifyLazyDeoptimized(MacroAssembler* masm) { + Generate_NotifyDeoptimizedHelper(masm, Deoptimizer::EAGER); +} + + +void Builtins::Generate_NotifyOSR(MacroAssembler* masm) { + __ int3(); } @@ -450,17 +843,6 @@ void Builtins::Generate_FunctionApply(MacroAssembler* masm) { } -// Load the built-in Array function from the current context. -static void GenerateLoadArrayFunction(MacroAssembler* masm, Register result) { - // Load the global context. - __ movq(result, Operand(rsi, Context::SlotOffset(Context::GLOBAL_INDEX))); - __ movq(result, FieldOperand(result, GlobalObject::kGlobalContextOffset)); - // Load the Array function from the global context. - __ movq(result, - Operand(result, Context::SlotOffset(Context::ARRAY_FUNCTION_INDEX))); -} - - // Number of empty elements to allocate for an empty array. static const int kPreallocatedArrayElements = 4; @@ -813,7 +1195,7 @@ void Builtins::Generate_ArrayCode(MacroAssembler* masm) { Label generic_array_code; // Get the Array function. - GenerateLoadArrayFunction(masm, rdi); + __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, rdi); if (FLAG_debug_code) { // Initial map for the builtin Array function shoud be a map. @@ -850,7 +1232,7 @@ void Builtins::Generate_ArrayConstructCode(MacroAssembler* masm) { if (FLAG_debug_code) { // The array construct code is only set for the builtin Array function which // does always have a map. - GenerateLoadArrayFunction(masm, rbx); + __ LoadGlobalFunction(Context::ARRAY_FUNCTION_INDEX, rbx); __ cmpq(rdi, rbx); __ Check(equal, "Unexpected Array function"); // Initial map for the builtin Array function should be a map. @@ -882,470 +1264,127 @@ void Builtins::Generate_StringConstructCode(MacroAssembler* masm) { } -void Builtins::Generate_JSConstructCall(MacroAssembler* masm) { - // ----------- S t a t e ------------- - // -- rax: number of arguments - // -- rdi: constructor function - // ----------------------------------- - - Label non_function_call; - // Check that function is not a smi. - __ JumpIfSmi(rdi, &non_function_call); - // Check that function is a JSFunction. - __ CmpObjectType(rdi, JS_FUNCTION_TYPE, rcx); - __ j(not_equal, &non_function_call); - - // Jump to the function-specific construct stub. - __ movq(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset)); - __ movq(rbx, FieldOperand(rbx, SharedFunctionInfo::kConstructStubOffset)); - __ lea(rbx, FieldOperand(rbx, Code::kHeaderSize)); - __ jmp(rbx); - - // rdi: called object - // rax: number of arguments - __ bind(&non_function_call); - // Set expected number of arguments to zero (not changing rax). - __ movq(rbx, Immediate(0)); - __ GetBuiltinEntry(rdx, Builtins::CALL_NON_FUNCTION_AS_CONSTRUCTOR); - __ Jump(Handle<Code>(builtin(ArgumentsAdaptorTrampoline)), - RelocInfo::CODE_TARGET); -} - - -static void Generate_JSConstructStubHelper(MacroAssembler* masm, - bool is_api_function, - bool count_constructions) { - // Should never count constructions for api objects. - ASSERT(!is_api_function || !count_constructions); - - // Enter a construct frame. - __ EnterConstructFrame(); - - // Store a smi-tagged arguments count on the stack. - __ Integer32ToSmi(rax, rax); - __ push(rax); - - // Push the function to invoke on the stack. - __ push(rdi); - - // Try to allocate the object without transitioning into C code. If any of the - // preconditions is not met, the code bails out to the runtime call. - Label rt_call, allocated; - if (FLAG_inline_new) { - Label undo_allocation; - -#ifdef ENABLE_DEBUGGER_SUPPORT - ExternalReference debug_step_in_fp = - ExternalReference::debug_step_in_fp_address(); - __ movq(kScratchRegister, debug_step_in_fp); - __ cmpq(Operand(kScratchRegister, 0), Immediate(0)); - __ j(not_equal, &rt_call); -#endif - - // Verified that the constructor is a JSFunction. - // Load the initial map and verify that it is in fact a map. - // rdi: constructor - __ movq(rax, FieldOperand(rdi, JSFunction::kPrototypeOrInitialMapOffset)); - // Will both indicate a NULL and a Smi - ASSERT(kSmiTag == 0); - __ JumpIfSmi(rax, &rt_call); - // rdi: constructor - // rax: initial map (if proven valid below) - __ CmpObjectType(rax, MAP_TYPE, rbx); - __ j(not_equal, &rt_call); - - // Check that the constructor is not constructing a JSFunction (see comments - // in Runtime_NewObject in runtime.cc). In which case the initial map's - // instance type would be JS_FUNCTION_TYPE. - // rdi: constructor - // rax: initial map - __ CmpInstanceType(rax, JS_FUNCTION_TYPE); - __ j(equal, &rt_call); - - if (count_constructions) { - Label allocate; - // Decrease generous allocation count. - __ movq(rcx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset)); - __ decb(FieldOperand(rcx, SharedFunctionInfo::kConstructionCountOffset)); - __ j(not_zero, &allocate); - - __ push(rax); - __ push(rdi); - - __ push(rdi); // constructor - // The call will replace the stub, so the countdown is only done once. - __ CallRuntime(Runtime::kFinalizeInstanceSize, 1); - - __ pop(rdi); - __ pop(rax); - - __ bind(&allocate); - } - - // Now allocate the JSObject on the heap. - __ movzxbq(rdi, FieldOperand(rax, Map::kInstanceSizeOffset)); - __ shl(rdi, Immediate(kPointerSizeLog2)); - // rdi: size of new object - __ AllocateInNewSpace(rdi, - rbx, - rdi, - no_reg, - &rt_call, - NO_ALLOCATION_FLAGS); - // Allocated the JSObject, now initialize the fields. - // rax: initial map - // rbx: JSObject (not HeapObject tagged - the actual address). - // rdi: start of next object - __ movq(Operand(rbx, JSObject::kMapOffset), rax); - __ LoadRoot(rcx, Heap::kEmptyFixedArrayRootIndex); - __ movq(Operand(rbx, JSObject::kPropertiesOffset), rcx); - __ movq(Operand(rbx, JSObject::kElementsOffset), rcx); - // Set extra fields in the newly allocated object. - // rax: initial map - // rbx: JSObject - // rdi: start of next object - { Label loop, entry; - // To allow for truncation. - if (count_constructions) { - __ LoadRoot(rdx, Heap::kOnePointerFillerMapRootIndex); - } else { - __ LoadRoot(rdx, Heap::kUndefinedValueRootIndex); - } - __ lea(rcx, Operand(rbx, JSObject::kHeaderSize)); - __ jmp(&entry); - __ bind(&loop); - __ movq(Operand(rcx, 0), rdx); - __ addq(rcx, Immediate(kPointerSize)); - __ bind(&entry); - __ cmpq(rcx, rdi); - __ j(less, &loop); - } - - // Add the object tag to make the JSObject real, so that we can continue and - // jump into the continuation code at any time from now on. Any failures - // need to undo the allocation, so that the heap is in a consistent state - // and verifiable. - // rax: initial map - // rbx: JSObject - // rdi: start of next object - __ or_(rbx, Immediate(kHeapObjectTag)); - - // Check if a non-empty properties array is needed. - // Allocate and initialize a FixedArray if it is. - // rax: initial map - // rbx: JSObject - // rdi: start of next object - // Calculate total properties described map. - __ movzxbq(rdx, FieldOperand(rax, Map::kUnusedPropertyFieldsOffset)); - __ movzxbq(rcx, FieldOperand(rax, Map::kPreAllocatedPropertyFieldsOffset)); - __ addq(rdx, rcx); - // Calculate unused properties past the end of the in-object properties. - __ movzxbq(rcx, FieldOperand(rax, Map::kInObjectPropertiesOffset)); - __ subq(rdx, rcx); - // Done if no extra properties are to be allocated. - __ j(zero, &allocated); - __ Assert(positive, "Property allocation count failed."); - - // Scale the number of elements by pointer size and add the header for - // FixedArrays to the start of the next object calculation from above. - // rbx: JSObject - // rdi: start of next object (will be start of FixedArray) - // rdx: number of elements in properties array - __ AllocateInNewSpace(FixedArray::kHeaderSize, - times_pointer_size, - rdx, - rdi, - rax, - no_reg, - &undo_allocation, - RESULT_CONTAINS_TOP); - - // Initialize the FixedArray. - // rbx: JSObject - // rdi: FixedArray - // rdx: number of elements - // rax: start of next object - __ LoadRoot(rcx, Heap::kFixedArrayMapRootIndex); - __ movq(Operand(rdi, HeapObject::kMapOffset), rcx); // setup the map - __ Integer32ToSmi(rdx, rdx); - __ movq(Operand(rdi, FixedArray::kLengthOffset), rdx); // and length - - // Initialize the fields to undefined. - // rbx: JSObject - // rdi: FixedArray - // rax: start of next object - // rdx: number of elements - { Label loop, entry; - __ LoadRoot(rdx, Heap::kUndefinedValueRootIndex); - __ lea(rcx, Operand(rdi, FixedArray::kHeaderSize)); - __ jmp(&entry); - __ bind(&loop); - __ movq(Operand(rcx, 0), rdx); - __ addq(rcx, Immediate(kPointerSize)); - __ bind(&entry); - __ cmpq(rcx, rax); - __ j(below, &loop); - } - - // Store the initialized FixedArray into the properties field of - // the JSObject - // rbx: JSObject - // rdi: FixedArray - __ or_(rdi, Immediate(kHeapObjectTag)); // add the heap tag - __ movq(FieldOperand(rbx, JSObject::kPropertiesOffset), rdi); - - - // Continue with JSObject being successfully allocated - // rbx: JSObject - __ jmp(&allocated); +static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) { + __ push(rbp); + __ movq(rbp, rsp); - // Undo the setting of the new top so that the heap is verifiable. For - // example, the map's unused properties potentially do not match the - // allocated objects unused properties. - // rbx: JSObject (previous new top) - __ bind(&undo_allocation); - __ UndoAllocationInNewSpace(rbx); - } + // Store the arguments adaptor context sentinel. + __ Push(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); - // Allocate the new receiver object using the runtime call. - // rdi: function (constructor) - __ bind(&rt_call); - // Must restore rdi (constructor) before calling runtime. - __ movq(rdi, Operand(rsp, 0)); + // Push the function on the stack. __ push(rdi); - __ CallRuntime(Runtime::kNewObject, 1); - __ movq(rbx, rax); // store result in rbx - - // New object allocated. - // rbx: newly allocated object - __ bind(&allocated); - // Retrieve the function from the stack. - __ pop(rdi); - - // Retrieve smi-tagged arguments count from the stack. - __ movq(rax, Operand(rsp, 0)); - __ SmiToInteger32(rax, rax); - - // Push the allocated receiver to the stack. We need two copies - // because we may have to return the original one and the calling - // conventions dictate that the called function pops the receiver. - __ push(rbx); - __ push(rbx); - - // Setup pointer to last argument. - __ lea(rbx, Operand(rbp, StandardFrameConstants::kCallerSPOffset)); - - // Copy arguments and receiver to the expression stack. - Label loop, entry; - __ movq(rcx, rax); - __ jmp(&entry); - __ bind(&loop); - __ push(Operand(rbx, rcx, times_pointer_size, 0)); - __ bind(&entry); - __ decq(rcx); - __ j(greater_equal, &loop); - - // Call the function. - if (is_api_function) { - __ movq(rsi, FieldOperand(rdi, JSFunction::kContextOffset)); - Handle<Code> code = Handle<Code>( - Builtins::builtin(Builtins::HandleApiCallConstruct)); - ParameterCount expected(0); - __ InvokeCode(code, expected, expected, - RelocInfo::CODE_TARGET, CALL_FUNCTION); - } else { - ParameterCount actual(rax); - __ InvokeFunction(rdi, actual, CALL_FUNCTION); - } - - // Restore context from the frame. - __ movq(rsi, Operand(rbp, StandardFrameConstants::kContextOffset)); - // If the result is an object (in the ECMA sense), we should get rid - // of the receiver and use the result; see ECMA-262 section 13.2.2-7 - // on page 74. - Label use_receiver, exit; - // If the result is a smi, it is *not* an object in the ECMA sense. - __ JumpIfSmi(rax, &use_receiver); + // Preserve the number of arguments on the stack. Must preserve both + // rax and rbx because these registers are used when copying the + // arguments and the receiver. + __ Integer32ToSmi(rcx, rax); + __ push(rcx); +} - // If the type of the result (stored in its map) is less than - // FIRST_JS_OBJECT_TYPE, it is not an object in the ECMA sense. - __ CmpObjectType(rax, FIRST_JS_OBJECT_TYPE, rcx); - __ j(above_equal, &exit); - // Throw away the result of the constructor invocation and use the - // on-stack receiver as the result. - __ bind(&use_receiver); - __ movq(rax, Operand(rsp, 0)); +static void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) { + // Retrieve the number of arguments from the stack. Number is a Smi. + __ movq(rbx, Operand(rbp, ArgumentsAdaptorFrameConstants::kLengthOffset)); - // Restore the arguments count and leave the construct frame. - __ bind(&exit); - __ movq(rbx, Operand(rsp, kPointerSize)); // get arguments count - __ LeaveConstructFrame(); + // Leave the frame. + __ movq(rsp, rbp); + __ pop(rbp); - // Remove caller arguments from the stack and return. + // Remove caller arguments from the stack. __ pop(rcx); SmiIndex index = masm->SmiToIndex(rbx, rbx, kPointerSizeLog2); __ lea(rsp, Operand(rsp, index.reg, index.scale, 1 * kPointerSize)); __ push(rcx); - __ IncrementCounter(&Counters::constructed_objects, 1); - __ ret(0); } -void Builtins::Generate_JSConstructStubCountdown(MacroAssembler* masm) { - Generate_JSConstructStubHelper(masm, false, true); -} - - -void Builtins::Generate_JSConstructStubGeneric(MacroAssembler* masm) { - Generate_JSConstructStubHelper(masm, false, false); -} - - -void Builtins::Generate_JSConstructStubApi(MacroAssembler* masm) { - Generate_JSConstructStubHelper(masm, true, false); -} - - -static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, - bool is_construct) { - // Expects five C++ function parameters. - // - Address entry (ignored) - // - JSFunction* function ( - // - Object* receiver - // - int argc - // - Object*** argv - // (see Handle::Invoke in execution.cc). - - // Platform specific argument handling. After this, the stack contains - // an internal frame and the pushed function and receiver, and - // register rax and rbx holds the argument count and argument array, - // while rdi holds the function pointer and rsi the context. -#ifdef _WIN64 - // MSVC parameters in: - // rcx : entry (ignored) - // rdx : function - // r8 : receiver - // r9 : argc - // [rsp+0x20] : argv - - // Clear the context before we push it when entering the JS frame. - __ xor_(rsi, rsi); - __ EnterInternalFrame(); +void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- rax : actual number of arguments + // -- rbx : expected number of arguments + // -- rdx : code entry to call + // ----------------------------------- - // Load the function context into rsi. - __ movq(rsi, FieldOperand(rdx, JSFunction::kContextOffset)); + Label invoke, dont_adapt_arguments; + __ IncrementCounter(&Counters::arguments_adaptors, 1); - // Push the function and the receiver onto the stack. - __ push(rdx); - __ push(r8); + Label enough, too_few; + __ cmpq(rax, rbx); + __ j(less, &too_few); + __ cmpq(rbx, Immediate(SharedFunctionInfo::kDontAdaptArgumentsSentinel)); + __ j(equal, &dont_adapt_arguments); - // Load the number of arguments and setup pointer to the arguments. - __ movq(rax, r9); - // Load the previous frame pointer to access C argument on stack - __ movq(kScratchRegister, Operand(rbp, 0)); - __ movq(rbx, Operand(kScratchRegister, EntryFrameConstants::kArgvOffset)); - // Load the function pointer into rdi. - __ movq(rdi, rdx); -#else // _WIN64 - // GCC parameters in: - // rdi : entry (ignored) - // rsi : function - // rdx : receiver - // rcx : argc - // r8 : argv + { // Enough parameters: Actual >= expected. + __ bind(&enough); + EnterArgumentsAdaptorFrame(masm); - __ movq(rdi, rsi); - // rdi : function + // Copy receiver and all expected arguments. + const int offset = StandardFrameConstants::kCallerSPOffset; + __ lea(rax, Operand(rbp, rax, times_pointer_size, offset)); + __ movq(rcx, Immediate(-1)); // account for receiver - // Clear the context before we push it when entering the JS frame. - __ xor_(rsi, rsi); - // Enter an internal frame. - __ EnterInternalFrame(); + Label copy; + __ bind(©); + __ incq(rcx); + __ push(Operand(rax, 0)); + __ subq(rax, Immediate(kPointerSize)); + __ cmpq(rcx, rbx); + __ j(less, ©); + __ jmp(&invoke); + } - // Push the function and receiver and setup the context. - __ push(rdi); - __ push(rdx); - __ movq(rsi, FieldOperand(rdi, JSFunction::kContextOffset)); + { // Too few parameters: Actual < expected. + __ bind(&too_few); + EnterArgumentsAdaptorFrame(masm); - // Load the number of arguments and setup pointer to the arguments. - __ movq(rax, rcx); - __ movq(rbx, r8); -#endif // _WIN64 + // Copy receiver and all actual arguments. + const int offset = StandardFrameConstants::kCallerSPOffset; + __ lea(rdi, Operand(rbp, rax, times_pointer_size, offset)); + __ movq(rcx, Immediate(-1)); // account for receiver - // Current stack contents: - // [rsp + 2 * kPointerSize ... ]: Internal frame - // [rsp + kPointerSize] : function - // [rsp] : receiver - // Current register contents: - // rax : argc - // rbx : argv - // rsi : context - // rdi : function + Label copy; + __ bind(©); + __ incq(rcx); + __ push(Operand(rdi, 0)); + __ subq(rdi, Immediate(kPointerSize)); + __ cmpq(rcx, rax); + __ j(less, ©); - // Copy arguments to the stack in a loop. - // Register rbx points to array of pointers to handle locations. - // Push the values of these handles. - Label loop, entry; - __ xor_(rcx, rcx); // Set loop variable to 0. - __ jmp(&entry); - __ bind(&loop); - __ movq(kScratchRegister, Operand(rbx, rcx, times_pointer_size, 0)); - __ push(Operand(kScratchRegister, 0)); // dereference handle - __ addq(rcx, Immediate(1)); - __ bind(&entry); - __ cmpq(rcx, rax); - __ j(not_equal, &loop); + // Fill remaining expected arguments with undefined values. + Label fill; + __ LoadRoot(kScratchRegister, Heap::kUndefinedValueRootIndex); + __ bind(&fill); + __ incq(rcx); + __ push(kScratchRegister); + __ cmpq(rcx, rbx); + __ j(less, &fill); - // Invoke the code. - if (is_construct) { - // Expects rdi to hold function pointer. - __ Call(Handle<Code>(Builtins::builtin(Builtins::JSConstructCall)), - RelocInfo::CODE_TARGET); - } else { - ParameterCount actual(rax); - // Function must be in rdi. - __ InvokeFunction(rdi, actual, CALL_FUNCTION); + // Restore function pointer. + __ movq(rdi, Operand(rbp, JavaScriptFrameConstants::kFunctionOffset)); } - // Exit the JS frame. Notice that this also removes the empty - // context and the function left on the stack by the code - // invocation. - __ LeaveInternalFrame(); - // TODO(X64): Is argument correct? Is there a receiver to remove? - __ ret(1 * kPointerSize); // remove receiver -} + // Call the entry point. + __ bind(&invoke); + __ call(rdx); + // Leave frame and return. + LeaveArgumentsAdaptorFrame(masm); + __ ret(0); -void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { - Generate_JSEntryTrampolineHelper(masm, false); + // ------------------------------------------- + // Dont adapt arguments. + // ------------------------------------------- + __ bind(&dont_adapt_arguments); + __ jmp(rdx); } -void Builtins::Generate_JSConstructEntryTrampoline(MacroAssembler* masm) { - Generate_JSEntryTrampolineHelper(masm, true); +void Builtins::Generate_OnStackReplacement(MacroAssembler* masm) { + __ int3(); } -void Builtins::Generate_LazyCompile(MacroAssembler* masm) { - // Enter an internal frame. - __ EnterInternalFrame(); - - // Push a copy of the function onto the stack. - __ push(rdi); - - __ push(rdi); // Function is also the parameter to the runtime call. - __ CallRuntime(Runtime::kLazyCompile, 1); - __ pop(rdi); - - // Tear down temporary frame. - __ LeaveInternalFrame(); - - // Do a tail-call of the compiled function. - __ lea(rcx, FieldOperand(rax, Code::kHeaderSize)); - __ jmp(rcx); -} +#undef __ } } // namespace v8::internal diff --git a/src/x64/code-stubs-x64.cc b/src/x64/code-stubs-x64.cc index 14e35273..c3eb5bf4 100644 --- a/src/x64/code-stubs-x64.cc +++ b/src/x64/code-stubs-x64.cc @@ -57,12 +57,14 @@ void FastNewClosureStub::Generate(MacroAssembler* masm) { // write barrier because the allocated object is in new space. __ LoadRoot(rbx, Heap::kEmptyFixedArrayRootIndex); __ LoadRoot(rcx, Heap::kTheHoleValueRootIndex); + __ LoadRoot(rdi, Heap::kUndefinedValueRootIndex); __ movq(FieldOperand(rax, JSObject::kPropertiesOffset), rbx); __ movq(FieldOperand(rax, JSObject::kElementsOffset), rbx); __ movq(FieldOperand(rax, JSFunction::kPrototypeOrInitialMapOffset), rcx); __ movq(FieldOperand(rax, JSFunction::kSharedFunctionInfoOffset), rdx); __ movq(FieldOperand(rax, JSFunction::kContextOffset), rsi); __ movq(FieldOperand(rax, JSFunction::kLiteralsOffset), rbx); + __ movq(FieldOperand(rax, JSFunction::kNextFunctionLinkOffset), rdi); // Initialize the code pointer in the function to be the one // found in the shared function info object. @@ -983,6 +985,14 @@ Handle<Code> GetBinaryOpStub(int key, BinaryOpIC::TypeInfo type_info) { } +Handle<Code> GetTypeRecordingBinaryOpStub(int key, + TRBinaryOpIC::TypeInfo type_info, + TRBinaryOpIC::TypeInfo result_type_info) { + UNIMPLEMENTED(); + return Handle<Code>::null(); +} + + void TranscendentalCacheStub::Generate(MacroAssembler* masm) { // Input on stack: // rsp[8]: argument (should be number). @@ -1107,6 +1117,7 @@ Runtime::FunctionId TranscendentalCacheStub::RuntimeFunction() { // Add more cases when necessary. case TranscendentalCache::SIN: return Runtime::kMath_sin; case TranscendentalCache::COS: return Runtime::kMath_cos; + case TranscendentalCache::LOG: return Runtime::kMath_log; default: UNIMPLEMENTED(); return Runtime::kAbort; @@ -1121,73 +1132,76 @@ void TranscendentalCacheStub::GenerateOperation(MacroAssembler* masm, // rcx: Pointer to cache entry. Must be preserved. // st(0): Input double Label done; - ASSERT(type_ == TranscendentalCache::SIN || - type_ == TranscendentalCache::COS); - // More transcendental types can be added later. - - // Both fsin and fcos require arguments in the range +/-2^63 and - // return NaN for infinities and NaN. They can share all code except - // the actual fsin/fcos operation. - Label in_range; - // If argument is outside the range -2^63..2^63, fsin/cos doesn't - // work. We must reduce it to the appropriate range. - __ movq(rdi, rbx); - // Move exponent and sign bits to low bits. - __ shr(rdi, Immediate(HeapNumber::kMantissaBits)); - // Remove sign bit. - __ andl(rdi, Immediate((1 << HeapNumber::kExponentBits) - 1)); - int supported_exponent_limit = (63 + HeapNumber::kExponentBias); - __ cmpl(rdi, Immediate(supported_exponent_limit)); - __ j(below, &in_range); - // Check for infinity and NaN. Both return NaN for sin. - __ cmpl(rdi, Immediate(0x7ff)); - __ j(equal, on_nan_result); - - // Use fpmod to restrict argument to the range +/-2*PI. - __ fldpi(); - __ fadd(0); - __ fld(1); - // FPU Stack: input, 2*pi, input. - { - Label no_exceptions; - __ fwait(); - __ fnstsw_ax(); - // Clear if Illegal Operand or Zero Division exceptions are set. - __ testl(rax, Immediate(5)); // #IO and #ZD flags of FPU status word. - __ j(zero, &no_exceptions); - __ fnclex(); - __ bind(&no_exceptions); - } + if (type_ == TranscendentalCache::SIN || type_ == TranscendentalCache::COS) { + // Both fsin and fcos require arguments in the range +/-2^63 and + // return NaN for infinities and NaN. They can share all code except + // the actual fsin/fcos operation. + Label in_range; + // If argument is outside the range -2^63..2^63, fsin/cos doesn't + // work. We must reduce it to the appropriate range. + __ movq(rdi, rbx); + // Move exponent and sign bits to low bits. + __ shr(rdi, Immediate(HeapNumber::kMantissaBits)); + // Remove sign bit. + __ andl(rdi, Immediate((1 << HeapNumber::kExponentBits) - 1)); + int supported_exponent_limit = (63 + HeapNumber::kExponentBias); + __ cmpl(rdi, Immediate(supported_exponent_limit)); + __ j(below, &in_range); + // Check for infinity and NaN. Both return NaN for sin. + __ cmpl(rdi, Immediate(0x7ff)); + __ j(equal, on_nan_result); + + // Use fpmod to restrict argument to the range +/-2*PI. + __ fldpi(); + __ fadd(0); + __ fld(1); + // FPU Stack: input, 2*pi, input. + { + Label no_exceptions; + __ fwait(); + __ fnstsw_ax(); + // Clear if Illegal Operand or Zero Division exceptions are set. + __ testl(rax, Immediate(5)); // #IO and #ZD flags of FPU status word. + __ j(zero, &no_exceptions); + __ fnclex(); + __ bind(&no_exceptions); + } - // Compute st(0) % st(1) - { - NearLabel partial_remainder_loop; - __ bind(&partial_remainder_loop); - __ fprem1(); - __ fwait(); - __ fnstsw_ax(); - __ testl(rax, Immediate(0x400)); // Check C2 bit of FPU status word. - // If C2 is set, computation only has partial result. Loop to - // continue computation. - __ j(not_zero, &partial_remainder_loop); - } - // FPU Stack: input, 2*pi, input % 2*pi - __ fstp(2); - // FPU Stack: input % 2*pi, 2*pi, - __ fstp(0); - // FPU Stack: input % 2*pi - __ bind(&in_range); - switch (type_) { - case TranscendentalCache::SIN: - __ fsin(); - break; - case TranscendentalCache::COS: - __ fcos(); - break; - default: - UNREACHABLE(); + // Compute st(0) % st(1) + { + NearLabel partial_remainder_loop; + __ bind(&partial_remainder_loop); + __ fprem1(); + __ fwait(); + __ fnstsw_ax(); + __ testl(rax, Immediate(0x400)); // Check C2 bit of FPU status word. + // If C2 is set, computation only has partial result. Loop to + // continue computation. + __ j(not_zero, &partial_remainder_loop); + } + // FPU Stack: input, 2*pi, input % 2*pi + __ fstp(2); + // FPU Stack: input % 2*pi, 2*pi, + __ fstp(0); + // FPU Stack: input % 2*pi + __ bind(&in_range); + switch (type_) { + case TranscendentalCache::SIN: + __ fsin(); + break; + case TranscendentalCache::COS: + __ fcos(); + break; + default: + UNREACHABLE(); + } + __ bind(&done); + } else { + ASSERT(type_ == TranscendentalCache::LOG); + __ fldln2(); + __ fxch(); + __ fyl2x(); } - __ bind(&done); } @@ -1999,6 +2013,90 @@ void RegExpExecStub::Generate(MacroAssembler* masm) { } +void RegExpConstructResultStub::Generate(MacroAssembler* masm) { + const int kMaxInlineLength = 100; + Label slowcase; + Label done; + __ movq(r8, Operand(rsp, kPointerSize * 3)); + __ JumpIfNotSmi(r8, &slowcase); + __ SmiToInteger32(rbx, r8); + __ cmpl(rbx, Immediate(kMaxInlineLength)); + __ j(above, &slowcase); + // Smi-tagging is equivalent to multiplying by 2. + STATIC_ASSERT(kSmiTag == 0); + STATIC_ASSERT(kSmiTagSize == 1); + // Allocate RegExpResult followed by FixedArray with size in ebx. + // JSArray: [Map][empty properties][Elements][Length-smi][index][input] + // Elements: [Map][Length][..elements..] + __ AllocateInNewSpace(JSRegExpResult::kSize + FixedArray::kHeaderSize, + times_pointer_size, + rbx, // In: Number of elements. + rax, // Out: Start of allocation (tagged). + rcx, // Out: End of allocation. + rdx, // Scratch register + &slowcase, + TAG_OBJECT); + // rax: Start of allocated area, object-tagged. + // rbx: Number of array elements as int32. + // r8: Number of array elements as smi. + + // Set JSArray map to global.regexp_result_map(). + __ movq(rdx, ContextOperand(rsi, Context::GLOBAL_INDEX)); + __ movq(rdx, FieldOperand(rdx, GlobalObject::kGlobalContextOffset)); + __ movq(rdx, ContextOperand(rdx, Context::REGEXP_RESULT_MAP_INDEX)); + __ movq(FieldOperand(rax, HeapObject::kMapOffset), rdx); + + // Set empty properties FixedArray. + __ Move(FieldOperand(rax, JSObject::kPropertiesOffset), + Factory::empty_fixed_array()); + + // Set elements to point to FixedArray allocated right after the JSArray. + __ lea(rcx, Operand(rax, JSRegExpResult::kSize)); + __ movq(FieldOperand(rax, JSObject::kElementsOffset), rcx); + + // Set input, index and length fields from arguments. + __ movq(r8, Operand(rsp, kPointerSize * 1)); + __ movq(FieldOperand(rax, JSRegExpResult::kInputOffset), r8); + __ movq(r8, Operand(rsp, kPointerSize * 2)); + __ movq(FieldOperand(rax, JSRegExpResult::kIndexOffset), r8); + __ movq(r8, Operand(rsp, kPointerSize * 3)); + __ movq(FieldOperand(rax, JSArray::kLengthOffset), r8); + + // Fill out the elements FixedArray. + // rax: JSArray. + // rcx: FixedArray. + // rbx: Number of elements in array as int32. + + // Set map. + __ Move(FieldOperand(rcx, HeapObject::kMapOffset), + Factory::fixed_array_map()); + // Set length. + __ Integer32ToSmi(rdx, rbx); + __ movq(FieldOperand(rcx, FixedArray::kLengthOffset), rdx); + // Fill contents of fixed-array with the-hole. + __ Move(rdx, Factory::the_hole_value()); + __ lea(rcx, FieldOperand(rcx, FixedArray::kHeaderSize)); + // Fill fixed array elements with hole. + // rax: JSArray. + // rbx: Number of elements in array that remains to be filled, as int32. + // rcx: Start of elements in FixedArray. + // rdx: the hole. + Label loop; + __ testl(rbx, rbx); + __ bind(&loop); + __ j(less_equal, &done); // Jump if ecx is negative or zero. + __ subl(rbx, Immediate(1)); + __ movq(Operand(rcx, rbx, times_pointer_size, 0), rdx); + __ jmp(&loop); + + __ bind(&done); + __ ret(3 * kPointerSize); + + __ bind(&slowcase); + __ TailCallRuntime(Runtime::kRegExpConstructResult, 3, 1); +} + + void NumberToStringStub::GenerateLookupNumberStringCache(MacroAssembler* masm, Register object, Register result, @@ -3986,6 +4084,25 @@ void StringCompareStub::Generate(MacroAssembler* masm) { __ TailCallRuntime(Runtime::kStringCompare, 2, 1); } +void ICCompareStub::GenerateSmis(MacroAssembler* masm) { + UNIMPLEMENTED(); +} + + +void ICCompareStub::GenerateHeapNumbers(MacroAssembler* masm) { + UNIMPLEMENTED(); +} + + +void ICCompareStub::GenerateObjects(MacroAssembler* masm) { + UNIMPLEMENTED(); +} + + +void ICCompareStub::GenerateMiss(MacroAssembler* masm) { + UNIMPLEMENTED(); +} + #undef __ } } // namespace v8::internal diff --git a/src/x64/code-stubs-x64.h b/src/x64/code-stubs-x64.h index 18213b93..eb7ad267 100644 --- a/src/x64/code-stubs-x64.h +++ b/src/x64/code-stubs-x64.h @@ -149,7 +149,7 @@ class GenericBinaryOpStub: public CodeStub { class ArgsReversedBits: public BitField<bool, 10, 1> {}; class FlagBits: public BitField<GenericBinaryFlags, 11, 1> {}; class StaticTypeInfoBits: public BitField<int, 12, 3> {}; - class RuntimeTypeInfoBits: public BitField<BinaryOpIC::TypeInfo, 15, 2> {}; + class RuntimeTypeInfoBits: public BitField<BinaryOpIC::TypeInfo, 15, 3> {}; Major MajorKey() { return GenericBinaryOp; } int MinorKey() { diff --git a/src/x64/codegen-x64.cc b/src/x64/codegen-x64.cc index e9f29f0b..9a255722 100644 --- a/src/x64/codegen-x64.cc +++ b/src/x64/codegen-x64.cc @@ -104,12 +104,12 @@ void VirtualFrameRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { } -void ICRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::BeforeCall(MacroAssembler* masm) const { masm->EnterInternalFrame(); } -void ICRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { +void StubRuntimeCallHelper::AfterCall(MacroAssembler* masm) const { masm->LeaveInternalFrame(); } @@ -6490,94 +6490,13 @@ void CodeGenerator::GenerateRegExpExec(ZoneList<Expression*>* args) { void CodeGenerator::GenerateRegExpConstructResult(ZoneList<Expression*>* args) { - // No stub. This code only occurs a few times in regexp.js. - const int kMaxInlineLength = 100; ASSERT_EQ(3, args->length()); Load(args->at(0)); // Size of array, smi. Load(args->at(1)); // "index" property value. Load(args->at(2)); // "input" property value. - { - VirtualFrame::SpilledScope spilled_scope; - - Label slowcase; - Label done; - __ movq(r8, Operand(rsp, kPointerSize * 2)); - __ JumpIfNotSmi(r8, &slowcase); - __ SmiToInteger32(rbx, r8); - __ cmpl(rbx, Immediate(kMaxInlineLength)); - __ j(above, &slowcase); - // Smi-tagging is equivalent to multiplying by 2. - STATIC_ASSERT(kSmiTag == 0); - STATIC_ASSERT(kSmiTagSize == 1); - // Allocate RegExpResult followed by FixedArray with size in ebx. - // JSArray: [Map][empty properties][Elements][Length-smi][index][input] - // Elements: [Map][Length][..elements..] - __ AllocateInNewSpace(JSRegExpResult::kSize + FixedArray::kHeaderSize, - times_pointer_size, - rbx, // In: Number of elements. - rax, // Out: Start of allocation (tagged). - rcx, // Out: End of allocation. - rdx, // Scratch register - &slowcase, - TAG_OBJECT); - // rax: Start of allocated area, object-tagged. - // rbx: Number of array elements as int32. - // r8: Number of array elements as smi. - - // Set JSArray map to global.regexp_result_map(). - __ movq(rdx, ContextOperand(rsi, Context::GLOBAL_INDEX)); - __ movq(rdx, FieldOperand(rdx, GlobalObject::kGlobalContextOffset)); - __ movq(rdx, ContextOperand(rdx, Context::REGEXP_RESULT_MAP_INDEX)); - __ movq(FieldOperand(rax, HeapObject::kMapOffset), rdx); - - // Set empty properties FixedArray. - __ Move(FieldOperand(rax, JSObject::kPropertiesOffset), - Factory::empty_fixed_array()); - - // Set elements to point to FixedArray allocated right after the JSArray. - __ lea(rcx, Operand(rax, JSRegExpResult::kSize)); - __ movq(FieldOperand(rax, JSObject::kElementsOffset), rcx); - - // Set input, index and length fields from arguments. - __ pop(FieldOperand(rax, JSRegExpResult::kInputOffset)); - __ pop(FieldOperand(rax, JSRegExpResult::kIndexOffset)); - __ lea(rsp, Operand(rsp, kPointerSize)); - __ movq(FieldOperand(rax, JSArray::kLengthOffset), r8); - - // Fill out the elements FixedArray. - // rax: JSArray. - // rcx: FixedArray. - // rbx: Number of elements in array as int32. - - // Set map. - __ Move(FieldOperand(rcx, HeapObject::kMapOffset), - Factory::fixed_array_map()); - // Set length. - __ Integer32ToSmi(rdx, rbx); - __ movq(FieldOperand(rcx, FixedArray::kLengthOffset), rdx); - // Fill contents of fixed-array with the-hole. - __ Move(rdx, Factory::the_hole_value()); - __ lea(rcx, FieldOperand(rcx, FixedArray::kHeaderSize)); - // Fill fixed array elements with hole. - // rax: JSArray. - // rbx: Number of elements in array that remains to be filled, as int32. - // rcx: Start of elements in FixedArray. - // rdx: the hole. - Label loop; - __ testl(rbx, rbx); - __ bind(&loop); - __ j(less_equal, &done); // Jump if ecx is negative or zero. - __ subl(rbx, Immediate(1)); - __ movq(Operand(rcx, rbx, times_pointer_size, 0), rdx); - __ jmp(&loop); - - __ bind(&slowcase); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); - - __ bind(&done); - } - frame_->Forget(3); - frame_->Push(rax); + RegExpConstructResultStub stub; + Result result = frame_->CallStub(&stub, 3); + frame_->Push(&result); } @@ -6865,9 +6784,9 @@ void CodeGenerator::GenerateSwapElements(ZoneList<Expression*>* args) { // Check that both indices are valid. __ movq(tmp2.reg(), FieldOperand(object.reg(), JSArray::kLengthOffset)); - __ cmpl(tmp2.reg(), index1.reg()); + __ SmiCompare(tmp2.reg(), index1.reg()); deferred->Branch(below_equal); - __ cmpl(tmp2.reg(), index2.reg()); + __ SmiCompare(tmp2.reg(), index2.reg()); deferred->Branch(below_equal); // Bring addresses into index1 and index2. @@ -7118,6 +7037,15 @@ void CodeGenerator::GenerateMathCos(ZoneList<Expression*>* args) { } +void CodeGenerator::GenerateMathLog(ZoneList<Expression*>* args) { + ASSERT_EQ(args->length(), 1); + Load(args->at(0)); + TranscendentalCacheStub stub(TranscendentalCache::LOG); + Result result = frame_->CallStub(&stub, 1); + frame_->Push(&result); +} + + // Generates the Math.sqrt method. Please note - this function assumes that // the callsite has executed ToNumber on the argument. void CodeGenerator::GenerateMathSqrt(ZoneList<Expression*>* args) { @@ -7946,7 +7874,7 @@ void CodeGenerator::VisitCompareOperation(CompareOperation* node) { case Token::INSTANCEOF: { Load(left); Load(right); - InstanceofStub stub; + InstanceofStub stub(InstanceofStub::kNoFlags); Result answer = frame_->CallStub(&stub, 2); answer.ToRegister(); __ testq(answer.reg(), answer.reg()); diff --git a/src/x64/codegen-x64.h b/src/x64/codegen-x64.h index 1a5e7df3..b308f64c 100644 --- a/src/x64/codegen-x64.h +++ b/src/x64/codegen-x64.h @@ -308,6 +308,9 @@ class CodeGenerator: public AstVisitor { Code::Flags flags, CompilationInfo* info); + // Print the code after compiling it. + static void PrintCode(Handle<Code> code, CompilationInfo* info); + #ifdef ENABLE_LOGGING_AND_PROFILING static bool ShouldGenerateLog(Expression* type); #endif @@ -370,8 +373,9 @@ class CodeGenerator: public AstVisitor { // Node visitors. void VisitStatements(ZoneList<Statement*>* statements); -#define DEF_VISIT(type) \ - void Visit##type(type* node); + virtual void VisitSlot(Slot* node); +#define DEF_VISIT(type) \ + virtual void Visit##type(type* node); AST_NODE_LIST(DEF_VISIT) #undef DEF_VISIT @@ -664,14 +668,16 @@ class CodeGenerator: public AstVisitor { void GenerateMathSin(ZoneList<Expression*>* args); void GenerateMathCos(ZoneList<Expression*>* args); void GenerateMathSqrt(ZoneList<Expression*>* args); + void GenerateMathLog(ZoneList<Expression*>* args); + // Check whether two RegExps are equivalent. void GenerateIsRegExpEquivalent(ZoneList<Expression*>* args); void GenerateHasCachedArrayIndex(ZoneList<Expression*>* args); void GenerateGetCachedArrayIndex(ZoneList<Expression*>* args); void GenerateFastAsciiArrayJoin(ZoneList<Expression*>* args); -// Simple condition analysis. + // Simple condition analysis. enum ConditionAnalysis { ALWAYS_TRUE, ALWAYS_FALSE, diff --git a/src/x64/cpu-x64.cc b/src/x64/cpu-x64.cc index a43a02bb..30134bf1 100644 --- a/src/x64/cpu-x64.cc +++ b/src/x64/cpu-x64.cc @@ -42,7 +42,7 @@ namespace v8 { namespace internal { void CPU::Setup() { - CpuFeatures::Probe(); + CpuFeatures::Probe(true); } diff --git a/src/x64/deoptimizer-x64.cc b/src/x64/deoptimizer-x64.cc new file mode 100644 index 00000000..4e890cd4 --- /dev/null +++ b/src/x64/deoptimizer-x64.cc @@ -0,0 +1,77 @@ +// Copyright 2010 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. + +#include "v8.h" + +#include "codegen.h" +#include "deoptimizer.h" +#include "full-codegen.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + + +int Deoptimizer::table_entry_size_ = 10; + +void Deoptimizer::DeoptimizeFunction(JSFunction* function) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::PatchStackCheckCode(RelocInfo* rinfo, + Code* replacement_code) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::RevertStackCheckCode(RelocInfo* rinfo, Code* check_code) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::DoComputeOsrOutputFrame() { + UNIMPLEMENTED(); +} + + +void Deoptimizer::DoComputeFrame(TranslationIterator* iterator, + int frame_index) { + UNIMPLEMENTED(); +} + + +void Deoptimizer::EntryGenerator::Generate() { + UNIMPLEMENTED(); +} + + +void Deoptimizer::TableEntryGenerator::GeneratePrologue() { + UNIMPLEMENTED(); +} + +} } // namespace v8::internal diff --git a/src/x64/disasm-x64.cc b/src/x64/disasm-x64.cc index 4213912b..7502d618 100644 --- a/src/x64/disasm-x64.cc +++ b/src/x64/disasm-x64.cc @@ -906,7 +906,9 @@ int DisassemblerX64::RegisterFPUInstruction(int escape_opcode, case 0xE4: mnem = "ftst"; break; case 0xE8: mnem = "fld1"; break; case 0xEB: mnem = "fldpi"; break; + case 0xED: mnem = "fldln2"; break; case 0xEE: mnem = "fldz"; break; + case 0xF1: mnem = "fyl2x"; break; case 0xF5: mnem = "fprem1"; break; case 0xF7: mnem = "fincstp"; break; case 0xF8: mnem = "fprem"; break; diff --git a/src/x64/frames-x64.h b/src/x64/frames-x64.h index 9991981a..fbbf176e 100644 --- a/src/x64/frames-x64.h +++ b/src/x64/frames-x64.h @@ -43,6 +43,12 @@ static const int kNumJSCallerSaved = 5; typedef Object* JSCallerSavedBuffer[kNumJSCallerSaved]; +// Number of registers for which space is reserved in safepoints. +// TODO(x64): This should not be 0. +static const int kNumSafepointRegisters = 0; + +// ---------------------------------------------------- + class StackHandlerConstants : public AllStatic { public: static const int kNextOffset = 0 * kPointerSize; diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index a1aa976a..dd28d4d8 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -170,7 +170,12 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { } } + if (FLAG_trace) { + __ CallRuntime(Runtime::kTraceEnter, 0); + } + { Comment cmnt(masm_, "[ Stack check"); + PrepareForBailout(info->function(), NO_REGISTERS); NearLabel ok; __ CompareRoot(rsp, Heap::kStackLimitRootIndex); __ j(above_equal, &ok); @@ -179,10 +184,6 @@ void FullCodeGenerator::Generate(CompilationInfo* info) { __ bind(&ok); } - if (FLAG_trace) { - __ CallRuntime(Runtime::kTraceEnter, 0); - } - { Comment cmnt(masm_, "[ Body"); ASSERT(loop_depth() == 0); VisitStatements(function()->body()); @@ -202,6 +203,20 @@ void FullCodeGenerator::ClearAccumulator() { } +void FullCodeGenerator::EmitStackCheck(IterationStatement* stmt) { + Comment cmnt(masm_, "[ Stack check"); + NearLabel ok; + __ CompareRoot(rsp, Heap::kStackLimitRootIndex); + __ j(above_equal, &ok); + StackCheckStub stub; + __ CallStub(&stub); + __ bind(&ok); + PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS); + PrepareForBailoutForId(stmt->OsrEntryId(), NO_REGISTERS); + RecordStackCheck(stmt->OsrEntryId()); +} + + void FullCodeGenerator::EmitReturnSequence() { Comment cmnt(masm_, "[ Return sequence"); if (return_label_.is_bound()) { @@ -266,6 +281,7 @@ void FullCodeGenerator::StackValueContext::Plug(Slot* slot) const { void FullCodeGenerator::TestContext::Plug(Slot* slot) const { codegen()->Move(result_register(), slot); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } @@ -287,12 +303,16 @@ void FullCodeGenerator::StackValueContext::Plug( void FullCodeGenerator::TestContext::Plug(Heap::RootListIndex index) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); if (index == Heap::kUndefinedValueRootIndex || index == Heap::kNullValueRootIndex || index == Heap::kFalseValueRootIndex) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else if (index == Heap::kTrueValueRootIndex) { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } else { __ LoadRoot(result_register(), index); codegen()->DoTest(true_label_, false_label_, fall_through_); @@ -316,22 +336,26 @@ void FullCodeGenerator::StackValueContext::Plug(Handle<Object> lit) const { void FullCodeGenerator::TestContext::Plug(Handle<Object> lit) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, + true, + true_label_, + false_label_); ASSERT(!lit->IsUndetectableObject()); // There are no undetectable literals. if (lit->IsUndefined() || lit->IsNull() || lit->IsFalse()) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else if (lit->IsTrue() || lit->IsJSObject()) { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } else if (lit->IsString()) { if (String::cast(*lit)->length() == 0) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } } else if (lit->IsSmi()) { if (Smi::cast(*lit)->value() == 0) { - __ jmp(false_label_); + if (false_label_ != fall_through_) __ jmp(false_label_); } else { - __ jmp(true_label_); + if (true_label_ != fall_through_) __ jmp(true_label_); } } else { // For simplicity we always test the accumulator register. @@ -371,13 +395,14 @@ void FullCodeGenerator::TestContext::DropAndPlug(int count, // For simplicity we always test the accumulator register. __ Drop(count); __ Move(result_register(), reg); + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); codegen()->DoTest(true_label_, false_label_, fall_through_); } void FullCodeGenerator::EffectContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT_EQ(materialize_true, materialize_false); + ASSERT(materialize_true == materialize_false); __ bind(materialize_true); } @@ -410,8 +435,8 @@ void FullCodeGenerator::StackValueContext::Plug( void FullCodeGenerator::TestContext::Plug(Label* materialize_true, Label* materialize_false) const { - ASSERT(materialize_false == false_label_); ASSERT(materialize_true == true_label_); + ASSERT(materialize_false == false_label_); } @@ -434,6 +459,7 @@ void FullCodeGenerator::StackValueContext::Plug(bool flag) const { void FullCodeGenerator::TestContext::Plug(bool flag) const { + codegen()->PrepareForBailoutBeforeSplit(TOS_REG, false, NULL, NULL); if (flag) { if (true_label_ != fall_through_) __ jmp(true_label_); } else { @@ -525,6 +551,13 @@ void FullCodeGenerator::Move(Slot* dst, } +void FullCodeGenerator::PrepareForBailoutBeforeSplit(State state, + bool should_normalize, + Label* if_true, + Label* if_false) { +} + + void FullCodeGenerator::EmitDeclaration(Variable* variable, Variable::Mode mode, FunctionLiteral* function) { @@ -811,26 +844,20 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { __ bind(&update_each); __ movq(result_register(), rbx); // Perform the assignment as if via '='. - EmitAssignment(stmt->each()); + { EffectContext context(this); + EmitAssignment(stmt->each(), stmt->AssignmentId()); + } // Generate code for the body of the loop. - Label stack_limit_hit, stack_check_done; Visit(stmt->body()); - __ StackLimitCheck(&stack_limit_hit); - __ bind(&stack_check_done); - // Generate code for going to the next element by incrementing the // index (smi) stored on top of the stack. __ bind(loop_statement.continue_target()); __ SmiAddConstant(Operand(rsp, 0 * kPointerSize), Smi::FromInt(1)); - __ jmp(&loop); - // Slow case for the stack limit check. - StackCheckStub stack_check_stub; - __ bind(&stack_limit_hit); - __ CallStub(&stack_check_stub); - __ jmp(&stack_check_done); + EmitStackCheck(stmt); + __ jmp(&loop); // Remove the pointers stored on the stack. __ bind(loop_statement.break_target()); @@ -1393,6 +1420,7 @@ void FullCodeGenerator::VisitAssignment(Assignment* expr) { case VARIABLE: EmitVariableAssignment(expr->target()->AsVariableProxy()->var(), expr->op()); + context()->Plug(rax); break; case NAMED_PROPERTY: EmitNamedPropertyAssignment(expr); @@ -1501,7 +1529,7 @@ void FullCodeGenerator::EmitBinaryOp(Token::Value op, } -void FullCodeGenerator::EmitAssignment(Expression* expr) { +void FullCodeGenerator::EmitAssignment(Expression* expr, int bailout_id) { // Invalid left-hand sides are rewritten to have a 'throw // ReferenceError' on the left-hand side. if (!expr->IsValidLeftHandSide()) { @@ -1549,6 +1577,7 @@ void FullCodeGenerator::EmitAssignment(Expression* expr) { break; } } + context()->Plug(rax); } @@ -1621,8 +1650,6 @@ void FullCodeGenerator::EmitVariableAssignment(Variable* var, } __ bind(&done); } - - context()->Plug(rax); } @@ -1659,10 +1686,9 @@ void FullCodeGenerator::EmitNamedPropertyAssignment(Assignment* expr) { __ push(Operand(rsp, kPointerSize)); // Receiver is under value. __ CallRuntime(Runtime::kToFastProperties, 1); __ pop(rax); - context()->DropAndPlug(1, rax); - } else { - context()->Plug(rax); + __ Drop(1); } + context()->Plug(rax); } @@ -1711,13 +1737,14 @@ void FullCodeGenerator::VisitProperty(Property* expr) { if (key->IsPropertyName()) { VisitForAccumulatorValue(expr->obj()); EmitNamedPropertyLoad(expr); + context()->Plug(rax); } else { VisitForStackValue(expr->obj()); VisitForAccumulatorValue(expr->key()); __ pop(rdx); EmitKeyedPropertyLoad(expr); + context()->Plug(rax); } - context()->Plug(rax); } @@ -1727,14 +1754,14 @@ void FullCodeGenerator::EmitCallWithIC(Call* expr, // Code common for calls using the IC. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } __ Move(rcx, name); } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); // Call the IC initialization code. InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeCallInitialize(arg_count, in_loop); @@ -1760,13 +1787,13 @@ void FullCodeGenerator::EmitKeyedCallWithIC(Call* expr, // Load the arguments. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); // Call the IC initialization code. InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; Handle<Code> ic = StubCache::ComputeKeyedCallInitialize(arg_count, in_loop); @@ -1782,13 +1809,13 @@ void FullCodeGenerator::EmitCallWithStub(Call* expr) { // Code common for calls using the call stub. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); for (int i = 0; i < arg_count; i++) { VisitForStackValue(args->at(i)); } } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); @@ -1811,7 +1838,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { // arguments. ZoneList<Expression*>* args = expr->arguments(); int arg_count = args->length(); - { PreserveStatementPositionScope pos_scope(masm()->positions_recorder()); + { PreservePositionScope pos_scope(masm()->positions_recorder()); VisitForStackValue(fun); __ PushRoot(Heap::kUndefinedValueRootIndex); // Reserved receiver slot. @@ -1840,7 +1867,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { __ movq(Operand(rsp, (arg_count + 1) * kPointerSize), rax); } // Record source position for debugger. - SetSourcePosition(expr->position(), FORCED_POSITION); + SetSourcePosition(expr->position()); InLoopFlag in_loop = (loop_depth() > 0) ? IN_LOOP : NOT_IN_LOOP; CallFunctionStub stub(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE); __ CallStub(&stub); @@ -1857,7 +1884,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a lookup slot (dynamically introduced variable). Label slow, done; - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); // Generate code for loading from variables potentially shadowed // by eval-introduced variables. EmitDynamicLoadFromSlotFastCase(var->AsSlot(), @@ -1898,7 +1925,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { Literal* key = prop->key()->AsLiteral(); if (key != NULL && key->handle()->IsSymbol()) { // Call to a named property, use call IC. - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(prop->obj()); } EmitCallWithIC(expr, key->handle(), RelocInfo::CODE_TARGET); @@ -1906,16 +1933,16 @@ void FullCodeGenerator::VisitCall(Call* expr) { // Call to a keyed property. // For a synthetic property use keyed load IC followed by function call, // for a regular property use KeyedCallIC. - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(prop->obj()); } if (prop->is_synthetic()) { - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForAccumulatorValue(prop->key()); __ movq(rdx, Operand(rsp, 0)); } // Record source code position for IC call. - SetSourcePosition(prop->position(), FORCED_POSITION); + SetSourcePosition(prop->position()); Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); EmitCallIC(ic, RelocInfo::CODE_TARGET); // Pop receiver. @@ -1940,7 +1967,7 @@ void FullCodeGenerator::VisitCall(Call* expr) { loop_depth() == 0) { lit->set_try_full_codegen(true); } - { PreserveStatementPositionScope scope(masm()->positions_recorder()); + { PreservePositionScope scope(masm()->positions_recorder()); VisitForStackValue(fun); } // Load global receiver object. @@ -2628,6 +2655,16 @@ void FullCodeGenerator::EmitMathCos(ZoneList<Expression*>* args) { } +void FullCodeGenerator::EmitMathLog(ZoneList<Expression*>* args) { + // Load the argument on the stack and call the stub. + TranscendentalCacheStub stub(TranscendentalCache::LOG); + ASSERT(args->length() == 1); + VisitForStackValue(args->at(0)); + __ CallStub(&stub); + context()->Plug(rax); +} + + void FullCodeGenerator::EmitMathSqrt(ZoneList<Expression*>* args) { // Load the argument on the stack and call the runtime function. ASSERT(args->length() == 1); @@ -2657,11 +2694,12 @@ void FullCodeGenerator::EmitCallFunction(ZoneList<Expression*>* args) { void FullCodeGenerator::EmitRegExpConstructResult(ZoneList<Expression*>* args) { + RegExpConstructResultStub stub; ASSERT(args->length() == 3); VisitForStackValue(args->at(0)); VisitForStackValue(args->at(1)); VisitForStackValue(args->at(2)); - __ CallRuntime(Runtime::kRegExpConstructResult, 3); + __ CallStub(&stub); context()->Plug(rax); } @@ -2923,7 +2961,7 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { case Token::ADD: { Comment cmt(masm_, "[ UnaryOperation (ADD)"); VisitForAccumulatorValue(expr->expression()); - NearLabel no_conversion; + Label no_conversion; Condition is_smi = masm_->CheckSmi(result_register()); __ j(is_smi, &no_conversion); __ push(result_register()); @@ -3076,6 +3114,10 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { __ SmiAddConstant(rax, rax, Smi::FromInt(1)); } } + + // Record position before stub call. + SetSourcePosition(expr->position()); + // Call stub for +1/-1. GenericBinaryOpStub stub(expr->binary_op(), NO_OVERWRITE, @@ -3091,6 +3133,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { { EffectContext context(this); EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + context.Plug(rax); } // For all contexts except kEffect: We have the result on // top of the stack. @@ -3101,6 +3144,7 @@ void FullCodeGenerator::VisitCountOperation(CountOperation* expr) { // Perform the assignment as if via '='. EmitVariableAssignment(expr->expression()->AsVariableProxy()->var(), Token::ASSIGN); + context()->Plug(rax); } break; case NAMED_PROPERTY: { @@ -3292,7 +3336,7 @@ void FullCodeGenerator::VisitCompareOperation(CompareOperation* expr) { case Token::INSTANCEOF: { VisitForStackValue(expr->right()); - InstanceofStub stub; + InstanceofStub stub(InstanceofStub::kNoFlags); __ CallStub(&stub); __ testq(rax, rax); // The stub returns 0 for true. @@ -3413,6 +3457,9 @@ void FullCodeGenerator::EmitCallIC(Handle<Code> ic, RelocInfo::Mode mode) { mode == RelocInfo::CODE_TARGET_CONTEXT); __ call(ic, mode); + // Crankshaft doesn't need patching of inlined loads and stores. + if (V8::UseCrankshaft()) return; + // If we're calling a (keyed) load or store stub, we have to mark // the call as containing no inlined code so we will not attempt to // patch it. diff --git a/src/x64/ic-x64.cc b/src/x64/ic-x64.cc index 204dd6ab..aff778a5 100644 --- a/src/x64/ic-x64.cc +++ b/src/x64/ic-x64.cc @@ -108,9 +108,6 @@ static void GenerateStringDictionaryProbes(MacroAssembler* masm, Register name, Register r0, Register r1) { - // Assert that name contains a string. - if (FLAG_debug_code) __ AbortIfNotString(name); - // Compute the capacity mask. const int kCapacityOffset = StringDictionary::kHeaderSize + @@ -386,6 +383,8 @@ static const byte kTestEaxByte = 0xA9; static bool PatchInlinedMapCheck(Address address, Object* map) { + if (V8::UseCrankshaft()) return false; + // Arguments are address of start of call sequence that called // the IC, Address test_instruction_address = @@ -751,7 +750,7 @@ void KeyedLoadIC::GenerateString(MacroAssembler* masm) { char_at_generator.GenerateFast(masm); __ ret(0); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm, call_helper); __ bind(&miss); @@ -1590,13 +1589,7 @@ void KeyedCallIC::GenerateNormal(MacroAssembler* masm, int argc) { // rsp[(argc + 1) * 8] : argument 0 = receiver // ----------------------------------- - // Check if the name is a string. - Label miss; - __ JumpIfSmi(rcx, &miss); - Condition cond = masm->IsObjectStringType(rcx, rax, rax); - __ j(NegateCondition(cond), &miss); GenerateCallNormal(masm, argc); - __ bind(&miss); GenerateMiss(masm, argc); } @@ -1708,6 +1701,8 @@ void LoadIC::GenerateStringLength(MacroAssembler* masm) { bool LoadIC::PatchInlinedLoad(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // The address of the instruction following the call. Address test_instruction_address = address + Assembler::kCallTargetAddressOffset; @@ -1750,6 +1745,8 @@ const int StoreIC::kOffsetToStoreInstruction = 20; bool StoreIC::PatchInlinedStore(Address address, Object* map, int offset) { + if (V8::UseCrankshaft()) return false; + // The address of the instruction following the call. Address test_instruction_address = address + Assembler::kCallTargetAddressOffset; @@ -1908,9 +1905,77 @@ void StoreIC::GenerateNormal(MacroAssembler* masm) { } +void StoreIC::GenerateGlobalProxy(MacroAssembler* masm) { + // ----------- S t a t e ------------- + // -- rax : value + // -- rcx : name + // -- rdx : receiver + // -- rsp[0] : return address + // ----------------------------------- + __ pop(rbx); + __ push(rdx); + __ push(rcx); + __ push(rax); + __ push(rbx); + + // Do tail-call to runtime routine. + __ TailCallRuntime(Runtime::kSetProperty, 3, 1); +} + + #undef __ +Condition CompareIC::ComputeCondition(Token::Value op) { + switch (op) { + case Token::EQ_STRICT: + case Token::EQ: + return equal; + case Token::LT: + return less; + case Token::GT: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return less; + case Token::LTE: + // Reverse left and right operands to obtain ECMA-262 conversion order. + return greater_equal; + case Token::GTE: + return greater_equal; + default: + UNREACHABLE(); + return no_condition; + } +} + + +void CompareIC::UpdateCaches(Handle<Object> x, Handle<Object> y) { + HandleScope scope; + Handle<Code> rewritten; + State previous_state = GetState(); + State state = TargetState(previous_state, false, x, y); + if (state == GENERIC) { + CompareStub stub(GetCondition(), strict(), NO_COMPARE_FLAGS); + rewritten = stub.GetCode(); + } else { + ICCompareStub stub(op_, state); + rewritten = stub.GetCode(); + } + set_target(*rewritten); + +#ifdef DEBUG + if (FLAG_trace_ic) { + PrintF("[CompareIC (%s->%s)#%s]\n", + GetStateName(previous_state), + GetStateName(state), + Token::Name(op_)); + } +#endif +} + +void PatchInlinedSmiCode(Address address) { + UNIMPLEMENTED(); +} + } } // namespace v8::internal #endif // V8_TARGET_ARCH_X64 diff --git a/src/x64/lithium-codegen-x64.h b/src/x64/lithium-codegen-x64.h new file mode 100644 index 00000000..cd1f08de --- /dev/null +++ b/src/x64/lithium-codegen-x64.h @@ -0,0 +1,62 @@ +// Copyright 2010 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. + +#ifndef V8_X64_LITHIUM_CODEGEN_X64_H_ +#define V8_X64_LITHIUM_CODEGEN_X64_H_ + +#include "x64/lithium-x64.h" + +#include "deoptimizer.h" +#include "safepoint-table.h" +#include "scopes.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LDeferredCode; + +class LCodeGen BASE_EMBEDDED { + public: + LCodeGen(LChunk* chunk, MacroAssembler* assembler, CompilationInfo* info) { } + + // Try to generate code for the entire chunk, but it may fail if the + // chunk contains constructs we cannot handle. Returns true if the + // code generation attempt succeeded. + bool GenerateCode() { + UNIMPLEMENTED(); + return false; + } + + // Finish the code by setting stack height, safepoint, and bailout + // information on it. + void FinishCode(Handle<Code> code) { UNIMPLEMENTED(); } +}; + +} } // namespace v8::internal + +#endif // V8_X64_LITHIUM_CODEGEN_X64_H_ diff --git a/src/x64/lithium-x64.h b/src/x64/lithium-x64.h new file mode 100644 index 00000000..f66ec168 --- /dev/null +++ b/src/x64/lithium-x64.h @@ -0,0 +1,251 @@ +// Copyright 2010 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. + +#ifndef V8_X64_LITHIUM_X64_H_ +#define V8_X64_LITHIUM_X64_H_ + +#include "hydrogen.h" +#include "lithium-allocator.h" +#include "safepoint-table.h" + +namespace v8 { +namespace internal { + +// Forward declarations. +class LCodeGen; +class LEnvironment; +class Translation; + +class LInstruction: public ZoneObject { + public: + LInstruction() { } + virtual ~LInstruction() { } + + // Predicates should be generated by macro as in lithium-ia32.h. + virtual bool IsLabel() const { + UNIMPLEMENTED(); + return false; + } + virtual bool IsOsrEntry() const { + UNIMPLEMENTED(); + return false; + } + + LPointerMap* pointer_map() const { + UNIMPLEMENTED(); + return NULL; + } + + bool HasPointerMap() const { + UNIMPLEMENTED(); + return false; + } + + virtual void PrintTo(StringStream* stream) const { UNIMPLEMENTED(); } +}; + + +class LParallelMove : public ZoneObject { + public: + LParallelMove() { } + + void AddMove(LOperand* from, LOperand* to) { + UNIMPLEMENTED(); + } + + const ZoneList<LMoveOperands>* move_operands() const { + UNIMPLEMENTED(); + return NULL; + } +}; + + +class LGap: public LInstruction { + public: + explicit LGap(HBasicBlock* block) { } + + HBasicBlock* block() const { + UNIMPLEMENTED(); + return NULL; + } + + enum InnerPosition { + BEFORE, + START, + END, + AFTER, + FIRST_INNER_POSITION = BEFORE, + LAST_INNER_POSITION = AFTER + }; + + LParallelMove* GetOrCreateParallelMove(InnerPosition pos) { + UNIMPLEMENTED(); + return NULL; + } + + LParallelMove* GetParallelMove(InnerPosition pos) { + UNIMPLEMENTED(); + return NULL; + } +}; + + +class LLabel: public LGap { + public: + explicit LLabel(HBasicBlock* block) : LGap(block) { } +}; + + +class LOsrEntry: public LInstruction { + public: + // Function could be generated by a macro as in lithium-ia32.h. + static LOsrEntry* cast(LInstruction* instr) { + UNIMPLEMENTED(); + return NULL; + } + + LOperand** SpilledRegisterArray() { + UNIMPLEMENTED(); + return NULL; + } + LOperand** SpilledDoubleRegisterArray() { + UNIMPLEMENTED(); + return NULL; + } + + void MarkSpilledRegister(int allocation_index, LOperand* spill_operand) { + UNIMPLEMENTED(); + } + void MarkSpilledDoubleRegister(int allocation_index, + LOperand* spill_operand) { + UNIMPLEMENTED(); + } +}; + + +class LPointerMap: public ZoneObject { + public: + explicit LPointerMap(int position) { } + + int lithium_position() const { + UNIMPLEMENTED(); + return 0; + } + + void RecordPointer(LOperand* op) { UNIMPLEMENTED(); } +}; + + +class LChunk: public ZoneObject { + public: + explicit LChunk(HGraph* graph) { } + + HGraph* graph() const { + UNIMPLEMENTED(); + return NULL; + } + + const ZoneList<LPointerMap*>* pointer_maps() const { + UNIMPLEMENTED(); + return NULL; + } + + LOperand* GetNextSpillSlot(bool double_slot) { + UNIMPLEMENTED(); + return NULL; + } + + LConstantOperand* DefineConstantOperand(HConstant* constant) { + UNIMPLEMENTED(); + return NULL; + } + + LLabel* GetLabel(int block_id) const { + UNIMPLEMENTED(); + return NULL; + } + + const ZoneList<LInstruction*>* instructions() const { + UNIMPLEMENTED(); + return NULL; + } + + int GetParameterStackSlot(int index) const { + UNIMPLEMENTED(); + return 0; + } + + void AddGapMove(int index, LOperand* from, LOperand* to) { UNIMPLEMENTED(); } + + LGap* GetGapAt(int index) const { + UNIMPLEMENTED(); + return NULL; + } + + bool IsGapAt(int index) const { + UNIMPLEMENTED(); + return false; + } + + int NearestGapPos(int index) const { + UNIMPLEMENTED(); + return 0; + } + + void MarkEmptyBlocks() { UNIMPLEMENTED(); } + +#ifdef DEBUG + void Verify() { UNIMPLEMENTED(); } +#endif +}; + + +class LChunkBuilder BASE_EMBEDDED { + public: + LChunkBuilder(HGraph* graph, LAllocator* allocator) { } + + // Build the sequence for the graph. + LChunk* Build() { + UNIMPLEMENTED(); + return NULL; + }; + + // Declare methods that deal with the individual node types. +#define DECLARE_DO(type) LInstruction* Do##type(H##type* node) { \ + UNIMPLEMENTED(); \ + return NULL; \ + } + HYDROGEN_CONCRETE_INSTRUCTION_LIST(DECLARE_DO) +#undef DECLARE_DO + + DISALLOW_COPY_AND_ASSIGN(LChunkBuilder); +}; + + +} } // namespace v8::internal + +#endif // V8_X64_LITHIUM_X64_H_ diff --git a/src/x64/macro-assembler-x64.cc b/src/x64/macro-assembler-x64.cc index 5ea480ff..1df9b475 100644 --- a/src/x64/macro-assembler-x64.cc +++ b/src/x64/macro-assembler-x64.cc @@ -74,12 +74,6 @@ void MacroAssembler::CompareRoot(Operand with, Heap::RootListIndex index) { } -void MacroAssembler::StackLimitCheck(Label* on_stack_overflow) { - CompareRoot(rsp, Heap::kStackLimitRootIndex); - j(below, on_stack_overflow); -} - - void MacroAssembler::RecordWriteHelper(Register object, Register addr, Register scratch) { @@ -1504,17 +1498,6 @@ void MacroAssembler::AbortIfNotSmi(Register object) { } -void MacroAssembler::AbortIfNotString(Register object) { - testb(object, Immediate(kSmiTagMask)); - Assert(not_equal, "Operand is not a string"); - push(object); - movq(object, FieldOperand(object, HeapObject::kMapOffset)); - CmpInstanceType(object, FIRST_NONSTRING_TYPE); - pop(object); - Assert(below, "Operand is not a string"); -} - - void MacroAssembler::AbortIfNotRootValue(Register src, Heap::RootListIndex root_value_index, const char* message) { @@ -2267,6 +2250,31 @@ void MacroAssembler::LoadContext(Register dst, int context_chain_length) { } +void MacroAssembler::LoadGlobalFunction(int index, Register function) { + // Load the global or builtins object from the current context. + movq(function, Operand(rsi, Context::SlotOffset(Context::GLOBAL_INDEX))); + // Load the global context from the global or builtins object. + movq(function, FieldOperand(function, GlobalObject::kGlobalContextOffset)); + // Load the function from the global context. + movq(function, Operand(function, Context::SlotOffset(index))); +} + + +void MacroAssembler::LoadGlobalFunctionInitialMap(Register function, + Register map) { + // Load the initial map. The global functions all have initial maps. + movq(map, FieldOperand(function, JSFunction::kPrototypeOrInitialMapOffset)); + if (FLAG_debug_code) { + Label ok, fail; + CheckMap(map, Factory::meta_map(), &fail, false); + jmp(&ok); + bind(&fail); + Abort("Global functions must have initial map"); + bind(&ok); + } +} + + int MacroAssembler::ArgumentStackSlotsForCFunctionCall(int num_arguments) { // On Windows 64 stack slots are reserved by the caller for all arguments // including the ones passed in registers, and space is always allocated for diff --git a/src/x64/macro-assembler-x64.h b/src/x64/macro-assembler-x64.h index 219ae4f8..d8f2fba4 100644 --- a/src/x64/macro-assembler-x64.h +++ b/src/x64/macro-assembler-x64.h @@ -1,4 +1,4 @@ -// Copyright 2009 the V8 project authors. All rights reserved. +// Copyright 2010 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: @@ -137,12 +137,6 @@ class MacroAssembler: public Assembler { #endif // --------------------------------------------------------------------------- - // Stack limit support - - // Do simple test for stack overflow. This doesn't handle an overflow. - void StackLimitCheck(Label* on_stack_limit_hit); - - // --------------------------------------------------------------------------- // Activation frames void EnterInternalFrame() { EnterFrame(StackFrame::INTERNAL); } @@ -173,6 +167,14 @@ class MacroAssembler: public Assembler { // register rax (untouched). void LeaveApiExitFrame(); + // Push and pop the registers that can hold pointers. + void PushSafepointRegisters() { UNIMPLEMENTED(); } + void PopSafepointRegisters() { UNIMPLEMENTED(); } + static int SafepointRegisterStackIndex(int reg_code) { + UNIMPLEMENTED(); + return 0; + } + // --------------------------------------------------------------------------- // JavaScript invokes @@ -629,9 +631,6 @@ class MacroAssembler: public Assembler { // Abort execution if argument is not a smi. Used in debug code. void AbortIfNotSmi(Register object); - // Abort execution if argument is a string. Used in debug code. - void AbortIfNotString(Register object); - // Abort execution if argument is not the root value with the given index. void AbortIfNotRootValue(Register src, Heap::RootListIndex root_value_index, @@ -773,6 +772,13 @@ class MacroAssembler: public Assembler { // Find the function context up the context chain. void LoadContext(Register dst, int context_chain_length); + // Load the global function with the given index. + void LoadGlobalFunction(int index, Register function); + + // Load the initial map from the global function. The registers + // function and map can be the same. + void LoadGlobalFunctionInitialMap(Register function, Register map); + // --------------------------------------------------------------------------- // Runtime calls diff --git a/src/x64/stub-cache-x64.cc b/src/x64/stub-cache-x64.cc index 7ba482c8..63e97695 100644 --- a/src/x64/stub-cache-x64.cc +++ b/src/x64/stub-cache-x64.cc @@ -923,22 +923,20 @@ void CallStubCompiler::GenerateLoadFunctionFromCell(JSGlobalPropertyCell* cell, MaybeObject* CallStubCompiler::GenerateMissBranch() { + MaybeObject* maybe_obj = + StubCache::ComputeCallMiss(arguments().immediate(), kind_); Object* obj; - { MaybeObject* maybe_obj = - StubCache::ComputeCallMiss(arguments().immediate(), kind_); - if (!maybe_obj->ToObject(&obj)) return maybe_obj; - } + if (!maybe_obj->ToObject(&obj)) return maybe_obj; __ Jump(Handle<Code>(Code::cast(obj)), RelocInfo::CODE_TARGET); return obj; } -MaybeObject* CallStubCompiler::CompileCallConstant( - Object* object, - JSObject* holder, - JSFunction* function, - String* name, - StubCompiler::CheckType check) { +MaybeObject* CallStubCompiler::CompileCallConstant(Object* object, + JSObject* holder, + JSFunction* function, + String* name, + CheckType check) { // ----------- S t a t e ------------- // rcx : function name // rsp[0] : return address @@ -950,8 +948,8 @@ MaybeObject* CallStubCompiler::CompileCallConstant( // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, NULL, function, name); Object* result; @@ -1467,7 +1465,7 @@ MaybeObject* CallStubCompiler::CompileStringCharAtCall( char_at_generator.GenerateFast(masm()); __ ret((argc + 1) * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1539,7 +1537,7 @@ MaybeObject* CallStubCompiler::CompileStringCharCodeAtCall( char_code_at_generator.GenerateFast(masm()); __ ret((argc + 1) * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_code_at_generator.GenerateSlow(masm(), call_helper); __ bind(&index_out_of_range); @@ -1608,7 +1606,7 @@ MaybeObject* CallStubCompiler::CompileStringFromCharCodeCall( char_from_code_generator.GenerateFast(masm()); __ ret(2 * kPointerSize); - ICRuntimeCallHelper call_helper; + StubRuntimeCallHelper call_helper; char_from_code_generator.GenerateSlow(masm(), call_helper); // Tail call the full function. We do not have to patch the receiver @@ -1832,8 +1830,8 @@ MaybeObject* CallStubCompiler::CompileCallGlobal(JSObject* object, // ----------------------------------- SharedFunctionInfo* function_info = function->shared(); - if (function_info->HasCustomCallGenerator()) { - const int id = function_info->custom_call_generator_id(); + if (function_info->HasBuiltinFunctionId()) { + BuiltinFunctionId id = function_info->builtin_function_id(); MaybeObject* maybe_result = CompileCustomCall( id, object, holder, cell, function, name); Object* result; @@ -2249,6 +2247,52 @@ MaybeObject* KeyedLoadStubCompiler::CompileLoadStringLength(String* name) { } +MaybeObject* KeyedLoadStubCompiler::CompileLoadSpecialized(JSObject* receiver) { + // ----------- S t a t e ------------- + // -- rax : key + // -- rdx : receiver + // -- esp[0] : return address + // ----------------------------------- + Label miss; + + // Check that the receiver isn't a smi. + __ JumpIfSmi(rdx, &miss); + + // Check that the map matches. + __ Cmp(FieldOperand(rdx, HeapObject::kMapOffset), + Handle<Map>(receiver->map())); + __ j(not_equal, &miss); + + // Check that the key is a smi. + __ JumpIfNotSmi(rax, &miss); + + // Get the elements array. + __ movq(rcx, FieldOperand(rdx, JSObject::kElementsOffset)); + __ AssertFastElements(rcx); + + // Check that the key is within bounds. + __ SmiCompare(rax, FieldOperand(rcx, FixedArray::kLengthOffset)); + __ j(above_equal, &miss); + + // Load the result and make sure it's not the hole. + SmiIndex index = masm()->SmiToIndex(rbx, rax, kPointerSizeLog2); + __ movq(rbx, FieldOperand(rcx, + index.reg, + index.scale, + FixedArray::kHeaderSize)); + __ CompareRoot(rbx, Heap::kTheHoleValueRootIndex); + __ j(equal, &miss); + __ movq(rax, rbx); + __ ret(0); + + __ bind(&miss); + GenerateLoadMiss(masm(), Code::KEYED_LOAD_IC); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + MaybeObject* StoreStubCompiler::CompileStoreCallback(JSObject* object, AccessorInfo* callback, String* name) { @@ -2477,6 +2521,63 @@ MaybeObject* KeyedStoreStubCompiler::CompileStoreField(JSObject* object, } +MaybeObject* KeyedStoreStubCompiler::CompileStoreSpecialized( + JSObject* receiver) { + // ----------- S t a t e ------------- + // -- rax : value + // -- rcx : key + // -- rdx : receiver + // -- rsp[0] : return address + // ----------------------------------- + Label miss; + + // Check that the receiver isn't a smi. + __ JumpIfSmi(rdx, &miss); + + // Check that the map matches. + __ Cmp(FieldOperand(rdx, HeapObject::kMapOffset), + Handle<Map>(receiver->map())); + __ j(not_equal, &miss); + + // Check that the key is a smi. + __ JumpIfNotSmi(rcx, &miss); + + // Get the elements array and make sure it is a fast element array, not 'cow'. + __ movq(rdi, FieldOperand(rdx, JSObject::kElementsOffset)); + __ Cmp(FieldOperand(rdi, HeapObject::kMapOffset), + Factory::fixed_array_map()); + __ j(not_equal, &miss); + + // Check that the key is within bounds. + if (receiver->IsJSArray()) { + __ SmiCompare(rcx, FieldOperand(rdx, JSArray::kLengthOffset)); + __ j(above_equal, &miss); + } else { + __ SmiCompare(rcx, FieldOperand(rdi, FixedArray::kLengthOffset)); + __ j(above_equal, &miss); + } + + // Do the store and update the write barrier. Make sure to preserve + // the value in register eax. + __ movq(rdx, rax); + __ SmiToInteger32(rcx, rcx); + __ movq(FieldOperand(rdi, rcx, times_pointer_size, FixedArray::kHeaderSize), + rax); + __ RecordWrite(rdi, 0, rdx, rcx); + + // Done. + __ ret(0); + + // Handle store cache miss. + __ bind(&miss); + Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Miss)); + __ jmp(ic, RelocInfo::CODE_TARGET); + + // Return the generated code. + return GetCode(NORMAL, NULL); +} + + void StubCompiler::GenerateLoadInterceptor(JSObject* object, JSObject* interceptor_holder, LookupResult* lookup, @@ -169,9 +169,19 @@ class ZoneList: public List<T, ZoneListAllocationPolicy> { // always zero. The capacity must be non-negative. explicit ZoneList(int capacity) : List<T, ZoneListAllocationPolicy>(capacity) { } + + // Construct a new ZoneList by copying the elements of the given ZoneList. + explicit ZoneList(const ZoneList<T>& other) + : List<T, ZoneListAllocationPolicy>(other.length()) { + AddAll(other); + } }; +// Introduce a convenience type for zone lists of map handles. +typedef ZoneList<Handle<Map> > ZoneMapList; + + // ZoneScopes keep track of the current parsing and compilation // nesting and cleans up generated ASTs in the Zone when exiting the // outer-most scope. |