diff --git a/gdb/dwarf2/cooked-index.c b/gdb/dwarf2/cooked-index.c index 7588b5c2896..fabffc75d92 100644 --- a/gdb/dwarf2/cooked-index.c +++ b/gdb/dwarf2/cooked-index.c @@ -66,6 +66,23 @@ language_requires_canonicalization (enum language lang) || lang == language_cplus); } +/* Return true if a plain "main" could be the main program for this + language. Languages that are known to use some other mechanism are + excluded here. */ + +static bool +language_may_use_plain_main (enum language lang) +{ + /* No need to handle "unknown" here. */ + return (lang == language_c + || lang == language_objc + || lang == language_cplus + || lang == language_m2 + || lang == language_asm + || lang == language_opencl + || lang == language_minimal); +} + /* See cooked-index.h. */ int @@ -242,23 +259,17 @@ cooked_index_shard::add (sect_offset die_offset, enum dwarf_tag tag, implicit "main" discovery. */ if ((flags & IS_MAIN) != 0) m_main = result; + else if (parent_entry == nullptr + && m_main == nullptr + && language_may_use_plain_main (per_cu->lang ()) + && strcmp (name, "main") == 0) + m_main = result; return result; } /* See cooked-index.h. */ -void -cooked_index_shard::finalize () -{ - m_future = gdb::thread_pool::g_thread_pool->post_task ([this] () - { - do_finalize (); - }); -} - -/* See cooked-index.h. */ - gdb::unique_xmalloc_ptr cooked_index_shard::handle_gnat_encoded_entry (cooked_index_entry *entry, htab_t gnat_entries) @@ -306,7 +317,7 @@ cooked_index_shard::handle_gnat_encoded_entry (cooked_index_entry *entry, /* See cooked-index.h. */ void -cooked_index_shard::do_finalize () +cooked_index_shard::finalize () { auto hash_name_ptr = [] (const void *p) { @@ -430,65 +441,77 @@ cooked_index_shard::find (const std::string &name, bool completing) const return range (lower, upper); } -/* See cooked-index.h. */ -void -cooked_index_shard::wait (bool allow_quit) const +cooked_index::cooked_index (dwarf2_per_objfile *per_objfile) + : m_state (std::make_unique (per_objfile)), + m_per_bfd (per_objfile->per_bfd) { - if (allow_quit) - { - std::chrono::milliseconds duration { 15 }; - while (m_future.wait_for (duration) == gdb::future_status::timeout) - QUIT; - } - else - m_future.wait (); -} - -cooked_index::cooked_index (vec_type &&vec) - : m_vector (std::move (vec)) -{ - for (auto &idx : m_vector) - idx->finalize (); - /* ACTIVE_VECTORS is not locked, and this assert ensures that this will be caught if ever moved to the background. */ gdb_assert (is_main_thread ()); active_vectors.insert (this); } -/* See cooked-index.h. */ +void +cooked_index::start_reading () +{ + m_state->start (); +} void -cooked_index::start_writing_index (dwarf2_per_bfd *per_bfd) +cooked_index::wait (cooked_state desired_state, bool allow_quit) { - index_cache_store_context ctx (global_index_cache, per_bfd); + gdb_assert (desired_state != cooked_state::INITIAL); - /* This must be set after all the finalization tasks have been - started, because it may call 'wait'. */ - m_write_future - = gdb::thread_pool::g_thread_pool->post_task ([this, per_bfd, - ctx = std::move (ctx)] () - { - maybe_write_index (per_bfd, ctx); - }); + /* If the state object has been deleted, then that means waiting is + completely done. */ + if (m_state == nullptr) + return; + + if (m_state->wait (desired_state, allow_quit)) + { + /* Only the main thread can modify this. */ + gdb_assert (is_main_thread ()); + m_state.reset (nullptr); + } +} + +void +cooked_index::set_contents (vec_type &&vec) +{ + gdb_assert (m_vector.empty ()); + m_vector = std::move (vec); + + m_state->set (cooked_state::MAIN_AVAILABLE); + + index_cache_store_context ctx (global_index_cache, m_per_bfd); + + /* This is run after finalization is done -- but not before. If + this task were submitted earlier, it would have to wait for + finalization. However, that would take a slot in the global + thread pool, and if enough such tasks were submitted at once, it + would cause a livelock. */ + gdb::task_group finalizers ([this, ctx = std::move (ctx)] () + { + m_state->set (cooked_state::FINALIZED); + maybe_write_index (m_per_bfd, ctx); + }); + + for (auto &idx : m_vector) + { + auto this_index = idx.get (); + finalizers.add_task ([=] () { this_index->finalize (); }); + } + + finalizers.start (); } cooked_index::~cooked_index () { - /* The 'finalize' method may be run in a different thread. If - this object is destroyed before this completes, then the method - will end up writing to freed memory. Waiting for this to - complete avoids this problem; and the cost seems ignorable - because creating and immediately destroying the debug info is a - relatively rare thing to do. */ - for (auto &item : m_vector) - item->wait (false); - - /* Likewise for the index-creating future, though this one must also + /* Wait for index-creation to be done, though this one must also waited for by the per-BFD object to ensure the required data remains live. */ - wait_completely (); + wait (cooked_state::CACHE_DONE); /* Remove our entry from the global list. See the assert in the constructor to understand this. */ @@ -501,6 +524,8 @@ cooked_index::~cooked_index () dwarf2_per_cu_data * cooked_index::lookup (CORE_ADDR addr) { + /* Ensure that the address maps are ready. */ + wait (cooked_state::MAIN_AVAILABLE, true); for (const auto &index : m_vector) { dwarf2_per_cu_data *result = index->lookup (addr); @@ -513,8 +538,10 @@ cooked_index::lookup (CORE_ADDR addr) /* See cooked-index.h. */ std::vector -cooked_index::get_addrmaps () const +cooked_index::get_addrmaps () { + /* Ensure that the address maps are ready. */ + wait (cooked_state::MAIN_AVAILABLE, true); std::vector result; for (const auto &index : m_vector) result.push_back (index->m_addrmap); @@ -524,9 +551,9 @@ cooked_index::get_addrmaps () const /* See cooked-index.h. */ cooked_index::range -cooked_index::find (const std::string &name, bool completing) const +cooked_index::find (const std::string &name, bool completing) { - wait (); + wait (cooked_state::FINALIZED, true); std::vector result_range; result_range.reserve (m_vector.size ()); for (auto &entry : m_vector) @@ -536,38 +563,65 @@ cooked_index::find (const std::string &name, bool completing) const /* See cooked-index.h. */ +const char * +cooked_index::get_main_name (struct obstack *obstack, enum language *lang) + const +{ + const cooked_index_entry *entry = get_main (); + if (entry == nullptr) + return nullptr; + + *lang = entry->per_cu->lang (); + return entry->full_name (obstack, true); +} + +/* See cooked_index.h. */ + const cooked_index_entry * cooked_index::get_main () const { - const cooked_index_entry *result = nullptr; - + const cooked_index_entry *best_entry = nullptr; for (const auto &index : m_vector) { const cooked_index_entry *entry = index->get_main (); - /* Choose the first "main" we see. The choice among several is - arbitrary. See the comment by the sole caller to understand - the rationale for filtering by language. */ - if (entry != nullptr - && !language_requires_canonicalization (entry->per_cu->lang ())) + /* Choose the first "main" we see. We only do this for names + not requiring canonicalization. At this point in the process + names might not have been canonicalized. However, currently, + languages that require this step also do not use + DW_AT_main_subprogram. An assert is appropriate here because + this filtering is done in get_main. */ + if (entry != nullptr) { - result = entry; - break; + if ((entry->flags & IS_MAIN) != 0) + { + if (!language_requires_canonicalization (entry->per_cu->lang ())) + { + /* There won't be one better than this. */ + return entry; + } + } + else + { + /* This is one that is named "main". Here we don't care + if the language requires canonicalization, due to how + the entry is detected. Entries like this have worse + priority than IS_MAIN entries. */ + if (best_entry == nullptr) + best_entry = entry; + } } } - return result; + return best_entry; } /* See cooked-index.h. */ void -cooked_index::dump (gdbarch *arch) const +cooked_index::dump (gdbarch *arch) { auto_obstack temp_storage; - /* Ensure the index is done building. */ - this->wait (); - gdb_printf (" entries:\n"); gdb_printf ("\n"); @@ -640,11 +694,9 @@ void cooked_index::maybe_write_index (dwarf2_per_bfd *per_bfd, const index_cache_store_context &ctx) { - /* Wait for finalization. */ - wait (); - /* (maybe) store an index in the cache. */ - global_index_cache.store (per_bfd, ctx); + global_index_cache.store (m_per_bfd, ctx); + m_state->set (cooked_state::CACHE_DONE); } /* Wait for all the index cache entries to be written before gdb diff --git a/gdb/dwarf2/cooked-index.h b/gdb/dwarf2/cooked-index.h index 914a595a059..909e4c08e3a 100644 --- a/gdb/dwarf2/cooked-index.h +++ b/gdb/dwarf2/cooked-index.h @@ -32,9 +32,18 @@ #include "gdbsupport/iterator-range.h" #include "gdbsupport/thread-pool.h" #include "dwarf2/mapped-index.h" +#include "dwarf2/read.h" #include "dwarf2/tag.h" #include "dwarf2/abbrev-cache.h" #include "gdbsupport/range-chain.h" +#include "gdbsupport/task-group.h" +#include "complaints.h" +#include "run-on-main-thread.h" + +#if CXX_STD_THREAD +#include +#include +#endif /* CXX_STD_THREAD */ struct dwarf2_per_cu_data; struct dwarf2_per_bfd; @@ -64,7 +73,7 @@ std::string to_string (cooked_index_flag flags); /* Return true if LANG requires canonicalization. This is used primarily to work around an issue computing the name of "main". This function must be kept in sync with - cooked_index_shard::do_finalize. */ + cooked_index_shard::finalize. */ extern bool language_requires_canonicalization (enum language lang); @@ -271,14 +280,6 @@ public: m_addrmap = new (&m_storage) addrmap_fixed (&m_storage, map); } - /* Finalize the index. This should be called a single time, when - the index has been fully populated. It enters all the entries - into the internal table. */ - void finalize (); - - /* Wait for this index's finalization to be complete. */ - void wait (bool allow_quit = true) const; - friend class cooked_index; /* A simple range over part of m_entries. */ @@ -335,8 +336,11 @@ private: gdb::unique_xmalloc_ptr handle_gnat_encoded_entry (cooked_index_entry *entry, htab_t gnat_entries); - /* A helper method that does the work of 'finalize'. */ - void do_finalize (); + /* Finalize the index. This should be called a single time, when + the index has been fully populated. It enters all the entries + into the internal table. This may be invoked in a worker + thread. */ + void finalize (); /* Storage for the entries. */ auto_obstack m_storage; @@ -349,10 +353,6 @@ private: addrmap *m_addrmap = nullptr; /* Storage for canonical names. */ std::vector> m_names; - /* A future that tracks when the 'finalize' method is done. Note - that the 'get' method is never called on this future, only - 'wait'. */ - gdb::future m_future; }; class cutu_reader; @@ -424,10 +424,164 @@ private: addrmap_mutable m_addrmap; }; -/* The main index of DIEs. The parallel DIE indexers create - cooked_index_shard objects. Then, these are all handled to a - cooked_index for storage and final indexing. The index is - made by iterating over the entries previously created. */ +/* The possible states of the index. See the explanatory comment + before cooked_index for more details. */ +enum class cooked_state +{ + /* The default state. This is not a valid argument to 'wait'. */ + INITIAL, + /* The initial scan has completed. The name of "main" is now + available (if known). The addrmaps are usable now. + Finalization has started but is not complete. */ + MAIN_AVAILABLE, + /* Finalization has completed. This means the index is fully + available for queries. */ + FINALIZED, + /* Writing to the index cache has finished. */ + CACHE_DONE, +}; + +/* An object of this type controls the scanning of the DWARF. It + schedules the worker tasks and tracks the current state. Once + scanning is done, this object is discarded. */ + +class cooked_index_worker +{ +public: + + explicit cooked_index_worker (dwarf2_per_objfile *per_objfile); + DISABLE_COPY_AND_ASSIGN (cooked_index_worker); + + /* Start reading. */ + void start (); + + /* Wait for a particular state to be achieved. If ALLOW_QUIT is + true, then the loop will check the QUIT flag. Normally this + method may only be called from the main thread; however, it can + be called from a worker thread provided that the desired state + has already been attained. (This oddity is used by the index + cache writer.) */ + bool wait (cooked_state desired_state, bool allow_quit); + +private: + + /* Let cooked_index call the 'set' method. */ + friend class cooked_index; + void set (cooked_state desired_state); + + /* Start reading DWARF. This can be run in a worker thread without + problems. */ + void start_reading (); + + /* Helper function that does most of the work for start_reading. */ + void do_reading (); + + /* After the last DWARF-reading task has finished, this function + does the remaining work to finish the scan. */ + void done_reading (); + + /* An iterator for the comp units. */ + typedef std::vector::iterator unit_iterator; + + /* Process a batch of CUs. This may be called multiple times in + separate threads. TASK_NUMBER indicates which task this is -- + the result is stored in that slot of M_RESULTS. */ + void process_cus (size_t task_number, unit_iterator first, + unit_iterator end); + + /* Each thread returns a tuple holding a cooked index, any collected + complaints, and a vector of errors that should be printed. The + latter is done because GDB's I/O system is not thread-safe. + run_on_main_thread could be used, but that would mean the + messages are printed after the prompt, which looks weird. */ + using result_type = std::tuple, + complaint_collection, + std::vector>; + + /* The per-objfile object. */ + dwarf2_per_objfile *m_per_objfile; + /* A storage object for "leftovers" -- see the 'start' method, but + essentially things not parsed during the normal CU parsing + passes. */ + cooked_index_storage m_index_storage; + /* Result of each worker task. */ + std::vector m_results; + /* Any warnings emitted. This is not in 'result_type' because (for + the time being at least), it's only needed in do_reading, not in + every worker. Note that deferred_warnings uses gdb_stderr in its + constructor, and this should only be done from the main thread. + This is enforced in the cooked_index_worker constructor. */ + deferred_warnings m_warnings; + +#if CXX_STD_THREAD + /* Current state of this object. */ + cooked_state m_state = cooked_state::INITIAL; + /* This flag indicates whether any complaints or exceptions that + arose during scanning have been reported by 'wait'. This may + only be modified on the main thread. */ + bool m_reported = false; + /* Mutex and condition variable used to synchronize. */ + std::mutex m_mutex; + std::condition_variable m_cond; + /* If set, an exception occurred during start_reading; in this case + the scanning is stopped and this exception will later be reported + by the 'wait' method. */ + std::optional m_failed; +#endif /* CXX_STD_THREAD */ +}; + +/* The main index of DIEs. + + The index is created by multiple threads. The overall process is + somewhat complicated, so here's a diagram to help sort it out. + + The basic idea behind this design is (1) to do as much work as + possible in worker threads, and (2) to start the work as early as + possible. This combination should help hide the effort from the + user to the maximum possible degree. + + . Main Thread | Worker Threads + ============================================================ + . dwarf2_initialize_objfile + . | + . v + . cooked index ------------> cooked_index_worker::start + . | / | \ + . v / | \ + . install / | \ + . cooked_index_functions scan CUs in workers + . | create cooked_index_shard objects + . | \ | / + . v \|/ + . return to caller v + . initial scan is done + . state = MAIN_AVAILABLE + . "main" name now available + . | + . | + . if main thread calls... v + . compute_main_name cooked_index::set_contents + . | / | \ + . v / | \ + . wait (MAIN_AVAILABLE) finalization + . | \ | / + . v \ | / + . done state = FINALIZED + . | + . v + . maybe write to index cache + . state = CACHE_DONE + . + . + . if main thread calls... + . any other "quick" API + . | + . v + . wait (FINALIZED) + . | + . v + . use the index +*/ class cooked_index : public dwarf_scanner_base { @@ -437,17 +591,17 @@ public: object. */ using vec_type = std::vector>; - explicit cooked_index (vec_type &&vec); + explicit cooked_index (dwarf2_per_objfile *per_objfile); ~cooked_index () override; + DISABLE_COPY_AND_ASSIGN (cooked_index); - /* Wait until the finalization of the entire cooked_index is - done. */ - void wait () const - { - for (auto &item : m_vector) - item->wait (); - } + /* Start reading the DWARF. */ + void start_reading (); + + /* Called by cooked_index_worker to set the contents of this index + and transition to the MAIN_AVAILABLE state. */ + void set_contents (vec_type &&vec); /* A range over a vector of subranges. */ using range = range_chain; @@ -455,12 +609,12 @@ public: /* Look up an entry by name. Returns a range of all matching results. If COMPLETING is true, then a larger range, suitable for completion, will be returned. */ - range find (const std::string &name, bool completing) const; + range find (const std::string &name, bool completing); /* Return a range of all the entries. */ - range all_entries () const + range all_entries () { - wait (); + wait (cooked_state::FINALIZED, true); std::vector result_range; result_range.reserve (m_vector.size ()); for (auto &entry : m_vector) @@ -475,34 +629,38 @@ public: /* Return a new vector of all the addrmaps used by all the indexes held by this object. */ - std::vector get_addrmaps () const; + std::vector get_addrmaps (); /* Return the entry that is believed to represent the program's "main". This will return NULL if no such entry is available. */ const cooked_index_entry *get_main () const; + const char *get_main_name (struct obstack *obstack, enum language *lang) + const; + cooked_index *index_for_writing () override { + wait (cooked_state::FINALIZED, true); return this; } quick_symbol_functions_up make_quick_functions () const override; /* Dump a human-readable form of the contents of the index. */ - void dump (gdbarch *arch) const; + void dump (gdbarch *arch); + + /* Wait until this object reaches the desired state. Note that + DESIRED_STATE may not be INITIAL -- it does not make sense to + wait for this. If ALLOW_QUIT is true, timed waits will be done + and the quit flag will be checked in a loop. This may normally + only be called from the main thread; however, it is ok to call + from a worker as long as the desired state has already been + attained. (This property is needed by the index cache + writer.) */ + void wait (cooked_state desired_state, bool allow_quit = false); - /* Wait for the index to be completely finished. For ordinary uses, - the index code ensures this itself -- e.g., 'all_entries' will - wait on the 'finalize' future. However, on destruction, if an - index is being written, it's also necessary to wait for that to - complete. */ void wait_completely () override - { - m_write_future.wait (); - } - - /* Start writing to the index cache, if the user asked for this. */ - void start_writing_index (dwarf2_per_bfd *per_bfd); + { wait (cooked_state::CACHE_DONE); } private: @@ -514,8 +672,12 @@ private: entries are stored on the obstacks in those objects. */ vec_type m_vector; - /* A future that tracks when the 'index_write' method is done. */ - gdb::future m_write_future; + /* This tracks the current state. When this is nullptr, it means + that the state is CACHE_DONE -- it's important to note that only + the main thread may change the value of this pointer. */ + std::unique_ptr m_state; + + dwarf2_per_bfd *m_per_bfd; }; #endif /* GDB_DWARF2_COOKED_INDEX_H */ diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 507b1526b89..49a87db2982 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -94,8 +94,8 @@ #include "dwarf2/abbrev-cache.h" #include "cooked-index.h" #include "split-name.h" -#include "gdbsupport/parallel-for.h" #include "gdbsupport/thread-pool.h" +#include "run-on-main-thread.h" /* When == 1, print basic high level tracing messages. When > 1, be more verbose. @@ -757,8 +757,6 @@ static void dwarf2_find_base_address (struct die_info *die, static void build_type_psymtabs_reader (cutu_reader *reader, cooked_index_storage *storage); -static void dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile); - static void var_decode_location (struct attribute *attr, struct symbol *sym, struct dwarf2_cu *cu); @@ -3196,7 +3194,8 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz) return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res); } -static quick_symbol_functions_up make_cooked_index_funcs (); +static quick_symbol_functions_up make_cooked_index_funcs + (dwarf2_per_objfile *); /* See dwarf2/public.h. */ @@ -3273,31 +3272,11 @@ dwarf2_initialize_objfile (struct objfile *objfile) } global_index_cache.miss (); - objfile->qf.push_front (make_cooked_index_funcs ()); + objfile->qf.push_front (make_cooked_index_funcs (per_objfile)); } -/* Build a partial symbol table. */ - -static void -dwarf2_build_psymtabs (struct objfile *objfile) -{ - dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - - if (per_objfile->per_bfd->index_table != nullptr) - return; - - try - { - dwarf2_build_psymtabs_hard (per_objfile); - } - catch (const gdb_exception_error &except) - { - exception_print (gdb_stderr, except); - } -} - /* Find the base address of the compilation unit for range lists and location lists. It will normally be specified by DW_AT_low_pc. In DWARF-3 draft 4, the base address could be overridden by @@ -3931,7 +3910,7 @@ static struct dwo_unit * lookup_dwo_unit (dwarf2_cu *cu, die_info *comp_unit_die, const char *dwo_name) { #if CXX_STD_THREAD - /* We need a lock here both to handle the DWO hash table. */ + /* We need a lock here to handle the DWO hash table. */ static std::mutex dwo_lock; std::lock_guard guard (dwo_lock); @@ -4863,12 +4842,11 @@ process_skeletonless_type_units (dwarf2_per_objfile *per_objfile, } } -/* Build the partial symbol table by doing a quick pass through the - .debug_info and .debug_abbrev sections. */ - -static void -dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile) +cooked_index_worker::cooked_index_worker (dwarf2_per_objfile *per_objfile) + : m_per_objfile (per_objfile) { + gdb_assert (is_main_thread ()); + struct objfile *objfile = per_objfile->objfile; dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; @@ -4876,113 +4854,254 @@ dwarf2_build_psymtabs_hard (dwarf2_per_objfile *per_objfile) objfile_name (objfile)); per_bfd->map_info_sections (objfile); +} - cooked_index_storage index_storage; - create_all_units (per_objfile); - build_type_psymtabs (per_objfile, &index_storage); +void +cooked_index_worker::start () +{ + gdb::thread_pool::g_thread_pool->post_task ([=] () + { + this->start_reading (); + }); +} + +void +cooked_index_worker::process_cus (size_t task_number, unit_iterator first, + unit_iterator end) +{ + SCOPE_EXIT { bfd_thread_cleanup (); }; + + /* Ensure that complaints are handled correctly. */ + complaint_interceptor complaint_handler; + + std::vector errors; + cooked_index_storage thread_storage; + for (auto inner = first; inner != end; ++inner) + { + dwarf2_per_cu_data *per_cu = inner->get (); + try + { + process_psymtab_comp_unit (per_cu, m_per_objfile, &thread_storage); + } + catch (gdb_exception &except) + { + errors.push_back (std::move (except)); + } + } + + m_results[task_number] = result_type (thread_storage.release (), + complaint_handler.release (), + std::move (errors)); +} + +void +cooked_index_worker::done_reading () +{ + /* Only handle the scanning results here. Complaints and exceptions + can only be dealt with on the main thread. */ std::vector> indexes; + for (auto &one_result : m_results) + indexes.push_back (std::move (std::get<0> (one_result))); + + /* This has to wait until we read the CUs, we need the list of DWOs. */ + process_skeletonless_type_units (m_per_objfile, &m_index_storage); + + indexes.push_back (m_index_storage.release ()); + indexes.shrink_to_fit (); + + dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd; + cooked_index *table + = (gdb::checked_static_cast + (per_bfd->index_table.get ())); + table->set_contents (std::move (indexes)); +} + +void +cooked_index_worker::start_reading () +{ + SCOPE_EXIT { bfd_thread_cleanup (); }; + + try + { + do_reading (); + } + catch (const gdb_exception &exc) + { + m_failed = exc; + set (cooked_state::CACHE_DONE); + } +} + +void +cooked_index_worker::do_reading () +{ + dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd; + + create_all_units (m_per_objfile); + build_type_psymtabs (m_per_objfile, &m_index_storage); per_bfd->quick_file_names_table = create_quick_file_names_table (per_bfd->all_units.size ()); if (!per_bfd->debug_aranges.empty ()) + read_addrmap_from_aranges (m_per_objfile, &per_bfd->debug_aranges, + m_index_storage.get_addrmap (), + &m_warnings); + + /* We want to balance the load between the worker threads. This is + done by using the size of each CU as a rough estimate of how + difficult it will be to operate on. This isn't ideal -- for + example if dwz is used, the early CUs will all tend to be + "included" and won't be parsed independently. However, this + heuristic works well for typical compiler output. */ + + size_t total_size = 0; + for (const auto &per_cu : per_bfd->all_units) + total_size += per_cu->length (); + + /* How many worker threads we plan to use. We may not actually use + this many. We use 1 as the minimum to avoid division by zero, + and anyway in the N==0 case the work will be done + synchronously. */ + const size_t n_worker_threads + = std::max (gdb::thread_pool::g_thread_pool->thread_count (), (size_t) 1); + + /* How much effort should be put into each worker. */ + const size_t size_per_thread + = std::max (total_size / n_worker_threads, (size_t) 1); + + /* Work is done in a task group. */ + gdb::task_group workers ([this] () + { + this->done_reading (); + }); + + auto end = per_bfd->all_units.end (); + size_t task_count = 0; + for (auto iter = per_bfd->all_units.begin (); iter != end; ) { - deferred_warnings warn; - read_addrmap_from_aranges (per_objfile, &per_bfd->debug_aranges, - index_storage.get_addrmap (), &warn); - warn.emit (); + auto last = iter; + /* Put all remaining CUs into the last task. */ + if (task_count == n_worker_threads - 1) + last = end; + else + { + size_t chunk_size = 0; + for (; last != end && chunk_size < size_per_thread; ++last) + chunk_size += (*last)->length (); + } + + gdb_assert (iter != last); + workers.add_task ([=] () + { + process_cus (task_count, iter, last); + }); + + ++task_count; + iter = last; } + m_results.resize (task_count); + workers.start (); +} + +bool +cooked_index_worker::wait (cooked_state desired_state, bool allow_quit) +{ + bool done; +#if CXX_STD_THREAD { - using iter_type = decltype (per_bfd->all_units.begin ()); + std::unique_lock lock (m_mutex); - auto task_size_ = [] (iter_type iter) + /* This may be called from a non-main thread -- this functionality + is needed for the index cache -- but in this case we require + that the desired state already have been attained. */ + gdb_assert (is_main_thread () || desired_state <= m_state); + + while (desired_state > m_state) { - dwarf2_per_cu_data *per_cu = iter->get (); - return (size_t)per_cu->length (); - }; - auto task_size = gdb::make_function_view (task_size_); - - /* Each thread returns a tuple holding a cooked index, any - collected complaints, and a vector of errors that should be - printed. The latter is done because GDB's I/O system is not - thread-safe. run_on_main_thread could be used, but that would - mean the messages are printed after the prompt, which looks - weird. */ - using result_type = std::tuple, - complaint_collection, - std::vector>; - std::vector results - = gdb::parallel_for_each (1, per_bfd->all_units.begin (), - per_bfd->all_units.end (), - [=] (iter_type iter, iter_type end) - { - /* Ensure that complaints are handled correctly. */ - complaint_interceptor complaint_handler; - - std::vector errors; - cooked_index_storage thread_storage; - for (; iter != end; ++iter) + if (allow_quit) { - dwarf2_per_cu_data *per_cu = iter->get (); - try - { - process_psymtab_comp_unit (per_cu, per_objfile, - &thread_storage); - } - catch (gdb_exception &except) - { - errors.push_back (std::move (except)); - } + std::chrono::milliseconds duration { 15 }; + if (m_cond.wait_for (lock, duration) == std::cv_status::timeout) + QUIT; } - return result_type (thread_storage.release (), - complaint_handler.release (), - std::move (errors)); - }, task_size); - - /* Only show a given exception a single time. */ - std::unordered_set seen_exceptions; - for (auto &one_result : results) - { - indexes.push_back (std::move (std::get<0> (one_result))); - re_emit_complaints (std::get<1> (one_result)); - for (auto &one_exc : std::get<2> (one_result)) - if (seen_exceptions.insert (one_exc).second) - exception_print (gdb_stderr, one_exc); + else + m_cond.wait (lock); } + done = m_state == cooked_state::CACHE_DONE; } +#else + /* Without threads, all the work is done immediately on the main + thread, and there is never anything to wait for. */ + done = true; +#endif /* CXX_STD_THREAD */ - /* This has to wait until we read the CUs, we need the list of DWOs. */ - process_skeletonless_type_units (per_objfile, &index_storage); + /* Only the main thread is allowed to report complaints and the + like. */ + if (!is_main_thread ()) + return false; + + if (m_reported) + return done; + m_reported = true; + + /* Emit warnings first, maybe they were emitted before an exception + (if any) was thrown. */ + m_warnings.emit (); + + if (m_failed.has_value ()) + { + /* start_reading failed -- report it. */ + exception_print (gdb_stderr, *m_failed); + m_failed.reset (); + return done; + } + + /* Only show a given exception a single time. */ + std::unordered_set seen_exceptions; + for (auto &one_result : m_results) + { + re_emit_complaints (std::get<1> (one_result)); + for (auto &one_exc : std::get<2> (one_result)) + if (seen_exceptions.insert (one_exc).second) + exception_print (gdb_stderr, one_exc); + } if (dwarf_read_debug > 0) - print_tu_stats (per_objfile); + print_tu_stats (m_per_objfile); - indexes.push_back (index_storage.release ()); - indexes.shrink_to_fit (); + struct objfile *objfile = m_per_objfile->objfile; + dwarf2_per_bfd *per_bfd = m_per_objfile->per_bfd; + cooked_index *table + = (gdb::checked_static_cast + (per_bfd->index_table.get ())); - cooked_index *vec = new cooked_index (std::move (indexes)); - per_bfd->index_table.reset (vec); - - /* Cannot start writing the index entry until after the - 'index_table' member has been set. */ - vec->start_writing_index (per_bfd); - - const cooked_index_entry *main_entry = vec->get_main (); - if (main_entry != nullptr) - { - /* We only do this for names not requiring canonicalization. At - this point in the process names have not been canonicalized. - However, currently, languages that require this step also do - not use DW_AT_main_subprogram. An assert is appropriate here - because this filtering is done in get_main. */ - enum language lang = main_entry->per_cu->lang (); - gdb_assert (!language_requires_canonicalization (lang)); - const char *full_name = main_entry->full_name (&per_bfd->obstack, true); - set_objfile_main_name (objfile, full_name, lang); - } + auto_obstack temp_storage; + enum language lang = language_unknown; + const char *main_name = table->get_main_name (&temp_storage, &lang); + if (main_name != nullptr) + set_objfile_main_name (objfile, main_name, lang); dwarf_read_debug_printf ("Done building psymtabs of %s", objfile_name (objfile)); + + return done; +} + +void +cooked_index_worker::set (cooked_state desired_state) +{ + gdb_assert (desired_state != cooked_state::INITIAL); + +#if CXX_STD_THREAD + std::lock_guard guard (m_mutex); + gdb_assert (desired_state > m_state); + m_state = desired_state; + m_cond.notify_one (); +#else + /* Without threads, all the work is done immediately on the main + thread, and there is never anything to do. */ +#endif /* CXX_STD_THREAD */ } static void @@ -16534,26 +16653,60 @@ cooked_indexer::make_index (cutu_reader *reader) struct cooked_index_functions : public dwarf2_base_index_functions { + cooked_index *wait (struct objfile *objfile, bool allow_quit) + { + dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); + cooked_index *table + = (gdb::checked_static_cast + (per_objfile->per_bfd->index_table.get ())); + table->wait (cooked_state::MAIN_AVAILABLE, allow_quit); + return table; + } + dwarf2_per_cu_data *find_per_cu (dwarf2_per_bfd *per_bfd, CORE_ADDR adjusted_pc) override; struct compunit_symtab *find_compunit_symtab_by_address (struct objfile *objfile, CORE_ADDR address) override; + bool has_unexpanded_symtabs (struct objfile *objfile) override + { + wait (objfile, true); + return dwarf2_base_index_functions::has_unexpanded_symtabs (objfile); + } + + struct symtab *find_last_source_symtab (struct objfile *objfile) override + { + wait (objfile, true); + return dwarf2_base_index_functions::find_last_source_symtab (objfile); + } + + void forget_cached_source_info (struct objfile *objfile) override + { + wait (objfile, true); + dwarf2_base_index_functions::forget_cached_source_info (objfile); + } + + void print_stats (struct objfile *objfile, bool print_bcache) override + { + wait (objfile, true); + dwarf2_base_index_functions::print_stats (objfile, print_bcache); + } + void dump (struct objfile *objfile) override { - dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - cooked_index *index - = (gdb::checked_static_cast - (per_objfile->per_bfd->index_table.get ())); - if (index == nullptr) - return; - + cooked_index *index = wait (objfile, true); gdb_printf ("Cooked index in use:\n"); gdb_printf ("\n"); index->dump (objfile->arch ()); } + void expand_all_symtabs (struct objfile *objfile) override + { + wait (objfile, true); + dwarf2_base_index_functions::expand_all_symtabs (objfile); + } + bool expand_symtabs_matching (struct objfile *objfile, gdb::function_view file_matcher, @@ -16564,55 +16717,28 @@ struct cooked_index_functions : public dwarf2_base_index_functions domain_enum domain, enum search_domain kind) override; - bool can_lazily_read_symbols () override + struct compunit_symtab *find_pc_sect_compunit_symtab + (struct objfile *objfile, struct bound_minimal_symbol msymbol, + CORE_ADDR pc, struct obj_section *section, int warn_if_readin) override { - return true; + wait (objfile, true); + return (dwarf2_base_index_functions::find_pc_sect_compunit_symtab + (objfile, msymbol, pc, section, warn_if_readin)); } - void read_partial_symbols (struct objfile *objfile) override + void map_symbol_filenames + (struct objfile *objfile, + gdb::function_view fun, + bool need_fullname) override { - if (dwarf2_has_info (objfile, nullptr)) - dwarf2_build_psymtabs (objfile); + wait (objfile, true); + return (dwarf2_base_index_functions::map_symbol_filenames + (objfile, fun, need_fullname)); } - enum language lookup_global_symbol_language (struct objfile *objfile, - const char *name, - domain_enum domain, - bool *symbol_found_p) override + void compute_main_name (struct objfile *objfile) override { - *symbol_found_p = false; - - if (!(domain == VAR_DOMAIN && streq (name, "main"))) - return language_unknown; - - dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; - if (per_bfd->index_table == nullptr) - return language_unknown; - - /* Expansion of large CUs can be slow. By returning the language of main - here for C and C++, we avoid CU expansion during set_initial_language. - But by doing a symbol lookup in the cooked index, we are forced to wait - for finalization to complete. See PR symtab/30174 for ideas how to - bypass that as well. */ - cooked_index *table - = (gdb::checked_static_cast - (per_bfd->index_table.get ())); - - for (const cooked_index_entry *entry : table->find (name, false)) - { - if (entry->tag != DW_TAG_subprogram) - continue; - - enum language lang = entry->per_cu->lang (); - if (!(lang == language_c || lang == language_cplus)) - continue; - - *symbol_found_p = true; - return lang; - } - - return language_unknown; + wait (objfile, false); } }; @@ -16623,8 +16749,6 @@ cooked_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd, cooked_index *table = (gdb::checked_static_cast (per_bfd->index_table.get ())); - if (table == nullptr) - return nullptr; return table->lookup (adjusted_pc); } @@ -16636,11 +16760,7 @@ cooked_index_functions::find_compunit_symtab_by_address return nullptr; dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - cooked_index *table - = (gdb::checked_static_cast - (per_objfile->per_bfd->index_table.get ())); - if (table == nullptr) - return nullptr; + cooked_index *table = wait (objfile, true); CORE_ADDR baseaddr = objfile->data_section_offset (); dwarf2_per_cu_data *per_cu = table->lookup (address - baseaddr); @@ -16663,13 +16783,7 @@ cooked_index_functions::expand_symtabs_matching { dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile); - cooked_index *table - = (gdb::checked_static_cast - (per_objfile->per_bfd->index_table.get ())); - if (table == nullptr) - return true; - - table->wait (); + cooked_index *table = wait (objfile, true); dw_expand_symtabs_matching_file_matcher (per_objfile, file_matcher); @@ -16789,15 +16903,27 @@ cooked_index_functions::expand_symtabs_matching /* Return a new cooked_index_functions object. */ static quick_symbol_functions_up -make_cooked_index_funcs () +make_cooked_index_funcs (dwarf2_per_objfile *per_objfile) { + /* Set the index table early so that sharing works even while + scanning; and then start the scanning. */ + dwarf2_per_bfd *per_bfd = per_objfile->per_bfd; + cooked_index *idx = new cooked_index (per_objfile); + per_bfd->index_table.reset (idx); + /* Don't start reading until after 'index_table' is set. This + avoids races. */ + idx->start_reading (); + + if (dwarf_synchronous) + idx->wait_completely (); + return quick_symbol_functions_up (new cooked_index_functions); } quick_symbol_functions_up cooked_index::make_quick_functions () const { - return make_cooked_index_funcs (); + return quick_symbol_functions_up (new cooked_index_functions); } diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h index 4ab869cdd29..493d64acf68 100644 --- a/gdb/dwarf2/read.h +++ b/gdb/dwarf2/read.h @@ -898,7 +898,7 @@ struct dwarf2_base_index_functions : public quick_symbol_functions struct compunit_symtab *find_pc_sect_compunit_symtab (struct objfile *objfile, struct bound_minimal_symbol msymbol, CORE_ADDR pc, struct obj_section *section, int warn_if_readin) - override final; + override; struct compunit_symtab *find_compunit_symtab_by_address (struct objfile *objfile, CORE_ADDR address) override diff --git a/gdb/testsuite/gdb.dwarf2/debug-aranges-duplicate-offset-warning.exp b/gdb/testsuite/gdb.dwarf2/debug-aranges-duplicate-offset-warning.exp index 4e8ce14defb..af0c8df5e0b 100644 --- a/gdb/testsuite/gdb.dwarf2/debug-aranges-duplicate-offset-warning.exp +++ b/gdb/testsuite/gdb.dwarf2/debug-aranges-duplicate-offset-warning.exp @@ -57,9 +57,12 @@ Dwarf::assemble $asm_file { } } -if { [prepare_for_testing "failed to prepare" ${testfile} \ - [list $srcfile $asm_file] {nodebug}] } { - return -1 +save_vars { GDBFLAGS } { + append GDBFLAGS " -iex \"maint set dwarf synchronous on\"" + if { [prepare_for_testing "failed to prepare" ${testfile} \ + [list $srcfile $asm_file] {nodebug}] } { + return -1 + } } set readnow_p [readnow] diff --git a/gdb/testsuite/gdb.dwarf2/dw2-error.exp b/gdb/testsuite/gdb.dwarf2/dw2-error.exp index 76886d5c1b6..f8dc08d47aa 100644 --- a/gdb/testsuite/gdb.dwarf2/dw2-error.exp +++ b/gdb/testsuite/gdb.dwarf2/dw2-error.exp @@ -31,6 +31,7 @@ if {[build_executable $testfile.exp $testfile $srcfile {nodebug quiet}]} { clean_restart gdb_test_no_output "set breakpoint pending off" +gdb_test_no_output "maint set dwarf synchronous on" set host_binfile [gdb_remote_download host $binfile] diff --git a/gdb/testsuite/gdb.dwarf2/dw2-missing-cu-tag.exp b/gdb/testsuite/gdb.dwarf2/dw2-missing-cu-tag.exp index f57e8086a7c..f24c7938568 100644 --- a/gdb/testsuite/gdb.dwarf2/dw2-missing-cu-tag.exp +++ b/gdb/testsuite/gdb.dwarf2/dw2-missing-cu-tag.exp @@ -49,6 +49,8 @@ set host_binfile [gdb_remote_download host $binfile] # Restart with no executable. clean_restart +gdb_test_no_output "maint set dwarf synchronous on" + # This pattern is hit when GDB does not use -readnow (i.e. the default # behaviour). set pattern1 \ diff --git a/gdb/testsuite/gdb.dwarf2/dw2-stack-boundary.exp b/gdb/testsuite/gdb.dwarf2/dw2-stack-boundary.exp index a72564c075c..65b91b3a504 100644 --- a/gdb/testsuite/gdb.dwarf2/dw2-stack-boundary.exp +++ b/gdb/testsuite/gdb.dwarf2/dw2-stack-boundary.exp @@ -25,6 +25,8 @@ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" object {}] != "" clean_restart +gdb_test_no_output "maint set dwarf synchronous on" + set host_binfile [gdb_remote_download host $binfile] gdb_test_no_output "set complaints 100" set w1 0 diff --git a/gdb/testsuite/gdb.dwarf2/dw2-using-debug-str.exp b/gdb/testsuite/gdb.dwarf2/dw2-using-debug-str.exp index 7974cb7f20b..ec5d3183f60 100644 --- a/gdb/testsuite/gdb.dwarf2/dw2-using-debug-str.exp +++ b/gdb/testsuite/gdb.dwarf2/dw2-using-debug-str.exp @@ -128,6 +128,8 @@ if {[run_on_host "objcopy" [gdb_find_objcopy] "$args"]} { # executable we're going to get an error, which we check for below. clean_restart +gdb_test_no_output "maint set dwarf synchronous on" + set line1 "Reading symbols from \[^\r\n\]+" set dwarf_error "Dwarf Error: DW_FORM_strp used without required section" diff --git a/gdb/testsuite/gdb.dwarf2/dw2-zero-range.exp b/gdb/testsuite/gdb.dwarf2/dw2-zero-range.exp index 7bef05684f3..38c85a47b80 100644 --- a/gdb/testsuite/gdb.dwarf2/dw2-zero-range.exp +++ b/gdb/testsuite/gdb.dwarf2/dw2-zero-range.exp @@ -124,7 +124,7 @@ foreach_with_prefix ranges_sect {ranges rnglists} { with_complaints 1 { set test "Zero address complaint - relocated - psymtab" set have_complaint 0 - gdb_test_multiple "sharedlibrary [file tail $lib1]" $test { + gdb_test_multiple "maint with dwarf synchronous on -- sharedlibrary [file tail $lib1]" $test { -re -wrap $re { set have_complaint 1 } @@ -144,12 +144,14 @@ foreach_with_prefix ranges_sect {ranges rnglists} { clean_restart # Test for presence of complaint, with lib1 unrelocated. + gdb_test_no_output "maint set dwarf synchronous on" with_complaints 1 { gdb_load $lib1 set test "Zero address complaint - unrelocated - psymtab" set have_complaint [regexp $re.* $gdb_file_cmd_msg] gdb_assert { $have_complaint } $test } + gdb_test_no_output "maint set dwarf synchronous off" if { ! $readnow_p } { with_complaints 1 { diff --git a/gdb/testsuite/gdb.dwarf2/fission-reread.exp b/gdb/testsuite/gdb.dwarf2/fission-reread.exp index 01e9eada575..884a8359fa5 100644 --- a/gdb/testsuite/gdb.dwarf2/fission-reread.exp +++ b/gdb/testsuite/gdb.dwarf2/fission-reread.exp @@ -60,7 +60,10 @@ pass "$testfile - unload" # Test-case for PR24620: Delete the .dwo file and verify that # save gdb-index doesn't crash. remote_file target delete $dwo -clean_restart $binfile +save_vars { GDBFLAGS } { + append GDBFLAGS " -iex \"maint set dwarf synchronous on\"" + clean_restart $binfile +} set output_dir [standard_output_file ""] set cmd "save gdb-index" gdb_test_multiple "$cmd $output_dir" $cmd { diff --git a/gdb/testsuite/gdb.dwarf2/no-gnu-debuglink.exp b/gdb/testsuite/gdb.dwarf2/no-gnu-debuglink.exp index a76136fdfd8..ff9c4dd142d 100644 --- a/gdb/testsuite/gdb.dwarf2/no-gnu-debuglink.exp +++ b/gdb/testsuite/gdb.dwarf2/no-gnu-debuglink.exp @@ -36,6 +36,7 @@ if { [build_executable $testfile.exp $testfile [list $srcfile $asm_file]] } { } clean_restart +gdb_test_no_output "maint set dwarf synchronous on" set msg "\r\nwarning: could not find '\.gnu_debugaltlink' file for \[^\r\n\]*" gdb_test "file $binfile" "$msg" "file command" diff --git a/gdb/testsuite/gdb.dwarf2/struct-with-sig-2.exp b/gdb/testsuite/gdb.dwarf2/struct-with-sig-2.exp index f6877fc6a17..8bf200e8902 100644 --- a/gdb/testsuite/gdb.dwarf2/struct-with-sig-2.exp +++ b/gdb/testsuite/gdb.dwarf2/struct-with-sig-2.exp @@ -119,9 +119,13 @@ Dwarf::assemble $asm_file { } } -if { [prepare_for_testing "failed to prepare" ${testfile} \ - [list $srcfile $asm_file] {nodebug}] } { - return -1 +save_vars { GDBFLAGS } { + append GDBFLAGS " -iex \"maint set dwarf synchronous on\"" + + if { [prepare_for_testing "failed to prepare" ${testfile} \ + [list $srcfile $asm_file] {nodebug}] } { + return -1 + } } set re "Dwarf Error: .debug_types section not supported in dwz file"