| // Copyright 2013 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/deoptimizer.h" | |
| #include <memory> | |
| #include "src/accessors.h" | |
| #include "src/ast/prettyprinter.h" | |
| #include "src/codegen.h" | |
| #include "src/disasm.h" | |
| #include "src/frames-inl.h" | |
| #include "src/full-codegen/full-codegen.h" | |
| #include "src/global-handles.h" | |
| #include "src/interpreter/interpreter.h" | |
| #include "src/macro-assembler.h" | |
| #include "src/tracing/trace-event.h" | |
| #include "src/v8.h" | |
| namespace v8 { | |
| namespace internal { | |
| static MemoryChunk* AllocateCodeChunk(MemoryAllocator* allocator) { | |
| return allocator->AllocateChunk(Deoptimizer::GetMaxDeoptTableSize(), | |
| MemoryAllocator::GetCommitPageSize(), | |
| EXECUTABLE, NULL); | |
| } | |
| DeoptimizerData::DeoptimizerData(MemoryAllocator* allocator) | |
| : allocator_(allocator), | |
| current_(NULL) { | |
| for (int i = 0; i <= Deoptimizer::kLastBailoutType; ++i) { | |
| deopt_entry_code_entries_[i] = -1; | |
| deopt_entry_code_[i] = AllocateCodeChunk(allocator); | |
| } | |
| } | |
| DeoptimizerData::~DeoptimizerData() { | |
| for (int i = 0; i <= Deoptimizer::kLastBailoutType; ++i) { | |
| allocator_->Free<MemoryAllocator::kFull>(deopt_entry_code_[i]); | |
| deopt_entry_code_[i] = NULL; | |
| } | |
| } | |
| Code* Deoptimizer::FindDeoptimizingCode(Address addr) { | |
| if (function_->IsHeapObject()) { | |
| // Search all deoptimizing code in the native context of the function. | |
| Isolate* isolate = function_->GetIsolate(); | |
| Context* native_context = function_->context()->native_context(); | |
| Object* element = native_context->DeoptimizedCodeListHead(); | |
| while (!element->IsUndefined(isolate)) { | |
| Code* code = Code::cast(element); | |
| CHECK(code->kind() == Code::OPTIMIZED_FUNCTION); | |
| if (code->contains(addr)) return code; | |
| element = code->next_code_link(); | |
| } | |
| } | |
| return NULL; | |
| } | |
| // We rely on this function not causing a GC. It is called from generated code | |
| // without having a real stack frame in place. | |
| Deoptimizer* Deoptimizer::New(JSFunction* function, | |
| BailoutType type, | |
| unsigned bailout_id, | |
| Address from, | |
| int fp_to_sp_delta, | |
| Isolate* isolate) { | |
| Deoptimizer* deoptimizer = new Deoptimizer(isolate, function, type, | |
| bailout_id, from, fp_to_sp_delta); | |
| CHECK(isolate->deoptimizer_data()->current_ == NULL); | |
| isolate->deoptimizer_data()->current_ = deoptimizer; | |
| return deoptimizer; | |
| } | |
| // No larger than 2K on all platforms | |
| static const int kDeoptTableMaxEpilogueCodeSize = 2 * KB; | |
| size_t Deoptimizer::GetMaxDeoptTableSize() { | |
| int entries_size = | |
| Deoptimizer::kMaxNumberOfEntries * Deoptimizer::table_entry_size_; | |
| int commit_page_size = static_cast<int>(MemoryAllocator::GetCommitPageSize()); | |
| int page_count = ((kDeoptTableMaxEpilogueCodeSize + entries_size - 1) / | |
| commit_page_size) + 1; | |
| return static_cast<size_t>(commit_page_size * page_count); | |
| } | |
| Deoptimizer* Deoptimizer::Grab(Isolate* isolate) { | |
| Deoptimizer* result = isolate->deoptimizer_data()->current_; | |
| CHECK_NOT_NULL(result); | |
| result->DeleteFrameDescriptions(); | |
| isolate->deoptimizer_data()->current_ = NULL; | |
| return result; | |
| } | |
| DeoptimizedFrameInfo* Deoptimizer::DebuggerInspectableFrame( | |
| JavaScriptFrame* frame, | |
| int jsframe_index, | |
| Isolate* isolate) { | |
| CHECK(frame->is_optimized()); | |
| TranslatedState translated_values(frame); | |
| translated_values.Prepare(false, frame->fp()); | |
| TranslatedState::iterator frame_it = translated_values.end(); | |
| int counter = jsframe_index; | |
| for (auto it = translated_values.begin(); it != translated_values.end(); | |
| it++) { | |
| if (it->kind() == TranslatedFrame::kFunction || | |
| it->kind() == TranslatedFrame::kInterpretedFunction) { | |
| if (counter == 0) { | |
| frame_it = it; | |
| break; | |
| } | |
| counter--; | |
| } | |
| } | |
| CHECK(frame_it != translated_values.end()); | |
| DeoptimizedFrameInfo* info = | |
| new DeoptimizedFrameInfo(&translated_values, frame_it, isolate); | |
| return info; | |
| } | |
| void Deoptimizer::GenerateDeoptimizationEntries(MacroAssembler* masm, | |
| int count, | |
| BailoutType type) { | |
| TableEntryGenerator generator(masm, type, count); | |
| generator.Generate(); | |
| } | |
| void Deoptimizer::VisitAllOptimizedFunctionsForContext( | |
| Context* context, OptimizedFunctionVisitor* visitor) { | |
| DisallowHeapAllocation no_allocation; | |
| CHECK(context->IsNativeContext()); | |
| visitor->EnterContext(context); | |
| // Visit the list of optimized functions, removing elements that | |
| // no longer refer to optimized code. | |
| JSFunction* prev = NULL; | |
| Object* element = context->OptimizedFunctionsListHead(); | |
| Isolate* isolate = context->GetIsolate(); | |
| while (!element->IsUndefined(isolate)) { | |
| JSFunction* function = JSFunction::cast(element); | |
| Object* next = function->next_function_link(); | |
| if (function->code()->kind() != Code::OPTIMIZED_FUNCTION || | |
| (visitor->VisitFunction(function), | |
| function->code()->kind() != Code::OPTIMIZED_FUNCTION)) { | |
| // The function no longer refers to optimized code, or the visitor | |
| // changed the code to which it refers to no longer be optimized code. | |
| // Remove the function from this list. | |
| if (prev != NULL) { | |
| prev->set_next_function_link(next, UPDATE_WEAK_WRITE_BARRIER); | |
| } else { | |
| context->SetOptimizedFunctionsListHead(next); | |
| } | |
| // The visitor should not alter the link directly. | |
| CHECK_EQ(function->next_function_link(), next); | |
| // Set the next function link to undefined to indicate it is no longer | |
| // in the optimized functions list. | |
| function->set_next_function_link(context->GetHeap()->undefined_value(), | |
| SKIP_WRITE_BARRIER); | |
| } else { | |
| // The visitor should not alter the link directly. | |
| CHECK_EQ(function->next_function_link(), next); | |
| // preserve this element. | |
| prev = function; | |
| } | |
| element = next; | |
| } | |
| visitor->LeaveContext(context); | |
| } | |
| void Deoptimizer::VisitAllOptimizedFunctions( | |
| Isolate* isolate, | |
| OptimizedFunctionVisitor* visitor) { | |
| DisallowHeapAllocation no_allocation; | |
| // Run through the list of all native contexts. | |
| Object* context = isolate->heap()->native_contexts_list(); | |
| while (!context->IsUndefined(isolate)) { | |
| VisitAllOptimizedFunctionsForContext(Context::cast(context), visitor); | |
| context = Context::cast(context)->next_context_link(); | |
| } | |
| } | |
| // Unlink functions referring to code marked for deoptimization, then move | |
| // marked code from the optimized code list to the deoptimized code list, | |
| // and patch code for lazy deopt. | |
| void Deoptimizer::DeoptimizeMarkedCodeForContext(Context* context) { | |
| DisallowHeapAllocation no_allocation; | |
| // A "closure" that unlinks optimized code that is going to be | |
| // deoptimized from the functions that refer to it. | |
| class SelectedCodeUnlinker: public OptimizedFunctionVisitor { | |
| public: | |
| virtual void EnterContext(Context* context) { } // Don't care. | |
| virtual void LeaveContext(Context* context) { } // Don't care. | |
| virtual void VisitFunction(JSFunction* function) { | |
| Code* code = function->code(); | |
| if (!code->marked_for_deoptimization()) return; | |
| // Unlink this function and evict from optimized code map. | |
| SharedFunctionInfo* shared = function->shared(); | |
| function->set_code(shared->code()); | |
| if (FLAG_trace_deopt) { | |
| CodeTracer::Scope scope(code->GetHeap()->isolate()->GetCodeTracer()); | |
| PrintF(scope.file(), "[deoptimizer unlinked: "); | |
| function->PrintName(scope.file()); | |
| PrintF(scope.file(), | |
| " / %" V8PRIxPTR "]\n", reinterpret_cast<intptr_t>(function)); | |
| } | |
| } | |
| }; | |
| // Unlink all functions that refer to marked code. | |
| SelectedCodeUnlinker unlinker; | |
| VisitAllOptimizedFunctionsForContext(context, &unlinker); | |
| Isolate* isolate = context->GetHeap()->isolate(); | |
| #ifdef DEBUG | |
| Code* topmost_optimized_code = NULL; | |
| bool safe_to_deopt_topmost_optimized_code = false; | |
| // Make sure all activations of optimized code can deopt at their current PC. | |
| // The topmost optimized code has special handling because it cannot be | |
| // deoptimized due to weak object dependency. | |
| for (StackFrameIterator it(isolate, isolate->thread_local_top()); | |
| !it.done(); it.Advance()) { | |
| StackFrame::Type type = it.frame()->type(); | |
| if (type == StackFrame::OPTIMIZED) { | |
| Code* code = it.frame()->LookupCode(); | |
| JSFunction* function = | |
| static_cast<OptimizedFrame*>(it.frame())->function(); | |
| if (FLAG_trace_deopt) { | |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); | |
| PrintF(scope.file(), "[deoptimizer found activation of function: "); | |
| function->PrintName(scope.file()); | |
| PrintF(scope.file(), | |
| " / %" V8PRIxPTR "]\n", reinterpret_cast<intptr_t>(function)); | |
| } | |
| SafepointEntry safepoint = code->GetSafepointEntry(it.frame()->pc()); | |
| int deopt_index = safepoint.deoptimization_index(); | |
| // Turbofan deopt is checked when we are patching addresses on stack. | |
| bool turbofanned = code->is_turbofanned() && | |
| function->shared()->asm_function() && | |
| !FLAG_turbo_asm_deoptimization; | |
| bool safe_to_deopt = | |
| deopt_index != Safepoint::kNoDeoptimizationIndex || turbofanned; | |
| bool builtin = code->kind() == Code::BUILTIN; | |
| CHECK(topmost_optimized_code == NULL || safe_to_deopt || turbofanned || | |
| builtin); | |
| if (topmost_optimized_code == NULL) { | |
| topmost_optimized_code = code; | |
| safe_to_deopt_topmost_optimized_code = safe_to_deopt; | |
| } | |
| } | |
| } | |
| #endif | |
| // Move marked code from the optimized code list to the deoptimized | |
| // code list, collecting them into a ZoneList. | |
| Zone zone(isolate->allocator(), ZONE_NAME); | |
| ZoneList<Code*> codes(10, &zone); | |
| // Walk over all optimized code objects in this native context. | |
| Code* prev = NULL; | |
| Object* element = context->OptimizedCodeListHead(); | |
| while (!element->IsUndefined(isolate)) { | |
| Code* code = Code::cast(element); | |
| CHECK_EQ(code->kind(), Code::OPTIMIZED_FUNCTION); | |
| Object* next = code->next_code_link(); | |
| if (code->marked_for_deoptimization()) { | |
| // Put the code into the list for later patching. | |
| codes.Add(code, &zone); | |
| if (prev != NULL) { | |
| // Skip this code in the optimized code list. | |
| prev->set_next_code_link(next); | |
| } else { | |
| // There was no previous node, the next node is the new head. | |
| context->SetOptimizedCodeListHead(next); | |
| } | |
| // Move the code to the _deoptimized_ code list. | |
| code->set_next_code_link(context->DeoptimizedCodeListHead()); | |
| context->SetDeoptimizedCodeListHead(code); | |
| } else { | |
| // Not marked; preserve this element. | |
| prev = code; | |
| } | |
| element = next; | |
| } | |
| // We need a handle scope only because of the macro assembler, | |
| // which is used in code patching in EnsureCodeForDeoptimizationEntry. | |
| HandleScope scope(isolate); | |
| // Now patch all the codes for deoptimization. | |
| for (int i = 0; i < codes.length(); i++) { | |
| #ifdef DEBUG | |
| if (codes[i] == topmost_optimized_code) { | |
| DCHECK(safe_to_deopt_topmost_optimized_code); | |
| } | |
| #endif | |
| // It is finally time to die, code object. | |
| // Remove the code from optimized code map. | |
| DeoptimizationInputData* deopt_data = | |
| DeoptimizationInputData::cast(codes[i]->deoptimization_data()); | |
| SharedFunctionInfo* shared = | |
| SharedFunctionInfo::cast(deopt_data->SharedFunctionInfo()); | |
| shared->EvictFromOptimizedCodeMap(codes[i], "deoptimized code"); | |
| // Do platform-specific patching to force any activations to lazy deopt. | |
| PatchCodeForDeoptimization(isolate, codes[i]); | |
| // We might be in the middle of incremental marking with compaction. | |
| // Tell collector to treat this code object in a special way and | |
| // ignore all slots that might have been recorded on it. | |
| isolate->heap()->mark_compact_collector()->InvalidateCode(codes[i]); | |
| } | |
| } | |
| void Deoptimizer::DeoptimizeAll(Isolate* isolate) { | |
| RuntimeCallTimerScope runtimeTimer(isolate, | |
| &RuntimeCallStats::DeoptimizeCode); | |
| TimerEventScope<TimerEventDeoptimizeCode> timer(isolate); | |
| TRACE_EVENT0("v8", "V8.DeoptimizeCode"); | |
| if (FLAG_trace_deopt) { | |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); | |
| PrintF(scope.file(), "[deoptimize all code in all contexts]\n"); | |
| } | |
| DisallowHeapAllocation no_allocation; | |
| // For all contexts, mark all code, then deoptimize. | |
| Object* context = isolate->heap()->native_contexts_list(); | |
| while (!context->IsUndefined(isolate)) { | |
| Context* native_context = Context::cast(context); | |
| MarkAllCodeForContext(native_context); | |
| DeoptimizeMarkedCodeForContext(native_context); | |
| context = native_context->next_context_link(); | |
| } | |
| } | |
| void Deoptimizer::DeoptimizeMarkedCode(Isolate* isolate) { | |
| RuntimeCallTimerScope runtimeTimer(isolate, | |
| &RuntimeCallStats::DeoptimizeCode); | |
| TimerEventScope<TimerEventDeoptimizeCode> timer(isolate); | |
| TRACE_EVENT0("v8", "V8.DeoptimizeCode"); | |
| if (FLAG_trace_deopt) { | |
| CodeTracer::Scope scope(isolate->GetCodeTracer()); | |
| PrintF(scope.file(), "[deoptimize marked code in all contexts]\n"); | |
| } | |
| DisallowHeapAllocation no_allocation; | |
| // For all contexts, deoptimize code already marked. | |
| Object* context = isolate->heap()->native_contexts_list(); | |
| while (!context->IsUndefined(isolate)) { | |
| Context* native_context = Context::cast(context); | |
| DeoptimizeMarkedCodeForContext(native_context); | |
| context = native_context->next_context_link(); | |
| } | |
| } | |
| void Deoptimizer::MarkAllCodeForContext(Context* context) { | |
| Object* element = context->OptimizedCodeListHead(); | |
| Isolate* isolate = context->GetIsolate(); | |
| while (!element->IsUndefined(isolate)) { | |
| Code* code = Code::cast(element); | |
| CHECK_EQ(code->kind(), Code::OPTIMIZED_FUNCTION); | |
| code->set_marked_for_deoptimization(true); | |
| element = code->next_code_link(); | |
| } | |
| } | |
| void Deoptimizer::DeoptimizeFunction(JSFunction* function) { | |
| Isolate* isolate = function->GetIsolate(); | |
| RuntimeCallTimerScope runtimeTimer(isolate, | |
| &RuntimeCallStats::DeoptimizeCode); | |
| TimerEventScope<TimerEventDeoptimizeCode> timer(isolate); | |
| TRACE_EVENT0("v8", "V8.DeoptimizeCode"); | |
| Code* code = function->code(); | |
| if (code->kind() == Code::OPTIMIZED_FUNCTION) { | |
| // Mark the code for deoptimization and unlink any functions that also | |
| // refer to that code. The code cannot be shared across native contexts, | |
| // so we only need to search one. | |
| code->set_marked_for_deoptimization(true); | |
| DeoptimizeMarkedCodeForContext(function->context()->native_context()); | |
| } | |
| } | |
| void Deoptimizer::ComputeOutputFrames(Deoptimizer* deoptimizer) { | |
| deoptimizer->DoComputeOutputFrames(); | |
| } | |
| bool Deoptimizer::TraceEnabledFor(StackFrame::Type frame_type) { | |
| return (frame_type == StackFrame::STUB) ? FLAG_trace_stub_failures | |
| : FLAG_trace_deopt; | |
| } | |
| const char* Deoptimizer::MessageFor(BailoutType type) { | |
| switch (type) { | |
| case EAGER: return "eager"; | |
| case SOFT: return "soft"; | |
| case LAZY: return "lazy"; | |
| } | |
| FATAL("Unsupported deopt type"); | |
| return NULL; | |
| } | |
| Deoptimizer::Deoptimizer(Isolate* isolate, JSFunction* function, | |
| BailoutType type, unsigned bailout_id, Address from, | |
| int fp_to_sp_delta) | |
| : isolate_(isolate), | |
| function_(function), | |
| bailout_id_(bailout_id), | |
| bailout_type_(type), | |
| from_(from), | |
| fp_to_sp_delta_(fp_to_sp_delta), | |
| deoptimizing_throw_(false), | |
| catch_handler_data_(-1), | |
| catch_handler_pc_offset_(-1), | |
| input_(nullptr), | |
| output_count_(0), | |
| jsframe_count_(0), | |
| output_(nullptr), | |
| caller_frame_top_(0), | |
| caller_fp_(0), | |
| caller_pc_(0), | |
| caller_constant_pool_(0), | |
| input_frame_context_(0), | |
| stack_fp_(0), | |
| trace_scope_(nullptr) { | |
| if (isolate->deoptimizer_lazy_throw()) { | |
| isolate->set_deoptimizer_lazy_throw(false); | |
| deoptimizing_throw_ = true; | |
| } | |
| // For COMPILED_STUBs called from builtins, the function pointer is a SMI | |
| // indicating an internal frame. | |
| if (function->IsSmi()) { | |
| function = nullptr; | |
| } | |
| DCHECK(from != nullptr); | |
| if (function != nullptr && function->IsOptimized()) { | |
| function->shared()->increment_deopt_count(); | |
| if (bailout_type_ == Deoptimizer::SOFT) { | |
| isolate->counters()->soft_deopts_executed()->Increment(); | |
| // Soft deopts shouldn't count against the overall re-optimization count | |
| // that can eventually lead to disabling optimization for a function. | |
| int opt_count = function->shared()->opt_count(); | |
| if (opt_count > 0) opt_count--; | |
| function->shared()->set_opt_count(opt_count); | |
| } | |
| } | |
| compiled_code_ = FindOptimizedCode(function); | |
| #if DEBUG | |
| DCHECK(compiled_code_ != NULL); | |
| if (type == EAGER || type == SOFT || type == LAZY) { | |
| DCHECK(compiled_code_->kind() != Code::FUNCTION); | |
| } | |
| #endif | |
| StackFrame::Type frame_type = function == NULL | |
| ? StackFrame::STUB | |
| : StackFrame::JAVA_SCRIPT; | |
| trace_scope_ = TraceEnabledFor(frame_type) | |
| ? new CodeTracer::Scope(isolate->GetCodeTracer()) | |
| : NULL; | |
| #ifdef DEBUG | |
| CHECK(AllowHeapAllocation::IsAllowed()); | |
| disallow_heap_allocation_ = new DisallowHeapAllocation(); | |
| #endif // DEBUG | |
| if (compiled_code_->kind() == Code::OPTIMIZED_FUNCTION) { | |
| PROFILE(isolate_, CodeDeoptEvent(compiled_code_, from_, fp_to_sp_delta_)); | |
| } | |
| unsigned size = ComputeInputFrameSize(); | |
| int parameter_count = | |
| function == nullptr | |
| ? 0 | |
| : (function->shared()->internal_formal_parameter_count() + 1); | |
| input_ = new (size) FrameDescription(size, parameter_count); | |
| input_->SetFrameType(frame_type); | |
| } | |
| Code* Deoptimizer::FindOptimizedCode(JSFunction* function) { | |
| Code* compiled_code = FindDeoptimizingCode(from_); | |
| return (compiled_code == NULL) | |
| ? static_cast<Code*>(isolate_->FindCodeObject(from_)) | |
| : compiled_code; | |
| } | |
| void Deoptimizer::PrintFunctionName() { | |
| if (function_ != nullptr && function_->IsJSFunction()) { | |
| function_->ShortPrint(trace_scope_->file()); | |
| } else { | |
| PrintF(trace_scope_->file(), | |
| "%s", Code::Kind2String(compiled_code_->kind())); | |
| } | |
| } | |
| Deoptimizer::~Deoptimizer() { | |
| DCHECK(input_ == NULL && output_ == NULL); | |
| DCHECK(disallow_heap_allocation_ == NULL); | |
| delete trace_scope_; | |
| } | |
| 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; | |
| #ifdef DEBUG | |
| CHECK(!AllowHeapAllocation::IsAllowed()); | |
| CHECK(disallow_heap_allocation_ != NULL); | |
| delete disallow_heap_allocation_; | |
| disallow_heap_allocation_ = NULL; | |
| #endif // DEBUG | |
| } | |
| Address Deoptimizer::GetDeoptimizationEntry(Isolate* isolate, | |
| int id, | |
| BailoutType type, | |
| GetEntryMode mode) { | |
| CHECK_GE(id, 0); | |
| if (id >= kMaxNumberOfEntries) return NULL; | |
| if (mode == ENSURE_ENTRY_CODE) { | |
| EnsureCodeForDeoptimizationEntry(isolate, type, id); | |
| } else { | |
| CHECK_EQ(mode, CALCULATE_ENTRY_ADDRESS); | |
| } | |
| DeoptimizerData* data = isolate->deoptimizer_data(); | |
| CHECK_LE(type, kLastBailoutType); | |
| MemoryChunk* base = data->deopt_entry_code_[type]; | |
| return base->area_start() + (id * table_entry_size_); | |
| } | |
| int Deoptimizer::GetDeoptimizationId(Isolate* isolate, | |
| Address addr, | |
| BailoutType type) { | |
| DeoptimizerData* data = isolate->deoptimizer_data(); | |
| MemoryChunk* base = data->deopt_entry_code_[type]; | |
| Address start = base->area_start(); | |
| if (addr < start || | |
| addr >= start + (kMaxNumberOfEntries * table_entry_size_)) { | |
| return kNotDeoptimizationEntry; | |
| } | |
| DCHECK_EQ(0, | |
| static_cast<int>(addr - start) % table_entry_size_); | |
| return static_cast<int>(addr - start) / table_entry_size_; | |
| } | |
| int Deoptimizer::GetOutputInfo(DeoptimizationOutputData* data, | |
| BailoutId 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(); | |
| for (int i = 0; i < length; i++) { | |
| if (data->AstId(i) == id) { | |
| return data->PcAndState(i)->value(); | |
| } | |
| } | |
| OFStream os(stderr); | |
| os << "[couldn't find pc offset for node=" << id.ToInt() << "]\n" | |
| << "[method: " << shared->DebugName()->ToCString().get() << "]\n" | |
| << "[source:\n" << SourceCodeOf(shared) << "\n]" << std::endl; | |
| shared->GetHeap()->isolate()->PushStackTraceAndDie(0xfefefefe, data, shared, | |
| 0xfefefeff); | |
| FATAL("unable to find pc offset during deoptimization"); | |
| return -1; | |
| } | |
| int Deoptimizer::GetDeoptimizedCodeCount(Isolate* isolate) { | |
| int length = 0; | |
| // Count all entries in the deoptimizing code list of every context. | |
| Object* context = isolate->heap()->native_contexts_list(); | |
| while (!context->IsUndefined(isolate)) { | |
| Context* native_context = Context::cast(context); | |
| Object* element = native_context->DeoptimizedCodeListHead(); | |
| while (!element->IsUndefined(isolate)) { | |
| Code* code = Code::cast(element); | |
| DCHECK(code->kind() == Code::OPTIMIZED_FUNCTION); | |
| length++; | |
| element = code->next_code_link(); | |
| } | |
| context = Context::cast(context)->next_context_link(); | |
| } | |
| return length; | |
| } | |
| namespace { | |
| int LookupCatchHandler(TranslatedFrame* translated_frame, int* data_out) { | |
| switch (translated_frame->kind()) { | |
| case TranslatedFrame::kFunction: { | |
| BailoutId node_id = translated_frame->node_id(); | |
| JSFunction* function = | |
| JSFunction::cast(translated_frame->begin()->GetRawValue()); | |
| Code* non_optimized_code = function->shared()->code(); | |
| FixedArray* raw_data = non_optimized_code->deoptimization_data(); | |
| DeoptimizationOutputData* data = DeoptimizationOutputData::cast(raw_data); | |
| unsigned pc_and_state = | |
| Deoptimizer::GetOutputInfo(data, node_id, function->shared()); | |
| unsigned pc_offset = FullCodeGenerator::PcField::decode(pc_and_state); | |
| HandlerTable* table = | |
| HandlerTable::cast(non_optimized_code->handler_table()); | |
| HandlerTable::CatchPrediction prediction; | |
| return table->LookupRange(pc_offset, data_out, &prediction); | |
| } | |
| case TranslatedFrame::kInterpretedFunction: { | |
| int bytecode_offset = translated_frame->node_id().ToInt(); | |
| JSFunction* function = | |
| JSFunction::cast(translated_frame->begin()->GetRawValue()); | |
| BytecodeArray* bytecode = function->shared()->bytecode_array(); | |
| HandlerTable* table = HandlerTable::cast(bytecode->handler_table()); | |
| HandlerTable::CatchPrediction prediction; | |
| return table->LookupRange(bytecode_offset, data_out, &prediction); | |
| } | |
| default: | |
| break; | |
| } | |
| return -1; | |
| } | |
| } // namespace | |
| // We rely on this function not causing a GC. It is called from generated code | |
| // without having a real stack frame in place. | |
| void Deoptimizer::DoComputeOutputFrames() { | |
| base::ElapsedTimer timer; | |
| // Determine basic deoptimization information. The optimized frame is | |
| // described by the input data. | |
| DeoptimizationInputData* input_data = | |
| DeoptimizationInputData::cast(compiled_code_->deoptimization_data()); | |
| { | |
| // Read caller's PC, caller's FP and caller's constant pool values | |
| // from input frame. Compute caller's frame top address. | |
| Register fp_reg = JavaScriptFrame::fp_register(); | |
| stack_fp_ = input_->GetRegister(fp_reg.code()); | |
| caller_frame_top_ = stack_fp_ + ComputeInputFrameAboveFpFixedSize(); | |
| Address fp_address = input_->GetFramePointerAddress(); | |
| caller_fp_ = Memory::intptr_at(fp_address); | |
| caller_pc_ = | |
| Memory::intptr_at(fp_address + CommonFrameConstants::kCallerPCOffset); | |
| input_frame_context_ = Memory::intptr_at( | |
| fp_address + CommonFrameConstants::kContextOrFrameTypeOffset); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| caller_constant_pool_ = Memory::intptr_at( | |
| fp_address + CommonFrameConstants::kConstantPoolOffset); | |
| } | |
| } | |
| if (trace_scope_ != NULL) { | |
| timer.Start(); | |
| PrintF(trace_scope_->file(), "[deoptimizing (DEOPT %s): begin ", | |
| MessageFor(bailout_type_)); | |
| PrintFunctionName(); | |
| PrintF(trace_scope_->file(), | |
| " (opt #%d) @%d, FP to SP delta: %d, caller sp: 0x%08" V8PRIxPTR | |
| "]\n", | |
| input_data->OptimizationId()->value(), bailout_id_, fp_to_sp_delta_, | |
| caller_frame_top_); | |
| if (bailout_type_ == EAGER || bailout_type_ == SOFT || | |
| (compiled_code_->is_hydrogen_stub())) { | |
| compiled_code_->PrintDeoptLocation(trace_scope_->file(), from_); | |
| } | |
| } | |
| BailoutId node_id = input_data->AstId(bailout_id_); | |
| ByteArray* translations = input_data->TranslationByteArray(); | |
| unsigned translation_index = | |
| input_data->TranslationIndex(bailout_id_)->value(); | |
| TranslationIterator state_iterator(translations, translation_index); | |
| translated_state_.Init( | |
| input_->GetFramePointerAddress(), &state_iterator, | |
| input_data->LiteralArray(), input_->GetRegisterValues(), | |
| trace_scope_ == nullptr ? nullptr : trace_scope_->file()); | |
| // Do the input frame to output frame(s) translation. | |
| size_t count = translated_state_.frames().size(); | |
| // If we are supposed to go to the catch handler, find the catching frame | |
| // for the catch and make sure we only deoptimize upto that frame. | |
| if (deoptimizing_throw_) { | |
| size_t catch_handler_frame_index = count; | |
| for (size_t i = count; i-- > 0;) { | |
| catch_handler_pc_offset_ = LookupCatchHandler( | |
| &(translated_state_.frames()[i]), &catch_handler_data_); | |
| if (catch_handler_pc_offset_ >= 0) { | |
| catch_handler_frame_index = i; | |
| break; | |
| } | |
| } | |
| CHECK_LT(catch_handler_frame_index, count); | |
| count = catch_handler_frame_index + 1; | |
| } | |
| DCHECK(output_ == NULL); | |
| output_ = new FrameDescription*[count]; | |
| for (size_t i = 0; i < count; ++i) { | |
| output_[i] = NULL; | |
| } | |
| output_count_ = static_cast<int>(count); | |
| // Translate each output frame. | |
| int frame_index = 0; // output_frame_index | |
| for (size_t i = 0; i < count; ++i, ++frame_index) { | |
| // Read the ast node id, function, and frame height for this output frame. | |
| TranslatedFrame* translated_frame = &(translated_state_.frames()[i]); | |
| switch (translated_frame->kind()) { | |
| case TranslatedFrame::kFunction: | |
| DoComputeJSFrame(translated_frame, frame_index, | |
| deoptimizing_throw_ && i == count - 1); | |
| jsframe_count_++; | |
| break; | |
| case TranslatedFrame::kInterpretedFunction: | |
| DoComputeInterpretedFrame(translated_frame, frame_index, | |
| deoptimizing_throw_ && i == count - 1); | |
| jsframe_count_++; | |
| break; | |
| case TranslatedFrame::kArgumentsAdaptor: | |
| DoComputeArgumentsAdaptorFrame(translated_frame, frame_index); | |
| break; | |
| case TranslatedFrame::kTailCallerFunction: | |
| DoComputeTailCallerFrame(translated_frame, frame_index); | |
| // Tail caller frame translations do not produce output frames. | |
| frame_index--; | |
| output_count_--; | |
| break; | |
| case TranslatedFrame::kConstructStub: | |
| DoComputeConstructStubFrame(translated_frame, frame_index); | |
| break; | |
| case TranslatedFrame::kGetter: | |
| DoComputeAccessorStubFrame(translated_frame, frame_index, false); | |
| break; | |
| case TranslatedFrame::kSetter: | |
| DoComputeAccessorStubFrame(translated_frame, frame_index, true); | |
| break; | |
| case TranslatedFrame::kCompiledStub: | |
| DoComputeCompiledStubFrame(translated_frame, frame_index); | |
| break; | |
| case TranslatedFrame::kInvalid: | |
| FATAL("invalid frame"); | |
| break; | |
| } | |
| } | |
| // Print some helpful diagnostic information. | |
| if (trace_scope_ != NULL) { | |
| double ms = timer.Elapsed().InMillisecondsF(); | |
| int index = output_count_ - 1; // Index of the topmost frame. | |
| PrintF(trace_scope_->file(), "[deoptimizing (%s): end ", | |
| MessageFor(bailout_type_)); | |
| PrintFunctionName(); | |
| PrintF(trace_scope_->file(), | |
| " @%d => node=%d, pc=0x%08" V8PRIxPTR ", caller sp=0x%08" V8PRIxPTR | |
| ", state=%s, took %0.3f ms]\n", | |
| bailout_id_, node_id.ToInt(), output_[index]->GetPc(), | |
| caller_frame_top_, BailoutStateToString(static_cast<BailoutState>( | |
| output_[index]->GetState()->value())), | |
| ms); | |
| } | |
| } | |
| void Deoptimizer::DoComputeJSFrame(TranslatedFrame* translated_frame, | |
| int frame_index, bool goto_catch_handler) { | |
| SharedFunctionInfo* shared = translated_frame->raw_shared_info(); | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| bool is_bottommost = (0 == frame_index); | |
| bool is_topmost = (output_count_ - 1 == frame_index); | |
| int input_index = 0; | |
| BailoutId node_id = translated_frame->node_id(); | |
| unsigned height = | |
| translated_frame->height() - 1; // Do not count the context. | |
| unsigned height_in_bytes = height * kPointerSize; | |
| if (goto_catch_handler) { | |
| // Take the stack height from the handler table. | |
| height = catch_handler_data_; | |
| // We also make space for the exception itself. | |
| height_in_bytes = (height + 1) * kPointerSize; | |
| CHECK(is_topmost); | |
| } | |
| JSFunction* function = JSFunction::cast(value_iterator->GetRawValue()); | |
| value_iterator++; | |
| input_index++; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), " translating frame "); | |
| std::unique_ptr<char[]> name = shared->DebugName()->ToCString(); | |
| PrintF(trace_scope_->file(), "%s", name.get()); | |
| PrintF(trace_scope_->file(), " => node=%d, height=%d%s\n", node_id.ToInt(), | |
| height_in_bytes, goto_catch_handler ? " (throw)" : ""); | |
| } | |
| // The 'fixed' part of the frame consists of the incoming parameters and | |
| // the part described by JavaScriptFrameConstants. | |
| unsigned fixed_frame_size = ComputeJavascriptFixedSize(shared); | |
| unsigned output_frame_size = height_in_bytes + fixed_frame_size; | |
| // Allocate and store the output frame description. | |
| int parameter_count = shared->internal_formal_parameter_count() + 1; | |
| FrameDescription* output_frame = new (output_frame_size) | |
| FrameDescription(output_frame_size, parameter_count); | |
| output_frame->SetFrameType(StackFrame::JAVA_SCRIPT); | |
| CHECK(frame_index >= 0 && frame_index < output_count_); | |
| CHECK_NULL(output_[frame_index]); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address; | |
| if (is_bottommost) { | |
| top_address = caller_frame_top_ - output_frame_size; | |
| } else { | |
| top_address = output_[frame_index - 1]->GetTop() - output_frame_size; | |
| } | |
| output_frame->SetTop(top_address); | |
| // Compute the incoming parameter translation. | |
| unsigned output_offset = output_frame_size; | |
| for (int i = 0; i < parameter_count; ++i) { | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), " -------------------------\n"); | |
| } | |
| // 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 -= kPCOnStackSize; | |
| intptr_t value; | |
| if (is_bottommost) { | |
| value = caller_pc_; | |
| } else { | |
| value = output_[frame_index - 1]->GetPc(); | |
| } | |
| output_frame->SetCallerPc(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's pc\n"); | |
| // 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 -= kFPOnStackSize; | |
| if (is_bottommost) { | |
| value = caller_fp_; | |
| } else { | |
| value = output_[frame_index - 1]->GetFp(); | |
| } | |
| output_frame->SetCallerFp(output_offset, value); | |
| intptr_t fp_value = top_address + output_offset; | |
| output_frame->SetFp(fp_value); | |
| if (is_topmost) { | |
| Register fp_reg = JavaScriptFrame::fp_register(); | |
| output_frame->SetRegister(fp_reg.code(), fp_value); | |
| } | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // For the bottommost output frame the constant pool pointer can be gotten | |
| // from the input frame. For subsequent output frames, it can be read from | |
| // the previous frame. | |
| output_offset -= kPointerSize; | |
| if (is_bottommost) { | |
| value = caller_constant_pool_; | |
| } else { | |
| value = output_[frame_index - 1]->GetConstantPool(); | |
| } | |
| output_frame->SetCallerConstantPool(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // For the bottommost output frame the context can be gotten from the input | |
| // frame. For all subsequent output frames it can be gotten from the function | |
| // so long as we don't inline functions that need local contexts. | |
| output_offset -= kPointerSize; | |
| // When deoptimizing into a catch block, we need to take the context | |
| // from just above the top of the operand stack (we push the context | |
| // at the entry of the try block). | |
| TranslatedFrame::iterator context_pos = value_iterator; | |
| int context_input_index = input_index; | |
| if (goto_catch_handler) { | |
| for (unsigned i = 0; i < height + 1; ++i) { | |
| context_pos++; | |
| context_input_index++; | |
| } | |
| } | |
| // Read the context from the translations. | |
| Object* context = context_pos->GetRawValue(); | |
| if (context->IsUndefined(isolate_)) { | |
| // If the context was optimized away, just use the context from | |
| // the activation. This should only apply to Crankshaft code. | |
| CHECK(!compiled_code_->is_turbofanned()); | |
| context = is_bottommost ? reinterpret_cast<Object*>(input_frame_context_) | |
| : function->context(); | |
| } | |
| value = reinterpret_cast<intptr_t>(context); | |
| output_frame->SetContext(value); | |
| WriteValueToOutput(context, context_input_index, frame_index, output_offset, | |
| "context "); | |
| if (context == isolate_->heap()->arguments_marker()) { | |
| Address output_address = | |
| reinterpret_cast<Address>(output_[frame_index]->GetTop()) + | |
| output_offset; | |
| values_to_materialize_.push_back({output_address, context_pos}); | |
| } | |
| value_iterator++; | |
| input_index++; | |
| // The function was mentioned explicitly in the BEGIN_FRAME. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(function); | |
| WriteValueToOutput(function, 0, frame_index, output_offset, "function "); | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), " -------------------------\n"); | |
| } | |
| // Translate the rest of the frame. | |
| for (unsigned i = 0; i < height; ++i) { | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| if (goto_catch_handler) { | |
| // Write out the exception for the catch handler. | |
| output_offset -= kPointerSize; | |
| Object* exception_obj = reinterpret_cast<Object*>( | |
| input_->GetRegister(FullCodeGenerator::result_register().code())); | |
| WriteValueToOutput(exception_obj, input_index, frame_index, output_offset, | |
| "exception "); | |
| input_index++; | |
| } | |
| CHECK_EQ(0u, output_offset); | |
| // Update constant pool. | |
| Code* non_optimized_code = shared->code(); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(non_optimized_code->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| if (is_topmost) { | |
| Register constant_pool_reg = | |
| JavaScriptFrame::constant_pool_pointer_register(); | |
| output_frame->SetRegister(constant_pool_reg.code(), constant_pool_value); | |
| } | |
| } | |
| // Compute this frame's PC and state. | |
| 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 = goto_catch_handler | |
| ? catch_handler_pc_offset_ | |
| : FullCodeGenerator::PcField::decode(pc_and_state); | |
| intptr_t pc_value = reinterpret_cast<intptr_t>(start + pc_offset); | |
| output_frame->SetPc(pc_value); | |
| // If we are going to the catch handler, then the exception lives in | |
| // the accumulator. | |
| BailoutState state = | |
| goto_catch_handler | |
| ? BailoutState::TOS_REGISTER | |
| : FullCodeGenerator::BailoutStateField::decode(pc_and_state); | |
| output_frame->SetState(Smi::FromInt(static_cast<int>(state))); | |
| // Clear the context register. The context might be a de-materialized object | |
| // and will be materialized by {Runtime_NotifyDeoptimized}. For additional | |
| // safety we use Smi(0) instead of the potential {arguments_marker} here. | |
| if (is_topmost) { | |
| intptr_t context_value = reinterpret_cast<intptr_t>(Smi::kZero); | |
| Register context_reg = JavaScriptFrame::context_register(); | |
| output_frame->SetRegister(context_reg.code(), context_value); | |
| } | |
| // Set the continuation for the topmost frame. | |
| if (is_topmost) { | |
| Builtins* builtins = isolate_->builtins(); | |
| Code* continuation = builtins->builtin(Builtins::kNotifyDeoptimized); | |
| if (bailout_type_ == LAZY) { | |
| continuation = builtins->builtin(Builtins::kNotifyLazyDeoptimized); | |
| } else if (bailout_type_ == SOFT) { | |
| continuation = builtins->builtin(Builtins::kNotifySoftDeoptimized); | |
| } else { | |
| CHECK_EQ(bailout_type_, EAGER); | |
| } | |
| output_frame->SetContinuation( | |
| reinterpret_cast<intptr_t>(continuation->entry())); | |
| } | |
| } | |
| void Deoptimizer::DoComputeInterpretedFrame(TranslatedFrame* translated_frame, | |
| int frame_index, | |
| bool goto_catch_handler) { | |
| SharedFunctionInfo* shared = translated_frame->raw_shared_info(); | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| bool is_bottommost = (0 == frame_index); | |
| bool is_topmost = (output_count_ - 1 == frame_index); | |
| int input_index = 0; | |
| int bytecode_offset = translated_frame->node_id().ToInt(); | |
| unsigned height = translated_frame->height(); | |
| unsigned height_in_bytes = height * kPointerSize; | |
| // All tranlations for interpreted frames contain the accumulator and hence | |
| // are assumed to be in bailout state {BailoutState::TOS_REGISTER}. However | |
| // such a state is only supported for the topmost frame. We need to skip | |
| // pushing the accumulator for any non-topmost frame. | |
| if (!is_topmost) height_in_bytes -= kPointerSize; | |
| JSFunction* function = JSFunction::cast(value_iterator->GetRawValue()); | |
| value_iterator++; | |
| input_index++; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), " translating interpreted frame "); | |
| std::unique_ptr<char[]> name = shared->DebugName()->ToCString(); | |
| PrintF(trace_scope_->file(), "%s", name.get()); | |
| PrintF(trace_scope_->file(), " => bytecode_offset=%d, height=%d%s\n", | |
| bytecode_offset, height_in_bytes, | |
| goto_catch_handler ? " (throw)" : ""); | |
| } | |
| if (goto_catch_handler) { | |
| bytecode_offset = catch_handler_pc_offset_; | |
| } | |
| // The 'fixed' part of the frame consists of the incoming parameters and | |
| // the part described by InterpreterFrameConstants. | |
| unsigned fixed_frame_size = ComputeInterpretedFixedSize(shared); | |
| unsigned output_frame_size = height_in_bytes + fixed_frame_size; | |
| // Allocate and store the output frame description. | |
| int parameter_count = shared->internal_formal_parameter_count() + 1; | |
| FrameDescription* output_frame = new (output_frame_size) | |
| FrameDescription(output_frame_size, parameter_count); | |
| output_frame->SetFrameType(StackFrame::INTERPRETED); | |
| CHECK(frame_index >= 0 && frame_index < output_count_); | |
| CHECK_NULL(output_[frame_index]); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address; | |
| if (is_bottommost) { | |
| top_address = caller_frame_top_ - output_frame_size; | |
| } else { | |
| top_address = output_[frame_index - 1]->GetTop() - output_frame_size; | |
| } | |
| output_frame->SetTop(top_address); | |
| // Compute the incoming parameter translation. | |
| unsigned output_offset = output_frame_size; | |
| for (int i = 0; i < parameter_count; ++i) { | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), " -------------------------\n"); | |
| } | |
| // There are no translation commands for the caller's pc and fp, the | |
| // context, the function, new.target and the bytecode offset. 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 -= kPCOnStackSize; | |
| intptr_t value; | |
| if (is_bottommost) { | |
| value = caller_pc_; | |
| } else { | |
| value = output_[frame_index - 1]->GetPc(); | |
| } | |
| output_frame->SetCallerPc(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's pc\n"); | |
| // 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 -= kFPOnStackSize; | |
| if (is_bottommost) { | |
| value = caller_fp_; | |
| } else { | |
| value = output_[frame_index - 1]->GetFp(); | |
| } | |
| output_frame->SetCallerFp(output_offset, value); | |
| intptr_t fp_value = top_address + output_offset; | |
| output_frame->SetFp(fp_value); | |
| if (is_topmost) { | |
| Register fp_reg = InterpretedFrame::fp_register(); | |
| output_frame->SetRegister(fp_reg.code(), fp_value); | |
| } | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // For the bottommost output frame the constant pool pointer can be gotten | |
| // from the input frame. For subsequent output frames, it can be read from | |
| // the previous frame. | |
| output_offset -= kPointerSize; | |
| if (is_bottommost) { | |
| value = caller_constant_pool_; | |
| } else { | |
| value = output_[frame_index - 1]->GetConstantPool(); | |
| } | |
| output_frame->SetCallerConstantPool(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // For the bottommost output frame the context can be gotten from the input | |
| // frame. For all subsequent output frames it can be gotten from the function | |
| // so long as we don't inline functions that need local contexts. | |
| output_offset -= kPointerSize; | |
| // When deoptimizing into a catch block, we need to take the context | |
| // from a register that was specified in the handler table. | |
| TranslatedFrame::iterator context_pos = value_iterator; | |
| int context_input_index = input_index; | |
| if (goto_catch_handler) { | |
| // Skip to the translated value of the register specified | |
| // in the handler table. | |
| for (int i = 0; i < catch_handler_data_ + 1; ++i) { | |
| context_pos++; | |
| context_input_index++; | |
| } | |
| } | |
| // Read the context from the translations. | |
| Object* context = context_pos->GetRawValue(); | |
| value = reinterpret_cast<intptr_t>(context); | |
| output_frame->SetContext(value); | |
| WriteValueToOutput(context, context_input_index, frame_index, output_offset, | |
| "context "); | |
| if (context == isolate_->heap()->arguments_marker()) { | |
| Address output_address = | |
| reinterpret_cast<Address>(output_[frame_index]->GetTop()) + | |
| output_offset; | |
| values_to_materialize_.push_back({output_address, context_pos}); | |
| } | |
| value_iterator++; | |
| input_index++; | |
| // The function was mentioned explicitly in the BEGIN_FRAME. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(function); | |
| WriteValueToOutput(function, 0, frame_index, output_offset, "function "); | |
| // The new.target slot is only used during function activiation which is | |
| // before the first deopt point, so should never be needed. Just set it to | |
| // undefined. | |
| output_offset -= kPointerSize; | |
| Object* new_target = isolate_->heap()->undefined_value(); | |
| WriteValueToOutput(new_target, 0, frame_index, output_offset, "new_target "); | |
| // Set the bytecode array pointer. | |
| output_offset -= kPointerSize; | |
| Object* bytecode_array = shared->HasDebugInfo() | |
| ? shared->GetDebugInfo()->DebugBytecodeArray() | |
| : shared->bytecode_array(); | |
| WriteValueToOutput(bytecode_array, 0, frame_index, output_offset, | |
| "bytecode array "); | |
| // The bytecode offset was mentioned explicitly in the BEGIN_FRAME. | |
| output_offset -= kPointerSize; | |
| int raw_bytecode_offset = | |
| BytecodeArray::kHeaderSize - kHeapObjectTag + bytecode_offset; | |
| Smi* smi_bytecode_offset = Smi::FromInt(raw_bytecode_offset); | |
| WriteValueToOutput(smi_bytecode_offset, 0, frame_index, output_offset, | |
| "bytecode offset "); | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), " -------------------------\n"); | |
| } | |
| // Translate the rest of the interpreter registers in the frame. | |
| for (unsigned i = 0; i < height - 1; ++i) { | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| // Translate the accumulator register (depending on frame position). | |
| if (is_topmost) { | |
| // For topmost frame, put the accumulator on the stack. The bailout state | |
| // for interpreted frames is always set to {BailoutState::TOS_REGISTER} and | |
| // the {NotifyDeoptimized} builtin pops it off the topmost frame (possibly | |
| // after materialization). | |
| output_offset -= kPointerSize; | |
| if (goto_catch_handler) { | |
| // If we are lazy deopting to a catch handler, we set the accumulator to | |
| // the exception (which lives in the result register). | |
| intptr_t accumulator_value = | |
| input_->GetRegister(FullCodeGenerator::result_register().code()); | |
| WriteValueToOutput(reinterpret_cast<Object*>(accumulator_value), 0, | |
| frame_index, output_offset, "accumulator "); | |
| value_iterator++; | |
| } else { | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset, "accumulator "); | |
| } | |
| } else { | |
| // For non-topmost frames, skip the accumulator translation. For those | |
| // frames, the return value from the callee will become the accumulator. | |
| value_iterator++; | |
| input_index++; | |
| } | |
| CHECK_EQ(0u, output_offset); | |
| // Compute this frame's PC and state. The PC will be a special builtin that | |
| // continues the bytecode dispatch. Note that non-topmost and lazy-style | |
| // bailout handlers also advance the bytecode offset before dispatch, hence | |
| // simulating what normal handlers do upon completion of the operation. | |
| Builtins* builtins = isolate_->builtins(); | |
| Code* dispatch_builtin = | |
| (!is_topmost || (bailout_type_ == LAZY)) && !goto_catch_handler | |
| ? builtins->builtin(Builtins::kInterpreterEnterBytecodeAdvance) | |
| : builtins->builtin(Builtins::kInterpreterEnterBytecodeDispatch); | |
| output_frame->SetPc(reinterpret_cast<intptr_t>(dispatch_builtin->entry())); | |
| // Restore accumulator (TOS) register. | |
| output_frame->SetState( | |
| Smi::FromInt(static_cast<int>(BailoutState::TOS_REGISTER))); | |
| // Update constant pool. | |
| if (FLAG_enable_embedded_constant_pool) { | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(dispatch_builtin->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| if (is_topmost) { | |
| Register constant_pool_reg = | |
| InterpretedFrame::constant_pool_pointer_register(); | |
| output_frame->SetRegister(constant_pool_reg.code(), constant_pool_value); | |
| } | |
| } | |
| // Clear the context register. The context might be a de-materialized object | |
| // and will be materialized by {Runtime_NotifyDeoptimized}. For additional | |
| // safety we use Smi(0) instead of the potential {arguments_marker} here. | |
| if (is_topmost) { | |
| intptr_t context_value = reinterpret_cast<intptr_t>(Smi::kZero); | |
| Register context_reg = JavaScriptFrame::context_register(); | |
| output_frame->SetRegister(context_reg.code(), context_value); | |
| } | |
| // Set the continuation for the topmost frame. | |
| if (is_topmost) { | |
| Code* continuation = builtins->builtin(Builtins::kNotifyDeoptimized); | |
| if (bailout_type_ == LAZY) { | |
| continuation = builtins->builtin(Builtins::kNotifyLazyDeoptimized); | |
| } else if (bailout_type_ == SOFT) { | |
| continuation = builtins->builtin(Builtins::kNotifySoftDeoptimized); | |
| } else { | |
| CHECK_EQ(bailout_type_, EAGER); | |
| } | |
| output_frame->SetContinuation( | |
| reinterpret_cast<intptr_t>(continuation->entry())); | |
| } | |
| } | |
| void Deoptimizer::DoComputeArgumentsAdaptorFrame( | |
| TranslatedFrame* translated_frame, int frame_index) { | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| bool is_bottommost = (0 == frame_index); | |
| int input_index = 0; | |
| unsigned height = translated_frame->height(); | |
| unsigned height_in_bytes = height * kPointerSize; | |
| JSFunction* function = JSFunction::cast(value_iterator->GetRawValue()); | |
| value_iterator++; | |
| input_index++; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), | |
| " translating arguments adaptor => height=%d\n", height_in_bytes); | |
| } | |
| unsigned fixed_frame_size = ArgumentsAdaptorFrameConstants::kFixedFrameSize; | |
| unsigned output_frame_size = height_in_bytes + fixed_frame_size; | |
| // Allocate and store the output frame description. | |
| int parameter_count = height; | |
| FrameDescription* output_frame = new (output_frame_size) | |
| FrameDescription(output_frame_size, parameter_count); | |
| output_frame->SetFrameType(StackFrame::ARGUMENTS_ADAPTOR); | |
| // Arguments adaptor can not be topmost. | |
| CHECK(frame_index < output_count_ - 1); | |
| CHECK(output_[frame_index] == NULL); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address; | |
| if (is_bottommost) { | |
| top_address = caller_frame_top_ - output_frame_size; | |
| } else { | |
| top_address = output_[frame_index - 1]->GetTop() - output_frame_size; | |
| } | |
| output_frame->SetTop(top_address); | |
| // Compute the incoming parameter translation. | |
| unsigned output_offset = output_frame_size; | |
| for (int i = 0; i < parameter_count; ++i) { | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| // Read caller's PC from the previous frame. | |
| output_offset -= kPCOnStackSize; | |
| intptr_t value; | |
| if (is_bottommost) { | |
| value = caller_pc_; | |
| } else { | |
| value = output_[frame_index - 1]->GetPc(); | |
| } | |
| output_frame->SetCallerPc(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's pc\n"); | |
| // Read caller's FP from the previous frame, and set this frame's FP. | |
| output_offset -= kFPOnStackSize; | |
| if (is_bottommost) { | |
| value = caller_fp_; | |
| } else { | |
| value = output_[frame_index - 1]->GetFp(); | |
| } | |
| output_frame->SetCallerFp(output_offset, value); | |
| intptr_t fp_value = top_address + output_offset; | |
| output_frame->SetFp(fp_value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // Read the caller's constant pool from the previous frame. | |
| output_offset -= kPointerSize; | |
| if (is_bottommost) { | |
| value = caller_constant_pool_; | |
| } else { | |
| value = output_[frame_index - 1]->GetConstantPool(); | |
| } | |
| output_frame->SetCallerConstantPool(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // A marker value is used in place of the context. | |
| output_offset -= kPointerSize; | |
| intptr_t context = reinterpret_cast<intptr_t>( | |
| Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)); | |
| output_frame->SetFrameSlot(output_offset, context); | |
| DebugPrintOutputSlot(context, frame_index, output_offset, | |
| "context (adaptor sentinel)\n"); | |
| // The function was mentioned explicitly in the ARGUMENTS_ADAPTOR_FRAME. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(function); | |
| WriteValueToOutput(function, 0, frame_index, output_offset, "function "); | |
| // Number of incoming arguments. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(Smi::FromInt(height - 1)); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "argc "); | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), "(%d)\n", height - 1); | |
| } | |
| DCHECK(0 == output_offset); | |
| Builtins* builtins = isolate_->builtins(); | |
| Code* adaptor_trampoline = | |
| builtins->builtin(Builtins::kArgumentsAdaptorTrampoline); | |
| intptr_t pc_value = reinterpret_cast<intptr_t>( | |
| adaptor_trampoline->instruction_start() + | |
| isolate_->heap()->arguments_adaptor_deopt_pc_offset()->value()); | |
| output_frame->SetPc(pc_value); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(adaptor_trampoline->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| } | |
| } | |
| void Deoptimizer::DoComputeTailCallerFrame(TranslatedFrame* translated_frame, | |
| int frame_index) { | |
| SharedFunctionInfo* shared = translated_frame->raw_shared_info(); | |
| bool is_bottommost = (0 == frame_index); | |
| // Tail caller frame can't be topmost. | |
| CHECK_NE(output_count_ - 1, frame_index); | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), " translating tail caller frame "); | |
| std::unique_ptr<char[]> name = shared->DebugName()->ToCString(); | |
| PrintF(trace_scope_->file(), "%s\n", name.get()); | |
| } | |
| if (!is_bottommost) return; | |
| // Drop arguments adaptor frame below current frame if it exsits. | |
| Address fp_address = input_->GetFramePointerAddress(); | |
| Address adaptor_fp_address = | |
| Memory::Address_at(fp_address + CommonFrameConstants::kCallerFPOffset); | |
| if (Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR) != | |
| Memory::Object_at(adaptor_fp_address + | |
| CommonFrameConstants::kContextOrFrameTypeOffset)) { | |
| return; | |
| } | |
| int caller_params_count = | |
| Smi::cast( | |
| Memory::Object_at(adaptor_fp_address + | |
| ArgumentsAdaptorFrameConstants::kLengthOffset)) | |
| ->value(); | |
| int callee_params_count = | |
| function_->shared()->internal_formal_parameter_count(); | |
| // Both caller and callee parameters count do not include receiver. | |
| int offset = (caller_params_count - callee_params_count) * kPointerSize; | |
| intptr_t new_stack_fp = | |
| reinterpret_cast<intptr_t>(adaptor_fp_address) + offset; | |
| intptr_t new_caller_frame_top = new_stack_fp + | |
| (callee_params_count + 1) * kPointerSize + | |
| CommonFrameConstants::kFixedFrameSizeAboveFp; | |
| intptr_t adaptor_caller_pc = Memory::intptr_at( | |
| adaptor_fp_address + CommonFrameConstants::kCallerPCOffset); | |
| intptr_t adaptor_caller_fp = Memory::intptr_at( | |
| adaptor_fp_address + CommonFrameConstants::kCallerFPOffset); | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), | |
| " dropping caller arguments adaptor frame: offset=%d, " | |
| "fp: 0x%08" V8PRIxPTR " -> 0x%08" V8PRIxPTR | |
| ", " | |
| "caller sp: 0x%08" V8PRIxPTR " -> 0x%08" V8PRIxPTR "\n", | |
| offset, stack_fp_, new_stack_fp, caller_frame_top_, | |
| new_caller_frame_top); | |
| } | |
| caller_frame_top_ = new_caller_frame_top; | |
| caller_fp_ = adaptor_caller_fp; | |
| caller_pc_ = adaptor_caller_pc; | |
| } | |
| void Deoptimizer::DoComputeConstructStubFrame(TranslatedFrame* translated_frame, | |
| int frame_index) { | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| bool is_topmost = (output_count_ - 1 == frame_index); | |
| // The construct frame could become topmost only if we inlined a constructor | |
| // call which does a tail call (otherwise the tail callee's frame would be | |
| // the topmost one). So it could only be the LAZY case. | |
| CHECK(!is_topmost || bailout_type_ == LAZY); | |
| int input_index = 0; | |
| Builtins* builtins = isolate_->builtins(); | |
| Code* construct_stub = builtins->builtin(Builtins::kJSConstructStubGeneric); | |
| unsigned height = translated_frame->height(); | |
| unsigned height_in_bytes = height * kPointerSize; | |
| // If the construct frame appears to be topmost we should ensure that the | |
| // value of result register is preserved during continuation execution. | |
| // We do this here by "pushing" the result of the constructor function to the | |
| // top of the reconstructed stack and then using the | |
| // BailoutState::TOS_REGISTER machinery. | |
| if (is_topmost) { | |
| height_in_bytes += kPointerSize; | |
| } | |
| // Skip function. | |
| value_iterator++; | |
| input_index++; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), | |
| " translating construct stub => height=%d\n", height_in_bytes); | |
| } | |
| unsigned fixed_frame_size = ConstructFrameConstants::kFixedFrameSize; | |
| 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); | |
| output_frame->SetFrameType(StackFrame::CONSTRUCT); | |
| // Construct stub can not be topmost. | |
| DCHECK(frame_index > 0 && frame_index < output_count_); | |
| DCHECK(output_[frame_index] == NULL); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address; | |
| top_address = output_[frame_index - 1]->GetTop() - output_frame_size; | |
| output_frame->SetTop(top_address); | |
| // Compute the incoming parameter translation. | |
| int parameter_count = height; | |
| unsigned output_offset = output_frame_size; | |
| for (int i = 0; i < parameter_count; ++i) { | |
| output_offset -= kPointerSize; | |
| // The allocated receiver of a construct stub frame is passed as the | |
| // receiver parameter through the translation. It might be encoding | |
| // a captured object, override the slot address for a captured object. | |
| WriteTranslatedValueToOutput( | |
| &value_iterator, &input_index, frame_index, output_offset, nullptr, | |
| (i == 0) ? reinterpret_cast<Address>(top_address) : nullptr); | |
| } | |
| // Read caller's PC from the previous frame. | |
| output_offset -= kPCOnStackSize; | |
| intptr_t callers_pc = output_[frame_index - 1]->GetPc(); | |
| output_frame->SetCallerPc(output_offset, callers_pc); | |
| DebugPrintOutputSlot(callers_pc, frame_index, output_offset, "caller's pc\n"); | |
| // Read caller's FP from the previous frame, and set this frame's FP. | |
| output_offset -= kFPOnStackSize; | |
| intptr_t value = output_[frame_index - 1]->GetFp(); | |
| output_frame->SetCallerFp(output_offset, value); | |
| intptr_t fp_value = top_address + output_offset; | |
| output_frame->SetFp(fp_value); | |
| if (is_topmost) { | |
| Register fp_reg = JavaScriptFrame::fp_register(); | |
| output_frame->SetRegister(fp_reg.code(), fp_value); | |
| } | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // Read the caller's constant pool from the previous frame. | |
| output_offset -= kPointerSize; | |
| value = output_[frame_index - 1]->GetConstantPool(); | |
| output_frame->SetCallerConstantPool(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // A marker value is used to mark the frame. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(Smi::FromInt(StackFrame::CONSTRUCT)); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "typed frame marker\n"); | |
| // The context can be gotten from the previous frame. | |
| output_offset -= kPointerSize; | |
| value = output_[frame_index - 1]->GetContext(); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "context\n"); | |
| // Number of incoming arguments. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(Smi::FromInt(height - 1)); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "argc "); | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), "(%d)\n", height - 1); | |
| } | |
| // The newly allocated object was passed as receiver in the artificial | |
| // constructor stub environment created by HEnvironment::CopyForInlining(). | |
| output_offset -= kPointerSize; | |
| value = output_frame->GetFrameSlot(output_frame_size - kPointerSize); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "allocated receiver\n"); | |
| if (is_topmost) { | |
| // Ensure the result is restored back when we return to the stub. | |
| output_offset -= kPointerSize; | |
| Register result_reg = FullCodeGenerator::result_register(); | |
| value = input_->GetRegister(result_reg.code()); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "constructor result\n"); | |
| output_frame->SetState( | |
| Smi::FromInt(static_cast<int>(BailoutState::TOS_REGISTER))); | |
| } | |
| CHECK_EQ(0u, output_offset); | |
| intptr_t pc = reinterpret_cast<intptr_t>( | |
| construct_stub->instruction_start() + | |
| isolate_->heap()->construct_stub_deopt_pc_offset()->value()); | |
| output_frame->SetPc(pc); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(construct_stub->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| if (is_topmost) { | |
| Register constant_pool_reg = | |
| JavaScriptFrame::constant_pool_pointer_register(); | |
| output_frame->SetRegister(constant_pool_reg.code(), fp_value); | |
| } | |
| } | |
| // Clear the context register. The context might be a de-materialized object | |
| // and will be materialized by {Runtime_NotifyDeoptimized}. For additional | |
| // safety we use Smi(0) instead of the potential {arguments_marker} here. | |
| if (is_topmost) { | |
| intptr_t context_value = reinterpret_cast<intptr_t>(Smi::kZero); | |
| Register context_reg = JavaScriptFrame::context_register(); | |
| output_frame->SetRegister(context_reg.code(), context_value); | |
| } | |
| // Set the continuation for the topmost frame. | |
| if (is_topmost) { | |
| Builtins* builtins = isolate_->builtins(); | |
| DCHECK_EQ(LAZY, bailout_type_); | |
| Code* continuation = builtins->builtin(Builtins::kNotifyLazyDeoptimized); | |
| output_frame->SetContinuation( | |
| reinterpret_cast<intptr_t>(continuation->entry())); | |
| } | |
| } | |
| void Deoptimizer::DoComputeAccessorStubFrame(TranslatedFrame* translated_frame, | |
| int frame_index, | |
| bool is_setter_stub_frame) { | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| bool is_topmost = (output_count_ - 1 == frame_index); | |
| // The accessor frame could become topmost only if we inlined an accessor | |
| // call which does a tail call (otherwise the tail callee's frame would be | |
| // the topmost one). So it could only be the LAZY case. | |
| CHECK(!is_topmost || bailout_type_ == LAZY); | |
| int input_index = 0; | |
| // Skip accessor. | |
| value_iterator++; | |
| input_index++; | |
| // The receiver (and the implicit return value, if any) are expected in | |
| // registers by the LoadIC/StoreIC, so they don't belong to the output stack | |
| // frame. This means that we have to use a height of 0. | |
| unsigned height = 0; | |
| unsigned height_in_bytes = height * kPointerSize; | |
| // If the accessor frame appears to be topmost we should ensure that the | |
| // value of result register is preserved during continuation execution. | |
| // We do this here by "pushing" the result of the accessor function to the | |
| // top of the reconstructed stack and then using the | |
| // BailoutState::TOS_REGISTER machinery. | |
| // We don't need to restore the result in case of a setter call because we | |
| // have to return the stored value but not the result of the setter function. | |
| bool should_preserve_result = is_topmost && !is_setter_stub_frame; | |
| if (should_preserve_result) { | |
| height_in_bytes += kPointerSize; | |
| } | |
| const char* kind = is_setter_stub_frame ? "setter" : "getter"; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), | |
| " translating %s stub => height=%u\n", kind, height_in_bytes); | |
| } | |
| // We need 1 stack entry for the return address and enough entries for the | |
| // StackFrame::INTERNAL (FP, frame type, context, code object and constant | |
| // pool (if enabled)- see MacroAssembler::EnterFrame). | |
| // For a setter stub frame we need one additional entry for the implicit | |
| // return value, see StoreStubCompiler::CompileStoreViaSetter. | |
| unsigned fixed_frame_entries = | |
| (StandardFrameConstants::kFixedFrameSize / kPointerSize) + 1 + | |
| (is_setter_stub_frame ? 1 : 0); | |
| unsigned fixed_frame_size = fixed_frame_entries * kPointerSize; | |
| 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); | |
| output_frame->SetFrameType(StackFrame::INTERNAL); | |
| // A frame for an accessor stub can not be bottommost. | |
| CHECK(frame_index > 0 && frame_index < output_count_); | |
| CHECK_NULL(output_[frame_index]); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address = output_[frame_index - 1]->GetTop() - output_frame_size; | |
| output_frame->SetTop(top_address); | |
| unsigned output_offset = output_frame_size; | |
| // Read caller's PC from the previous frame. | |
| output_offset -= kPCOnStackSize; | |
| intptr_t callers_pc = output_[frame_index - 1]->GetPc(); | |
| output_frame->SetCallerPc(output_offset, callers_pc); | |
| DebugPrintOutputSlot(callers_pc, frame_index, output_offset, "caller's pc\n"); | |
| // Read caller's FP from the previous frame, and set this frame's FP. | |
| output_offset -= kFPOnStackSize; | |
| intptr_t value = output_[frame_index - 1]->GetFp(); | |
| output_frame->SetCallerFp(output_offset, value); | |
| intptr_t fp_value = top_address + output_offset; | |
| output_frame->SetFp(fp_value); | |
| if (is_topmost) { | |
| Register fp_reg = JavaScriptFrame::fp_register(); | |
| output_frame->SetRegister(fp_reg.code(), fp_value); | |
| } | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // Read the caller's constant pool from the previous frame. | |
| output_offset -= kPointerSize; | |
| value = output_[frame_index - 1]->GetConstantPool(); | |
| output_frame->SetCallerConstantPool(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // Set the frame type. | |
| output_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>(Smi::FromInt(StackFrame::INTERNAL)); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "frame type "); | |
| if (trace_scope_ != nullptr) { | |
| PrintF(trace_scope_->file(), "(%s sentinel)\n", kind); | |
| } | |
| // Get Code object from accessor stub. | |
| output_offset -= kPointerSize; | |
| Builtins::Name name = is_setter_stub_frame ? | |
| Builtins::kStoreIC_Setter_ForDeopt : | |
| Builtins::kLoadIC_Getter_ForDeopt; | |
| Code* accessor_stub = isolate_->builtins()->builtin(name); | |
| value = reinterpret_cast<intptr_t>(accessor_stub); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "code object\n"); | |
| // The context can be gotten from the previous frame. | |
| output_offset -= kPointerSize; | |
| value = output_[frame_index - 1]->GetContext(); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, "context\n"); | |
| // Skip receiver. | |
| value_iterator++; | |
| input_index++; | |
| if (is_setter_stub_frame) { | |
| // The implicit return value was part of the artificial setter stub | |
| // environment. | |
| output_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, frame_index, | |
| output_offset); | |
| } | |
| if (should_preserve_result) { | |
| // Ensure the result is restored back when we return to the stub. | |
| output_offset -= kPointerSize; | |
| Register result_reg = FullCodeGenerator::result_register(); | |
| value = input_->GetRegister(result_reg.code()); | |
| output_frame->SetFrameSlot(output_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_offset, | |
| "accessor result\n"); | |
| output_frame->SetState( | |
| Smi::FromInt(static_cast<int>(BailoutState::TOS_REGISTER))); | |
| } else { | |
| output_frame->SetState( | |
| Smi::FromInt(static_cast<int>(BailoutState::NO_REGISTERS))); | |
| } | |
| CHECK_EQ(0u, output_offset); | |
| Smi* offset = is_setter_stub_frame ? | |
| isolate_->heap()->setter_stub_deopt_pc_offset() : | |
| isolate_->heap()->getter_stub_deopt_pc_offset(); | |
| intptr_t pc = reinterpret_cast<intptr_t>( | |
| accessor_stub->instruction_start() + offset->value()); | |
| output_frame->SetPc(pc); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(accessor_stub->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| if (is_topmost) { | |
| Register constant_pool_reg = | |
| JavaScriptFrame::constant_pool_pointer_register(); | |
| output_frame->SetRegister(constant_pool_reg.code(), fp_value); | |
| } | |
| } | |
| // Clear the context register. The context might be a de-materialized object | |
| // and will be materialized by {Runtime_NotifyDeoptimized}. For additional | |
| // safety we use Smi(0) instead of the potential {arguments_marker} here. | |
| if (is_topmost) { | |
| intptr_t context_value = reinterpret_cast<intptr_t>(Smi::kZero); | |
| Register context_reg = JavaScriptFrame::context_register(); | |
| output_frame->SetRegister(context_reg.code(), context_value); | |
| } | |
| // Set the continuation for the topmost frame. | |
| if (is_topmost) { | |
| Builtins* builtins = isolate_->builtins(); | |
| DCHECK_EQ(LAZY, bailout_type_); | |
| Code* continuation = builtins->builtin(Builtins::kNotifyLazyDeoptimized); | |
| output_frame->SetContinuation( | |
| reinterpret_cast<intptr_t>(continuation->entry())); | |
| } | |
| } | |
| void Deoptimizer::DoComputeCompiledStubFrame(TranslatedFrame* translated_frame, | |
| int frame_index) { | |
| // | |
| // FROM TO | |
| // | .... | | .... | | |
| // +-------------------------+ +-------------------------+ | |
| // | JSFunction continuation | | JSFunction continuation | | |
| // +-------------------------+ +-------------------------+ | |
| // | | saved frame (FP) | | saved frame (FP) | | |
| // | +=========================+<-fpreg +=========================+<-fpreg | |
| // | |constant pool (if ool_cp)| |constant pool (if ool_cp)| | |
| // | +-------------------------+ +-------------------------| | |
| // | | JSFunction context | | JSFunction context | | |
| // v +-------------------------+ +-------------------------| | |
| // | COMPILED_STUB marker | | STUB_FAILURE marker | | |
| // +-------------------------+ +-------------------------+ | |
| // | | | caller args.arguments_ | | |
| // | ... | +-------------------------+ | |
| // | | | caller args.length_ | | |
| // |-------------------------|<-spreg +-------------------------+ | |
| // | caller args pointer | | |
| // +-------------------------+ | |
| // | caller stack param 1 | | |
| // parameters in registers +-------------------------+ | |
| // and spilled to stack | .... | | |
| // +-------------------------+ | |
| // | caller stack param n | | |
| // +-------------------------+<-spreg | |
| // reg = number of parameters | |
| // reg = failure handler address | |
| // reg = saved frame | |
| // reg = JSFunction context | |
| // | |
| // Caller stack params contain the register parameters to the stub first, | |
| // and then, if the descriptor specifies a constant number of stack | |
| // parameters, the stack parameters as well. | |
| TranslatedFrame::iterator value_iterator = translated_frame->begin(); | |
| int input_index = 0; | |
| CHECK(compiled_code_->is_hydrogen_stub()); | |
| int major_key = CodeStub::GetMajorKey(compiled_code_); | |
| CodeStubDescriptor descriptor(isolate_, compiled_code_->stub_key()); | |
| // The output frame must have room for all pushed register parameters | |
| // and the standard stack frame slots. Include space for an argument | |
| // object to the callee and optionally the space to pass the argument | |
| // object to the stub failure handler. | |
| int param_count = descriptor.GetRegisterParameterCount(); | |
| int stack_param_count = descriptor.GetStackParameterCount(); | |
| // The translated frame contains all of the register parameters | |
| // plus the context. | |
| CHECK_EQ(translated_frame->height(), param_count + 1); | |
| CHECK_GE(param_count, 0); | |
| int height_in_bytes = kPointerSize * (param_count + stack_param_count); | |
| int fixed_frame_size = StubFailureTrampolineFrameConstants::kFixedFrameSize; | |
| int output_frame_size = height_in_bytes + fixed_frame_size; | |
| if (trace_scope_ != NULL) { | |
| PrintF(trace_scope_->file(), | |
| " translating %s => StubFailureTrampolineStub, height=%d\n", | |
| CodeStub::MajorName(static_cast<CodeStub::Major>(major_key)), | |
| height_in_bytes); | |
| } | |
| // The stub failure trampoline is a single frame. | |
| FrameDescription* output_frame = | |
| new (output_frame_size) FrameDescription(output_frame_size); | |
| output_frame->SetFrameType(StackFrame::STUB_FAILURE_TRAMPOLINE); | |
| CHECK_EQ(frame_index, 0); | |
| output_[frame_index] = output_frame; | |
| // The top address of the frame is computed from the previous frame's top and | |
| // this frame's size. | |
| intptr_t top_address = caller_frame_top_ - output_frame_size; | |
| output_frame->SetTop(top_address); | |
| // Set caller's PC (JSFunction continuation). | |
| unsigned output_frame_offset = output_frame_size - kFPOnStackSize; | |
| intptr_t value = caller_pc_; | |
| output_frame->SetCallerPc(output_frame_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, | |
| "caller's pc\n"); | |
| // Read caller's FP from the input frame, and set this frame's FP. | |
| value = caller_fp_; | |
| output_frame_offset -= kFPOnStackSize; | |
| output_frame->SetCallerFp(output_frame_offset, value); | |
| intptr_t frame_ptr = top_address + output_frame_offset; | |
| Register fp_reg = StubFailureTrampolineFrame::fp_register(); | |
| output_frame->SetRegister(fp_reg.code(), frame_ptr); | |
| output_frame->SetFp(frame_ptr); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, | |
| "caller's fp\n"); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| // Read the caller's constant pool from the input frame. | |
| value = caller_constant_pool_; | |
| output_frame_offset -= kPointerSize; | |
| output_frame->SetCallerConstantPool(output_frame_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, | |
| "caller's constant_pool\n"); | |
| } | |
| // The marker for the typed stack frame | |
| output_frame_offset -= kPointerSize; | |
| value = reinterpret_cast<intptr_t>( | |
| Smi::FromInt(StackFrame::STUB_FAILURE_TRAMPOLINE)); | |
| output_frame->SetFrameSlot(output_frame_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, | |
| "function (stub failure sentinel)\n"); | |
| intptr_t caller_arg_count = stack_param_count; | |
| bool arg_count_known = !descriptor.stack_parameter_count().is_valid(); | |
| // Build the Arguments object for the caller's parameters and a pointer to it. | |
| output_frame_offset -= kPointerSize; | |
| int args_arguments_offset = output_frame_offset; | |
| intptr_t the_hole = reinterpret_cast<intptr_t>( | |
| isolate_->heap()->the_hole_value()); | |
| if (arg_count_known) { | |
| value = frame_ptr + StandardFrameConstants::kCallerSPOffset + | |
| (caller_arg_count - 1) * kPointerSize; | |
| } else { | |
| value = the_hole; | |
| } | |
| output_frame->SetFrameSlot(args_arguments_offset, value); | |
| DebugPrintOutputSlot( | |
| value, frame_index, args_arguments_offset, | |
| arg_count_known ? "args.arguments\n" : "args.arguments (the hole)\n"); | |
| output_frame_offset -= kPointerSize; | |
| int length_frame_offset = output_frame_offset; | |
| value = arg_count_known ? caller_arg_count : the_hole; | |
| output_frame->SetFrameSlot(length_frame_offset, value); | |
| DebugPrintOutputSlot( | |
| value, frame_index, length_frame_offset, | |
| arg_count_known ? "args.length\n" : "args.length (the hole)\n"); | |
| output_frame_offset -= kPointerSize; | |
| value = frame_ptr + StandardFrameConstants::kCallerSPOffset - | |
| (output_frame_size - output_frame_offset) + kPointerSize; | |
| output_frame->SetFrameSlot(output_frame_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, "args*\n"); | |
| // Copy the register parameters to the failure frame. | |
| int arguments_length_offset = -1; | |
| for (int i = 0; i < param_count; ++i) { | |
| output_frame_offset -= kPointerSize; | |
| WriteTranslatedValueToOutput(&value_iterator, &input_index, 0, | |
| output_frame_offset); | |
| if (!arg_count_known && | |
| descriptor.GetRegisterParameter(i) | |
| .is(descriptor.stack_parameter_count())) { | |
| arguments_length_offset = output_frame_offset; | |
| } | |
| } | |
| Object* maybe_context = value_iterator->GetRawValue(); | |
| CHECK(maybe_context->IsContext()); | |
| Register context_reg = StubFailureTrampolineFrame::context_register(); | |
| value = reinterpret_cast<intptr_t>(maybe_context); | |
| output_frame->SetRegister(context_reg.code(), value); | |
| ++value_iterator; | |
| // Copy constant stack parameters to the failure frame. If the number of stack | |
| // parameters is not known in the descriptor, the arguments object is the way | |
| // to access them. | |
| for (int i = 0; i < stack_param_count; i++) { | |
| output_frame_offset -= kPointerSize; | |
| Object** stack_parameter = reinterpret_cast<Object**>( | |
| frame_ptr + StandardFrameConstants::kCallerSPOffset + | |
| (stack_param_count - i - 1) * kPointerSize); | |
| value = reinterpret_cast<intptr_t>(*stack_parameter); | |
| output_frame->SetFrameSlot(output_frame_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, output_frame_offset, | |
| "stack parameter\n"); | |
| } | |
| CHECK_EQ(0u, output_frame_offset); | |
| if (!arg_count_known) { | |
| CHECK_GE(arguments_length_offset, 0); | |
| // We know it's a smi because 1) the code stub guarantees the stack | |
| // parameter count is in smi range, and 2) the DoTranslateCommand in the | |
| // parameter loop above translated that to a tagged value. | |
| Smi* smi_caller_arg_count = reinterpret_cast<Smi*>( | |
| output_frame->GetFrameSlot(arguments_length_offset)); | |
| caller_arg_count = smi_caller_arg_count->value(); | |
| output_frame->SetFrameSlot(length_frame_offset, caller_arg_count); | |
| DebugPrintOutputSlot(caller_arg_count, frame_index, length_frame_offset, | |
| "args.length\n"); | |
| value = frame_ptr + StandardFrameConstants::kCallerSPOffset + | |
| (caller_arg_count - 1) * kPointerSize; | |
| output_frame->SetFrameSlot(args_arguments_offset, value); | |
| DebugPrintOutputSlot(value, frame_index, args_arguments_offset, | |
| "args.arguments"); | |
| } | |
| // Copy the double registers from the input into the output frame. | |
| CopyDoubleRegisters(output_frame); | |
| // Fill registers containing handler and number of parameters. | |
| SetPlatformCompiledStubRegisters(output_frame, &descriptor); | |
| // Compute this frame's PC, state, and continuation. | |
| Code* trampoline = NULL; | |
| StubFunctionMode function_mode = descriptor.function_mode(); | |
| StubFailureTrampolineStub(isolate_, function_mode) | |
| .FindCodeInCache(&trampoline); | |
| DCHECK(trampoline != NULL); | |
| output_frame->SetPc(reinterpret_cast<intptr_t>( | |
| trampoline->instruction_start())); | |
| if (FLAG_enable_embedded_constant_pool) { | |
| Register constant_pool_reg = | |
| StubFailureTrampolineFrame::constant_pool_pointer_register(); | |
| intptr_t constant_pool_value = | |
| reinterpret_cast<intptr_t>(trampoline->constant_pool()); | |
| output_frame->SetConstantPool(constant_pool_value); | |
| output_frame->SetRegister(constant_pool_reg.code(), constant_pool_value); | |
| } | |
| output_frame->SetState( | |
| Smi::FromInt(static_cast<int>(BailoutState::NO_REGISTERS))); | |
| Code* notify_failure = | |
| isolate_->builtins()->builtin(Builtins::kNotifyStubFailureSaveDoubles); | |
| output_frame->SetContinuation( | |
| reinterpret_cast<intptr_t>(notify_failure->entry())); | |
| } | |
| void Deoptimizer::MaterializeHeapObjects(JavaScriptFrameIterator* it) { | |
| // Walk to the last JavaScript output frame to find out if it has | |
| // adapted arguments. | |
| for (int frame_index = 0; frame_index < jsframe_count(); ++frame_index) { | |
| if (frame_index != 0) it->Advance(); | |
| } | |
| translated_state_.Prepare(it->frame()->has_adapted_arguments(), | |
| reinterpret_cast<Address>(stack_fp_)); | |
| for (auto& materialization : values_to_materialize_) { | |
| Handle<Object> value = materialization.value_->GetValue(); | |
| if (trace_scope_ != nullptr) { | |
| PrintF("Materialization [0x%08" V8PRIxPTR "] <- 0x%08" V8PRIxPTR " ; ", | |
| reinterpret_cast<intptr_t>(materialization.output_slot_address_), | |
| reinterpret_cast<intptr_t>(*value)); | |
| value->ShortPrint(trace_scope_->file()); | |
| PrintF(trace_scope_->file(), "\n"); | |
| } | |
| *(reinterpret_cast<intptr_t*>(materialization.output_slot_address_)) = | |
| reinterpret_cast<intptr_t>(*value); | |
| } | |
| isolate_->materialized_object_store()->Remove( | |
| reinterpret_cast<Address>(stack_fp_)); | |
| } | |
| void Deoptimizer::WriteTranslatedValueToOutput( | |
| TranslatedFrame::iterator* iterator, int* input_index, int frame_index, | |
| unsigned output_offset, const char* debug_hint_string, | |
| Address output_address_for_materialization) { | |
| Object* value = (*iterator)->GetRawValue(); | |
| WriteValueToOutput(value, *input_index, frame_index, output_offset, | |
| debug_hint_string); | |
| if (value == isolate_->heap()->arguments_marker()) { | |
| Address output_address = | |
| reinterpret_cast<Address>(output_[frame_index]->GetTop()) + | |
| output_offset; | |
| if (output_address_for_materialization == nullptr) { | |
| output_address_for_materialization = output_address; | |
| } | |
| values_to_materialize_.push_back( | |
| {output_address_for_materialization, *iterator}); | |
| } | |
| (*iterator)++; | |
| (*input_index)++; | |
| } | |
| void Deoptimizer::WriteValueToOutput(Object* value, int input_index, | |
| int frame_index, unsigned output_offset, | |
| const char* debug_hint_string) { | |
| output_[frame_index]->SetFrameSlot(output_offset, | |
| reinterpret_cast<intptr_t>(value)); | |
| if (trace_scope_ != nullptr) { | |
| DebugPrintOutputSlot(reinterpret_cast<intptr_t>(value), frame_index, | |
| output_offset, debug_hint_string); | |
| value->ShortPrint(trace_scope_->file()); | |
| PrintF(trace_scope_->file(), " (input #%d)\n", input_index); | |
| } | |
| } | |
| void Deoptimizer::DebugPrintOutputSlot(intptr_t value, int frame_index, | |
| unsigned output_offset, | |
| const char* debug_hint_string) { | |
| if (trace_scope_ != nullptr) { | |
| Address output_address = | |
| reinterpret_cast<Address>(output_[frame_index]->GetTop()) + | |
| output_offset; | |
| PrintF(trace_scope_->file(), | |
| " 0x%08" V8PRIxPTR ": [top + %d] <- 0x%08" V8PRIxPTR " ; %s", | |
| reinterpret_cast<intptr_t>(output_address), output_offset, value, | |
| debug_hint_string == nullptr ? "" : debug_hint_string); | |
| } | |
| } | |
| unsigned Deoptimizer::ComputeInputFrameAboveFpFixedSize() const { | |
| unsigned fixed_size = CommonFrameConstants::kFixedFrameSizeAboveFp; | |
| if (!function_->IsSmi()) { | |
| fixed_size += ComputeIncomingArgumentSize(function_->shared()); | |
| } | |
| return fixed_size; | |
| } | |
| unsigned Deoptimizer::ComputeInputFrameSize() const { | |
| // The fp-to-sp delta already takes the context, constant pool pointer and the | |
| // function into account so we have to avoid double counting them. | |
| unsigned fixed_size_above_fp = ComputeInputFrameAboveFpFixedSize(); | |
| unsigned result = fixed_size_above_fp + fp_to_sp_delta_; | |
| if (compiled_code_->kind() == Code::OPTIMIZED_FUNCTION) { | |
| unsigned stack_slots = compiled_code_->stack_slots(); | |
| unsigned outgoing_size = | |
| ComputeOutgoingArgumentSize(compiled_code_, bailout_id_); | |
| CHECK_EQ(fixed_size_above_fp + (stack_slots * kPointerSize) - | |
| CommonFrameConstants::kFixedFrameSizeAboveFp + outgoing_size, | |
| result); | |
| } | |
| return result; | |
| } | |
| // static | |
| unsigned Deoptimizer::ComputeJavascriptFixedSize(SharedFunctionInfo* shared) { | |
| // The fixed part of the frame consists of the return address, frame | |
| // pointer, function, context, and all the incoming arguments. | |
| return ComputeIncomingArgumentSize(shared) + | |
| StandardFrameConstants::kFixedFrameSize; | |
| } | |
| // static | |
| unsigned Deoptimizer::ComputeInterpretedFixedSize(SharedFunctionInfo* shared) { | |
| // The fixed part of the frame consists of the return address, frame | |
| // pointer, function, context, new.target, bytecode offset and all the | |
| // incoming arguments. | |
| return ComputeIncomingArgumentSize(shared) + | |
| InterpreterFrameConstants::kFixedFrameSize; | |
| } | |
| // static | |
| unsigned Deoptimizer::ComputeIncomingArgumentSize(SharedFunctionInfo* shared) { | |
| return (shared->internal_formal_parameter_count() + 1) * kPointerSize; | |
| } | |
| // static | |
| unsigned Deoptimizer::ComputeOutgoingArgumentSize(Code* code, | |
| unsigned bailout_id) { | |
| DeoptimizationInputData* data = | |
| DeoptimizationInputData::cast(code->deoptimization_data()); | |
| unsigned height = data->ArgumentsStackHeight(bailout_id)->value(); | |
| return height * kPointerSize; | |
| } | |
| void Deoptimizer::EnsureCodeForDeoptimizationEntry(Isolate* isolate, | |
| BailoutType type, | |
| int max_entry_id) { | |
| // 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. | |
| CHECK(type == EAGER || type == SOFT || type == LAZY); | |
| DeoptimizerData* data = isolate->deoptimizer_data(); | |
| int entry_count = data->deopt_entry_code_entries_[type]; | |
| if (max_entry_id < entry_count) return; | |
| entry_count = Max(entry_count, Deoptimizer::kMinNumberOfEntries); | |
| while (max_entry_id >= entry_count) entry_count *= 2; | |
| CHECK(entry_count <= Deoptimizer::kMaxNumberOfEntries); | |
| MacroAssembler masm(isolate, NULL, 16 * KB, CodeObjectRequired::kYes); | |
| masm.set_emit_debug_code(false); | |
| GenerateDeoptimizationEntries(&masm, entry_count, type); | |
| CodeDesc desc; | |
| masm.GetCode(&desc); | |
| DCHECK(!RelocInfo::RequiresRelocation(desc)); | |
| MemoryChunk* chunk = data->deopt_entry_code_[type]; | |
| CHECK(static_cast<int>(Deoptimizer::GetMaxDeoptTableSize()) >= | |
| desc.instr_size); | |
| if (!chunk->CommitArea(desc.instr_size)) { | |
| V8::FatalProcessOutOfMemory( | |
| "Deoptimizer::EnsureCodeForDeoptimizationEntry"); | |
| } | |
| CopyBytes(chunk->area_start(), desc.buffer, | |
| static_cast<size_t>(desc.instr_size)); | |
| Assembler::FlushICache(isolate, chunk->area_start(), desc.instr_size); | |
| data->deopt_entry_code_entries_[type] = entry_count; | |
| } | |
| FrameDescription::FrameDescription(uint32_t frame_size, int parameter_count) | |
| : frame_size_(frame_size), | |
| parameter_count_(parameter_count), | |
| top_(kZapUint32), | |
| pc_(kZapUint32), | |
| fp_(kZapUint32), | |
| context_(kZapUint32), | |
| constant_pool_(kZapUint32) { | |
| // Zap all the registers. | |
| for (int r = 0; r < Register::kNumRegisters; r++) { | |
| // TODO(jbramley): It isn't safe to use kZapUint32 here. If the register | |
| // isn't used before the next safepoint, the GC will try to scan it as a | |
| // tagged value. kZapUint32 looks like a valid tagged pointer, but it isn't. | |
| SetRegister(r, kZapUint32); | |
| } | |
| // Zap all the slots. | |
| for (unsigned o = 0; o < frame_size; o += kPointerSize) { | |
| SetFrameSlot(o, kZapUint32); | |
| } | |
| } | |
| void TranslationBuffer::Add(int32_t value) { | |
| // This wouldn't handle kMinInt correctly if it ever encountered it. | |
| DCHECK(value != kMinInt); | |
| // 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_.push_back(((bits << 1) & 0xFF) | (next != 0)); | |
| bits = next; | |
| } while (bits != 0); | |
| } | |
| int32_t TranslationIterator::Next() { | |
| // 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) { | |
| DCHECK(HasNext()); | |
| 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(Factory* factory) { | |
| Handle<ByteArray> result = factory->NewByteArray(CurrentIndex(), TENURED); | |
| contents_.CopyTo(result->GetDataStartAddress()); | |
| return result; | |
| } | |
| void Translation::BeginConstructStubFrame(int literal_id, unsigned height) { | |
| buffer_->Add(CONSTRUCT_STUB_FRAME); | |
| buffer_->Add(literal_id); | |
| buffer_->Add(height); | |
| } | |
| void Translation::BeginGetterStubFrame(int literal_id) { | |
| buffer_->Add(GETTER_STUB_FRAME); | |
| buffer_->Add(literal_id); | |
| } | |
| void Translation::BeginSetterStubFrame(int literal_id) { | |
| buffer_->Add(SETTER_STUB_FRAME); | |
| buffer_->Add(literal_id); | |
| } | |
| void Translation::BeginArgumentsAdaptorFrame(int literal_id, unsigned height) { | |
| buffer_->Add(ARGUMENTS_ADAPTOR_FRAME); | |
| buffer_->Add(literal_id); | |
| buffer_->Add(height); | |
| } | |
| void Translation::BeginTailCallerFrame(int literal_id) { | |
| buffer_->Add(TAIL_CALLER_FRAME); | |
| buffer_->Add(literal_id); | |
| } | |
| void Translation::BeginJSFrame(BailoutId node_id, | |
| int literal_id, | |
| unsigned height) { | |
| buffer_->Add(JS_FRAME); | |
| buffer_->Add(node_id.ToInt()); | |
| buffer_->Add(literal_id); | |
| buffer_->Add(height); | |
| } | |
| void Translation::BeginInterpretedFrame(BailoutId bytecode_offset, | |
| int literal_id, unsigned height) { | |
| buffer_->Add(INTERPRETED_FRAME); | |
| buffer_->Add(bytecode_offset.ToInt()); | |
| buffer_->Add(literal_id); | |
| buffer_->Add(height); | |
| } | |
| void Translation::BeginCompiledStubFrame(int height) { | |
| buffer_->Add(COMPILED_STUB_FRAME); | |
| buffer_->Add(height); | |
| } | |
| void Translation::BeginArgumentsObject(int args_length) { | |
| buffer_->Add(ARGUMENTS_OBJECT); | |
| buffer_->Add(args_length); | |
| } | |
| void Translation::BeginCapturedObject(int length) { | |
| buffer_->Add(CAPTURED_OBJECT); | |
| buffer_->Add(length); | |
| } | |
| void Translation::DuplicateObject(int object_index) { | |
| buffer_->Add(DUPLICATED_OBJECT); | |
| buffer_->Add(object_index); | |
| } | |
| 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::StoreUint32Register(Register reg) { | |
| buffer_->Add(UINT32_REGISTER); | |
| buffer_->Add(reg.code()); | |
| } | |
| void Translation::StoreBoolRegister(Register reg) { | |
| buffer_->Add(BOOL_REGISTER); | |
| buffer_->Add(reg.code()); | |
| } | |
| void Translation::StoreFloatRegister(FloatRegister reg) { | |
| buffer_->Add(FLOAT_REGISTER); | |
| buffer_->Add(reg.code()); | |
| } | |
| void Translation::StoreDoubleRegister(DoubleRegister reg) { | |
| buffer_->Add(DOUBLE_REGISTER); | |
| buffer_->Add(reg.code()); | |
| } | |
| 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::StoreUint32StackSlot(int index) { | |
| buffer_->Add(UINT32_STACK_SLOT); | |
| buffer_->Add(index); | |
| } | |
| void Translation::StoreBoolStackSlot(int index) { | |
| buffer_->Add(BOOL_STACK_SLOT); | |
| buffer_->Add(index); | |
| } | |
| void Translation::StoreFloatStackSlot(int index) { | |
| buffer_->Add(FLOAT_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(bool args_known, | |
| int args_index, | |
| int args_length) { | |
| buffer_->Add(ARGUMENTS_OBJECT); | |
| buffer_->Add(args_known); | |
| buffer_->Add(args_index); | |
| buffer_->Add(args_length); | |
| } | |
| void Translation::StoreJSFrameFunction() { | |
| StoreStackSlot((StandardFrameConstants::kCallerPCOffset - | |
| StandardFrameConstants::kFunctionOffset) / | |
| kPointerSize); | |
| } | |
| int Translation::NumberOfOperandsFor(Opcode opcode) { | |
| switch (opcode) { | |
| case GETTER_STUB_FRAME: | |
| case SETTER_STUB_FRAME: | |
| case DUPLICATED_OBJECT: | |
| case ARGUMENTS_OBJECT: | |
| case CAPTURED_OBJECT: | |
| case REGISTER: | |
| case INT32_REGISTER: | |
| case UINT32_REGISTER: | |
| case BOOL_REGISTER: | |
| case FLOAT_REGISTER: | |
| case DOUBLE_REGISTER: | |
| case STACK_SLOT: | |
| case INT32_STACK_SLOT: | |
| case UINT32_STACK_SLOT: | |
| case BOOL_STACK_SLOT: | |
| case FLOAT_STACK_SLOT: | |
| case DOUBLE_STACK_SLOT: | |
| case LITERAL: | |
| case COMPILED_STUB_FRAME: | |
| case TAIL_CALLER_FRAME: | |
| return 1; | |
| case BEGIN: | |
| case ARGUMENTS_ADAPTOR_FRAME: | |
| case CONSTRUCT_STUB_FRAME: | |
| return 2; | |
| case JS_FRAME: | |
| case INTERPRETED_FRAME: | |
| return 3; | |
| } | |
| FATAL("Unexpected translation type"); | |
| return -1; | |
| } | |
| #if defined(OBJECT_PRINT) || defined(ENABLE_DISASSEMBLER) | |
| const char* Translation::StringFor(Opcode opcode) { | |
| #define TRANSLATION_OPCODE_CASE(item) case item: return #item; | |
| switch (opcode) { | |
| TRANSLATION_OPCODE_LIST(TRANSLATION_OPCODE_CASE) | |
| } | |
| #undef TRANSLATION_OPCODE_CASE | |
| UNREACHABLE(); | |
| return ""; | |
| } | |
| #endif | |
| Handle<FixedArray> MaterializedObjectStore::Get(Address fp) { | |
| int index = StackIdToIndex(fp); | |
| if (index == -1) { | |
| return Handle<FixedArray>::null(); | |
| } | |
| Handle<FixedArray> array = GetStackEntries(); | |
| CHECK_GT(array->length(), index); | |
| return Handle<FixedArray>::cast(Handle<Object>(array->get(index), isolate())); | |
| } | |
| void MaterializedObjectStore::Set(Address fp, | |
| Handle<FixedArray> materialized_objects) { | |
| int index = StackIdToIndex(fp); | |
| if (index == -1) { | |
| index = frame_fps_.length(); | |
| frame_fps_.Add(fp); | |
| } | |
| Handle<FixedArray> array = EnsureStackEntries(index + 1); | |
| array->set(index, *materialized_objects); | |
| } | |
| bool MaterializedObjectStore::Remove(Address fp) { | |
| int index = StackIdToIndex(fp); | |
| if (index == -1) { | |
| return false; | |
| } | |
| CHECK_GE(index, 0); | |
| frame_fps_.Remove(index); | |
| FixedArray* array = isolate()->heap()->materialized_objects(); | |
| CHECK_LT(index, array->length()); | |
| for (int i = index; i < frame_fps_.length(); i++) { | |
| array->set(i, array->get(i + 1)); | |
| } | |
| array->set(frame_fps_.length(), isolate()->heap()->undefined_value()); | |
| return true; | |
| } | |
| int MaterializedObjectStore::StackIdToIndex(Address fp) { | |
| for (int i = 0; i < frame_fps_.length(); i++) { | |
| if (frame_fps_[i] == fp) { | |
| return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| Handle<FixedArray> MaterializedObjectStore::GetStackEntries() { | |
| return Handle<FixedArray>(isolate()->heap()->materialized_objects()); | |
| } | |
| Handle<FixedArray> MaterializedObjectStore::EnsureStackEntries(int length) { | |
| Handle<FixedArray> array = GetStackEntries(); | |
| if (array->length() >= length) { | |
| return array; | |
| } | |
| int new_length = length > 10 ? length : 10; | |
| if (new_length < 2 * array->length()) { | |
| new_length = 2 * array->length(); | |
| } | |
| Handle<FixedArray> new_array = | |
| isolate()->factory()->NewFixedArray(new_length, TENURED); | |
| for (int i = 0; i < array->length(); i++) { | |
| new_array->set(i, array->get(i)); | |
| } | |
| for (int i = array->length(); i < length; i++) { | |
| new_array->set(i, isolate()->heap()->undefined_value()); | |
| } | |
| isolate()->heap()->SetRootMaterializedObjects(*new_array); | |
| return new_array; | |
| } | |
| namespace { | |
| Handle<Object> GetValueForDebugger(TranslatedFrame::iterator it, | |
| Isolate* isolate) { | |
| if (it->GetRawValue() == isolate->heap()->arguments_marker()) { | |
| if (!it->IsMaterializableByDebugger()) { | |
| return isolate->factory()->undefined_value(); | |
| } | |
| } | |
| return it->GetValue(); | |
| } | |
| } // namespace | |
| DeoptimizedFrameInfo::DeoptimizedFrameInfo(TranslatedState* state, | |
| TranslatedState::iterator frame_it, | |
| Isolate* isolate) { | |
| // If the previous frame is an adaptor frame, we will take the parameters | |
| // from there. | |
| TranslatedState::iterator parameter_frame = frame_it; | |
| if (parameter_frame != state->begin()) { | |
| parameter_frame--; | |
| } | |
| int parameter_count; | |
| if (parameter_frame->kind() == TranslatedFrame::kArgumentsAdaptor) { | |
| parameter_count = parameter_frame->height() - 1; // Ignore the receiver. | |
| } else { | |
| parameter_frame = frame_it; | |
| parameter_count = | |
| frame_it->shared_info()->internal_formal_parameter_count(); | |
| } | |
| TranslatedFrame::iterator parameter_it = parameter_frame->begin(); | |
| parameter_it++; // Skip the function. | |
| parameter_it++; // Skip the receiver. | |
| // Figure out whether there is a construct stub frame on top of | |
| // the parameter frame. | |
| has_construct_stub_ = | |
| parameter_frame != state->begin() && | |
| (parameter_frame - 1)->kind() == TranslatedFrame::kConstructStub; | |
| if (frame_it->kind() == TranslatedFrame::kInterpretedFunction) { | |
| source_position_ = Deoptimizer::ComputeSourcePositionFromBytecodeArray( | |
| *frame_it->shared_info(), frame_it->node_id()); | |
| } else { | |
| DCHECK_EQ(TranslatedFrame::kFunction, frame_it->kind()); | |
| source_position_ = Deoptimizer::ComputeSourcePositionFromBaselineCode( | |
| *frame_it->shared_info(), frame_it->node_id()); | |
| } | |
| TranslatedFrame::iterator value_it = frame_it->begin(); | |
| // Get the function. Note that this might materialize the function. | |
| // In case the debugger mutates this value, we should deoptimize | |
| // the function and remember the value in the materialized value store. | |
| function_ = Handle<JSFunction>::cast(value_it->GetValue()); | |
| parameters_.resize(static_cast<size_t>(parameter_count)); | |
| for (int i = 0; i < parameter_count; i++) { | |
| Handle<Object> parameter = GetValueForDebugger(parameter_it, isolate); | |
| SetParameter(i, parameter); | |
| parameter_it++; | |
| } | |
| // Skip the function, the receiver and the arguments. | |
| int skip_count = | |
| frame_it->shared_info()->internal_formal_parameter_count() + 2; | |
| TranslatedFrame::iterator stack_it = frame_it->begin(); | |
| for (int i = 0; i < skip_count; i++) { | |
| stack_it++; | |
| } | |
| // Get the context. | |
| context_ = GetValueForDebugger(stack_it, isolate); | |
| stack_it++; | |
| // Get the expression stack. | |
| int stack_height = frame_it->height(); | |
| if (frame_it->kind() == TranslatedFrame::kFunction || | |
| frame_it->kind() == TranslatedFrame::kInterpretedFunction) { | |
| // For full-code frames, we should not count the context. | |
| // For interpreter frames, we should not count the accumulator. | |
| // TODO(jarin): Clean up the indexing in translated frames. | |
| stack_height--; | |
| } | |
| expression_stack_.resize(static_cast<size_t>(stack_height)); | |
| for (int i = 0; i < stack_height; i++) { | |
| Handle<Object> expression = GetValueForDebugger(stack_it, isolate); | |
| SetExpression(i, expression); | |
| stack_it++; | |
| } | |
| // For interpreter frame, skip the accumulator. | |
| if (frame_it->kind() == TranslatedFrame::kInterpretedFunction) { | |
| stack_it++; | |
| } | |
| CHECK(stack_it == frame_it->end()); | |
| } | |
| Deoptimizer::DeoptInfo Deoptimizer::GetDeoptInfo(Code* code, Address pc) { | |
| SourcePosition last_position = SourcePosition::Unknown(); | |
| DeoptimizeReason last_reason = DeoptimizeReason::kNoReason; | |
| int last_deopt_id = kNoDeoptimizationId; | |
| int mask = RelocInfo::ModeMask(RelocInfo::DEOPT_REASON) | | |
| RelocInfo::ModeMask(RelocInfo::DEOPT_ID) | | |
| RelocInfo::ModeMask(RelocInfo::DEOPT_SCRIPT_OFFSET) | | |
| RelocInfo::ModeMask(RelocInfo::DEOPT_INLINING_ID); | |
| for (RelocIterator it(code, mask); !it.done(); it.next()) { | |
| RelocInfo* info = it.rinfo(); | |
| if (info->pc() >= pc) { | |
| return DeoptInfo(last_position, last_reason, last_deopt_id); | |
| } | |
| if (info->rmode() == RelocInfo::DEOPT_SCRIPT_OFFSET) { | |
| int script_offset = static_cast<int>(info->data()); | |
| it.next(); | |
| DCHECK(it.rinfo()->rmode() == RelocInfo::DEOPT_INLINING_ID); | |
| int inlining_id = static_cast<int>(it.rinfo()->data()); | |
| last_position = SourcePosition(script_offset, inlining_id); | |
| } else if (info->rmode() == RelocInfo::DEOPT_ID) { | |
| last_deopt_id = static_cast<int>(info->data()); | |
| } else if (info->rmode() == RelocInfo::DEOPT_REASON) { | |
| last_reason = static_cast<DeoptimizeReason>(info->data()); | |
| } | |
| } | |
| return DeoptInfo(SourcePosition::Unknown(), DeoptimizeReason::kNoReason, -1); | |
| } | |
| // static | |
| int Deoptimizer::ComputeSourcePositionFromBaselineCode( | |
| SharedFunctionInfo* shared, BailoutId node_id) { | |
| DCHECK(shared->HasBaselineCode()); | |
| Code* code = shared->code(); | |
| FixedArray* raw_data = code->deoptimization_data(); | |
| DeoptimizationOutputData* data = DeoptimizationOutputData::cast(raw_data); | |
| unsigned pc_and_state = Deoptimizer::GetOutputInfo(data, node_id, shared); | |
| int code_offset = | |
| static_cast<int>(FullCodeGenerator::PcField::decode(pc_and_state)); | |
| return AbstractCode::cast(code)->SourcePosition(code_offset); | |
| } | |
| // static | |
| int Deoptimizer::ComputeSourcePositionFromBytecodeArray( | |
| SharedFunctionInfo* shared, BailoutId node_id) { | |
| DCHECK(shared->HasBytecodeArray()); | |
| return AbstractCode::cast(shared->bytecode_array()) | |
| ->SourcePosition(node_id.ToInt()); | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewArgumentsObject(TranslatedState* container, | |
| int length, | |
| int object_index) { | |
| TranslatedValue slot(container, kArgumentsObject); | |
| slot.materialization_info_ = {object_index, length}; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewDeferredObject(TranslatedState* container, | |
| int length, | |
| int object_index) { | |
| TranslatedValue slot(container, kCapturedObject); | |
| slot.materialization_info_ = {object_index, length}; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewDuplicateObject(TranslatedState* container, | |
| int id) { | |
| TranslatedValue slot(container, kDuplicatedObject); | |
| slot.materialization_info_ = {id, -1}; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewFloat(TranslatedState* container, | |
| float value) { | |
| TranslatedValue slot(container, kFloat); | |
| slot.float_value_ = value; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewDouble(TranslatedState* container, | |
| double value) { | |
| TranslatedValue slot(container, kDouble); | |
| slot.double_value_ = value; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewInt32(TranslatedState* container, | |
| int32_t value) { | |
| TranslatedValue slot(container, kInt32); | |
| slot.int32_value_ = value; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewUInt32(TranslatedState* container, | |
| uint32_t value) { | |
| TranslatedValue slot(container, kUInt32); | |
| slot.uint32_value_ = value; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewBool(TranslatedState* container, | |
| uint32_t value) { | |
| TranslatedValue slot(container, kBoolBit); | |
| slot.uint32_value_ = value; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewTagged(TranslatedState* container, | |
| Object* literal) { | |
| TranslatedValue slot(container, kTagged); | |
| slot.raw_literal_ = literal; | |
| return slot; | |
| } | |
| // static | |
| TranslatedValue TranslatedValue::NewInvalid(TranslatedState* container) { | |
| return TranslatedValue(container, kInvalid); | |
| } | |
| Isolate* TranslatedValue::isolate() const { return container_->isolate(); } | |
| Object* TranslatedValue::raw_literal() const { | |
| DCHECK_EQ(kTagged, kind()); | |
| return raw_literal_; | |
| } | |
| int32_t TranslatedValue::int32_value() const { | |
| DCHECK_EQ(kInt32, kind()); | |
| return int32_value_; | |
| } | |
| uint32_t TranslatedValue::uint32_value() const { | |
| DCHECK(kind() == kUInt32 || kind() == kBoolBit); | |
| return uint32_value_; | |
| } | |
| float TranslatedValue::float_value() const { | |
| DCHECK_EQ(kFloat, kind()); | |
| return float_value_; | |
| } | |
| double TranslatedValue::double_value() const { | |
| DCHECK_EQ(kDouble, kind()); | |
| return double_value_; | |
| } | |
| int TranslatedValue::object_length() const { | |
| DCHECK(kind() == kArgumentsObject || kind() == kCapturedObject); | |
| return materialization_info_.length_; | |
| } | |
| int TranslatedValue::object_index() const { | |
| DCHECK(kind() == kArgumentsObject || kind() == kCapturedObject || | |
| kind() == kDuplicatedObject); | |
| return materialization_info_.id_; | |
| } | |
| Object* TranslatedValue::GetRawValue() const { | |
| // If we have a value, return it. | |
| Handle<Object> result_handle; | |
| if (value_.ToHandle(&result_handle)) { | |
| return *result_handle; | |
| } | |
| // Otherwise, do a best effort to get the value without allocation. | |
| switch (kind()) { | |
| case kTagged: | |
| return raw_literal(); | |
| case kInt32: { | |
| bool is_smi = Smi::IsValid(int32_value()); | |
| if (is_smi) { | |
| return Smi::FromInt(int32_value()); | |
| } | |
| break; | |
| } | |
| case kUInt32: { | |
| bool is_smi = (uint32_value() <= static_cast<uintptr_t>(Smi::kMaxValue)); | |
| if (is_smi) { | |
| return Smi::FromInt(static_cast<int32_t>(uint32_value())); | |
| } | |
| break; | |
| } | |
| case kBoolBit: { | |
| if (uint32_value() == 0) { | |
| return isolate()->heap()->false_value(); | |
| } else { | |
| CHECK_EQ(1U, uint32_value()); | |
| return isolate()->heap()->true_value(); | |
| } | |
| } | |
| default: | |
| break; | |
| } | |
| // If we could not get the value without allocation, return the arguments | |
| // marker. | |
| return isolate()->heap()->arguments_marker(); | |
| } | |
| Handle<Object> TranslatedValue::GetValue() { | |
| Handle<Object> result; | |
| // If we already have a value, then get it. | |
| if (value_.ToHandle(&result)) return result; | |
| // Otherwise we have to materialize. | |
| switch (kind()) { | |
| case TranslatedValue::kTagged: | |
| case TranslatedValue::kInt32: | |
| case TranslatedValue::kUInt32: | |
| case TranslatedValue::kBoolBit: | |
| case TranslatedValue::kFloat: | |
| case TranslatedValue::kDouble: { | |
| MaterializeSimple(); | |
| return value_.ToHandleChecked(); | |
| } | |
| case TranslatedValue::kArgumentsObject: | |
| case TranslatedValue::kCapturedObject: | |
| case TranslatedValue::kDuplicatedObject: | |
| return container_->MaterializeObjectAt(object_index()); | |
| case TranslatedValue::kInvalid: | |
| FATAL("unexpected case"); | |
| return Handle<Object>::null(); | |
| } | |
| FATAL("internal error: value missing"); | |
| return Handle<Object>::null(); | |
| } | |
| void TranslatedValue::MaterializeSimple() { | |
| // If we already have materialized, return. | |
| if (!value_.is_null()) return; | |
| Object* raw_value = GetRawValue(); | |
| if (raw_value != isolate()->heap()->arguments_marker()) { | |
| // We can get the value without allocation, just return it here. | |
| value_ = Handle<Object>(raw_value, isolate()); | |
| return; | |
| } | |
| switch (kind()) { | |
| case kInt32: { | |
| value_ = Handle<Object>(isolate()->factory()->NewNumber(int32_value())); | |
| return; | |
| } | |
| case kUInt32: | |
| value_ = Handle<Object>(isolate()->factory()->NewNumber(uint32_value())); | |
| return; | |
| case kFloat: | |
| value_ = Handle<Object>(isolate()->factory()->NewNumber(float_value())); | |
| return; | |
| case kDouble: | |
| value_ = Handle<Object>(isolate()->factory()->NewNumber(double_value())); | |
| return; | |
| case kCapturedObject: | |
| case kDuplicatedObject: | |
| case kArgumentsObject: | |
| case kInvalid: | |
| case kTagged: | |
| case kBoolBit: | |
| FATAL("internal error: unexpected materialization."); | |
| break; | |
| } | |
| } | |
| bool TranslatedValue::IsMaterializedObject() const { | |
| switch (kind()) { | |
| case kCapturedObject: | |
| case kDuplicatedObject: | |
| case kArgumentsObject: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| bool TranslatedValue::IsMaterializableByDebugger() const { | |
| // At the moment, we only allow materialization of doubles. | |
| return (kind() == kDouble); | |
| } | |
| int TranslatedValue::GetChildrenCount() const { | |
| if (kind() == kCapturedObject || kind() == kArgumentsObject) { | |
| return object_length(); | |
| } else { | |
| return 0; | |
| } | |
| } | |
| uint32_t TranslatedState::GetUInt32Slot(Address fp, int slot_offset) { | |
| Address address = fp + slot_offset; | |
| #if V8_TARGET_BIG_ENDIAN && V8_HOST_ARCH_64_BIT | |
| return Memory::uint32_at(address + kIntSize); | |
| #else | |
| return Memory::uint32_at(address); | |
| #endif | |
| } | |
| void TranslatedValue::Handlify() { | |
| if (kind() == kTagged) { | |
| value_ = Handle<Object>(raw_literal(), isolate()); | |
| raw_literal_ = nullptr; | |
| } | |
| } | |
| TranslatedFrame TranslatedFrame::JSFrame(BailoutId node_id, | |
| SharedFunctionInfo* shared_info, | |
| int height) { | |
| TranslatedFrame frame(kFunction, shared_info->GetIsolate(), shared_info, | |
| height); | |
| frame.node_id_ = node_id; | |
| return frame; | |
| } | |
| TranslatedFrame TranslatedFrame::InterpretedFrame( | |
| BailoutId bytecode_offset, SharedFunctionInfo* shared_info, int height) { | |
| TranslatedFrame frame(kInterpretedFunction, shared_info->GetIsolate(), | |
| shared_info, height); | |
| frame.node_id_ = bytecode_offset; | |
| return frame; | |
| } | |
| TranslatedFrame TranslatedFrame::AccessorFrame( | |
| Kind kind, SharedFunctionInfo* shared_info) { | |
| DCHECK(kind == kSetter || kind == kGetter); | |
| return TranslatedFrame(kind, shared_info->GetIsolate(), shared_info); | |
| } | |
| TranslatedFrame TranslatedFrame::ArgumentsAdaptorFrame( | |
| SharedFunctionInfo* shared_info, int height) { | |
| return TranslatedFrame(kArgumentsAdaptor, shared_info->GetIsolate(), | |
| shared_info, height); | |
| } | |
| TranslatedFrame TranslatedFrame::TailCallerFrame( | |
| SharedFunctionInfo* shared_info) { | |
| return TranslatedFrame(kTailCallerFunction, shared_info->GetIsolate(), | |
| shared_info, 0); | |
| } | |
| TranslatedFrame TranslatedFrame::ConstructStubFrame( | |
| SharedFunctionInfo* shared_info, int height) { | |
| return TranslatedFrame(kConstructStub, shared_info->GetIsolate(), shared_info, | |
| height); | |
| } | |
| int TranslatedFrame::GetValueCount() { | |
| switch (kind()) { | |
| case kFunction: { | |
| int parameter_count = | |
| raw_shared_info_->internal_formal_parameter_count() + 1; | |
| // + 1 for function. | |
| return height_ + parameter_count + 1; | |
| } | |
| case kInterpretedFunction: { | |
| int parameter_count = | |
| raw_shared_info_->internal_formal_parameter_count() + 1; | |
| // + 2 for function and context. | |
| return height_ + parameter_count + 2; | |
| } | |
| case kGetter: | |
| return 2; // Function and receiver. | |
| case kSetter: | |
| return 3; // Function, receiver and the value to set. | |
| case kArgumentsAdaptor: | |
| case kConstructStub: | |
| return 1 + height_; | |
| case kTailCallerFunction: | |
| return 1; // Function. | |
| case kCompiledStub: | |
| return height_; | |
| case kInvalid: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| UNREACHABLE(); | |
| return -1; | |
| } | |
| void TranslatedFrame::Handlify() { | |
| if (raw_shared_info_ != nullptr) { | |
| shared_info_ = Handle<SharedFunctionInfo>(raw_shared_info_); | |
| raw_shared_info_ = nullptr; | |
| } | |
| for (auto& value : values_) { | |
| value.Handlify(); | |
| } | |
| } | |
| TranslatedFrame TranslatedState::CreateNextTranslatedFrame( | |
| TranslationIterator* iterator, FixedArray* literal_array, Address fp, | |
| FILE* trace_file) { | |
| Translation::Opcode opcode = | |
| static_cast<Translation::Opcode>(iterator->Next()); | |
| switch (opcode) { | |
| case Translation::JS_FRAME: { | |
| BailoutId node_id = BailoutId(iterator->Next()); | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| int height = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading input frame %s", name.get()); | |
| int arg_count = shared_info->internal_formal_parameter_count() + 1; | |
| PrintF(trace_file, " => node=%d, args=%d, height=%d; inputs:\n", | |
| node_id.ToInt(), arg_count, height); | |
| } | |
| return TranslatedFrame::JSFrame(node_id, shared_info, height); | |
| } | |
| case Translation::INTERPRETED_FRAME: { | |
| BailoutId bytecode_offset = BailoutId(iterator->Next()); | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| int height = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading input frame %s", name.get()); | |
| int arg_count = shared_info->internal_formal_parameter_count() + 1; | |
| PrintF(trace_file, | |
| " => bytecode_offset=%d, args=%d, height=%d; inputs:\n", | |
| bytecode_offset.ToInt(), arg_count, height); | |
| } | |
| return TranslatedFrame::InterpretedFrame(bytecode_offset, shared_info, | |
| height); | |
| } | |
| case Translation::ARGUMENTS_ADAPTOR_FRAME: { | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| int height = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading arguments adaptor frame %s", name.get()); | |
| PrintF(trace_file, " => height=%d; inputs:\n", height); | |
| } | |
| return TranslatedFrame::ArgumentsAdaptorFrame(shared_info, height); | |
| } | |
| case Translation::TAIL_CALLER_FRAME: { | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading tail caller frame marker %s\n", | |
| name.get()); | |
| } | |
| return TranslatedFrame::TailCallerFrame(shared_info); | |
| } | |
| case Translation::CONSTRUCT_STUB_FRAME: { | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| int height = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading construct stub frame %s", name.get()); | |
| PrintF(trace_file, " => height=%d; inputs:\n", height); | |
| } | |
| return TranslatedFrame::ConstructStubFrame(shared_info, height); | |
| } | |
| case Translation::GETTER_STUB_FRAME: { | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading getter frame %s; inputs:\n", name.get()); | |
| } | |
| return TranslatedFrame::AccessorFrame(TranslatedFrame::kGetter, | |
| shared_info); | |
| } | |
| case Translation::SETTER_STUB_FRAME: { | |
| SharedFunctionInfo* shared_info = | |
| SharedFunctionInfo::cast(literal_array->get(iterator->Next())); | |
| if (trace_file != nullptr) { | |
| std::unique_ptr<char[]> name = shared_info->DebugName()->ToCString(); | |
| PrintF(trace_file, " reading setter frame %s; inputs:\n", name.get()); | |
| } | |
| return TranslatedFrame::AccessorFrame(TranslatedFrame::kSetter, | |
| shared_info); | |
| } | |
| case Translation::COMPILED_STUB_FRAME: { | |
| int height = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, | |
| " reading compiler stub frame => height=%d; inputs:\n", height); | |
| } | |
| return TranslatedFrame::CompiledStubFrame(height, | |
| literal_array->GetIsolate()); | |
| } | |
| case Translation::BEGIN: | |
| case Translation::DUPLICATED_OBJECT: | |
| case Translation::ARGUMENTS_OBJECT: | |
| case Translation::CAPTURED_OBJECT: | |
| case Translation::REGISTER: | |
| case Translation::INT32_REGISTER: | |
| case Translation::UINT32_REGISTER: | |
| case Translation::BOOL_REGISTER: | |
| case Translation::FLOAT_REGISTER: | |
| case Translation::DOUBLE_REGISTER: | |
| case Translation::STACK_SLOT: | |
| case Translation::INT32_STACK_SLOT: | |
| case Translation::UINT32_STACK_SLOT: | |
| case Translation::BOOL_STACK_SLOT: | |
| case Translation::FLOAT_STACK_SLOT: | |
| case Translation::DOUBLE_STACK_SLOT: | |
| case Translation::LITERAL: | |
| break; | |
| } | |
| FATAL("We should never get here - unexpected deopt info."); | |
| return TranslatedFrame::InvalidFrame(); | |
| } | |
| // static | |
| void TranslatedFrame::AdvanceIterator( | |
| std::deque<TranslatedValue>::iterator* iter) { | |
| int values_to_skip = 1; | |
| while (values_to_skip > 0) { | |
| // Consume the current element. | |
| values_to_skip--; | |
| // Add all the children. | |
| values_to_skip += (*iter)->GetChildrenCount(); | |
| (*iter)++; | |
| } | |
| } | |
| // We can't intermix stack decoding and allocations because | |
| // deoptimization infrastracture is not GC safe. | |
| // Thus we build a temporary structure in malloced space. | |
| TranslatedValue TranslatedState::CreateNextTranslatedValue( | |
| int frame_index, int value_index, TranslationIterator* iterator, | |
| FixedArray* literal_array, Address fp, RegisterValues* registers, | |
| FILE* trace_file) { | |
| disasm::NameConverter converter; | |
| Translation::Opcode opcode = | |
| static_cast<Translation::Opcode>(iterator->Next()); | |
| switch (opcode) { | |
| case Translation::BEGIN: | |
| case Translation::JS_FRAME: | |
| case Translation::INTERPRETED_FRAME: | |
| case Translation::ARGUMENTS_ADAPTOR_FRAME: | |
| case Translation::TAIL_CALLER_FRAME: | |
| case Translation::CONSTRUCT_STUB_FRAME: | |
| case Translation::GETTER_STUB_FRAME: | |
| case Translation::SETTER_STUB_FRAME: | |
| case Translation::COMPILED_STUB_FRAME: | |
| // Peeled off before getting here. | |
| break; | |
| case Translation::DUPLICATED_OBJECT: { | |
| int object_id = iterator->Next(); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "duplicated object #%d", object_id); | |
| } | |
| object_positions_.push_back(object_positions_[object_id]); | |
| return TranslatedValue::NewDuplicateObject(this, object_id); | |
| } | |
| case Translation::ARGUMENTS_OBJECT: { | |
| int arg_count = iterator->Next(); | |
| int object_index = static_cast<int>(object_positions_.size()); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "argumets object #%d (length = %d)", object_index, | |
| arg_count); | |
| } | |
| object_positions_.push_back({frame_index, value_index}); | |
| return TranslatedValue::NewArgumentsObject(this, arg_count, object_index); | |
| } | |
| case Translation::CAPTURED_OBJECT: { | |
| int field_count = iterator->Next(); | |
| int object_index = static_cast<int>(object_positions_.size()); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "captured object #%d (length = %d)", object_index, | |
| field_count); | |
| } | |
| object_positions_.push_back({frame_index, value_index}); | |
| return TranslatedValue::NewDeferredObject(this, field_count, | |
| object_index); | |
| } | |
| case Translation::REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| intptr_t value = registers->GetRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "0x%08" V8PRIxPTR " ; %s ", value, | |
| converter.NameOfCPURegister(input_reg)); | |
| reinterpret_cast<Object*>(value)->ShortPrint(trace_file); | |
| } | |
| return TranslatedValue::NewTagged(this, reinterpret_cast<Object*>(value)); | |
| } | |
| case Translation::INT32_REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| intptr_t value = registers->GetRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%" V8PRIdPTR " ; %s ", value, | |
| converter.NameOfCPURegister(input_reg)); | |
| } | |
| return TranslatedValue::NewInt32(this, static_cast<int32_t>(value)); | |
| } | |
| case Translation::UINT32_REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| intptr_t value = registers->GetRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%" V8PRIuPTR " ; %s (uint)", value, | |
| converter.NameOfCPURegister(input_reg)); | |
| reinterpret_cast<Object*>(value)->ShortPrint(trace_file); | |
| } | |
| return TranslatedValue::NewUInt32(this, static_cast<uint32_t>(value)); | |
| } | |
| case Translation::BOOL_REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| intptr_t value = registers->GetRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%" V8PRIdPTR " ; %s (bool)", value, | |
| converter.NameOfCPURegister(input_reg)); | |
| } | |
| return TranslatedValue::NewBool(this, static_cast<uint32_t>(value)); | |
| } | |
| case Translation::FLOAT_REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| float value = registers->GetFloatRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%e ; %s (float)", value, | |
| RegisterConfiguration::Crankshaft()->GetFloatRegisterName( | |
| input_reg)); | |
| } | |
| return TranslatedValue::NewFloat(this, value); | |
| } | |
| case Translation::DOUBLE_REGISTER: { | |
| int input_reg = iterator->Next(); | |
| if (registers == nullptr) return TranslatedValue::NewInvalid(this); | |
| double value = registers->GetDoubleRegister(input_reg); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%e ; %s (double)", value, | |
| RegisterConfiguration::Crankshaft()->GetDoubleRegisterName( | |
| input_reg)); | |
| } | |
| return TranslatedValue::NewDouble(this, value); | |
| } | |
| case Translation::STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| intptr_t value = *(reinterpret_cast<intptr_t*>(fp + slot_offset)); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "0x%08" V8PRIxPTR " ; [fp %c %d] ", value, | |
| slot_offset < 0 ? '-' : '+', std::abs(slot_offset)); | |
| reinterpret_cast<Object*>(value)->ShortPrint(trace_file); | |
| } | |
| return TranslatedValue::NewTagged(this, reinterpret_cast<Object*>(value)); | |
| } | |
| case Translation::INT32_STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| uint32_t value = GetUInt32Slot(fp, slot_offset); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%d ; (int) [fp %c %d] ", | |
| static_cast<int32_t>(value), slot_offset < 0 ? '-' : '+', | |
| std::abs(slot_offset)); | |
| } | |
| return TranslatedValue::NewInt32(this, value); | |
| } | |
| case Translation::UINT32_STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| uint32_t value = GetUInt32Slot(fp, slot_offset); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%u ; (uint) [fp %c %d] ", value, | |
| slot_offset < 0 ? '-' : '+', std::abs(slot_offset)); | |
| } | |
| return TranslatedValue::NewUInt32(this, value); | |
| } | |
| case Translation::BOOL_STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| uint32_t value = GetUInt32Slot(fp, slot_offset); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%u ; (bool) [fp %c %d] ", value, | |
| slot_offset < 0 ? '-' : '+', std::abs(slot_offset)); | |
| } | |
| return TranslatedValue::NewBool(this, value); | |
| } | |
| case Translation::FLOAT_STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| float value = ReadFloatValue(fp + slot_offset); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%e ; (float) [fp %c %d] ", value, | |
| slot_offset < 0 ? '-' : '+', std::abs(slot_offset)); | |
| } | |
| return TranslatedValue::NewFloat(this, value); | |
| } | |
| case Translation::DOUBLE_STACK_SLOT: { | |
| int slot_offset = | |
| OptimizedFrame::StackSlotOffsetRelativeToFp(iterator->Next()); | |
| double value = ReadDoubleValue(fp + slot_offset); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "%e ; (double) [fp %c %d] ", value, | |
| slot_offset < 0 ? '-' : '+', std::abs(slot_offset)); | |
| } | |
| return TranslatedValue::NewDouble(this, value); | |
| } | |
| case Translation::LITERAL: { | |
| int literal_index = iterator->Next(); | |
| Object* value = literal_array->get(literal_index); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "0x%08" V8PRIxPTR " ; (literal %d) ", | |
| reinterpret_cast<intptr_t>(value), literal_index); | |
| reinterpret_cast<Object*>(value)->ShortPrint(trace_file); | |
| } | |
| return TranslatedValue::NewTagged(this, value); | |
| } | |
| } | |
| FATAL("We should never get here - unexpected deopt info."); | |
| return TranslatedValue(nullptr, TranslatedValue::kInvalid); | |
| } | |
| TranslatedState::TranslatedState(JavaScriptFrame* frame) | |
| : isolate_(nullptr), | |
| stack_frame_pointer_(nullptr), | |
| has_adapted_arguments_(false) { | |
| int deopt_index = Safepoint::kNoDeoptimizationIndex; | |
| DeoptimizationInputData* data = | |
| static_cast<OptimizedFrame*>(frame)->GetDeoptimizationData(&deopt_index); | |
| DCHECK(data != nullptr && deopt_index != Safepoint::kNoDeoptimizationIndex); | |
| TranslationIterator it(data->TranslationByteArray(), | |
| data->TranslationIndex(deopt_index)->value()); | |
| Init(frame->fp(), &it, data->LiteralArray(), nullptr /* registers */, | |
| nullptr /* trace file */); | |
| } | |
| TranslatedState::TranslatedState() | |
| : isolate_(nullptr), | |
| stack_frame_pointer_(nullptr), | |
| has_adapted_arguments_(false) {} | |
| void TranslatedState::Init(Address input_frame_pointer, | |
| TranslationIterator* iterator, | |
| FixedArray* literal_array, RegisterValues* registers, | |
| FILE* trace_file) { | |
| DCHECK(frames_.empty()); | |
| isolate_ = literal_array->GetIsolate(); | |
| // Read out the 'header' translation. | |
| Translation::Opcode opcode = | |
| static_cast<Translation::Opcode>(iterator->Next()); | |
| CHECK(opcode == Translation::BEGIN); | |
| int count = iterator->Next(); | |
| iterator->Next(); // Drop JS frames count. | |
| frames_.reserve(count); | |
| std::stack<int> nested_counts; | |
| // Read the frames | |
| for (int i = 0; i < count; i++) { | |
| // Read the frame descriptor. | |
| frames_.push_back(CreateNextTranslatedFrame( | |
| iterator, literal_array, input_frame_pointer, trace_file)); | |
| TranslatedFrame& frame = frames_.back(); | |
| // Read the values. | |
| int values_to_process = frame.GetValueCount(); | |
| while (values_to_process > 0 || !nested_counts.empty()) { | |
| if (trace_file != nullptr) { | |
| if (nested_counts.empty()) { | |
| // For top level values, print the value number. | |
| PrintF(trace_file, " %3i: ", | |
| frame.GetValueCount() - values_to_process); | |
| } else { | |
| // Take care of indenting for nested values. | |
| PrintF(trace_file, " "); | |
| for (size_t j = 0; j < nested_counts.size(); j++) { | |
| PrintF(trace_file, " "); | |
| } | |
| } | |
| } | |
| TranslatedValue value = CreateNextTranslatedValue( | |
| i, static_cast<int>(frame.values_.size()), iterator, literal_array, | |
| input_frame_pointer, registers, trace_file); | |
| frame.Add(value); | |
| if (trace_file != nullptr) { | |
| PrintF(trace_file, "\n"); | |
| } | |
| // Update the value count and resolve the nesting. | |
| values_to_process--; | |
| int children_count = value.GetChildrenCount(); | |
| if (children_count > 0) { | |
| nested_counts.push(values_to_process); | |
| values_to_process = children_count; | |
| } else { | |
| while (values_to_process == 0 && !nested_counts.empty()) { | |
| values_to_process = nested_counts.top(); | |
| nested_counts.pop(); | |
| } | |
| } | |
| } | |
| } | |
| CHECK(!iterator->HasNext() || | |
| static_cast<Translation::Opcode>(iterator->Next()) == | |
| Translation::BEGIN); | |
| } | |
| void TranslatedState::Prepare(bool has_adapted_arguments, | |
| Address stack_frame_pointer) { | |
| for (auto& frame : frames_) frame.Handlify(); | |
| stack_frame_pointer_ = stack_frame_pointer; | |
| has_adapted_arguments_ = has_adapted_arguments; | |
| UpdateFromPreviouslyMaterializedObjects(); | |
| } | |
| Handle<Object> TranslatedState::MaterializeAt(int frame_index, | |
| int* value_index) { | |
| TranslatedFrame* frame = &(frames_[frame_index]); | |
| CHECK(static_cast<size_t>(*value_index) < frame->values_.size()); | |
| TranslatedValue* slot = &(frame->values_[*value_index]); | |
| (*value_index)++; | |
| switch (slot->kind()) { | |
| case TranslatedValue::kTagged: | |
| case TranslatedValue::kInt32: | |
| case TranslatedValue::kUInt32: | |
| case TranslatedValue::kBoolBit: | |
| case TranslatedValue::kFloat: | |
| case TranslatedValue::kDouble: { | |
| slot->MaterializeSimple(); | |
| Handle<Object> value = slot->GetValue(); | |
| if (value->IsMutableHeapNumber()) { | |
| HeapNumber::cast(*value)->set_map(isolate()->heap()->heap_number_map()); | |
| } | |
| return value; | |
| } | |
| case TranslatedValue::kArgumentsObject: { | |
| int length = slot->GetChildrenCount(); | |
| Handle<JSObject> arguments; | |
| if (GetAdaptedArguments(&arguments, frame_index)) { | |
| // Store the materialized object and consume the nested values. | |
| for (int i = 0; i < length; ++i) { | |
| MaterializeAt(frame_index, value_index); | |
| } | |
| } else { | |
| Handle<JSFunction> function = | |
| Handle<JSFunction>::cast(frame->front().GetValue()); | |
| arguments = isolate_->factory()->NewArgumentsObject(function, length); | |
| Handle<FixedArray> array = isolate_->factory()->NewFixedArray(length); | |
| DCHECK_EQ(array->length(), length); | |
| arguments->set_elements(*array); | |
| for (int i = 0; i < length; ++i) { | |
| Handle<Object> value = MaterializeAt(frame_index, value_index); | |
| array->set(i, *value); | |
| } | |
| } | |
| slot->value_ = arguments; | |
| return arguments; | |
| } | |
| case TranslatedValue::kCapturedObject: { | |
| int length = slot->GetChildrenCount(); | |
| // The map must be a tagged object. | |
| CHECK(frame->values_[*value_index].kind() == TranslatedValue::kTagged); | |
| Handle<Object> result; | |
| if (slot->value_.ToHandle(&result)) { | |
| // This has been previously materialized, return the previous value. | |
| // We still need to skip all the nested objects. | |
| for (int i = 0; i < length; i++) { | |
| MaterializeAt(frame_index, value_index); | |
| } | |
| return result; | |
| } | |
| Handle<Object> map_object = MaterializeAt(frame_index, value_index); | |
| Handle<Map> map = | |
| Map::GeneralizeAllFieldRepresentations(Handle<Map>::cast(map_object)); | |
| switch (map->instance_type()) { | |
| case MUTABLE_HEAP_NUMBER_TYPE: | |
| case HEAP_NUMBER_TYPE: { | |
| // Reuse the HeapNumber value directly as it is already properly | |
| // tagged and skip materializing the HeapNumber explicitly. | |
| Handle<Object> object = MaterializeAt(frame_index, value_index); | |
| slot->value_ = object; | |
| // On 32-bit architectures, there is an extra slot there because | |
| // the escape analysis calculates the number of slots as | |
| // object-size/pointer-size. To account for this, we read out | |
| // any extra slots. | |
| for (int i = 0; i < length - 2; i++) { | |
| MaterializeAt(frame_index, value_index); | |
| } | |
| return object; | |
| } | |
| case JS_OBJECT_TYPE: | |
| case JS_ERROR_TYPE: | |
| case JS_ARGUMENTS_TYPE: { | |
| Handle<JSObject> object = | |
| isolate_->factory()->NewJSObjectFromMap(map, NOT_TENURED); | |
| slot->value_ = object; | |
| Handle<Object> properties = MaterializeAt(frame_index, value_index); | |
| Handle<Object> elements = MaterializeAt(frame_index, value_index); | |
| object->set_properties(FixedArray::cast(*properties)); | |
| object->set_elements(FixedArrayBase::cast(*elements)); | |
| for (int i = 0; i < length - 3; ++i) { | |
| Handle<Object> value = MaterializeAt(frame_index, value_index); | |
| FieldIndex index = FieldIndex::ForPropertyIndex(object->map(), i); | |
| object->FastPropertyAtPut(index, *value); | |
| } | |
| return object; | |
| } | |
| case JS_ARRAY_TYPE: { | |
| Handle<JSArray> object = Handle<JSArray>::cast( | |
| isolate_->factory()->NewJSObjectFromMap(map, NOT_TENURED)); | |
| slot->value_ = object; | |
| Handle<Object> properties = MaterializeAt(frame_index, value_index); | |
| Handle<Object> elements = MaterializeAt(frame_index, value_index); | |
| Handle<Object> length = MaterializeAt(frame_index, value_index); | |
| object->set_properties(FixedArray::cast(*properties)); | |
| object->set_elements(FixedArrayBase::cast(*elements)); | |
| object->set_length(*length); | |
| return object; | |
| } | |
| case JS_FUNCTION_TYPE: { | |
| Handle<SharedFunctionInfo> temporary_shared = | |
| isolate_->factory()->NewSharedFunctionInfo( | |
| isolate_->factory()->empty_string(), MaybeHandle<Code>(), | |
| false); | |
| Handle<JSFunction> object = | |
| isolate_->factory()->NewFunctionFromSharedFunctionInfo( | |
| map, temporary_shared, isolate_->factory()->undefined_value(), | |
| NOT_TENURED); | |
| slot->value_ = object; | |
| Handle<Object> properties = MaterializeAt(frame_index, value_index); | |
| Handle<Object> elements = MaterializeAt(frame_index, value_index); | |
| Handle<Object> prototype = MaterializeAt(frame_index, value_index); | |
| Handle<Object> shared = MaterializeAt(frame_index, value_index); | |
| Handle<Object> context = MaterializeAt(frame_index, value_index); | |
| Handle<Object> literals = MaterializeAt(frame_index, value_index); | |
| Handle<Object> entry = MaterializeAt(frame_index, value_index); | |
| Handle<Object> next_link = MaterializeAt(frame_index, value_index); | |
| object->ReplaceCode(*isolate_->builtins()->CompileLazy()); | |
| object->set_map(*map); | |
| object->set_properties(FixedArray::cast(*properties)); | |
| object->set_elements(FixedArrayBase::cast(*elements)); | |
| object->set_prototype_or_initial_map(*prototype); | |
| object->set_shared(SharedFunctionInfo::cast(*shared)); | |
| object->set_context(Context::cast(*context)); | |
| object->set_literals(LiteralsArray::cast(*literals)); | |
| CHECK(entry->IsNumber()); // Entry to compile lazy stub. | |
| CHECK(next_link->IsUndefined(isolate_)); | |
| return object; | |
| } | |
| case CONS_STRING_TYPE: { | |
| Handle<ConsString> object = Handle<ConsString>::cast( | |
| isolate_->factory() | |
| ->NewConsString(isolate_->factory()->undefined_string(), | |
| isolate_->factory()->undefined_string()) | |
| .ToHandleChecked()); | |
| slot->value_ = object; | |
| Handle<Object> hash = MaterializeAt(frame_index, value_index); | |
| Handle<Object> length = MaterializeAt(frame_index, value_index); | |
| Handle<Object> first = MaterializeAt(frame_index, value_index); | |
| Handle<Object> second = MaterializeAt(frame_index, value_index); | |
| object->set_map(*map); | |
| object->set_length(Smi::cast(*length)->value()); | |
| object->set_first(String::cast(*first)); | |
| object->set_second(String::cast(*second)); | |
| CHECK(hash->IsNumber()); // The {Name::kEmptyHashField} value. | |
| return object; | |
| } | |
| case CONTEXT_EXTENSION_TYPE: { | |
| Handle<ContextExtension> object = | |
| isolate_->factory()->NewContextExtension( | |
| isolate_->factory()->NewScopeInfo(1), | |
| isolate_->factory()->undefined_value()); | |
| slot->value_ = object; | |
| Handle<Object> scope_info = MaterializeAt(frame_index, value_index); | |
| Handle<Object> extension = MaterializeAt(frame_index, value_index); | |
| object->set_scope_info(ScopeInfo::cast(*scope_info)); | |
| object->set_extension(*extension); | |
| return object; | |
| } | |
| case FIXED_ARRAY_TYPE: { | |
| Handle<Object> lengthObject = MaterializeAt(frame_index, value_index); | |
| int32_t length = 0; | |
| CHECK(lengthObject->ToInt32(&length)); | |
| Handle<FixedArray> object = | |
| isolate_->factory()->NewFixedArray(length); | |
| // We need to set the map, because the fixed array we are | |
| // materializing could be a context or an arguments object, | |
| // in which case we must retain that information. | |
| object->set_map(*map); | |
| slot->value_ = object; | |
| for (int i = 0; i < length; ++i) { | |
| Handle<Object> value = MaterializeAt(frame_index, value_index); | |
| object->set(i, *value); | |
| } | |
| return object; | |
| } | |
| case FIXED_DOUBLE_ARRAY_TYPE: { | |
| DCHECK_EQ(*map, isolate_->heap()->fixed_double_array_map()); | |
| Handle<Object> lengthObject = MaterializeAt(frame_index, value_index); | |
| int32_t length = 0; | |
| CHECK(lengthObject->ToInt32(&length)); | |
| Handle<FixedArrayBase> object = | |
| isolate_->factory()->NewFixedDoubleArray(length); | |
| slot->value_ = object; | |
| if (length > 0) { | |
| Handle<FixedDoubleArray> double_array = | |
| Handle<FixedDoubleArray>::cast(object); | |
| for (int i = 0; i < length; ++i) { | |
| Handle<Object> value = MaterializeAt(frame_index, value_index); | |
| CHECK(value->IsNumber()); | |
| double_array->set(i, value->Number()); | |
| } | |
| } | |
| return object; | |
| } | |
| default: | |
| PrintF(stderr, "[couldn't handle instance type %d]\n", | |
| map->instance_type()); | |
| FATAL("unreachable"); | |
| return Handle<Object>::null(); | |
| } | |
| UNREACHABLE(); | |
| break; | |
| } | |
| case TranslatedValue::kDuplicatedObject: { | |
| int object_index = slot->object_index(); | |
| TranslatedState::ObjectPosition pos = object_positions_[object_index]; | |
| // Make sure the duplicate is refering to a previous object. | |
| CHECK(pos.frame_index_ < frame_index || | |
| (pos.frame_index_ == frame_index && | |
| pos.value_index_ < *value_index - 1)); | |
| Handle<Object> object = | |
| frames_[pos.frame_index_].values_[pos.value_index_].GetValue(); | |
| // The object should have a (non-sentinel) value. | |
| CHECK(!object.is_null() && | |
| !object.is_identical_to(isolate_->factory()->arguments_marker())); | |
| slot->value_ = object; | |
| return object; | |
| } | |
| case TranslatedValue::kInvalid: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| FATAL("We should never get here - unexpected deopt slot kind."); | |
| return Handle<Object>::null(); | |
| } | |
| Handle<Object> TranslatedState::MaterializeObjectAt(int object_index) { | |
| TranslatedState::ObjectPosition pos = object_positions_[object_index]; | |
| return MaterializeAt(pos.frame_index_, &(pos.value_index_)); | |
| } | |
| bool TranslatedState::GetAdaptedArguments(Handle<JSObject>* result, | |
| int frame_index) { | |
| if (frame_index == 0) { | |
| // Top level frame -> we need to go to the parent frame on the stack. | |
| if (!has_adapted_arguments_) return false; | |
| // This is top level frame, so we need to go to the stack to get | |
| // this function's argument. (Note that this relies on not inlining | |
| // recursive functions!) | |
| Handle<JSFunction> function = | |
| Handle<JSFunction>::cast(frames_[frame_index].front().GetValue()); | |
| *result = Accessors::FunctionGetArguments(function); | |
| return true; | |
| } else { | |
| TranslatedFrame* previous_frame = &(frames_[frame_index]); | |
| if (previous_frame->kind() != TranslatedFrame::kArgumentsAdaptor) { | |
| return false; | |
| } | |
| // We get the adapted arguments from the parent translation. | |
| int length = previous_frame->height(); | |
| Handle<JSFunction> function = | |
| Handle<JSFunction>::cast(previous_frame->front().GetValue()); | |
| Handle<JSObject> arguments = | |
| isolate_->factory()->NewArgumentsObject(function, length); | |
| Handle<FixedArray> array = isolate_->factory()->NewFixedArray(length); | |
| arguments->set_elements(*array); | |
| TranslatedFrame::iterator arg_iterator = previous_frame->begin(); | |
| arg_iterator++; // Skip function. | |
| for (int i = 0; i < length; ++i) { | |
| Handle<Object> value = arg_iterator->GetValue(); | |
| array->set(i, *value); | |
| arg_iterator++; | |
| } | |
| CHECK(arg_iterator == previous_frame->end()); | |
| *result = arguments; | |
| return true; | |
| } | |
| } | |
| TranslatedFrame* TranslatedState::GetArgumentsInfoFromJSFrameIndex( | |
| int jsframe_index, int* args_count) { | |
| for (size_t i = 0; i < frames_.size(); i++) { | |
| if (frames_[i].kind() == TranslatedFrame::kFunction || | |
| frames_[i].kind() == TranslatedFrame::kInterpretedFunction) { | |
| if (jsframe_index > 0) { | |
| jsframe_index--; | |
| } else { | |
| // We have the JS function frame, now check if it has arguments adaptor. | |
| if (i > 0 && | |
| frames_[i - 1].kind() == TranslatedFrame::kArgumentsAdaptor) { | |
| *args_count = frames_[i - 1].height(); | |
| return &(frames_[i - 1]); | |
| } | |
| *args_count = | |
| frames_[i].shared_info()->internal_formal_parameter_count() + 1; | |
| return &(frames_[i]); | |
| } | |
| } | |
| } | |
| return nullptr; | |
| } | |
| void TranslatedState::StoreMaterializedValuesAndDeopt() { | |
| MaterializedObjectStore* materialized_store = | |
| isolate_->materialized_object_store(); | |
| Handle<FixedArray> previously_materialized_objects = | |
| materialized_store->Get(stack_frame_pointer_); | |
| Handle<Object> marker = isolate_->factory()->arguments_marker(); | |
| int length = static_cast<int>(object_positions_.size()); | |
| bool new_store = false; | |
| if (previously_materialized_objects.is_null()) { | |
| previously_materialized_objects = | |
| isolate_->factory()->NewFixedArray(length); | |
| for (int i = 0; i < length; i++) { | |
| previously_materialized_objects->set(i, *marker); | |
| } | |
| new_store = true; | |
| } | |
| CHECK_EQ(length, previously_materialized_objects->length()); | |
| bool value_changed = false; | |
| for (int i = 0; i < length; i++) { | |
| TranslatedState::ObjectPosition pos = object_positions_[i]; | |
| TranslatedValue* value_info = | |
| &(frames_[pos.frame_index_].values_[pos.value_index_]); | |
| CHECK(value_info->IsMaterializedObject()); | |
| Handle<Object> value(value_info->GetRawValue(), isolate_); | |
| if (!value.is_identical_to(marker)) { | |
| if (previously_materialized_objects->get(i) == *marker) { | |
| previously_materialized_objects->set(i, *value); | |
| value_changed = true; | |
| } else { | |
| CHECK(previously_materialized_objects->get(i) == *value); | |
| } | |
| } | |
| } | |
| if (new_store && value_changed) { | |
| materialized_store->Set(stack_frame_pointer_, | |
| previously_materialized_objects); | |
| CHECK(frames_[0].kind() == TranslatedFrame::kFunction || | |
| frames_[0].kind() == TranslatedFrame::kInterpretedFunction || | |
| frames_[0].kind() == TranslatedFrame::kTailCallerFunction); | |
| Object* const function = frames_[0].front().GetRawValue(); | |
| Deoptimizer::DeoptimizeFunction(JSFunction::cast(function)); | |
| } | |
| } | |
| void TranslatedState::UpdateFromPreviouslyMaterializedObjects() { | |
| MaterializedObjectStore* materialized_store = | |
| isolate_->materialized_object_store(); | |
| Handle<FixedArray> previously_materialized_objects = | |
| materialized_store->Get(stack_frame_pointer_); | |
| // If we have no previously materialized objects, there is nothing to do. | |
| if (previously_materialized_objects.is_null()) return; | |
| Handle<Object> marker = isolate_->factory()->arguments_marker(); | |
| int length = static_cast<int>(object_positions_.size()); | |
| CHECK_EQ(length, previously_materialized_objects->length()); | |
| for (int i = 0; i < length; i++) { | |
| // For a previously materialized objects, inject their value into the | |
| // translated values. | |
| if (previously_materialized_objects->get(i) != *marker) { | |
| TranslatedState::ObjectPosition pos = object_positions_[i]; | |
| TranslatedValue* value_info = | |
| &(frames_[pos.frame_index_].values_[pos.value_index_]); | |
| CHECK(value_info->IsMaterializedObject()); | |
| value_info->value_ = | |
| Handle<Object>(previously_materialized_objects->get(i), isolate_); | |
| } | |
| } | |
| } | |
| } // namespace internal | |
| } // namespace v8 |