 Chromium Code Reviews
 Chromium Code Reviews Issue 260423002:
  Relocate suspended generator activations when enabling debug mode  (Closed) 
  Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge
    
  
    Issue 260423002:
  Relocate suspended generator activations when enabling debug mode  (Closed) 
  Base URL: https://v8.googlecode.com/svn/branches/bleeding_edge| Index: src/debug.cc | 
| diff --git a/src/debug.cc b/src/debug.cc | 
| index 33ca1579b258bcab958b30659f64ac0c13b2de9d..4051b70abd8c529964e937d96b1323cf58ac83ed 100644 | 
| --- a/src/debug.cc | 
| +++ b/src/debug.cc | 
| @@ -1847,6 +1847,59 @@ static void CollectActiveFunctionsFromThread( | 
| } | 
| +// Figure out how many bytes of "pc_offset" correspond to actual code by | 
| +// subtracting off the bytes that correspond to constant/veneer pools. See | 
| +// Assembler::CheckConstPool() and Assembler::CheckVeneerPool(). Note that this | 
| +// is only useful for architectures using constant pools or veneer pools. | 
| +static int ComputeCodeOffsetFromPcOffset(Code *code, int pc_offset) { | 
| + ASSERT_EQ(code->kind(), Code::FUNCTION); | 
| + ASSERT(!code->has_debug_break_slots()); | 
| + ASSERT_LE(0, pc_offset); | 
| + ASSERT_LT(pc_offset, code->instruction_end() - code->instruction_start()); | 
| + | 
| + int mask = RelocInfo::ModeMask(RelocInfo::CONST_POOL) | | 
| + RelocInfo::ModeMask(RelocInfo::VENEER_POOL); | 
| + byte *pc = code->instruction_start() + pc_offset; | 
| + int code_offset = pc_offset; | 
| + for (RelocIterator it(code, mask); !it.done(); it.next()) { | 
| + RelocInfo* info = it.rinfo(); | 
| + if (info->pc() >= pc) break; | 
| + ASSERT(RelocInfo::IsConstPool(info->rmode())); | 
| + code_offset -= static_cast<int>(info->data()); | 
| + ASSERT_LE(0, code_offset); | 
| + } | 
| + | 
| + return code_offset; | 
| +} | 
| + | 
| + | 
| +// The inverse of ComputeCodeOffsetFromPcOffset. | 
| +static int ComputePcOffsetFromCodeOffset(Code *code, int code_offset) { | 
| + ASSERT_EQ(code->kind(), Code::FUNCTION); | 
| + | 
| + int mask = RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) | | 
| + RelocInfo::ModeMask(RelocInfo::CONST_POOL) | | 
| + RelocInfo::ModeMask(RelocInfo::VENEER_POOL); | 
| + int reloc = 0; | 
| + for (RelocIterator it(code, mask); !it.done(); it.next()) { | 
| + RelocInfo* info = it.rinfo(); | 
| + if (info->pc() - code->instruction_start() - reloc >= code_offset) break; | 
| + if (RelocInfo::IsDebugBreakSlot(info->rmode())) { | 
| + reloc += Assembler::kDebugBreakSlotLength; | 
| + } else { | 
| + ASSERT(RelocInfo::IsConstPool(info->rmode())); | 
| + reloc += static_cast<int>(info->data()); | 
| + } | 
| + } | 
| + | 
| + int pc_offset = code_offset + reloc; | 
| + | 
| + ASSERT_LT(code->instruction_start() + pc_offset, code->instruction_end()); | 
| + | 
| + return pc_offset; | 
| +} | 
| + | 
| + | 
| static void RedirectActivationsToRecompiledCodeOnThread( | 
| Isolate* isolate, | 
| ThreadLocalTop* top) { | 
| @@ -1868,51 +1921,12 @@ static void RedirectActivationsToRecompiledCodeOnThread( | 
| continue; | 
| } | 
| - // Iterate over the RelocInfo in the original code to compute the sum of the | 
| - // constant pools and veneer pools sizes. (See Assembler::CheckConstPool() | 
| - // and Assembler::CheckVeneerPool()) | 
| - // Note that this is only useful for architectures using constant pools or | 
| - // veneer pools. | 
| - int pool_mask = RelocInfo::ModeMask(RelocInfo::CONST_POOL) | | 
| - RelocInfo::ModeMask(RelocInfo::VENEER_POOL); | 
| - int frame_pool_size = 0; | 
| - for (RelocIterator it(*frame_code, pool_mask); !it.done(); it.next()) { | 
| - RelocInfo* info = it.rinfo(); | 
| - if (info->pc() >= frame->pc()) break; | 
| - frame_pool_size += static_cast<int>(info->data()); | 
| - } | 
| - intptr_t frame_offset = | 
| - frame->pc() - frame_code->instruction_start() - frame_pool_size; | 
| - | 
| - // Iterate over the RelocInfo for new code to find the number of bytes | 
| - // generated for debug slots and constant pools. | 
| - int debug_break_slot_bytes = 0; | 
| - int new_code_pool_size = 0; | 
| - int mask = RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) | | 
| - RelocInfo::ModeMask(RelocInfo::CONST_POOL) | | 
| - RelocInfo::ModeMask(RelocInfo::VENEER_POOL); | 
| - for (RelocIterator it(*new_code, mask); !it.done(); it.next()) { | 
| - // Check if the pc in the new code with debug break | 
| - // slots is before this slot. | 
| - RelocInfo* info = it.rinfo(); | 
| - intptr_t new_offset = info->pc() - new_code->instruction_start() - | 
| - new_code_pool_size - debug_break_slot_bytes; | 
| - if (new_offset >= frame_offset) { | 
| - break; | 
| - } | 
| - | 
| - if (RelocInfo::IsDebugBreakSlot(info->rmode())) { | 
| - debug_break_slot_bytes += Assembler::kDebugBreakSlotLength; | 
| - } else { | 
| - ASSERT(RelocInfo::IsConstPool(info->rmode())); | 
| - // The size of the pools is encoded in the data. | 
| - new_code_pool_size += static_cast<int>(info->data()); | 
| - } | 
| - } | 
| + int old_pc_offset = frame->pc() - frame_code->instruction_start(); | 
| + int code_offset = ComputeCodeOffsetFromPcOffset(*frame_code, old_pc_offset); | 
| + int new_pc_offset = ComputePcOffsetFromCodeOffset(*new_code, code_offset); | 
| // Compute the equivalent pc in the new code. | 
| - byte* new_pc = new_code->instruction_start() + frame_offset + | 
| - debug_break_slot_bytes + new_code_pool_size; | 
| + byte* new_pc = new_code->instruction_start() + new_pc_offset; | 
| if (FLAG_trace_deopt) { | 
| PrintF("Replacing code %08" V8PRIxPTR " - %08" V8PRIxPTR " (%d) " | 
| @@ -1968,6 +1982,84 @@ class ActiveFunctionsRedirector : public ThreadVisitor { | 
| }; | 
| +void Debug::CollectSuspendedGenerators(List<Handle<JSGeneratorObject> > *acc) { | 
| + Heap* heap = isolate_->heap(); | 
| + | 
| + ASSERT(heap->IsHeapIterable()); | 
| + HeapIterator iterator(heap); | 
| + HeapObject* obj = NULL; | 
| + while (((obj = iterator.next()) != NULL)) { | 
| + if (!obj->IsJSGeneratorObject()) continue; | 
| + | 
| + JSGeneratorObject* gen = JSGeneratorObject::cast(obj); | 
| + if (!gen->is_suspended()) continue; | 
| + | 
| + JSFunction* fun = gen->function(); | 
| + ASSERT_EQ(fun->code()->kind(), Code::FUNCTION); | 
| + if (fun->code()->has_debug_break_slots()) continue; | 
| + | 
| + int pc_offset = gen->continuation(); | 
| + ASSERT_LT(0, pc_offset); | 
| + | 
| + int code_offset = ComputeCodeOffsetFromPcOffset(fun->code(), pc_offset); | 
| + | 
| + // This will be fixed after we recompile the functions. | 
| + gen->set_continuation(code_offset); | 
| + | 
| + acc->Add(Handle<JSGeneratorObject>(gen, isolate_)); | 
| + } | 
| +} | 
| + | 
| + | 
| +class ForceDebuggerActive { | 
| + public: | 
| + explicit ForceDebuggerActive(Isolate *isolate) { | 
| + isolate_ = isolate; | 
| + old_state_ = isolate->debugger()->force_debugger_active(); | 
| + isolate_->debugger()->set_force_debugger_active(true); | 
| + } | 
| + | 
| + ~ForceDebuggerActive() { | 
| + isolate_->debugger()->set_force_debugger_active(old_state_); | 
| + } | 
| + | 
| + private: | 
| + Isolate *isolate_; | 
| + bool old_state_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(ForceDebuggerActive); | 
| +}; | 
| + | 
| + | 
| +void Debug::MaybeRecompileFunctionForDebugging(Handle<JSFunction> function) { | 
| + ASSERT_EQ(Code::FUNCTION, function->code()->kind()); | 
| + ASSERT_EQ(function->code(), function->shared()->code()); | 
| + | 
| + if (function->code()->has_debug_break_slots()) return; | 
| + | 
| + ForceDebuggerActive force_debugger_active(isolate_); | 
| + MaybeHandle<Code> code = Compiler::GetCodeForDebugging(function); | 
| + // Recompilation can fail. In that case leave the code as it was. | 
| + if (!code.is_null()) | 
| + function->ReplaceCode(*code.ToHandleChecked()); | 
| + ASSERT_EQ(function->code(), function->shared()->code()); | 
| +} | 
| + | 
| + | 
| +void Debug::RecompileAndRelocateSuspendedGenerators( | 
| + const List<Handle<JSGeneratorObject> > &generators) { | 
| + for (int i = 0; i < generators.length(); i++) { | 
| + Handle<JSFunction> fun(generators[i]->function()); | 
| + | 
| + MaybeRecompileFunctionForDebugging(fun); | 
| + | 
| + int code_offset = generators[i]->continuation(); | 
| + int pc_offset = ComputePcOffsetFromCodeOffset(fun->code(), code_offset); | 
| + generators[i]->set_continuation(pc_offset); | 
| + } | 
| +} | 
| + | 
| + | 
| void Debug::PrepareForBreakPoints() { | 
| // If preparing for the first break point make sure to deoptimize all | 
| // functions as debugging does not work with optimized code. | 
| @@ -1987,6 +2079,21 @@ void Debug::PrepareForBreakPoints() { | 
| // is used both in GC and non-GC code. | 
| List<Handle<JSFunction> > active_functions(100); | 
| + // A list of all suspended generators. | 
| + List<Handle<JSGeneratorObject> > suspended_generators; | 
| + | 
| + // A list of all generator functions. We need to recompile all functions, | 
| + // but we don't know until after visiting the whole heap which generator | 
| + // functions have suspended activations and which do not. As in the case of | 
| + // functions with activations on the stack, we need to be careful with | 
| + // generator functions with suspended activations because although they | 
| + // should be recompiled, recompilation can fail, and we need to avoid | 
| + // leaving the heap in an inconsistent state. | 
| + // | 
| + // We could perhaps avoid this list and instead re-use the GC metadata | 
| + // links. | 
| + List<Handle<JSFunction> > generator_functions; | 
| + | 
| { | 
| // We are going to iterate heap to find all functions without | 
| // debug break slots. | 
| @@ -1994,6 +2101,11 @@ void Debug::PrepareForBreakPoints() { | 
| heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, | 
| "preparing for breakpoints"); | 
| + CollectSuspendedGenerators(&suspended_generators); | 
| 
Yang
2014/05/05 09:40:15
Do we really need to iterate the heap twice? Can t
 
wingo
2014/05/05 11:08:43
Hum, I think you are right.  The double iteration
 | 
| + | 
| + // Collecting the generators should not alter iterability of the heap. | 
| + ASSERT(heap->IsHeapIterable()); | 
| + | 
| // Ensure no GC in this scope as we are going to use gc_metadata | 
| // field in the Code object to mark active functions. | 
| DisallowHeapAllocation no_allocation; | 
| @@ -2024,6 +2136,11 @@ void Debug::PrepareForBreakPoints() { | 
| if (function->IsBuiltin()) continue; | 
| if (shared->code()->gc_metadata() == active_code_marker) continue; | 
| + if (shared->is_generator()) { | 
| + generator_functions.Add(Handle<JSFunction>(function, isolate_)); | 
| + continue; | 
| + } | 
| + | 
| Code::Kind kind = function->code()->kind(); | 
| if (kind == Code::FUNCTION && | 
| !function->code()->has_debug_break_slots()) { | 
| @@ -2053,42 +2170,35 @@ void Debug::PrepareForBreakPoints() { | 
| } | 
| } | 
| + // Recompile generator functions that have suspended activations, and | 
| + // relocate those activations. | 
| + RecompileAndRelocateSuspendedGenerators(suspended_generators); | 
| + | 
| + // Mark generator functions that didn't have suspended activations for lazy | 
| + // recompilation. Note that this set does not include any active functions. | 
| + for (int i = 0; i < generator_functions.length(); i++) { | 
| + Handle<JSFunction> &function = generator_functions[i]; | 
| + if (function->code()->kind() != Code::FUNCTION) continue; | 
| + if (function->code()->has_debug_break_slots()) continue; | 
| + function->set_code(*lazy_compile); | 
| + function->shared()->set_code(*lazy_compile); | 
| + } | 
| + | 
| // Now recompile all functions with activation frames and and | 
| - // patch the return address to run in the new compiled code. | 
| + // patch the return address to run in the new compiled code. It could be | 
| + // that some active functions were recompiled already by the suspended | 
| + // generator recompilation pass above; a generator with suspended | 
| + // activations could also have active activations. That's fine. | 
| for (int i = 0; i < active_functions.length(); i++) { | 
| Handle<JSFunction> function = active_functions[i]; | 
| Handle<SharedFunctionInfo> shared(function->shared()); | 
| - if (function->code()->kind() == Code::FUNCTION && | 
| - function->code()->has_debug_break_slots()) { | 
| - // Nothing to do. Function code already had debug break slots. | 
| - continue; | 
| - } | 
| - | 
| // If recompilation is not possible just skip it. | 
| - if (shared->is_toplevel() || | 
| - !shared->allows_lazy_compilation() || | 
| - shared->code()->kind() == Code::BUILTIN) { | 
| - continue; | 
| - } | 
| - | 
| - // Make sure that the shared full code is compiled with debug | 
| - // break slots. | 
| - if (!shared->code()->has_debug_break_slots()) { | 
| - // Try to compile the full code with debug break slots. If it | 
| - // fails just keep the current code. | 
| - bool prev_force_debugger_active = | 
| - isolate_->debugger()->force_debugger_active(); | 
| - isolate_->debugger()->set_force_debugger_active(true); | 
| - Handle<Code> code = Compiler::GetCodeForDebugging( | 
| - function).ToHandleChecked(); | 
| - function->ReplaceCode(*code); | 
| - isolate_->debugger()->set_force_debugger_active( | 
| - prev_force_debugger_active); | 
| - } | 
| + if (shared->is_toplevel()) continue; | 
| + if (!shared->allows_lazy_compilation()) continue; | 
| + if (shared->code()->kind() == Code::BUILTIN) continue; | 
| - // Keep function code in sync with shared function info. | 
| - function->set_code(shared->code()); | 
| + MaybeRecompileFunctionForDebugging(function); | 
| } | 
| RedirectActivationsToRecompiledCodeOnThread(isolate_, |