reduces the amount of locking required to find a new thread to run.
target-reloc.h \
target-select.h \
tls.h \
+ token.h \
workqueue.h \
workqueue-internal.h
target-reloc.h \
target-select.h \
tls.h \
+ token.h \
workqueue.h \
workqueue-internal.h
// table.
void
-Archive::setup()
+Archive::setup(Task* task)
{
// We need to ignore empty archives.
if (this->input_file_->file().filesize() == sarmag)
{
- this->input_file_->file().unlock();
+ this->input_file_->file().unlock(task);
return;
}
}
// Opening the file locked it. Unlock it now.
- this->input_file_->file().unlock();
+ this->input_file_->file().unlock(task);
}
// Read the archive symbol map.
// Return whether we can add the archive symbols. We are blocked by
// this_blocker_. We block next_blocker_. We also lock the file.
-Task::Is_runnable_type
-Add_archive_symbols::is_runnable(Workqueue*)
+Task_token*
+Add_archive_symbols::is_runnable()
{
if (this->this_blocker_ != NULL && this->this_blocker_->is_blocked())
- return IS_BLOCKED;
- return IS_RUNNABLE;
+ return this->this_blocker_;
+ return NULL;
}
-class Add_archive_symbols::Add_archive_symbols_locker : public Task_locker
-{
- public:
- Add_archive_symbols_locker(Task_token& token, Workqueue* workqueue,
- File_read& file)
- : blocker_(token, workqueue), filelock_(file)
- { }
-
- private:
- Task_locker_block blocker_;
- Task_locker_obj<File_read> filelock_;
-};
-
-Task_locker*
-Add_archive_symbols::locks(Workqueue* workqueue)
+void
+Add_archive_symbols::locks(Task_locker* tl)
{
- return new Add_archive_symbols_locker(*this->next_blocker_,
- workqueue,
- this->archive_->file());
+ tl->add(this, this->next_blocker_);
+ tl->add(this, this->archive_->token());
}
void
this->archive_->add_symbols(this->symtab_, this->layout_,
this->input_objects_);
+ this->archive_->release();
+
if (this->input_group_ != NULL)
this->input_group_->add_archive(this->archive_);
else
namespace gold
{
+class Task;
class Input_file;
class Input_objects;
class Input_group;
// Set up the archive: read the symbol map.
void
- setup();
+ setup(Task*);
// Get a reference to the underlying file.
File_read&
// Lock the underlying file.
void
- lock()
- { this->input_file_->file().lock(); }
+ lock(const Task* t)
+ { this->input_file_->file().lock(t); }
// Unlock the underlying file.
void
- unlock()
- { this->input_file_->file().unlock(); }
+ unlock(const Task* t)
+ { this->input_file_->file().unlock(t); }
// Return whether the underlying file is locked.
bool
is_locked() const
{ return this->input_file_->file().is_locked(); }
+ // Return the token, so that the task can be queued.
+ Task_token*
+ token()
+ { return this->input_file_->file().token(); }
+
+ // Release the underlying file.
+ void
+ release()
+ { this->input_file_->file().release(); }
+
// Select members from the archive as needed and add them to the
// link.
void
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
}
private:
- class Add_archive_symbols_locker;
-
Symbol_table* symtab_;
Layout* layout_;
Input_objects* input_objects_;
// This task allocates the common symbols. We need a lock on the
// symbol table.
-Task::Is_runnable_type
-Allocate_commons_task::is_runnable(Workqueue*)
+Task_token*
+Allocate_commons_task::is_runnable()
{
if (!this->symtab_lock_->is_writable())
- return IS_LOCKED;
- return IS_RUNNABLE;
+ return this->symtab_lock_;
+ return NULL;
}
// Return the locks we hold: one on the symbol table, and one blocker.
-class Allocate_commons_task::Allocate_commons_locker : public Task_locker
-{
- public:
- Allocate_commons_locker(Task_token& symtab_lock, Task* task,
- Task_token& blocker, Workqueue* workqueue)
- : symtab_locker_(symtab_lock, task),
- blocker_(blocker, workqueue)
- { }
-
- private:
- Task_locker_write symtab_locker_;
- Task_locker_block blocker_;
-};
-
-Task_locker*
-Allocate_commons_task::locks(Workqueue* workqueue)
+void
+Allocate_commons_task::locks(Task_locker* tl)
{
- return new Allocate_commons_locker(*this->symtab_lock_, this,
- *this->blocker_, workqueue);
+ tl->add(this, this->blocker_);
+ tl->add(this, this->symtab_lock_);
}
// Allocate the common symbols.
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
{ return "Allocate_commons_task"; }
private:
- class Allocate_commons_locker;
-
const General_options& options_;
Symbol_table* symtab_;
Layout* layout_;
#include <dirent.h>
#include "gold-threads.h"
+#include "options.h"
+#include "workqueue.h"
#include "dirsearch.h"
namespace
: dir_(dir), token_(token)
{ }
- Is_runnable_type
- is_runnable(gold::Workqueue*);
+ gold::Task_token*
+ is_runnable();
- gold::Task_locker*
- locks(gold::Workqueue*);
+ void
+ locks(gold::Task_locker*);
void
run(gold::Workqueue*);
// We can always run the task to read the directory.
-gold::Task::Is_runnable_type
-Dir_cache_task::is_runnable(gold::Workqueue*)
+gold::Task_token*
+Dir_cache_task::is_runnable()
{
- return IS_RUNNABLE;
+ return NULL;
}
// Return the locks to hold. We use a blocker lock to prevent file
// lookups from starting until the directory contents have been read.
-gold::Task_locker*
-Dir_cache_task::locks(gold::Workqueue* workqueue)
+void
+Dir_cache_task::locks(gold::Task_locker* tl)
{
- return new gold::Task_locker_block(this->token_, workqueue);
+ tl->add(this, &this->token_);
}
// Run the task--read the directory contents.
#include <string>
#include <list>
-#include "workqueue.h"
+#include "options.h"
+#include "token.h"
namespace gold
{
class General_options;
+class Workqueue;
// A simple interface to manage directories to be searched for
// libraries.
{
public:
Dirsearch()
- : directories_(NULL), token_()
+ : directories_(NULL), token_(true)
{ }
// Set the list of directories to search.
std::string
find(const std::string&, const std::string& n2, bool *is_in_sysroot) const;
- // Return a reference to the blocker token which controls access.
- const Task_token&
- token() const
- { return this->token_; }
+ // Return the blocker token which controls access.
+ Task_token*
+ token()
+ { return &this->token_; }
private:
// We can not copy this class.
File_read::~File_read()
{
- gold_assert(this->lock_count_ == 0);
+ gold_assert(this->token_.is_writable());
if (this->descriptor_ >= 0)
{
if (close(this->descriptor_) < 0)
// Open the file.
bool
-File_read::open(const std::string& name)
+File_read::open(const Task* task, const std::string& name)
{
- gold_assert(this->lock_count_ == 0
+ gold_assert(this->token_.is_writable()
&& this->descriptor_ < 0
&& this->name_.empty());
this->name_ = name;
this->size_ = s.st_size;
}
- ++this->lock_count_;
+ this->token_.add_writer(task);
return this->descriptor_ >= 0;
}
// Open the file for testing purposes.
bool
-File_read::open(const std::string& name, const unsigned char* contents,
- off_t size)
+File_read::open(const Task* task, const std::string& name,
+ const unsigned char* contents, off_t size)
{
- gold_assert(this->lock_count_ == 0
+ gold_assert(this->token_.is_writable()
&& this->descriptor_ < 0
&& this->name_.empty());
this->name_ = name;
this->contents_ = contents;
this->size_ = size;
- ++this->lock_count_;
+ this->token_.add_writer(task);
return true;
}
+// Release the file. This is called when we are done with the file in
+// a Task.
+
void
-File_read::lock()
+File_read::release()
{
- ++this->lock_count_;
+ gold_assert(this->is_locked());
+
+ File_read::total_mapped_bytes += this->mapped_bytes_;
+ File_read::current_mapped_bytes += this->mapped_bytes_;
+ this->mapped_bytes_ = 0;
+ if (File_read::current_mapped_bytes > File_read::maximum_mapped_bytes)
+ File_read::maximum_mapped_bytes = File_read::current_mapped_bytes;
+
+ this->clear_views(false);
+
+ this->released_ = true;
}
+// Lock the file.
+
void
-File_read::unlock()
+File_read::lock(const Task* task)
{
- gold_assert(this->lock_count_ > 0);
- --this->lock_count_;
- if (this->lock_count_ == 0)
- {
- File_read::total_mapped_bytes += this->mapped_bytes_;
- File_read::current_mapped_bytes += this->mapped_bytes_;
- this->mapped_bytes_ = 0;
- if (File_read::current_mapped_bytes > File_read::maximum_mapped_bytes)
- File_read::maximum_mapped_bytes = File_read::current_mapped_bytes;
+ gold_assert(this->released_);
+ this->token_.add_writer(task);
+ this->released_ = false;
+}
- this->clear_views(false);
- }
+// Unlock the file.
+
+void
+File_read::unlock(const Task* task)
+{
+ this->release();
+ this->token_.remove_writer(task);
}
+// Return whether the file is locked.
+
bool
File_read::is_locked() const
{
- return this->lock_count_ > 0;
+ if (!this->token_.is_writable())
+ return true;
+ // The file is not locked, so it should have been released.
+ gold_assert(this->released_);
+ return false;
}
// See if we have a view which covers the file starting at START for
File_read::View*
File_read::find_or_make_view(off_t start, off_t size, bool cache)
{
- gold_assert(this->lock_count_ > 0);
+ gold_assert(!this->token_.is_writable());
+ this->released_ = false;
off_t poff = File_read::page_offset(start);
return v;
}
-// This implementation of get_view just reads into a memory buffer,
-// which we store on view_list_. At some point we should support
-// mmap.
+// Get a view into the file.
const unsigned char*
File_read::get_view(off_t start, off_t size, bool cache)
{
- gold_assert(this->lock_count_ > 0);
File_read::View* pv = this->find_or_make_view(start, size, cache);
return pv->data() + (start - pv->start());
}
File_view*
File_read::get_lasting_view(off_t start, off_t size, bool cache)
{
- gold_assert(this->lock_count_ > 0);
File_read::View* pv = this->find_or_make_view(start, size, cache);
pv->lock();
return new File_view(*this, pv, pv->data() + (start - pv->start()));
// Create a file for testing.
-Input_file::Input_file(const char* name, const unsigned char* contents,
- off_t size)
+Input_file::Input_file(const Task* task, const char* name,
+ const unsigned char* contents, off_t size)
: file_()
{
this->input_argument_ =
new Input_file_argument(name, false, "", Position_dependent_options());
- bool ok = file_.open(name, contents, size);
+ bool ok = file_.open(task, name, contents, size);
gold_assert(ok);
}
// the file location, rather than the current directory.
bool
-Input_file::open(const General_options& options, const Dirsearch& dirpath)
+Input_file::open(const General_options& options, const Dirsearch& dirpath,
+ const Task* task)
{
std::string name;
}
// Now that we've figured out where the file lives, try to open it.
- if (!this->file_.open(name))
+ if (!this->file_.open(task, name))
{
gold_error(_("cannot open %s: %s"),
name.c_str(), strerror(errno));
#include <string>
#include "options.h"
+#include "token.h"
namespace gold
{
{
public:
File_read()
- : name_(), descriptor_(-1), size_(0), lock_count_(0), views_(),
- saved_views_(), contents_(NULL), mapped_bytes_(0)
+ : name_(), descriptor_(-1), size_(0), token_(false), views_(),
+ saved_views_(), contents_(NULL), mapped_bytes_(0), released_(true)
{ }
~File_read();
// Open a file.
bool
- open(const std::string& name);
+ open(const Task*, const std::string& name);
// Pretend to open the file, but provide the file contents. No
// actual file system activity will occur. This is used for
// testing.
bool
- open(const std::string& name, const unsigned char* contents, off_t size);
+ open(const Task*, const std::string& name, const unsigned char* contents,
+ off_t size);
// Return the file name.
const std::string&
filename() const
{ return this->name_; }
- // Lock the file for access within a particular Task::run execution.
- // This means that the descriptor can not be closed. This routine
- // may only be called from the main thread.
+ // Lock the file for exclusive access within a particular Task::run
+ // execution. This means that the descriptor can not be closed.
+ // This routine may only be called when the workqueue lock is held.
void
- lock();
+ lock(const Task* t);
// Unlock the descriptor, permitting it to be closed if necessary.
void
- unlock();
+ unlock(const Task* t);
// Test whether the object is locked.
bool
is_locked() const;
+ // Return the token, so that the task can be queued.
+ Task_token*
+ token()
+ { return &this->token_; }
+
+ // Release the file. This indicates that we aren't going to do
+ // anything further with it until it is unlocked. This is used
+ // because a Task which locks the file never calls either lock or
+ // unlock; it just locks the token. The basic rule is that a Task
+ // which locks a file via the Task::locks interface must explicitly
+ // call release() when it is done. This is not necessary for code
+ // which calls unlock() on the file.
+ void
+ release();
+
// Return the size of the file.
off_t
filesize() const
File_read(const File_read&);
File_read& operator=(const File_read&);
- // Total bytes mapped into memory during the link. This variable is
- // only accessed from the main thread, when unlocking the object.
+ // Total bytes mapped into memory during the link. This variable
+ // may not be accurate when running multi-threaded.
static unsigned long long total_mapped_bytes;
// Current number of bytes mapped into memory during the link. This
- // variable is only accessed from the main thread.
+ // variable may not be accurate when running multi-threaded.
static unsigned long long current_mapped_bytes;
// High water mark of bytes mapped into memory during the link.
- // This variable is only accessed from the main thread.
+ // This variable may not be accurate when running multi-threaded.
static unsigned long long maximum_mapped_bytes;
// A view into the file.
int descriptor_;
// File size.
off_t size_;
- // Number of locks on the file.
- int lock_count_;
+ // A token used to lock the file.
+ Task_token token_;
// Buffered views into the file.
Views views_;
// List of views which were locked but had to be removed from views_
// while the file is locked. When we unlock the file, we transfer
// the total to total_mapped_bytes, and reset this to zero.
size_t mapped_bytes_;
+ // Whether the file was released.
+ bool released_;
};
// A view of file data that persists even when the file is unlocked.
// Create an input file with the contents already provided. This is
// only used for testing. With this path, don't call the open
// method.
- Input_file(const char* name, const unsigned char* contents, off_t size);
+ Input_file(const Task*, const char* name, const unsigned char* contents,
+ off_t size);
// Open the file. If the open fails, this will report an error and
// return false.
bool
- open(const General_options&, const Dirsearch&);
+ open(const General_options&, const Dirsearch&, const Task*);
// Return the name given by the user. For -lc this will return "c".
const char*
{ }
void
- run(Workqueue*);
+ run(Workqueue*, const Task*);
private:
const General_options& options_;
};
void
-Middle_runner::run(Workqueue* workqueue)
+Middle_runner::run(Workqueue* workqueue, const Task* task)
{
- queue_middle_tasks(this->options_, this->input_objects_, this->symtab_,
+ queue_middle_tasks(this->options_, task, this->input_objects_, this->symtab_,
this->layout_, workqueue);
}
void
queue_initial_tasks(const General_options& options,
- const Dirsearch& search_path,
+ Dirsearch& search_path,
const Command_line& cmdline,
Workqueue* workqueue, Input_objects* input_objects,
Symbol_table* symtab, Layout* layout)
p != cmdline.end();
++p)
{
- Task_token* next_blocker = new Task_token();
+ Task_token* next_blocker = new Task_token(true);
next_blocker->add_blocker();
workqueue->queue(new Read_symbols(options, input_objects, symtab, layout,
- search_path, &*p, NULL, this_blocker,
+ &search_path, &*p, NULL, this_blocker,
next_blocker));
this_blocker = next_blocker;
}
void
queue_middle_tasks(const General_options& options,
+ const Task* task,
const Input_objects* input_objects,
Symbol_table* symtab,
Layout* layout,
// See if any of the input definitions violate the One Definition Rule.
// TODO: if this is too slow, do this as a task, rather than inline.
- symtab->detect_odr_violations(options.output_file_name());
+ symtab->detect_odr_violations(task, options.output_file_name());
// Define some sections and symbols needed for a dynamic link. This
// handles some cases we want to see before we read the relocs.
// Doing that is more complex, since we may later decide to discard
// some of the sections, and thus change our minds about the types
// of references made to the symbols.
- Task_token* blocker = new Task_token();
- Task_token* symtab_lock = new Task_token();
+ Task_token* blocker = new Task_token(true);
+ Task_token* symtab_lock = new Task_token(false);
for (Input_objects::Relobj_iterator p = input_objects->relobj_begin();
p != input_objects->relobj_end();
++p)
thread_count = input_objects->number_of_input_objects();
workqueue->set_thread_count(thread_count);
+ bool any_postprocessing_sections = layout->any_postprocessing_sections();
+
// Use a blocker to wait until all the input sections have been
// written out.
- Task_token* input_sections_blocker = new Task_token();
+ Task_token* input_sections_blocker = NULL;
+ if (!any_postprocessing_sections)
+ input_sections_blocker = new Task_token(true);
// Use a blocker to block any objects which have to wait for the
// output sections to complete before they can apply relocations.
- Task_token* output_sections_blocker = new Task_token();
+ Task_token* output_sections_blocker = new Task_token(true);
// Use a blocker to block the final cleanup task.
- Task_token* final_blocker = new Task_token();
-
- // Queue a task for each input object to relocate the sections and
- // write out the local symbols.
- for (Input_objects::Relobj_iterator p = input_objects->relobj_begin();
- p != input_objects->relobj_end();
- ++p)
- {
- input_sections_blocker->add_blocker();
- final_blocker->add_blocker();
- workqueue->queue(new Relocate_task(options, symtab, layout, *p, of,
- input_sections_blocker,
- output_sections_blocker,
- final_blocker));
- }
+ Task_token* final_blocker = new Task_token(true);
// Queue a task to write out the symbol table.
if (!options.strip_all())
final_blocker->add_blocker();
workqueue->queue(new Write_data_task(layout, symtab, of, final_blocker));
+ // Queue a task for each input object to relocate the sections and
+ // write out the local symbols.
+ for (Input_objects::Relobj_iterator p = input_objects->relobj_begin();
+ p != input_objects->relobj_end();
+ ++p)
+ {
+ if (input_sections_blocker != NULL)
+ input_sections_blocker->add_blocker();
+ final_blocker->add_blocker();
+ workqueue->queue(new Relocate_task(options, symtab, layout, *p, of,
+ input_sections_blocker,
+ output_sections_blocker,
+ final_blocker));
+ }
+
// Queue a task to write out the output sections which depend on
- // input sections.
- final_blocker->add_blocker();
- workqueue->queue(new Write_after_input_sections_task(layout, of,
- input_sections_blocker,
- final_blocker));
+ // input sections. If there are any sections which require
+ // postprocessing, then we need to do this last, since it may resize
+ // the output file.
+ if (!any_postprocessing_sections)
+ {
+ final_blocker->add_blocker();
+ Task* t = new Write_after_input_sections_task(layout, of,
+ input_sections_blocker,
+ final_blocker);
+ workqueue->queue(t);
+ }
+ else
+ {
+ Task_token *new_final_blocker = new Task_token(true);
+ new_final_blocker->add_blocker();
+ Task* t = new Write_after_input_sections_task(layout, of,
+ final_blocker,
+ new_final_blocker);
+ workqueue->queue(t);
+ final_blocker = new_final_blocker;
+ }
// Queue a task to close the output file. This will be blocked by
// FINAL_BLOCKER.
class Symbol;
class Symbol_table;
class Layout;
+class Task;
class Workqueue;
class Output_file;
template<int size, bool big_endian>
// Queue up the first set of tasks.
extern void
queue_initial_tasks(const General_options&,
- const Dirsearch&,
+ Dirsearch&,
const Command_line&,
Workqueue*,
Input_objects*,
// Queue up the middle set of tasks.
extern void
queue_middle_tasks(const General_options&,
+ const Task*,
const Input_objects*,
Symbol_table*,
Layout*,
// have been read.
void
-Layout_task_runner::run(Workqueue* workqueue)
+Layout_task_runner::run(Workqueue* workqueue, const Task* task)
{
off_t file_size = this->layout_->finalize(this->input_objects_,
- this->symtab_);
+ this->symtab_,
+ task);
// Now we know the final size of the output file and we know where
// each piece of information goes.
input_requires_executable_stack_(false),
input_with_gnu_stack_note_(false),
input_without_gnu_stack_note_(false),
- has_static_tls_(false)
+ has_static_tls_(false),
+ any_postprocessing_sections_(false)
{
// Make space for more than enough segments for a typical file.
// This is just for efficiency--it's OK if we wind up needing more.
// This function returns the size of the output file.
off_t
-Layout::finalize(const Input_objects* input_objects, Symbol_table* symtab)
+Layout::finalize(const Input_objects* input_objects, Symbol_table* symtab,
+ const Task* task)
{
Target* const target = input_objects->target();
target->finalize_sections(this);
- this->count_local_symbols(input_objects);
+ this->count_local_symbols(task, input_objects);
this->create_gold_note();
this->create_executable_stack_info(target);
off_t off = this->set_segment_offsets(target, load_seg, &shndx);
// Create the symbol table sections.
- this->create_symtab_sections(input_objects, symtab, &off);
+ this->create_symtab_sections(input_objects, symtab, task, &off);
if (!parameters->doing_static_link())
this->assign_local_dynsym_offsets(input_objects);
// Create the section table header.
this->create_shdrs(&off);
+ // If there are no sections which require postprocessing, we can
+ // handle the section names now, and avoid a resize later.
+ if (!this->any_postprocessing_sections_)
+ off = this->set_section_offsets(off,
+ STRTAB_AFTER_POSTPROCESSING_SECTIONS_PASS);
+
file_header->set_section_info(this->section_headers_, shstrtab_section);
// Now we know exactly where everything goes in the output file
if (pass == BEFORE_INPUT_SECTIONS_PASS
&& (*p)->requires_postprocessing())
- (*p)->create_postprocessing_buffer();
+ {
+ (*p)->create_postprocessing_buffer();
+ this->any_postprocessing_sections_ = true;
+ }
if (pass == BEFORE_INPUT_SECTIONS_PASS
&& (*p)->after_input_sections())
continue;
- else if (pass == AFTER_INPUT_SECTIONS_PASS
+ else if (pass == POSTPROCESSING_SECTIONS_PASS
&& (!(*p)->after_input_sections()
|| (*p)->type() == elfcpp::SHT_STRTAB))
continue;
- else if (pass == STRTAB_AFTER_INPUT_SECTIONS_PASS
+ else if (pass == STRTAB_AFTER_POSTPROCESSING_SECTIONS_PASS
&& (!(*p)->after_input_sections()
|| (*p)->type() != elfcpp::SHT_STRTAB))
continue;
off += (*p)->data_size();
// At this point the name must be set.
- if (pass != STRTAB_AFTER_INPUT_SECTIONS_PASS)
+ if (pass != STRTAB_AFTER_POSTPROCESSING_SECTIONS_PASS)
this->namepool_.add((*p)->name(), false, NULL);
}
return off;
// symbol table, and build the respective string pools.
void
-Layout::count_local_symbols(const Input_objects* input_objects)
+Layout::count_local_symbols(const Task* task,
+ const Input_objects* input_objects)
{
// First, figure out an upper bound on the number of symbols we'll
// be inserting into each pool. This helps us create the pools with
p != input_objects->relobj_end();
++p)
{
- Task_lock_obj<Object> tlo(**p);
+ Task_lock_obj<Object> tlo(task, *p);
(*p)->count_local_symbols(&this->sympool_, &this->dynpool_);
}
}
void
Layout::create_symtab_sections(const Input_objects* input_objects,
Symbol_table* symtab,
+ const Task* task,
off_t* poff)
{
int symsize;
== this->dynsym_section_->data_size() - locsize);
}
- off = symtab->finalize(local_symcount, off, dynoff, dyn_global_index,
+ off = symtab->finalize(task, local_symcount, off, dynoff, dyn_global_index,
dyncount, &this->sympool_);
if (!parameters->strip_all())
// file size. Note we finalize the .shstrab last, to allow the
// after_input_section sections to modify their section-names before
// writing.
- off_t off = this->output_file_size_;
- off = this->set_section_offsets(off, AFTER_INPUT_SECTIONS_PASS);
-
- // Now that we've finalized the names, we can finalize the shstrab.
- off = this->set_section_offsets(off, STRTAB_AFTER_INPUT_SECTIONS_PASS);
-
- if (off > this->output_file_size_)
+ if (this->any_postprocessing_sections_)
{
- of->resize(off);
- this->output_file_size_ = off;
+ off_t off = this->output_file_size_;
+ off = this->set_section_offsets(off, POSTPROCESSING_SECTIONS_PASS);
+
+ // Now that we've finalized the names, we can finalize the shstrab.
+ off =
+ this->set_section_offsets(off,
+ STRTAB_AFTER_POSTPROCESSING_SECTIONS_PASS);
+
+ if (off > this->output_file_size_)
+ {
+ of->resize(off);
+ this->output_file_size_ = off;
+ }
}
for (Section_list::const_iterator p = this->section_list_.begin();
// We can always run this task.
-Task::Is_runnable_type
-Write_sections_task::is_runnable(Workqueue*)
+Task_token*
+Write_sections_task::is_runnable()
{
- return IS_RUNNABLE;
+ return NULL;
}
// We need to unlock both OUTPUT_SECTIONS_BLOCKER and FINAL_BLOCKER
// when finished.
-class Write_sections_task::Write_sections_locker : public Task_locker
-{
- public:
- Write_sections_locker(Task_token& output_sections_blocker,
- Task_token& final_blocker,
- Workqueue* workqueue)
- : output_sections_block_(output_sections_blocker, workqueue),
- final_block_(final_blocker, workqueue)
- { }
-
- private:
- Task_block_token output_sections_block_;
- Task_block_token final_block_;
-};
-
-Task_locker*
-Write_sections_task::locks(Workqueue* workqueue)
+void
+Write_sections_task::locks(Task_locker* tl)
{
- return new Write_sections_locker(*this->output_sections_blocker_,
- *this->final_blocker_,
- workqueue);
+ tl->add(this, this->output_sections_blocker_);
+ tl->add(this, this->final_blocker_);
}
// Run the task--write out the data.
// We can always run this task.
-Task::Is_runnable_type
-Write_data_task::is_runnable(Workqueue*)
+Task_token*
+Write_data_task::is_runnable()
{
- return IS_RUNNABLE;
+ return NULL;
}
// We need to unlock FINAL_BLOCKER when finished.
-Task_locker*
-Write_data_task::locks(Workqueue* workqueue)
+void
+Write_data_task::locks(Task_locker* tl)
{
- return new Task_locker_block(*this->final_blocker_, workqueue);
+ tl->add(this, this->final_blocker_);
}
// Run the task--write out the data.
// We can always run this task.
-Task::Is_runnable_type
-Write_symbols_task::is_runnable(Workqueue*)
+Task_token*
+Write_symbols_task::is_runnable()
{
- return IS_RUNNABLE;
+ return NULL;
}
// We need to unlock FINAL_BLOCKER when finished.
-Task_locker*
-Write_symbols_task::locks(Workqueue* workqueue)
+void
+Write_symbols_task::locks(Task_locker* tl)
{
- return new Task_locker_block(*this->final_blocker_, workqueue);
+ tl->add(this, this->final_blocker_);
}
// Run the task--write out the symbols.
// We can only run this task after the input sections have completed.
-Task::Is_runnable_type
-Write_after_input_sections_task::is_runnable(Workqueue*)
+Task_token*
+Write_after_input_sections_task::is_runnable()
{
if (this->input_sections_blocker_->is_blocked())
- return IS_BLOCKED;
- return IS_RUNNABLE;
+ return this->input_sections_blocker_;
+ return NULL;
}
// We need to unlock FINAL_BLOCKER when finished.
-Task_locker*
-Write_after_input_sections_task::locks(Workqueue* workqueue)
+void
+Write_after_input_sections_task::locks(Task_locker* tl)
{
- return new Task_locker_block(*this->final_blocker_, workqueue);
+ tl->add(this, this->final_blocker_);
}
// Run the task.
// Run the task--close the file.
void
-Close_task_runner::run(Workqueue*)
+Close_task_runner::run(Workqueue*, const Task*)
{
this->of_->close();
}
// Run the operation.
void
- run(Workqueue*);
+ run(Workqueue*, const Task*);
private:
Layout_task_runner(const Layout_task_runner&);
// Finalize the layout after all the input sections have been added.
off_t
- finalize(const Input_objects*, Symbol_table*);
+ finalize(const Input_objects*, Symbol_table*, const Task*);
+
+ // Return whether any sections require postprocessing.
+ bool
+ any_postprocessing_sections() const
+ { return this->any_postprocessing_sections_; }
// Return the size of the output file.
off_t
// Count the local symbols in the regular symbol table and the dynamic
// symbol table, and build the respective string pools.
void
- count_local_symbols(const Input_objects*);
+ count_local_symbols(const Task*, const Input_objects*);
// Create the output sections for the symbol table.
void
- create_symtab_sections(const Input_objects*, Symbol_table*, off_t*);
+ create_symtab_sections(const Input_objects*, Symbol_table*, const Task*,
+ off_t*);
// Create the .shstrtab section.
Output_section*
// Set the final file offsets of all the sections not associated
// with a segment. We set section offsets in three passes: the
// first handles all allocated sections, the second sections that
- // can be handled after input-sections are processed, and the last
- // the late-bound STRTAB sections (probably only shstrtab, which is
- // the one we care about because it holds section names).
+ // require postprocessing, and the last the late-bound STRTAB
+ // sections (probably only shstrtab, which is the one we care about
+ // because it holds section names).
enum Section_offset_pass
{
BEFORE_INPUT_SECTIONS_PASS,
- AFTER_INPUT_SECTIONS_PASS,
- STRTAB_AFTER_INPUT_SECTIONS_PASS
+ POSTPROCESSING_SECTIONS_PASS,
+ STRTAB_AFTER_POSTPROCESSING_SECTIONS_PASS
};
off_t
set_section_offsets(off_t, Section_offset_pass pass);
bool input_without_gnu_stack_note_;
// Whether we have seen an object file that uses the static TLS model.
bool has_static_tls_;
+ // Whether any sections require postprocessing.
+ bool any_postprocessing_sections_;
};
// This task handles writing out data in output sections which is not
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
{ return "Write_after_input_sections_task"; }
private:
- class Write_sections_locker;
-
Layout* layout_;
Output_file* of_;
Task_token* input_sections_blocker_;
// Run the operation.
void
- run(Workqueue*);
+ run(Workqueue*, const Task*);
private:
Output_file* of_;
&symtab, &layout);
// Run the main task processing loop.
- workqueue.process();
+ workqueue.process(0);
if (command_line.options().print_stats())
{
}
// Finalize the local symbols. Here we add their names to *POOL and
-// *DYNPOOL, and we add their values to THIS->LOCAL_VALUES_.
-// This function is always called from the main thread. The actual
+// *DYNPOOL, and we add their values to THIS->LOCAL_VALUES_. This
+// function is always called from a singleton thread. The actual
// output of the local symbols will occur in a separate task.
template<int size, bool big_endian>
// Finalize the local symbols. Here we add their values to
// THIS->LOCAL_VALUES_ and set their output symbol table indexes.
-// This function is always called from the main thread. The actual
+// This function is always called from a singleton thread. The actual
// output of the local symbols will occur in a separate task.
template<int size, bool big_endian>
template<int size, bool big_endian>
void
-Sized_relobj<size, big_endian>::write_local_symbols(Output_file* of,
- const Stringpool* sympool,
- const Stringpool* dynpool)
+Sized_relobj<size, big_endian>::write_local_symbols(
+ Output_file* of,
+ const Stringpool* sympool,
+ const Stringpool* dynpool)
{
if (parameters->strip_all() && this->output_local_dynsym_count_ == 0)
return;
{
class General_options;
+class Task;
class Layout;
class Output_section;
class Output_file;
// Lock the underlying file.
void
- lock()
- { this->input_file()->file().lock(); }
+ lock(const Task* t)
+ { this->input_file()->file().lock(t); }
// Unlock the underlying file.
void
- unlock()
- { this->input_file()->file().unlock(); }
+ unlock(const Task* t)
+ { this->input_file()->file().unlock(t); }
// Return whether the underlying file is locked.
bool
is_locked() const
{ return this->input_file()->file().is_locked(); }
+ // Return the token, so that the task can be queued.
+ Task_token*
+ token()
+ { return this->input_file()->file().token(); }
+
+ // Release the underlying file.
+ void
+ release()
+ { this->input_file_->file().release(); }
+
// Return the sized target structure associated with this object.
// This is like the target method but it returns a pointer of
// appropriate checked type.
virtual unsigned int
do_section_info(unsigned int shndx) = 0;
- // Get the file.
+ // Get the file. We pass on const-ness.
Input_file*
input_file()
{ return this->input_file_; }
// any relocations for sections which require special handling, such
// as the exception frame section.
bool
- relocs_must_follow_section_writes()
+ relocs_must_follow_section_writes() const
{ return this->relocs_must_follow_section_writes_; }
// Return the object merge map.
// Write section data to the output file. Record the views and
// sizes in VIEWS for use when relocating.
void
- write_sections(const unsigned char* pshdrs, Output_file*, Views*);
+ write_sections(const unsigned char* pshdrs, Output_file*, Views*) const;
// Relocate the sections in the output file.
void
target-select.cc
target-select.h
tls.h
+token.h
version.cc
workqueue.cc
workqueue.h
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-11-29 16:33-0800\n"
+"POT-Creation-Date: 2007-12-14 09:45-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
msgid "%s: member at %zu is not an ELF object"
msgstr ""
-#: compressed_output.cc:140
-msgid "Not compressing section data: zlib error"
+#: compressed_output.cc:126
+msgid "not compressing section data: zlib error"
msgstr ""
-#: dirsearch.cc:68
+#: dirsearch.cc:70
#, c-format
msgid "%s: can not read directory: %s"
msgstr ""
msgid "dynamic symbol table name section has wrong type: %u"
msgstr ""
-#: dynobj.cc:402 object.cc:236 object.cc:571
+#: dynobj.cc:402 object.cc:238 object.cc:574
#, c-format
msgid "bad section name offset for section %u: %lu"
msgstr ""
msgid "%s: fstat failed: %s"
msgstr ""
-#: fileread.cc:210
+#: fileread.cc:231
#, c-format
msgid "%s: pread failed: %s"
msgstr ""
-#: fileread.cc:216
+#: fileread.cc:237
#, c-format
msgid "%s: file too short: read only %lld of %lld bytes at %lld"
msgstr ""
-#: fileread.cc:292
+#: fileread.cc:312
#, c-format
msgid "%s: mmap offset %lld size %lld failed: %s"
msgstr ""
-#: fileread.cc:371
+#: fileread.cc:393
#, c-format
msgid "%s: total bytes mapped for read: %llu\n"
msgstr ""
-#: fileread.cc:373
+#: fileread.cc:395
#, c-format
msgid "%s: maximum bytes mapped for read at one time: %llu\n"
msgstr ""
-#: fileread.cc:442
+#: fileread.cc:465
#, c-format
msgid "cannot find -l%s"
msgstr ""
-#: fileread.cc:469
+#: fileread.cc:492
#, c-format
msgid "cannot find %s"
msgstr ""
-#: fileread.cc:480
+#: fileread.cc:503
#, c-format
msgid "cannot open %s: %s"
msgstr ""
#. We had some input files, but we weren't able to open any of
#. them.
-#: gold.cc:118 gold.cc:165
+#: gold.cc:118 gold.cc:166
msgid "no input files"
msgstr ""
#. We print out just the first .so we see; there may be others.
-#: gold.cc:180
+#: gold.cc:181
#, c-format
msgid "cannot mix -static with dynamic object %s"
msgstr ""
msgstr ""
#. FIXME: This needs to specify the location somehow.
-#: i386.cc:160 i386.cc:1439 x86_64.cc:172 x86_64.cc:1269
+#: i386.cc:160 i386.cc:1480 x86_64.cc:172 x86_64.cc:1370
msgid "missing expected TLS relocation"
msgstr ""
-#: i386.cc:779 x86_64.cc:732 x86_64.cc:910
+#: i386.cc:806 x86_64.cc:761 x86_64.cc:975
#, c-format
msgid "%s: unsupported reloc %u against local symbol"
msgstr ""
-#: i386.cc:882 i386.cc:1169 x86_64.cc:851 x86_64.cc:1093
+#: i386.cc:913 i386.cc:1209 x86_64.cc:886 x86_64.cc:1157
#, c-format
msgid "%s: unexpected reloc %u in object file"
msgstr ""
-#: i386.cc:1018 x86_64.cc:924 x86_64.cc:1152
+#: i386.cc:1052 x86_64.cc:989 x86_64.cc:1253
#, c-format
msgid "%s: unsupported reloc %u against global symbol %s"
msgstr ""
-#: i386.cc:1322
+#: i386.cc:1363
#, c-format
msgid "%s: unsupported RELA reloc section"
msgstr ""
-#: i386.cc:1579 x86_64.cc:1467
+#: i386.cc:1620 x86_64.cc:1569
#, c-format
msgid "unexpected reloc %u in object file"
msgstr ""
-#: i386.cc:1611 i386.cc:1687 i386.cc:1694 i386.cc:1735 i386.cc:1791
-#: x86_64.cc:1488 x86_64.cc:1537 x86_64.cc:1548
+#: i386.cc:1652 i386.cc:1727 i386.cc:1734 i386.cc:1765 i386.cc:1818
+#: x86_64.cc:1590 x86_64.cc:1670 x86_64.cc:1694
#, c-format
msgid "unsupported reloc %u"
msgstr ""
-#: i386.cc:1702
+#: i386.cc:1742
msgid "both SUN and GNU model TLS relocations"
msgstr ""
-#: merge.cc:464
+#: merge.cc:472
msgid "mergeable string section length not multiple of character size"
msgstr ""
-#: merge.cc:480
+#: merge.cc:488
msgid "entry in mergeable string section not null terminated"
msgstr ""
msgid "%s: unsupported ELF machine number %d"
msgstr ""
-#: object.cc:71 script.cc:1226
+#: object.cc:71 script.cc:1229
#, c-format
msgid "%s: %s"
msgstr ""
msgid "section name section has wrong type: %u"
msgstr ""
-#: object.cc:308
+#: object.cc:311
#, c-format
msgid "invalid symbol table name index: %u"
msgstr ""
-#: object.cc:314
+#: object.cc:317
#, c-format
msgid "symbol table name section has wrong type: %u"
msgstr ""
-#: object.cc:394
+#: object.cc:397
#, c-format
msgid "section group %u info %u out of range"
msgstr ""
-#: object.cc:412
+#: object.cc:415
#, c-format
msgid "symbol %u name offset %u out of range"
msgstr ""
-#: object.cc:444
+#: object.cc:447
#, c-format
msgid "section %u in section group %u out of range"
msgstr ""
-#: object.cc:534 reloc.cc:229 reloc.cc:496
+#: object.cc:537 reloc.cc:205 reloc.cc:520
#, c-format
msgid "relocation section %u has bad info %u"
msgstr ""
-#: object.cc:706
+#: object.cc:709
msgid "size of symbols is not multiple of symbol size"
msgstr ""
-#. FIXME: Handle SHN_XINDEX.
-#: object.cc:798
+#: object.cc:808
#, c-format
-msgid "unknown section index %u for local symbol %u"
+msgid "local symbol %u section name out of range: %u >= %u"
msgstr ""
-#: object.cc:807
+#. FIXME: Handle SHN_XINDEX.
+#: object.cc:865
#, c-format
-msgid "local symbol %u section index %u out of range"
+msgid "unknown section index %u for local symbol %u"
msgstr ""
-#: object.cc:839
+#: object.cc:874
#, c-format
-msgid "local symbol %u section name out of range: %u >= %u"
+msgid "local symbol %u section index %u out of range"
msgstr ""
-#: object.cc:1070
+#: object.cc:1194
#, c-format
msgid "%s: incompatible target"
msgstr ""
-#: object.cc:1226
+#: object.cc:1349
#, c-format
msgid "%s: unsupported ELF file type %d"
msgstr ""
-#: object.cc:1245 object.cc:1291 object.cc:1325
+#: object.cc:1368 object.cc:1414 object.cc:1448
#, c-format
msgid "%s: ELF file too short"
msgstr ""
-#: object.cc:1253
+#: object.cc:1376
#, c-format
msgid "%s: invalid ELF version 0"
msgstr ""
-#: object.cc:1255
+#: object.cc:1378
#, c-format
msgid "%s: unsupported ELF version %d"
msgstr ""
-#: object.cc:1262
+#: object.cc:1385
#, c-format
msgid "%s: invalid ELF class 0"
msgstr ""
-#: object.cc:1268
+#: object.cc:1391
#, c-format
msgid "%s: unsupported ELF class %d"
msgstr ""
-#: object.cc:1275
+#: object.cc:1398
#, c-format
msgid "%s: invalid ELF data encoding"
msgstr ""
-#: object.cc:1281
+#: object.cc:1404
#, c-format
msgid "%s: unsupported ELF data encoding %d"
msgstr ""
-#: object.cc:1301
+#: object.cc:1424
#, c-format
msgid "%s: not configured to support 32-bit big-endian object"
msgstr ""
-#: object.cc:1314
+#: object.cc:1437
#, c-format
msgid "%s: not configured to support 32-bit little-endian object"
msgstr ""
-#: object.cc:1335
+#: object.cc:1458
#, c-format
msgid "%s: not configured to support 64-bit big-endian object"
msgstr ""
-#: object.cc:1348
+#: object.cc:1471
#, c-format
msgid "%s: not configured to support 64-bit little-endian object"
msgstr ""
msgid "%s: -%c: %s\n"
msgstr ""
-#: options.h:372
+#: options.h:331
+#, c-format
+msgid "invalid optimization level: %s"
+msgstr ""
+
+#: options.h:377
#, c-format
-msgid "Unsupported argument to --compress-debug-symbols: %s"
+msgid "unsupported argument to --compress-debug-sections: %s"
msgstr ""
-#: options.h:424
+#: options.h:428
#, c-format
-msgid "%s: invalid argument to -Ttext: %s\n"
+msgid "invalid argument to -Ttext: %s"
msgstr ""
#: options.h:437
#, c-format
-msgid "%s: invalid thread count: %s\n"
+msgid "invalid thread count: %s"
msgstr ""
-#: output.cc:1122
+#: options.h:445
+msgid "--threads not supported"
+msgstr ""
+
+#: output.cc:1467
#, c-format
msgid "invalid alignment %lu for section \"%s\""
msgstr ""
-#: output.cc:1941
+#: output.cc:2334
#, c-format
msgid "%s: open: %s"
msgstr ""
-#: output.cc:1953 output.cc:1986
+#: output.cc:2354
#, c-format
-msgid "%s: munmap: %s"
+msgid "%s: mremap: %s"
msgstr ""
-#: output.cc:1967
+#: output.cc:2390
#, c-format
msgid "%s: lseek: %s"
msgstr ""
-#: output.cc:1970
+#: output.cc:2393 output.cc:2430
#, c-format
msgid "%s: write: %s"
msgstr ""
-#: output.cc:1976
+#: output.cc:2401
#, c-format
msgid "%s: mmap: %s"
msgstr ""
-#: output.cc:1990
+#: output.cc:2411
+#, c-format
+msgid "%s: munmap: %s"
+msgstr ""
+
+#: output.cc:2428
+#, c-format
+msgid "%s: write: unexpected 0 return-value"
+msgstr ""
+
+#: output.cc:2440
#, c-format
msgid "%s: close: %s"
msgstr ""
-#: readsyms.cc:151
+#: readsyms.cc:150
#, c-format
msgid "%s: file is empty"
msgstr ""
-#: readsyms.cc:186
+#: readsyms.cc:185
#, c-format
msgid "%s: ordinary object found in input group"
msgstr ""
#. Here we have to handle any other input file types we need.
-#: readsyms.cc:234
+#: readsyms.cc:244
#, c-format
msgid "%s: not an object or archive"
msgstr ""
-#: reloc.cc:248 reloc.cc:514
+#: reloc.cc:224 reloc.cc:538
#, c-format
msgid "relocation section %u uses unexpected symbol table %u"
msgstr ""
-#: reloc.cc:263 reloc.cc:532
+#: reloc.cc:239 reloc.cc:556
#, c-format
msgid "unexpected entsize for reloc section %u: %lu != %u"
msgstr ""
-#: reloc.cc:272 reloc.cc:541
+#: reloc.cc:248 reloc.cc:565
#, c-format
msgid "reloc section %u size %lu uneven"
msgstr ""
-#: reloc.cc:732
+#: reloc.cc:759
#, c-format
msgid "reloc section size %zu is not a multiple of reloc size %d\n"
msgstr ""
#. There are some options that we could handle here--e.g.,
#. -lLIBRARY. Should we bother?
-#: script.cc:1330
+#: script.cc:1333
#, c-format
msgid ""
"%s: Ignoring command OPTION; OPTION is only valid for scripts specified via -"
"T"
msgstr ""
-#: symtab.cc:597
+#: stringpool.cc:535
+#, c-format
+msgid "%s: %s entries: %zu; buckets: %zu\n"
+msgstr ""
+
+#: stringpool.cc:539
+#, c-format
+msgid "%s: %s entries: %zu\n"
+msgstr ""
+
+#: stringpool.cc:542
+#, c-format
+msgid "%s: %s Stringdata structures: %zu\n"
+msgstr ""
+
+#: symtab.cc:595
#, c-format
msgid "bad global symbol name offset %u at %zu"
msgstr ""
-#: symtab.cc:675
+#: symtab.cc:673
msgid "too few symbol versions"
msgstr ""
-#: symtab.cc:704
+#: symtab.cc:702
#, c-format
msgid "bad symbol name offset %u at %zu"
msgstr ""
-#: symtab.cc:758
+#: symtab.cc:756
#, c-format
msgid "versym for symbol %zu out of range: %u"
msgstr ""
-#: symtab.cc:766
+#: symtab.cc:764
#, c-format
msgid "versym for symbol %zu has no name: %u"
msgstr ""
-#: symtab.cc:1484 symtab.cc:1697
+#: symtab.cc:1482 symtab.cc:1698
#, c-format
msgid "%s: unsupported symbol section 0x%x"
msgstr ""
-#: symtab.cc:1821
+#: symtab.cc:1822
#, c-format
msgid "%s: undefined reference to '%s'"
msgstr ""
-#: symtab.cc:1962
+#: symtab.cc:1907
+#, c-format
+msgid "%s: symbol table entries: %zu; buckets: %zu\n"
+msgstr ""
+
+#: symtab.cc:1910
+#, c-format
+msgid "%s: symbol table entries: %zu\n"
+msgstr ""
+
+#: symtab.cc:1979
#, c-format
msgid ""
"while linking %s: symbol '%s' defined in multiple places (possible ODR "
"This program has absolutely no warranty.\n"
msgstr ""
-#: workqueue.cc:484
-#, c-format
-msgid "gold task queue:\n"
-msgstr ""
-
-#: workqueue-threads.cc:107
+#: workqueue-threads.cc:106
#, c-format
msgid "%s failed: %s"
msgstr ""
-#: x86_64.cc:1177
+#: x86_64.cc:1278
#, c-format
msgid "%s: unsupported REL reloc section"
msgstr ""
-#: x86_64.cc:1513
-msgid "TLS reloc but no TLS segment"
-msgstr ""
-
-#: x86_64.cc:1576
+#: x86_64.cc:1742
#, c-format
msgid "unsupported reloc type %u"
msgstr ""
// If we fail to open the object, then we won't create an Add_symbols
// task. However, we still need to unblock the token, or else the
// link won't proceed to generate more error messages. We can only
-// unblock tokens in the main thread, so we need a dummy task to do
-// that. The dummy task has to maintain the right sequence of blocks,
-// so we need both this_blocker and next_blocker.
+// unblock tokens when the workqueue lock is held, so we need a dummy
+// task to do that. The dummy task has to maintain the right sequence
+// of blocks, so we need both this_blocker and next_blocker.
class Unblock_token : public Task
{
delete this->this_blocker_;
}
- Is_runnable_type
- is_runnable(Workqueue*)
+ Task_token*
+ is_runnable()
{
if (this->this_blocker_ != NULL && this->this_blocker_->is_blocked())
- return IS_BLOCKED;
- return IS_RUNNABLE;
+ return this->this_blocker_;
+ return NULL;
}
- Task_locker*
- locks(Workqueue* workqueue)
- { return new Task_locker_block(*this->next_blocker_, workqueue); }
+ void
+ locks(Task_locker* tl)
+ { tl->add(this, this->next_blocker_); }
void
run(Workqueue*)
// ordinary input file immediately. For an archive specified using
// -l, we have to wait until the search path is complete.
-Task::Is_runnable_type
-Read_symbols::is_runnable(Workqueue*)
+Task_token*
+Read_symbols::is_runnable()
{
if (this->input_argument_->is_file()
&& this->input_argument_->file().may_need_search()
- && this->dirpath_.token().is_blocked())
- return IS_BLOCKED;
+ && this->dirpath_->token()->is_blocked())
+ return this->dirpath_->token();
- return IS_RUNNABLE;
+ return NULL;
}
// Return a Task_locker for a Read_symbols task. We don't need any
// locks here.
-Task_locker*
-Read_symbols::locks(Workqueue*)
+void
+Read_symbols::locks(Task_locker*)
{
- return NULL;
}
// Run a Read_symbols task.
}
Input_file* input_file = new Input_file(&this->input_argument_->file());
- if (!input_file->open(this->options_, this->dirpath_))
+ if (!input_file->open(this->options_, *this->dirpath_, this))
return false;
// Read enough of the file to pick up the entire ELF header.
Read_symbols_data* sd = new Read_symbols_data;
obj->read_symbols(sd);
+
+ // Opening the file locked it, so now we need to unlock it.
+ // We need to unlock it before queuing the Add_symbols task,
+ // because the workqueue doesn't know about our lock on the
+ // file. If we queue the Add_symbols task first, it will be
+ // stuck on the end of the file lock, but since the
+ // workqueue doesn't know about that lock, it will never
+ // release the Add_symbols task.
+
+ input_file->file().unlock(this);
+
workqueue->queue_front(new Add_symbols(this->input_objects_,
this->symtab_, this->layout_,
obj, sd,
this->this_blocker_,
this->next_blocker_));
- // Opening the file locked it, so now we need to unlock it.
- input_file->file().unlock();
-
return true;
}
}
// This is an archive.
Archive* arch = new Archive(this->input_argument_->file().name(),
input_file);
- arch->setup();
- workqueue->queue(new Add_archive_symbols(this->symtab_,
- this->layout_,
- this->input_objects_,
- arch,
- this->input_group_,
- this->this_blocker_,
- this->next_blocker_));
+ arch->setup(this);
+
+ workqueue->queue_front(new Add_archive_symbols(this->symtab_,
+ this->layout_,
+ this->input_objects_,
+ arch,
+ this->input_group_,
+ this->this_blocker_,
+ this->next_blocker_));
return true;
}
}
const Input_file_group* group = this->input_argument_->group();
Task_token* this_blocker = this->this_blocker_;
+
for (Input_file_group::const_iterator p = group->begin();
p != group->end();
++p)
const Input_argument* arg = &*p;
gold_assert(arg->is_file());
- Task_token* next_blocker = new Task_token();
+ Task_token* next_blocker = new Task_token(true);
next_blocker->add_blocker();
workqueue->queue(new Read_symbols(this->options_, this->input_objects_,
this->symtab_, this->layout_,
// We are blocked by this_blocker_. We block next_blocker_. We also
// lock the file.
-Task::Is_runnable_type
-Add_symbols::is_runnable(Workqueue*)
+Task_token*
+Add_symbols::is_runnable()
{
if (this->this_blocker_ != NULL && this->this_blocker_->is_blocked())
- return IS_BLOCKED;
+ return this->this_blocker_;
if (this->object_->is_locked())
- return IS_LOCKED;
- return IS_RUNNABLE;
+ return this->object_->token();
+ return NULL;
}
-class Add_symbols::Add_symbols_locker : public Task_locker
-{
- public:
- Add_symbols_locker(Task_token& token, Workqueue* workqueue,
- Object* object)
- : blocker_(token, workqueue), objlock_(*object)
- { }
-
- private:
- Task_locker_block blocker_;
- Task_locker_obj<Object> objlock_;
-};
-
-Task_locker*
-Add_symbols::locks(Workqueue* workqueue)
+void
+Add_symbols::locks(Task_locker* tl)
{
- return new Add_symbols_locker(*this->next_blocker_, workqueue,
- this->object_);
+ tl->add(this, this->next_blocker_);
+ tl->add(this, this->object_->token());
}
// Add the symbols in the object to the symbol table.
{
this->object_->layout(this->symtab_, this->layout_, this->sd_);
this->object_->add_symbols(this->symtab_, this->sd_);
+ this->object_->release();
}
delete this->sd_;
this->sd_ = NULL;
// We need to wait for THIS_BLOCKER_ and unblock NEXT_BLOCKER_.
-Task::Is_runnable_type
-Finish_group::is_runnable(Workqueue*)
+Task_token*
+Finish_group::is_runnable()
{
if (this->this_blocker_ != NULL && this->this_blocker_->is_blocked())
- return IS_BLOCKED;
- return IS_RUNNABLE;
+ return this->this_blocker_;
+ return NULL;
}
-Task_locker*
-Finish_group::locks(Workqueue* workqueue)
+void
+Finish_group::locks(Task_locker* tl)
{
- return new Task_locker_block(*this->next_blocker_, workqueue);
+ tl->add(this, this->next_blocker_);
}
// Loop over the archives until there are no new undefined symbols.
p != this->input_group_->end();
++p)
{
- Task_lock_obj<Archive> tl(**p);
+ Task_lock_obj<Archive> tl(this, *p);
(*p)->add_symbols(this->symtab_, this->layout_,
this->input_objects_);
// NEXT_BLOCKER is used to block the next input file from adding
// symbols.
Read_symbols(const General_options& options, Input_objects* input_objects,
- Symbol_table* symtab, Layout* layout, const Dirsearch& dirpath,
+ Symbol_table* symtab, Layout* layout, Dirsearch* dirpath,
const Input_argument* input_argument, Input_group* input_group,
Task_token* this_blocker, Task_token* next_blocker)
: options_(options), input_objects_(input_objects), symtab_(symtab),
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
Input_objects* input_objects_;
Symbol_table* symtab_;
Layout* layout_;
- const Dirsearch& dirpath_;
+ Dirsearch* dirpath_;
const Input_argument* input_argument_;
Input_group* input_group_;
Task_token* this_blocker_;
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
{ return "Add_symbols " + this->object_->name(); }
private:
- class Add_symbols_locker;
-
Input_objects* input_objects_;
Symbol_table* symtab_;
Layout* layout_;
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
// After reading it, the start another task to process the
// information. These tasks requires access to the file.
-Task::Is_runnable_type
-Read_relocs::is_runnable(Workqueue*)
+Task_token*
+Read_relocs::is_runnable()
{
- return this->object_->is_locked() ? IS_LOCKED : IS_RUNNABLE;
+ return this->object_->is_locked() ? this->object_->token() : NULL;
}
// Lock the file.
-Task_locker*
-Read_relocs::locks(Workqueue*)
+void
+Read_relocs::locks(Task_locker* tl)
{
- return new Task_locker_obj<Object>(*this->object_);
+ tl->add(this, this->object_->token());
}
// Read the relocations and then start a Scan_relocs_task.
{
Read_relocs_data *rd = new Read_relocs_data;
this->object_->read_relocs(rd);
+ this->object_->release();
+
workqueue->queue_front(new Scan_relocs(this->options_, this->symtab_,
this->layout_, this->object_, rd,
this->symtab_lock_, this->blocker_));
// use a lock on the symbol table to keep them from interfering with
// each other.
-Task::Is_runnable_type
-Scan_relocs::is_runnable(Workqueue*)
+Task_token*
+Scan_relocs::is_runnable()
{
- if (!this->symtab_lock_->is_writable() || this->object_->is_locked())
- return IS_LOCKED;
- return IS_RUNNABLE;
+ if (!this->symtab_lock_->is_writable())
+ return this->symtab_lock_;
+ if (this->object_->is_locked())
+ return this->object_->token();
+ return NULL;
}
// Return the locks we hold: one on the file, one on the symbol table
// and one blocker.
-class Scan_relocs::Scan_relocs_locker : public Task_locker
-{
- public:
- Scan_relocs_locker(Object* object, Task_token& symtab_lock, Task* task,
- Task_token& blocker, Workqueue* workqueue)
- : objlock_(*object), symtab_locker_(symtab_lock, task),
- blocker_(blocker, workqueue)
- { }
-
- private:
- Task_locker_obj<Object> objlock_;
- Task_locker_write symtab_locker_;
- Task_locker_block blocker_;
-};
-
-Task_locker*
-Scan_relocs::locks(Workqueue* workqueue)
+void
+Scan_relocs::locks(Task_locker* tl)
{
- return new Scan_relocs_locker(this->object_, *this->symtab_lock_, this,
- *this->blocker_, workqueue);
+ tl->add(this, this->object_->token());
+ tl->add(this, this->symtab_lock_);
+ tl->add(this, this->blocker_);
}
// Scan the relocs.
{
this->object_->scan_relocs(this->options_, this->symtab_, this->layout_,
this->rd_);
+ this->object_->release();
delete this->rd_;
this->rd_ = NULL;
}
// We may have to wait for the output sections to be written.
-Task::Is_runnable_type
-Relocate_task::is_runnable(Workqueue*)
+Task_token*
+Relocate_task::is_runnable()
{
if (this->object_->relocs_must_follow_section_writes()
&& this->output_sections_blocker_->is_blocked())
- return IS_BLOCKED;
+ return this->output_sections_blocker_;
if (this->object_->is_locked())
- return IS_LOCKED;
+ return this->object_->token();
- return IS_RUNNABLE;
+ return NULL;
}
// We want to lock the file while we run. We want to unblock
// INPUT_SECTIONS_BLOCKER and FINAL_BLOCKER when we are done.
+// INPUT_SECTIONS_BLOCKER may be NULL.
-class Relocate_task::Relocate_locker : public Task_locker
-{
- public:
- Relocate_locker(Task_token& input_sections_blocker,
- Task_token& final_blocker, Workqueue* workqueue,
- Object* object)
- : input_sections_blocker_(input_sections_blocker, workqueue),
- final_blocker_(final_blocker, workqueue),
- objlock_(*object)
- { }
-
- private:
- Task_block_token input_sections_blocker_;
- Task_block_token final_blocker_;
- Task_locker_obj<Object> objlock_;
-};
-
-Task_locker*
-Relocate_task::locks(Workqueue* workqueue)
+void
+Relocate_task::locks(Task_locker* tl)
{
- return new Relocate_locker(*this->input_sections_blocker_,
- *this->final_blocker_,
- workqueue,
- this->object_);
+ if (this->input_sections_blocker_ != NULL)
+ tl->add(this, this->input_sections_blocker_);
+ tl->add(this, this->final_blocker_);
+ tl->add(this, this->object_->token());
}
// Run the task.
{
this->object_->relocate(this->options_, this->symtab_, this->layout_,
this->of_);
+ this->object_->release();
}
// Return a debugging name for the task.
void
Sized_relobj<size, big_endian>::write_sections(const unsigned char* pshdrs,
Output_file* of,
- Views* pviews)
+ Views* pviews) const
{
unsigned int shnum = this->shnum();
- std::vector<Map_to_output>& map_sections(this->map_to_output());
+ const std::vector<Map_to_output>& map_sections(this->map_to_output());
const unsigned char* p = pshdrs + This::shdr_size;
for (unsigned int i = 1; i < shnum; ++i, p += This::shdr_size)
unsigned int shnum = this->shnum();
Sized_target<size, big_endian>* target = this->sized_target();
- std::vector<Map_to_output>& map_sections(this->map_to_output());
+ const std::vector<Map_to_output>& map_sections(this->map_to_output());
Relocate_info<size, big_endian> relinfo;
relinfo.options = &options;
#ifndef GOLD_RELOC_H
#define GOLD_RELOC_H
+#include <vector>
#include <byteswap.h>
#include "elfcpp.h"
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
get_name() const;
private:
- class Scan_relocs_locker;
-
const General_options& options_;
Symbol_table* symtab_;
Layout* layout_;
// The standard Task methods.
- Is_runnable_type
- is_runnable(Workqueue*);
+ Task_token*
+ is_runnable();
- Task_locker*
- locks(Workqueue*);
+ void
+ locks(Task_locker*);
void
run(Workqueue*);
get_name() const;
private:
- class Relocate_locker;
-
const General_options& options_;
const Symbol_table* symtab_;
const Layout* layout_;
delete this->this_blocker_;
}
- Is_runnable_type
- is_runnable(Workqueue*)
+ Task_token*
+ is_runnable()
{
if (this->this_blocker_ != NULL && this->this_blocker_->is_blocked())
- return IS_BLOCKED;
- return IS_RUNNABLE;
+ return this->this_blocker_;
+ return NULL;
}
- Task_locker*
- locks(Workqueue* workqueue)
- {
- return new Task_locker_block(*this->next_blocker_, workqueue);
- }
+ void
+ locks(Task_locker* tl)
+ { tl->add(this, this->next_blocker_); }
void
run(Workqueue*)
// This class holds data passed through the parser to the lexer and to
// the parser support functions. This avoids global variables. We
-// can't use global variables because we need not be called in the
-// main thread.
+// can't use global variables because we need not be called by a
+// singleton thread.
class Parser_closure
{
bool
read_input_script(Workqueue* workqueue, const General_options& options,
Symbol_table* symtab, Layout* layout,
- const Dirsearch& dirsearch, Input_objects* input_objects,
+ Dirsearch* dirsearch, Input_objects* input_objects,
Input_group* input_group,
const Input_argument* input_argument,
Input_file* input_file, const unsigned char*, off_t,
{
// The script did not add any files to read. Note that we are
// not permitted to call NEXT_BLOCKER->unblock() here even if
- // THIS_BLOCKER is NULL, as we are not in the main thread.
+ // THIS_BLOCKER is NULL, as we do not hold the workqueue lock.
workqueue->queue(new Script_unblock(this_blocker, next_blocker));
return true;
}
nb = next_blocker;
else
{
- nb = new Task_token();
+ nb = new Task_token(true);
nb->add_blocker();
}
workqueue->queue(new Read_symbols(options, input_objects, symtab,
// using "." + cmdline->options()->search_path() -- not dirsearch.
Dirsearch dirsearch;
+ // The file locking code wants to record a Task, but we haven't
+ // started the workqueue yet. This is only for debugging purposes,
+ // so we invent a fake value.
+ const Task* task = reinterpret_cast<const Task*>(-1);
+
Input_file_argument input_argument(filename, false, "",
cmdline->position_dependent_options());
Input_file input_file(&input_argument);
- if (!input_file.open(cmdline->options(), dirsearch))
+ if (!input_file.open(cmdline->options(), dirsearch, task))
return false;
Lex lex(&input_file);
if (lex.tokenize().is_invalid())
{
// Opening the file locked it, so now we need to unlock it.
- input_file.file().unlock();
+ input_file.file().unlock(task);
return false;
}
&lex.tokens());
if (yyparse(&closure) != 0)
{
- input_file.file().unlock();
+ input_file.file().unlock(task);
return false;
}
- input_file.file().unlock();
+ input_file.file().unlock(task);
return true;
}
bool
read_input_script(Workqueue*, const General_options&, Symbol_table*, Layout*,
- const Dirsearch&, Input_objects*, Input_group*,
+ Dirsearch*, Input_objects*, Input_group*,
const Input_argument*, Input_file*, const unsigned char* p,
off_t bytes, Task_token* this_blocker,
Task_token* next_blocker);
// OFF. Add their names to POOL. Return the new file offset.
off_t
-Symbol_table::finalize(unsigned int index, off_t off, off_t dynoff,
- size_t dyn_global_index, size_t dyncount,
+Symbol_table::finalize(const Task* task, unsigned int index, off_t off,
+ off_t dynoff, size_t dyn_global_index, size_t dyncount,
Stringpool* pool)
{
off_t ret;
// Now that we have the final symbol table, we can reliably note
// which symbols should get warnings.
- this->warnings_.note_warnings(this);
+ this->warnings_.note_warnings(this, task);
return ret;
}
// but apparently different definitions (different source-file/line-no).
void
-Symbol_table::detect_odr_violations(const char* output_file_name) const
+Symbol_table::detect_odr_violations(const Task* task,
+ const char* output_file_name) const
{
for (Odr_map::const_iterator it = candidate_odr_violations_.begin();
it != candidate_odr_violations_.end();
++locs)
{
// We need to lock the object in order to read it. This
- // means that we can not run inside a Task. If we want to
- // run this in a Task for better performance, we will need
- // one Task for object, plus appropriate locking to ensure
- // that we don't conflict with other uses of the object.
- locs->object->lock();
+ // means that we have to run in a singleton Task. If we
+ // want to run this in a general Task for better
+ // performance, we will need one Task for object, plus
+ // appropriate locking to ensure that we don't conflict with
+ // other uses of the object.
+ Task_lock_obj<Object> tl(task, locs->object);
std::string lineno = Dwarf_line_info::one_addr2line(
locs->object, locs->shndx, locs->offset);
- locs->object->unlock();
if (!lineno.empty())
line_nums.insert(lineno);
}
// sources for all the symbols.
void
-Warnings::note_warnings(Symbol_table* symtab)
+Warnings::note_warnings(Symbol_table* symtab, const Task* task)
{
for (Warning_table::iterator p = this->warnings_.begin();
p != this->warnings_.end();
// the object then, as we might try to issue the same
// warning multiple times simultaneously.
{
- Task_locker_obj<Object> tl(*p->second.object);
+ Task_lock_obj<Object> tl(task, p->second.object);
const unsigned char* c;
off_t len;
c = p->second.object->section_contents(p->second.shndx, &len,
// For each symbol for which we should give a warning, make a note
// on the symbol.
void
- note_warnings(Symbol_table* symtab);
+ note_warnings(Symbol_table* symtab, const Task*);
// Issue a warning for a reference to SYM at RELINFO's location.
template<int size, bool big_endian>
// Check candidate_odr_violations_ to find symbols with the same name
// but apparently different definitions (different source-file/line-no).
void
- detect_odr_violations(const char* output_file_name) const;
+ detect_odr_violations(const Task*, const char* output_file_name) const;
// SYM is defined using a COPY reloc. Return the dynamic object
// where the original definition was found.
// symbol, and DYNCOUNT is the number of global dynamic symbols.
// This records the parameters, and returns the new file offset.
off_t
- finalize(unsigned int index, off_t off, off_t dynoff,
+ finalize(const Task*, unsigned int index, off_t off, off_t dynoff,
size_t dyn_global_index, size_t dyncount, Stringpool* pool);
// Write out the global symbols.
Sized_object_test(const unsigned char* test_file, unsigned int test_file_size,
Target* target_test_pointer)
{
- Input_file input_file("test.o", test_file, test_file_size);
+ // We need a pretend Task.
+ const Task* task = reinterpret_cast<const Task*>(-1);
+ Input_file input_file(task, "test.o", test_file, test_file_size);
Object* object = make_elf_object("test.o", &input_file, 0,
test_file, test_file_size);
CHECK(object->name() == "test.o");
CHECK(!object->is_dynamic());
CHECK(object->target() == target_test_pointer);
CHECK(object->is_locked());
- object->unlock();
+ object->unlock(task);
CHECK(!object->is_locked());
- object->lock();
+ object->lock(task);
CHECK(object->shnum() == 5);
CHECK(object->section_name(0).empty());
CHECK(object->section_name(1) == ".test");
CHECK(object->section_flags(0) == 0);
CHECK(object->section_flags(1) == elfcpp::SHF_ALLOC);
- object->unlock();
+ object->unlock(task);
return true;
}
--- /dev/null
+// token.h -- lock tokens for gold -*- C++ -*-
+
+// Copyright 2006, 2007 Free Software Foundation, Inc.
+// Written by Ian Lance Taylor <iant@google.com>.
+
+// This file is part of gold.
+
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+// MA 02110-1301, USA.
+
+#ifndef GOLD_TOKEN_H
+#define GOLD_TOKEN_H
+
+namespace gold
+{
+
+class Condvar;
+class Task;
+
+// A list of Tasks, managed through the next_locked_ field in the
+// class Task. We define this class here because we need it in
+// Task_token.
+
+class Task_list
+{
+ public:
+ Task_list()
+ : head_(NULL), tail_(NULL)
+ { }
+
+ ~Task_list()
+ { gold_assert(this->head_ == NULL && this->tail_ == NULL); }
+
+ // Return whether the list is empty.
+ bool
+ empty() const
+ { return this->head_ == NULL; }
+
+ // Add T to the end of the list.
+ void
+ push_back(Task* t);
+
+ // Remove the first Task on the list and return it. Return NULL if
+ // the list is empty.
+ Task*
+ pop_front();
+
+ private:
+ // The start of the list. NULL if the list is empty.
+ Task* head_;
+ // The end of the list. NULL if the list is empty.
+ Task* tail_;
+};
+
+// We support two basic types of locks, which are both implemented
+// using the single class Task_token.
+
+// A write lock may be held by a single Task at a time. This is used
+// to control access to a single shared resource such as an Object.
+
+// A blocker is used to indicate that a Task A must be run after some
+// set of Tasks B. For each of the Tasks B, we increment the blocker
+// when the Task is created, and decrement it when the Task is
+// completed. When the count goes to 0, the task A is ready to run.
+
+// There are no shared read locks. We always read and write objects
+// in predictable patterns. The purpose of the locks is to permit
+// some flexibility for the threading system, for cases where the
+// execution order does not matter.
+
+// These tokens are only manipulated when the workqueue lock is held
+// or when they are first created. They do not require any locking
+// themselves.
+
+class Task_token
+{
+ public:
+ Task_token(bool is_blocker)
+ : is_blocker_(is_blocker), blockers_(0), writer_(NULL), waiting_()
+ { }
+
+ ~Task_token()
+ {
+ gold_assert(this->blockers_ == 0);
+ gold_assert(this->writer_ == NULL);
+ }
+
+ // Return whether this is a blocker.
+ bool
+ is_blocker() const
+ { return this->is_blocker_; }
+
+ // A write lock token uses these methods.
+
+ // Is the token writable?
+ bool
+ is_writable() const
+ {
+ gold_assert(!this->is_blocker_);
+ return this->writer_ == NULL;
+ }
+
+ // Add the task as the token's writer (there may only be one
+ // writer).
+ void
+ add_writer(const Task* t)
+ {
+ gold_assert(!this->is_blocker_ && this->writer_ == NULL);
+ this->writer_ = t;
+ }
+
+ // Remove the task as the token's writer.
+ void
+ remove_writer(const Task* t)
+ {
+ gold_assert(!this->is_blocker_ && this->writer_ == t);
+ this->writer_ = NULL;
+ }
+
+ // A blocker token uses these methods.
+
+ // Add a blocker to the token.
+ void
+ add_blocker()
+ {
+ gold_assert(this->is_blocker_);
+ ++this->blockers_;
+ this->writer_ = NULL;
+ }
+
+ // Remove a blocker from the token. Returns true if block count
+ // drops to zero.
+ bool
+ remove_blocker()
+ {
+ gold_assert(this->is_blocker_ && this->blockers_ > 0);
+ --this->blockers_;
+ this->writer_ = NULL;
+ return this->blockers_ == 0;
+ }
+
+ // Is the token currently blocked?
+ bool
+ is_blocked() const
+ {
+ gold_assert(this->is_blocker_);
+ return this->blockers_ > 0;
+ }
+
+ // Both blocker and write lock tokens use these methods.
+
+ // Add T to the list of tasks waiting for this token to be released.
+ void
+ add_waiting(Task* t)
+ { this->waiting_.push_back(t); }
+
+ // Remove the first Task waiting for this token to be released, and
+ // return it. Return NULL if no Tasks are waiting.
+ Task*
+ remove_first_waiting()
+ { return this->waiting_.pop_front(); }
+
+ private:
+ // It makes no sense to copy these.
+ Task_token(const Task_token&);
+ Task_token& operator=(const Task_token&);
+
+ // Whether this is a blocker token.
+ bool is_blocker_;
+ // The number of blockers.
+ int blockers_;
+ // The single writer.
+ const Task* writer_;
+ // The list of Tasks waiting for this token to be released.
+ Task_list waiting_;
+};
+
+// In order to support tokens more reliably, we provide objects which
+// handle them using RAII.
+
+// RAII class to get a write lock on a token. This requires
+// specifying the task which is doing the lock.
+
+class Task_write_token
+{
+ public:
+ Task_write_token(Task_token* token, const Task* task)
+ : token_(token), task_(task)
+ { this->token_->add_writer(this->task_); }
+
+ ~Task_write_token()
+ { this->token_->remove_writer(this->task_); }
+
+ private:
+ Task_write_token(const Task_write_token&);
+ Task_write_token& operator=(const Task_write_token&);
+
+ Task_token* token_;
+ const Task* task_;
+};
+
+// RAII class for a blocker.
+
+class Task_block_token
+{
+ public:
+ // The blocker count must be incremented when the task is created.
+ // This object is created when the task is run, so we don't do
+ // anything in the constructor.
+ Task_block_token(Task_token* token)
+ : token_(token)
+ { gold_assert(this->token_->is_blocked()); }
+
+ ~Task_block_token()
+ { this->token_->remove_blocker(); }
+
+ private:
+ Task_block_token(const Task_block_token&);
+ Task_block_token& operator=(const Task_block_token&);
+
+ Task_token* token_;
+};
+
+// An object which implements an RAII lock for any object which
+// supports lock and unlock methods.
+
+template<typename Obj>
+class Task_lock_obj
+{
+ public:
+ Task_lock_obj(const Task* task, Obj* obj)
+ : task_(task), obj_(obj)
+ { this->obj_->lock(task); }
+
+ ~Task_lock_obj()
+ { this->obj_->unlock(this->task_); }
+
+ private:
+ Task_lock_obj(const Task_lock_obj&);
+ Task_lock_obj& operator=(const Task_lock_obj&);
+
+ const Task* task_;
+ Obj* obj_;
+};
+
+// A class which holds the set of Task_tokens which must be locked for
+// a Task. No Task requires more than four Task_tokens, so we set
+// that as a limit.
+
+class Task_locker
+{
+ public:
+ static const int max_task_count = 4;
+
+ Task_locker()
+ : count_(0)
+ { }
+
+ ~Task_locker()
+ { }
+
+ // Clear the locker.
+ void
+ clear()
+ { this->count_ = 0; }
+
+ // Add a token to the locker.
+ void
+ add(Task* t, Task_token* token)
+ {
+ gold_assert(this->count_ < max_task_count);
+ this->tokens_[this->count_] = token;
+ ++this->count_;
+ // A blocker will have been incremented when the task is created.
+ // A writer we need to lock now.
+ if (!token->is_blocker())
+ token->add_writer(t);
+ }
+
+ // Iterate over the tokens.
+
+ typedef Task_token** iterator;
+
+ iterator
+ begin()
+ { return &this->tokens_[0]; }
+
+ iterator
+ end()
+ { return &this->tokens_[this->count_]; }
+
+ private:
+ Task_locker(const Task_locker&);
+ Task_locker& operator=(const Task_locker&);
+
+ // The number of tokens.
+ int count_;
+ // The tokens.
+ Task_token* tokens_[max_task_count];
+};
+
+} // End namespace gold.
+
+#endif // !defined(GOLD_TOKEN_H)
#define GOLD_WORKQUEUE_INTERNAL_H
#include <queue>
+#include <csignal>
#include "gold-threads.h"
#include "workqueue.h"
class Workqueue_thread;
-// The Workqueue_runner abstract class. This is the interface used by
-// the general workqueue code to actually run a task.
+// The Workqueue_threader abstract class. This is the interface used
+// by the general workqueue code to manage threads.
-class Workqueue_runner
+class Workqueue_threader
{
public:
- Workqueue_runner(Workqueue* workqueue)
+ Workqueue_threader(Workqueue* workqueue)
: workqueue_(workqueue)
{ }
- virtual ~Workqueue_runner()
+ virtual ~Workqueue_threader()
{ }
- // Run a task. This is always called in the main thread.
- virtual void
- run(Task*, Task_locker*) = 0;
-
// Set the number of threads to use. This is ignored when not using
// threads.
virtual void
set_thread_count(int) = 0;
- protected:
- // This is called by an implementation when a task is completed.
- void completed(Task* t, Task_locker* tl)
- { this->workqueue_->completed(t, tl); }
+ // Return whether to cancel the current thread.
+ virtual bool
+ should_cancel_thread() = 0;
- Workqueue* get_workqueue() const
+ protected:
+ // Get the Workqueue.
+ Workqueue*
+ get_workqueue()
{ return this->workqueue_; }
private:
+ // The Workqueue.
Workqueue* workqueue_;
};
-// The threaded instantiation of Workqueue_runner.
+// The threaded instantiation of Workqueue_threader.
-class Workqueue_runner_threadpool : public Workqueue_runner
+class Workqueue_threader_threadpool : public Workqueue_threader
{
public:
- Workqueue_runner_threadpool(Workqueue* workqueue);
+ Workqueue_threader_threadpool(Workqueue*);
- ~Workqueue_runner_threadpool();
-
- void
- run(Task*, Task_locker*);
+ ~Workqueue_threader_threadpool();
+ // Set the thread count.
void
set_thread_count(int);
- private:
- // This class can not be copied.
- Workqueue_runner_threadpool(const Workqueue_runner_threadpool&);
- Workqueue_runner_threadpool& operator=(const Workqueue_runner_threadpool&);
-
- // Return the next Task and Task_locker to run. This returns false
- // if the calling thread should simply exit.
+ // Return whether to cancel a thread.
bool
- get_next(Task**, Task_locker**);
+ should_cancel_thread();
- // This is called when the thread completes a task.
+ // Process all tasks. This keeps running until told to cancel.
void
- thread_completed(Task*, Task_locker*);
-
- // The Workqueue_thread class calls functions from this and from the
- // parent Workqueue_runner.
- friend class Workqueue_thread;
-
- // An entry on the queue of tasks to run.
- typedef std::pair<Task*, Task_locker*> Task_queue_entry;
+ process(int thread_number)
+ { this->get_workqueue()->process(thread_number); }
- // A queue of tasks to run.
- typedef std::queue<Task_queue_entry> Task_queue;
+ private:
+ // This is set if we need to check the thread count.
+ volatile sig_atomic_t check_thread_count_;
- // The number of threads we want to create. This is only changed in
- // the main thread or when only one thread is running. This is set
- // to zero when all threads should exit.
- int desired_thread_count_;
- // A lock controlling access to the remaining fields.
+ // Lock for the remaining members.
Lock lock_;
- // The number of threads we have created.
- int actual_thread_count_;
- // The number of threads which are running a task.
- int running_thread_count_;
- // A queue of tasks to run.
- Task_queue task_queue_;
- // A condition variable which signals when the task_queue_ changed.
- Condvar task_queue_condvar_;
+ // The number of threads we want to create. This is set to zero
+ // when all threads should exit.
+ int desired_thread_count_;
+ // The number of threads currently running.
+ int threads_;
};
} // End namespace gold.
class Workqueue_thread
{
public:
- Workqueue_thread(Workqueue_runner_threadpool*);
+ Workqueue_thread(Workqueue_threader_threadpool*, int thread_number);
~Workqueue_thread();
static void*
thread_body(void*);
- // The main loop of the thread.
- void
- run();
-
// A pointer to the threadpool that this thread is part of.
- Workqueue_runner_threadpool* threadpool_;
+ Workqueue_threader_threadpool* threadpool_;
+ // The thread number.
+ int thread_number_;
// The thread ID.
pthread_t tid_;
};
// Create the thread in the constructor.
-Workqueue_thread::Workqueue_thread(Workqueue_runner_threadpool* threadpool)
- : threadpool_(threadpool)
+Workqueue_thread::Workqueue_thread(Workqueue_threader_threadpool* threadpool,
+ int thread_number)
+ : threadpool_(threadpool), thread_number_(thread_number)
{
pthread_attr_t attr;
int err = pthread_attr_init(&attr);
Workqueue_thread::thread_body(void* arg)
{
Workqueue_thread* pwt = reinterpret_cast<Workqueue_thread*>(arg);
- pwt->run();
+
+ pwt->threadpool_->process(pwt->thread_number_);
// Delete the thread object as we exit.
delete pwt;
return NULL;
}
-// This is the main loop of a worker thread. It picks up a new Task
-// and runs it.
-
-void
-Workqueue_thread::run()
-{
- Workqueue_runner_threadpool* threadpool = this->threadpool_;
- Workqueue* workqueue = threadpool->get_workqueue();
-
- while (true)
- {
- Task* t;
- Task_locker* tl;
- if (!threadpool->get_next(&t, &tl))
- return;
-
- gold_debug(DEBUG_TASK, "running task %s", t->name().c_str());
-
- t->run(workqueue);
- threadpool->thread_completed(t, tl);
- }
-}
-
-// Class Workqueue_runner_threadpool.
+// Class Workqueue_threader_threadpool.
// Constructor.
-Workqueue_runner_threadpool::Workqueue_runner_threadpool(Workqueue* workqueue)
- : Workqueue_runner(workqueue),
- desired_thread_count_(0),
+Workqueue_threader_threadpool::Workqueue_threader_threadpool(
+ Workqueue* workqueue)
+ : Workqueue_threader(workqueue),
+ check_thread_count_(0),
lock_(),
- actual_thread_count_(0),
- running_thread_count_(0),
- task_queue_(),
- task_queue_condvar_(this->lock_)
+ desired_thread_count_(1),
+ threads_(1)
{
}
// Destructor.
-Workqueue_runner_threadpool::~Workqueue_runner_threadpool()
+Workqueue_threader_threadpool::~Workqueue_threader_threadpool()
{
// Tell the threads to exit.
- Hold_lock hl(this->lock_);
- this->desired_thread_count_ = 0;
- this->task_queue_condvar_.broadcast();
-}
-
-// Run a task. This doesn't actually run the task: it pushes it on
-// the queue of tasks to run. This is always called in the main
-// thread.
-
-void
-Workqueue_runner_threadpool::run(Task* t, Task_locker* tl)
-{
- Hold_lock hl(this->lock_);
-
- // This is where we create threads as needed, subject to the limit
- // of the desired thread count.
- gold_assert(this->desired_thread_count_ > 0);
- gold_assert(this->actual_thread_count_ >= this->running_thread_count_);
- if (this->actual_thread_count_ == this->running_thread_count_
- && this->actual_thread_count_ < this->desired_thread_count_)
- {
- // Note that threads delete themselves when they exit, so we
- // don't keep pointers to them.
- new Workqueue_thread(this);
- ++this->actual_thread_count_;
- }
-
- this->task_queue_.push(std::make_pair(t, tl));
- this->task_queue_condvar_.signal();
+ this->get_workqueue()->set_thread_count(0);
}
-// Set the thread count. This is only called in the main thread, and
-// is only called when there are no threads running.
+// Set the thread count.
void
-Workqueue_runner_threadpool::set_thread_count(int thread_count)
-{
- gold_assert(this->running_thread_count_ <= 1);
- gold_assert(thread_count > 0);
- this->desired_thread_count_ = thread_count;
-}
-
-// Get the next task to run. This is always called by an instance of
-// Workqueue_thread, and is never called in the main thread. It
-// returns false if the calling thread should exit.
-
-bool
-Workqueue_runner_threadpool::get_next(Task** pt, Task_locker** ptl)
+Workqueue_threader_threadpool::set_thread_count(int thread_count)
{
- Hold_lock hl(this->lock_);
-
- // This is where we destroy threads, by telling them to exit.
- gold_assert(this->actual_thread_count_ > this->running_thread_count_);
- if (this->actual_thread_count_ > this->desired_thread_count_)
- {
- --this->actual_thread_count_;
- return false;
- }
+ int create;
+ {
+ Hold_lock hl(this->lock_);
- while (this->task_queue_.empty() && this->desired_thread_count_ > 0)
- {
- // Wait for a new task to become available.
- this->task_queue_condvar_.wait();
- }
+ this->desired_thread_count_ = thread_count;
+ create = this->desired_thread_count_ - this->threads_;
+ if (create < 0)
+ this->check_thread_count_ = 1;
+ }
- // Check whether we are exiting.
- if (this->desired_thread_count_ == 0)
+ if (create > 0)
{
- gold_assert(this->actual_thread_count_ > 0);
- --this->actual_thread_count_;
- return false;
+ for (int i = 0; i < create; ++i)
+ {
+ // Note that threads delete themselves when they exit, so we
+ // don't keep pointers to them.
+ new Workqueue_thread(this, this->threads_);
+ ++this->threads_;
+ }
}
-
- *pt = this->task_queue_.front().first;
- *ptl = this->task_queue_.front().second;
- this->task_queue_.pop();
-
- ++this->running_thread_count_;
-
- return true;
}
-// This is called when a thread completes its task.
+// Return whether the current thread should be cancelled.
-void
-Workqueue_runner_threadpool::thread_completed(Task* t, Task_locker* tl)
+bool
+Workqueue_threader_threadpool::should_cancel_thread()
{
+ // Fast exit without taking a lock.
+ if (!this->check_thread_count_)
+ return false;
+
{
Hold_lock hl(this->lock_);
- gold_assert(this->actual_thread_count_ > 0);
- gold_assert(this->running_thread_count_ > 0);
- --this->running_thread_count_;
+ if (this->threads_ > this->desired_thread_count_)
+ {
+ --this->threads_;
+ return true;
+ }
+ this->check_thread_count_ = 0;
}
- this->completed(t, tl);
+ return false;
}
} // End namespace gold.
#include "gold.h"
#include "debug.h"
+#include "options.h"
#include "workqueue.h"
#include "workqueue-internal.h"
namespace gold
{
-// Task_token methods.
+// Class Task_list.
-Task_token::Task_token()
- : is_blocker_(false), readers_(0), writer_(NULL)
-{
-}
-
-Task_token::~Task_token()
-{
- gold_assert(this->readers_ == 0 && this->writer_ == NULL);
-}
-
-bool
-Task_token::is_readable() const
-{
- gold_assert(!this->is_blocker_);
- return this->writer_ == NULL;
-}
-
-void
-Task_token::add_reader()
-{
- gold_assert(!this->is_blocker_);
- gold_assert(this->is_readable());
- ++this->readers_;
-}
-
-void
-Task_token::remove_reader()
-{
- gold_assert(!this->is_blocker_);
- gold_assert(this->readers_ > 0);
- --this->readers_;
-}
-
-bool
-Task_token::is_writable() const
-{
- gold_assert(!this->is_blocker_);
- return this->writer_ == NULL && this->readers_ == 0;
-}
-
-void
-Task_token::add_writer(const Task* t)
-{
- gold_assert(!this->is_blocker_);
- gold_assert(this->is_writable());
- this->writer_ = t;
-}
-
-void
-Task_token::remove_writer(const Task* t)
-{
- gold_assert(!this->is_blocker_);
- gold_assert(this->writer_ == t);
- this->writer_ = NULL;
-}
-
-bool
-Task_token::has_write_lock(const Task* t)
-{
- gold_assert(!this->is_blocker_);
- return this->writer_ == t;
-}
+// Add T to the end of the list.
-// For blockers, we just use the readers_ field.
-
-void
-Task_token::add_blocker()
+inline void
+Task_list::push_back(Task* t)
{
- if (this->readers_ == 0 && this->writer_ == NULL)
- this->is_blocker_ = true;
+ gold_assert(t->list_next() == NULL);
+ if (this->head_ == NULL)
+ {
+ this->head_ = t;
+ this->tail_ = t;
+ }
else
- gold_assert(this->is_blocker_);
- ++this->readers_;
-}
-
-bool
-Task_token::remove_blocker()
-{
- gold_assert(this->is_blocker_ && this->readers_ > 0);
- --this->readers_;
- return this->readers_ == 0;
-}
-
-bool
-Task_token::is_blocked() const
-{
- gold_assert(this->is_blocker_
- || (this->readers_ == 0 && this->writer_ == NULL));
- return this->readers_ > 0;
+ {
+ this->tail_->set_list_next(t);
+ this->tail_ = t;
+ }
}
-// The Task_block_token class.
+// Remove and return the first Task waiting for this lock to be
+// released.
-Task_block_token::Task_block_token(Task_token& token, Workqueue* workqueue)
- : token_(token), workqueue_(workqueue)
+inline Task*
+Task_list::pop_front()
{
- // We must increment the block count when the task is created and
- // put on the queue. This object is created when the task is run,
- // so we don't increment the block count here.
- gold_assert(this->token_.is_blocked());
-}
-
-Task_block_token::~Task_block_token()
-{
- if (this->token_.remove_blocker())
+ Task* ret = this->head_;
+ if (ret != NULL)
{
- // Tell the workqueue that a blocker was cleared. This is
- // always called in the main thread, so no locking is required.
- this->workqueue_->cleared_blocker();
+ if (ret == this->tail_)
+ {
+ gold_assert(ret->list_next() == NULL);
+ this->head_ = NULL;
+ this->tail_ = NULL;
+ }
+ else
+ {
+ this->head_ = ret->list_next();
+ gold_assert(this->head_ != NULL);
+ ret->clear_list_next();
+ }
}
+ return ret;
}
-// The simple single-threaded implementation of Workqueue_runner.
+// The simple single-threaded implementation of Workqueue_threader.
-class Workqueue_runner_single : public Workqueue_runner
+class Workqueue_threader_single : public Workqueue_threader
{
public:
- Workqueue_runner_single(Workqueue* workqueue)
- : Workqueue_runner(workqueue)
+ Workqueue_threader_single(Workqueue* workqueue)
+ : Workqueue_threader(workqueue)
{ }
- ~Workqueue_runner_single()
+ ~Workqueue_threader_single()
{ }
void
- run(Task*, Task_locker*);
+ set_thread_count(int thread_count)
+ { gold_assert(thread_count > 0); }
- void
- set_thread_count(int);
+ bool
+ should_cancel_thread()
+ { return false; }
};
-void
-Workqueue_runner_single::run(Task* t, Task_locker* tl)
-{
- t->run(this->get_workqueue());
- this->completed(t, tl);
-}
-
-void
-Workqueue_runner_single::set_thread_count(int thread_count)
-{
- gold_assert(thread_count > 0);
-}
-
// Workqueue methods.
Workqueue::Workqueue(const General_options& options)
- : tasks_lock_(),
+ : lock_(),
first_tasks_(),
tasks_(),
- completed_lock_(),
- completed_(),
running_(0),
- queued_(0),
- completed_condvar_(this->completed_lock_),
- cleared_blockers_(0),
- desired_thread_count_(1)
+ waiting_(0),
+ condvar_(this->lock_),
+ threader_(NULL)
{
bool threads = options.threads();
#ifndef ENABLE_THREADS
threads = false;
#endif
if (!threads)
- this->runner_ = new Workqueue_runner_single(this);
+ this->threader_ = new Workqueue_threader_single(this);
else
{
#ifdef ENABLE_THREADS
- this->runner_ = new Workqueue_runner_threadpool(this);
+ this->threader_ = new Workqueue_threader_threadpool(this);
#else
gold_unreachable();
#endif
Workqueue::~Workqueue()
{
- gold_assert(this->first_tasks_.empty());
- gold_assert(this->tasks_.empty());
- gold_assert(this->completed_.empty());
- gold_assert(this->running_ == 0);
+}
+
+// Add a task to the end of a specific queue, or put it on the list
+// waiting for a Token.
+
+void
+Workqueue::add_to_queue(Task_list* queue, Task* t)
+{
+ Hold_lock hl(this->lock_);
+
+ Task_token* token = t->is_runnable();
+ if (token != NULL)
+ {
+ token->add_waiting(t);
+ ++this->waiting_;
+ }
+ else
+ {
+ queue->push_back(t);
+ // Tell any waiting thread that there is work to do.
+ this->condvar_.signal();
+ }
}
// Add a task to the queue.
void
Workqueue::queue(Task* t)
{
- {
- Hold_lock hl(this->tasks_lock_);
- this->tasks_.push_back(t);
- }
- {
- Hold_lock hl(this->completed_lock_);
- ++this->queued_;
- }
+ this->add_to_queue(&this->tasks_, t);
}
// Add a task to the front of the queue.
void
Workqueue::queue_front(Task* t)
{
- {
- Hold_lock hl(this->tasks_lock_);
- this->first_tasks_.push_front(t);
- }
- {
- Hold_lock hl(this->completed_lock_);
- ++this->queued_;
- }
+ t->set_should_run_soon();
+ this->add_to_queue(&this->first_tasks_, t);
}
-// Clear the list of completed tasks. Return whether we cleared
-// anything. The completed_lock_ must be held when this is called.
+// Return whether to cancel the current thread.
-bool
-Workqueue::clear_completed()
+inline bool
+Workqueue::should_cancel_thread()
{
- if (this->completed_.empty())
- return false;
- do
- {
- delete this->completed_.front();
- this->completed_.pop_front();
- }
- while (!this->completed_.empty());
- return true;
+ return this->threader_->should_cancel_thread();
}
-// Find a runnable task in TASKS, which is non-empty. Return NULL if
-// none could be found. The tasks_lock_ must be held when this is
-// called. Sets ALL_BLOCKED if all non-runnable tasks are waiting on
-// a blocker.
+// Find a runnable task in TASKS. Return NULL if none could be found.
+// If we find a Task waiting for a Token, add it to the list for that
+// Token. The workqueue lock must be held when this is called.
Task*
-Workqueue::find_runnable(Task_list* tasks, bool* all_blocked)
+Workqueue::find_runnable_in_list(Task_list* tasks)
{
- Task* tlast = tasks->back();
- *all_blocked = true;
Task* t;
- do
+ while ((t = tasks->pop_front()) != NULL)
{
- t = tasks->front();
- tasks->pop_front();
+ Task_token* token = t->is_runnable();
+
+ if (token == NULL)
+ return t;
+
+ token->add_waiting(t);
+ ++this->waiting_;
+ }
+
+ // We couldn't find any runnable task.
+ return NULL;
+}
+
+// Find a runnable task. Return NULL if none could be found. The
+// workqueue lock must be held when this is called.
- Task::Is_runnable_type is_runnable = t->is_runnable(this);
- if (is_runnable == Task::IS_RUNNABLE)
+Task*
+Workqueue::find_runnable()
+{
+ Task* t = this->find_runnable_in_list(&this->first_tasks_);
+ if (t == NULL)
+ t = this->find_runnable_in_list(&this->tasks_);
+ return t;
+}
+
+// Find a runnable a task, and wait until we find one. Return NULL if
+// we should exit. The workqueue lock must be held when this is
+// called.
+
+Task*
+Workqueue::find_runnable_or_wait(int thread_number)
+{
+ Task* t = this->find_runnable();
+
+ while (t == NULL)
+ {
+ if (this->running_ == 0
+ && this->first_tasks_.empty()
+ && this->tasks_.empty())
{
- {
- Hold_lock hl(this->completed_lock_);
- --this->queued_;
- }
+ // Kick all the threads to make them exit.
+ this->condvar_.broadcast();
- return t;
+ gold_assert(this->waiting_ == 0);
+ return NULL;
}
- if (is_runnable != Task::IS_BLOCKED)
- *all_blocked = false;
+ if (this->should_cancel_thread())
+ return NULL;
+
+ gold_debug(DEBUG_TASK, "%3d sleeping", thread_number);
- tasks->push_back(t);
+ this->condvar_.wait();
+
+ gold_debug(DEBUG_TASK, "%3d awake", thread_number);
+
+ t = this->find_runnable();
}
- while (t != tlast);
- // We couldn't find any runnable task.
- return NULL;
+ return t;
}
-// Process all the tasks on the workqueue. This is the main loop in
-// the linker. Note that as we process tasks, new tasks will be
-// added.
+// Find and run tasks. If we can't find a runnable task, wait for one
+// to become available. If we run a task, and it frees up another
+// runnable task, then run that one too. This returns true if we
+// should look for another task, false if we are cancelling this
+// thread.
-void
-Workqueue::process()
+bool
+Workqueue::find_and_run_task(int thread_number)
{
- while (true)
+ Task* t;
+ Task_locker tl;
+
+ {
+ Hold_lock hl(this->lock_);
+
+ // Find a runnable task.
+ t = this->find_runnable_or_wait(thread_number);
+
+ if (t == NULL)
+ return false;
+
+ // Get the locks for the task. This must be called while we are
+ // still holding the Workqueue lock.
+ t->locks(&tl);
+
+ ++this->running_;
+ }
+
+ while (t != NULL)
{
- Task* t;
- bool empty;
- bool all_blocked;
+ gold_debug(DEBUG_TASK, "%3d running task %s", thread_number,
+ t->name().c_str());
- // Don't start more tasks than desired.
- {
- Hold_lock hl(this->completed_lock_);
+ t->run(this);
- this->clear_completed();
- while (this->running_ >= this->desired_thread_count_)
- {
- this->completed_condvar_.wait();
- this->clear_completed();
- }
- }
+ gold_debug(DEBUG_TASK, "%3d completed task %s", thread_number,
+ t->name().c_str());
+ Task* next;
{
- Hold_lock hl(this->tasks_lock_);
+ Hold_lock hl(this->lock_);
- bool first_empty;
- bool all_blocked_first;
- if (this->first_tasks_.empty())
- {
- t = NULL;
- empty = true;
- first_empty = true;
- all_blocked_first = false;
- }
- else
- {
- t = this->find_runnable(&this->first_tasks_, &all_blocked_first);
- empty = false;
- first_empty = false;
- }
+ --this->running_;
- if (t == NULL)
+ // Release the locks for the task. This must be done with the
+ // workqueue lock held. Get the next Task to run if any.
+ next = this->release_locks(t, &tl);
+
+ if (next == NULL)
+ next = this->find_runnable();
+
+ // If we have another Task to run, get the Locks. This must
+ // be called while we are still holding the Workqueue lock.
+ if (next != NULL)
{
- if (this->tasks_.empty())
- all_blocked = false;
- else
- {
- t = this->find_runnable(&this->tasks_, &all_blocked);
- if (!first_empty && !all_blocked_first)
- all_blocked = false;
- empty = false;
- }
+ tl.clear();
+ next->locks(&tl);
+
+ ++this->running_;
}
}
- // If T != NULL, it is a task we can run.
- // If T == NULL && empty, then there are no tasks waiting to
- // be run.
- // If T == NULL && !empty, then there tasks waiting to be
- // run, but they are waiting for something to unlock.
+ // We are done with this task.
+ delete t;
- if (t != NULL)
- this->run(t);
- else if (!empty)
- {
- {
- Hold_lock hl(this->completed_lock_);
-
- // There must be something for us to wait for, or we won't
- // be able to make progress.
- gold_assert(this->running_ > 0 || !this->completed_.empty());
-
- if (all_blocked)
- {
- this->cleared_blockers_ = 0;
- int queued = this->queued_;
- this->clear_completed();
- while (this->cleared_blockers_ == 0
- && queued == this->queued_)
- {
- if (this->running_ <= 0)
- {
- this->show_queued_tasks();
- gold_unreachable();
- }
- this->completed_condvar_.wait();
- this->clear_completed();
- }
- }
- else
- {
- if (this->running_ > 0)
- {
- // Wait for a task to finish.
- this->completed_condvar_.wait();
- }
- this->clear_completed();
- }
- }
- }
- else
- {
- {
- Hold_lock hl(this->completed_lock_);
-
- // If there are no running tasks, then we are done.
- if (this->running_ == 0)
- {
- this->clear_completed();
- return;
- }
-
- // Wait for a task to finish. Then we have to loop around
- // again in case it added any new tasks before finishing.
- this->completed_condvar_.wait();
- this->clear_completed();
- }
- }
+ t = next;
}
+
+ return true;
}
-// Run a task. This is always called in the main thread.
+// Handle the return value of release_locks, and get tasks ready to
+// run.
-void
-Workqueue::run(Task* t)
-{
- gold_debug(DEBUG_TASK, "starting task %s", t->name().c_str());
+// 1) If T is not runnable, queue it on the appropriate token.
- {
- Hold_lock hl(this->completed_lock_);
- ++this->running_;
- }
- this->runner_->run(t, t->locks(this));
-}
+// 2) Otherwise, T is runnable. If *PRET is not NULL, then we have
+// already decided which Task to run next. Add T to the list of
+// runnable tasks, and signal another thread.
-// This is called when a task is completed to put the locks on the
-// list to be released. We use a list because we only want the locks
-// to be released in the main thread.
+// 3) Otherwise, *PRET is NULL. If IS_BLOCKER is false, then T was
+// waiting on a write lock. We can grab that lock now, so we run T
+// now.
-void
-Workqueue::completed(Task* t, Task_locker* tl)
+// 4) Otherwise, IS_BLOCKER is true. If we should run T soon, then
+// run it now.
+
+// 5) Otherwise, check whether there are other tasks to run. If there
+// are, then we generally get a better ordering if we run those tasks
+// now, before T. A typical example is tasks waiting on the Dirsearch
+// blocker. We don't want to run those tasks right away just because
+// the Dirsearch was unblocked.
+
+// 6) Otherwise, there are no other tasks to run, so we might as well
+// run this one now.
+
+// This function must be called with the Workqueue lock held.
+
+// Return true if we set *PRET to T, false otherwise.
+
+bool
+Workqueue::return_or_queue(Task* t, bool is_blocker, Task** pret)
{
- gold_debug(DEBUG_TASK, "completed task %s", t->name().c_str());
+ Task_token* token = t->is_runnable();
- {
- Hold_lock hl(this->completed_lock_);
- gold_assert(this->running_ > 0);
- --this->running_;
- this->completed_.push_back(tl);
- this->completed_condvar_.signal();
- }
+ if (token != NULL)
+ {
+ token->add_waiting(t);
+ ++this->waiting_;
+ return false;
+ }
+
+ bool should_queue = false;
+ bool should_return = false;
+
+ if (*pret != NULL)
+ should_queue = true;
+ else if (!is_blocker)
+ should_return = true;
+ else if (t->should_run_soon())
+ should_return = true;
+ else if (!this->first_tasks_.empty() || !this->tasks_.empty())
+ should_queue = true;
+ else
+ should_return = true;
- delete t;
+ if (should_return)
+ {
+ gold_assert(*pret == NULL);
+ *pret = t;
+ return true;
+ }
+ else if (should_queue)
+ {
+ if (t->should_run_soon())
+ this->first_tasks_.push_back(t);
+ else
+ this->tasks_.push_back(t);
+ this->condvar_.signal();
+ return false;
+ }
+
+ gold_unreachable();
}
-// This is called when the last task for a blocker has completed.
-// This is always called in the main thread.
+// Release the locks associated with a Task. Return the first
+// runnable Task that we find. If we find more runnable tasks, add
+// them to the run queue and signal any other threads. This must be
+// called with the Workqueue lock held.
-void
-Workqueue::cleared_blocker()
+Task*
+Workqueue::release_locks(Task* t, Task_locker* tl)
{
- ++this->cleared_blockers_;
+ Task* ret = NULL;
+ for (Task_locker::iterator p = tl->begin(); p != tl->end(); ++p)
+ {
+ Task_token* token = *p;
+ if (token->is_blocker())
+ {
+ if (token->remove_blocker())
+ {
+ // The token has been unblocked. Every waiting Task may
+ // now be runnable.
+ Task* t;
+ while ((t = token->remove_first_waiting()) != NULL)
+ {
+ --this->waiting_;
+ this->return_or_queue(t, true, &ret);
+ }
+ }
+ }
+ else
+ {
+ token->remove_writer(t);
+
+ // One more waiting Task may now be runnable. If we are
+ // going to run it next, we can stop. Otherwise we need to
+ // move all the Tasks to the runnable queue, to avoid a
+ // potential deadlock if the locking status changes before
+ // we run the next thread.
+ Task* t;
+ while ((t = token->remove_first_waiting()) != NULL)
+ {
+ --this->waiting_;
+ if (this->return_or_queue(t, false, &ret))
+ break;
+ }
+ }
+ }
+ return ret;
}
-// Set the number of threads to use for the workqueue, if we are using
-// threads.
+// Process all the tasks on the workqueue. Keep going until the
+// workqueue is empty, or until we have been told to exit. This
+// function is called by all threads.
void
-Workqueue::set_thread_count(int threads)
+Workqueue::process(int thread_number)
{
- gold_assert(threads > 0);
- this->desired_thread_count_ = threads;
- this->runner_->set_thread_count(threads);
+ while (this->find_and_run_task(thread_number))
+ ;
}
-// Dump the list of queued tasks and their current state, for
-// debugging purposes.
+// Set the number of threads to use for the workqueue, if we are using
+// threads.
void
-Workqueue::show_queued_tasks()
+Workqueue::set_thread_count(int threads)
{
- fprintf(stderr, _("gold task queue:\n"));
- Hold_lock hl(this->tasks_lock_);
- for (Task_list::const_iterator p = this->tasks_.begin();
- p != this->tasks_.end();
- ++p)
- {
- fprintf(stderr, " %s ", (*p)->name().c_str());
- switch ((*p)->is_runnable(this))
- {
- case Task::IS_RUNNABLE:
- fprintf(stderr, "runnable");
- break;
- case Task::IS_BLOCKED:
- fprintf(stderr, "blocked");
- break;
- case Task::IS_LOCKED:
- fprintf(stderr, "locked");
- break;
- default:
- gold_unreachable();
- }
- putc('\n', stderr);
- }
+ Hold_lock hl(this->lock_);
+
+ this->threader_->set_thread_count(threads);
+ // Wake up all the threads, since something has changed.
+ this->condvar_.broadcast();
}
} // End namespace gold.
// driven from a work queue. This permits us to parallelize the
// linker where possible.
-// Task_token
-// A simple locking implementation to ensure proper task ordering.
-// Task_read_token, Task_write_token
-// Lock a Task_token for read or write.
-// Task_locker
-// Task locking using RAII.
-// Task
-// An abstract class for jobs to run.
-
#ifndef GOLD_WORKQUEUE_H
#define GOLD_WORKQUEUE_H
+#include <string>
+
#include "gold-threads.h"
-#include "fileread.h"
+#include "token.h"
namespace gold
{
class General_options;
-class Task;
class Workqueue;
-// Some tasks require access to shared data structures, such as the
-// symbol table. Some tasks must be executed in a particular order,
-// such as reading input file symbol tables--if we see foo.o -llib, we
-// have to read the symbols for foo.o before we read the ones for
-// -llib. To implement this safely and efficiently, we use tokens.
-// Task_tokens support shared read/exclusive write access to some
-// resource. Alternatively, they support blockers: blockers implement
-// the requirement that some set of tasks must complete before another
-// set of tasks can start. In such a case we increment the block
-// count when we create the task, and decrement it when the task
-// completes. Task_tokens are only manipulated by the main thread, so
-// they do not themselves require any locking.
-
-class Task_token
-{
- public:
- Task_token();
-
- ~Task_token();
-
- // A read/write token uses these methods.
-
- bool
- is_readable() const;
-
- void
- add_reader();
-
- void
- remove_reader();
-
- bool
- is_writable() const;
-
- void
- add_writer(const Task*);
-
- void
- remove_writer(const Task*);
-
- bool
- has_write_lock(const Task*);
-
- // A blocker token uses these methods.
-
- void
- add_blocker();
-
- // Returns true if block count drops to zero.
- bool
- remove_blocker();
-
- bool
- is_blocked() const;
-
- private:
- // It makes no sense to copy these.
- Task_token(const Task_token&);
- Task_token& operator=(const Task_token&);
-
- bool is_blocker_;
- int readers_;
- const Task* writer_;
-};
-
-// In order to support tokens more reliably, we provide objects which
-// handle them using RAII.
-
-class Task_read_token
-{
- public:
- Task_read_token(Task_token& token)
- : token_(token)
- { this->token_.add_reader(); }
-
- ~Task_read_token()
- { this->token_.remove_reader(); }
-
- private:
- Task_read_token(const Task_read_token&);
- Task_read_token& operator=(const Task_read_token&);
-
- Task_token& token_;
-};
-
-class Task_write_token
-{
- public:
- Task_write_token(Task_token& token, const Task* task)
- : token_(token), task_(task)
- { this->token_.add_writer(this->task_); }
-
- ~Task_write_token()
- { this->token_.remove_writer(this->task_); }
-
- private:
- Task_write_token(const Task_write_token&);
- Task_write_token& operator=(const Task_write_token&);
-
- Task_token& token_;
- const Task* task_;
-};
-
-class Task_block_token
-{
- public:
- // The blocker count must be incremented when the task is created.
- // This object is created when the task is run. When we unblock the
- // last task, we notify the workqueue.
- Task_block_token(Task_token& token, Workqueue* workqueue);
- ~Task_block_token();
-
- private:
- Task_block_token(const Task_block_token&);
- Task_block_token& operator=(const Task_block_token&);
-
- Task_token& token_;
- Workqueue* workqueue_;
-};
-
-// An object which implements an RAII lock for any object which
-// supports lock and unlock methods.
-
-template<typename Obj>
-class Task_lock_obj
-{
- public:
- Task_lock_obj(Obj& obj)
- : obj_(obj)
- { this->obj_.lock(); }
-
- ~Task_lock_obj()
- { this->obj_.unlock(); }
-
- private:
- Task_lock_obj(const Task_lock_obj&);
- Task_lock_obj& operator=(const Task_lock_obj&);
-
- Obj& obj_;
-};
-
-// An abstract class used to lock Task_tokens using RAII. A typical
-// implementation would simply have a set of members of type
-// Task_read_token, Task_write_token, and Task_block_token.
-
-class Task_locker
-{
- public:
- Task_locker()
- { }
-
- virtual ~Task_locker()
- { }
-};
-
-// A version of Task_locker which may be used for a single read lock.
-
-class Task_locker_read : public Task_locker
-{
- public:
- Task_locker_read(Task_token& token)
- : read_token_(token)
- { }
-
- private:
- Task_locker_read(const Task_locker_read&);
- Task_locker_read& operator=(const Task_locker_read&);
-
- Task_read_token read_token_;
-};
-
-// A version of Task_locker which may be used for a single write lock.
-
-class Task_locker_write : public Task_locker
-{
- public:
- Task_locker_write(Task_token& token, const Task* task)
- : write_token_(token, task)
- { }
-
- private:
- Task_locker_write(const Task_locker_write&);
- Task_locker_write& operator=(const Task_locker_write&);
-
- Task_write_token write_token_;
-};
-
-// A version of Task_locker which may be used for a single blocker
-// lock.
-
-class Task_locker_block : public Task_locker
-{
- public:
- Task_locker_block(Task_token& token, Workqueue* workqueue)
- : block_token_(token, workqueue)
- { }
-
- private:
- Task_locker_block(const Task_locker_block&);
- Task_locker_block& operator=(const Task_locker_block&);
-
- Task_block_token block_token_;
-};
-
-// A version of Task_locker which may be used to hold a lock on any
-// object which supports lock() and unlock() methods.
-
-template<typename Obj>
-class Task_locker_obj : public Task_locker
-{
- public:
- Task_locker_obj(Obj& obj)
- : obj_lock_(obj)
- { }
-
- private:
- Task_locker_obj(const Task_locker_obj&);
- Task_locker_obj& operator=(const Task_locker_obj&);
-
- Task_lock_obj<Obj> obj_lock_;
-};
-
// The superclass for tasks to be placed on the workqueue. Each
// specific task class will inherit from this one.
{
public:
Task()
- : name_()
+ : list_next_(NULL), name_(), should_run_soon_(false)
{ }
virtual ~Task()
{ }
- // Type returned by Is_runnable.
- enum Is_runnable_type
- {
- // Task is runnable.
- IS_RUNNABLE,
- // Task is waiting for a block to clear.
- IS_BLOCKED,
- // Task is not waiting for a block, but is not runnable--i.e., is
- // waiting for a lock.
- IS_LOCKED
- };
-
- // Return whether the task can be run now. This method is only
- // called from the main thread.
- virtual Is_runnable_type
- is_runnable(Workqueue*) = 0;
-
- // Return a pointer to a Task_locker which locks all the resources
- // required by the task. We delete the pointer when the task is
- // complete. This method can return NULL if no locks are required.
- // This method is only called from the main thread.
- virtual Task_locker*
- locks(Workqueue*) = 0;
+ // Check whether the Task can be run now. This method is only
+ // called with the workqueue lock held. If the Task can run, this
+ // returns NULL. Otherwise it returns a pointer to a token which
+ // must be released before the Task can run.
+ virtual Task_token*
+ is_runnable() = 0;
+
+ // Lock all the resources required by the Task, and store the locks
+ // in a Task_locker. This method does not need to do anything if no
+ // locks are required. This method is only called with the
+ // workqueue lock held.
+ virtual void
+ locks(Task_locker*) = 0;
// Run the task.
virtual void
run(Workqueue*) = 0;
+ // Return whether this task should run soon.
+ bool
+ should_run_soon() const
+ { return this->should_run_soon_; }
+
+ // Note that this task should run soon.
+ void
+ set_should_run_soon()
+ { this->should_run_soon_ = true; }
+
+ // Get the next Task on the list of Tasks. Called by Task_list.
+ Task*
+ list_next() const
+ { return this->list_next_; }
+
+ // Set the next Task on the list of Tasks. Called by Task_list.
+ void
+ set_list_next(Task* t)
+ {
+ gold_assert(this->list_next_ == NULL);
+ this->list_next_ = t;
+ }
+
+ // Clear the next Task on the list of Tasks. Called by Task_list.
+ void
+ clear_list_next()
+ { this->list_next_ = NULL; }
+
// Return the name of the Task. This is only used for debugging
// purposes.
const std::string&
get_name() const = 0;
private:
- // This task may not be copied.
+ // Tasks may not be copied.
Task(const Task&);
Task& operator=(const Task&);
+ // If this Task is on a list, this is a pointer to the next Task on
+ // the list. We use this simple list structure rather than building
+ // a container, in order to avoid memory allocation while holding
+ // the Workqueue lock.
+ Task* list_next_;
// Task name, for debugging purposes.
std::string name_;
+ // Whether this Task should be executed soon. This is used for
+ // Tasks which can be run after some data is read.
+ bool should_run_soon_;
};
-// A simple task which waits for a blocker and then runs a function.
+// An interface for Task_function. This is a convenience class to run
+// a single function.
class Task_function_runner
{
{ }
virtual void
- run(Workqueue*) = 0;
+ run(Workqueue*, const Task*) = 0;
};
+// A simple task which waits for a blocker and then runs a function.
+
class Task_function : public Task
{
public:
- // Both points should be allocated using new, and will be deleted
- // after the task runs.
+ // RUNNER and BLOCKER should be allocated using new, and will be
+ // deleted after the task runs.
Task_function(Task_function_runner* runner, Task_token* blocker,
const char* name)
: runner_(runner), blocker_(blocker), name_(name)
// The standard task methods.
// Wait until the task is unblocked.
- Is_runnable_type
- is_runnable(Workqueue*)
- { return this->blocker_->is_blocked() ? IS_BLOCKED : IS_RUNNABLE; }
+ Task_token*
+ is_runnable()
+ { return this->blocker_->is_blocked() ? this->blocker_ : NULL; }
// This type of task does not normally hold any locks.
- virtual Task_locker*
- locks(Workqueue*)
- { return NULL; }
+ virtual void
+ locks(Task_locker*)
+ { }
// Run the action.
void
run(Workqueue* workqueue)
- { this->runner_->run(workqueue); }
+ { this->runner_->run(workqueue, this); }
// The debugging name.
std::string
const char* name_;
};
-// The workqueue
+// The workqueue itself.
-class Workqueue_runner;
+class Workqueue_threader;
class Workqueue
{
void
queue_front(Task*);
- // Process all the tasks on the work queue.
+ // Process all the tasks on the work queue. This function runs
+ // until all tasks have completed. The argument is the thread
+ // number, used only for debugging.
void
- process();
+ process(int);
- // A complete set of blocking tasks has completed.
- void
- cleared_blocker();
-
- // Set the thread count.
+ // Set the desired thread count--the number of threads we want to
+ // have running.
void
set_thread_count(int);
Workqueue(const Workqueue&);
Workqueue& operator=(const Workqueue&);
- typedef std::list<Task*> Task_list;
-
- // Run a task.
+ // Add a task to a queue.
void
- run(Task*);
+ add_to_queue(Task_list* queue, Task* t);
- friend class Workqueue_runner;
+ // Find a runnable task, or wait for one.
+ Task*
+ find_runnable_or_wait(int thread_number);
// Find a runnable task.
Task*
- find_runnable(Task_list*, bool*);
+ find_runnable();
- // Add a lock to the completed queue.
- void
- completed(Task*, Task_locker*);
+ // Find a runnable task in a list.
+ Task*
+ find_runnable_in_list(Task_list*);
- // Clear the completed queue.
+ // Find an run a task.
bool
- clear_completed();
+ find_and_run_task(int);
- // Print the list of queued tasks.
- void
- show_queued_tasks();
+ // Release the locks for a Task. Return the next Task to run.
+ Task*
+ release_locks(Task*, Task_locker*);
- // How to run a task. Only accessed from main thread.
- Workqueue_runner* runner_;
+ // Store T into *PRET, or queue it as appropriate.
+ bool
+ return_or_queue(Task* t, bool is_blocker, Task** pret);
+
+ // Return whether to cancel this thread.
+ bool
+ should_cancel_thread();
- // Lock for access to tasks_ members.
- Lock tasks_lock_;
+ // Master Workqueue lock. This controls access to the following
+ // member variables.
+ Lock lock_;
// List of tasks to execute soon.
Task_list first_tasks_;
// List of tasks to execute after the ones in first_tasks_.
Task_list tasks_;
-
- // Lock for access to completed_, running_, and queued_.
- Lock completed_lock_;
- // List of Task_locker objects for main thread to free.
- std::list<Task_locker*> completed_;
// Number of tasks currently running.
int running_;
- // Number of tasks currently on queue (both first_tasks_ and
- // tasks_).
- int queued_;
- // Condition variable signalled when a new entry is added to completed_.
- Condvar completed_condvar_;
-
- // Number of blocker tokens which were fully cleared. Only accessed
- // from main thread.
- int cleared_blockers_;
-
- // The desired thread count. Only set by the main thread or by a
- // singleton thread. Only accessed from the main thread.
- int desired_thread_count_;
+ // Number of tasks waiting for a lock to release.
+ int waiting_;
+ // Condition variable associated with lock_. This is signalled when
+ // there may be a new Task to execute.
+ Condvar condvar_;
+
+ // The threading implementation. This is set at construction time
+ // and not changed thereafter.
+ Workqueue_threader* threader_;
};
} // End namespace gold.