Rewrite workqueue. This version eliminates the master thread, and
reduces the amount of locking required to find a new thread to run.
This commit is contained in:
parent
7004837e8d
commit
17a1d0a9b2
@ -87,6 +87,7 @@ HFILES = \
|
||||
target-reloc.h \
|
||||
target-select.h \
|
||||
tls.h \
|
||||
token.h \
|
||||
workqueue.h \
|
||||
workqueue-internal.h
|
||||
|
||||
|
@ -344,6 +344,7 @@ HFILES = \
|
||||
target-reloc.h \
|
||||
target-select.h \
|
||||
tls.h \
|
||||
token.h \
|
||||
workqueue.h \
|
||||
workqueue-internal.h
|
||||
|
||||
|
@ -73,12 +73,12 @@ const char Archive::arfmag[2] = { '`', '\n' };
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ Archive::setup()
|
||||
}
|
||||
|
||||
// Opening the file locked it. Unlock it now.
|
||||
this->input_file_->file().unlock();
|
||||
this->input_file_->file().unlock(task);
|
||||
}
|
||||
|
||||
// Read the archive symbol map.
|
||||
@ -434,33 +434,19 @@ Add_archive_symbols::~Add_archive_symbols()
|
||||
// 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
|
||||
void
|
||||
Add_archive_symbols::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
@ -469,6 +455,8 @@ Add_archive_symbols::run(Workqueue*)
|
||||
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
|
||||
|
@ -31,6 +31,7 @@
|
||||
namespace gold
|
||||
{
|
||||
|
||||
class Task;
|
||||
class Input_file;
|
||||
class Input_objects;
|
||||
class Input_group;
|
||||
@ -64,7 +65,7 @@ class Archive
|
||||
|
||||
// Set up the archive: read the symbol map.
|
||||
void
|
||||
setup();
|
||||
setup(Task*);
|
||||
|
||||
// Get a reference to the underlying file.
|
||||
File_read&
|
||||
@ -73,19 +74,29 @@ class Archive
|
||||
|
||||
// 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
|
||||
@ -178,11 +189,11 @@ class Add_archive_symbols : public Task
|
||||
|
||||
// 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*);
|
||||
@ -196,8 +207,6 @@ class Add_archive_symbols : public Task
|
||||
}
|
||||
|
||||
private:
|
||||
class Add_archive_symbols_locker;
|
||||
|
||||
Symbol_table* symtab_;
|
||||
Layout* layout_;
|
||||
Input_objects* input_objects_;
|
||||
|
@ -38,35 +38,21 @@ namespace gold
|
||||
// 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
|
||||
void
|
||||
Allocate_commons_task::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
|
@ -45,11 +45,11 @@ class Allocate_commons_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -59,8 +59,6 @@ class Allocate_commons_task : public Task
|
||||
{ return "Allocate_commons_task"; }
|
||||
|
||||
private:
|
||||
class Allocate_commons_locker;
|
||||
|
||||
const General_options& options_;
|
||||
Symbol_table* symtab_;
|
||||
Layout* layout_;
|
||||
|
@ -28,6 +28,8 @@
|
||||
#include <dirent.h>
|
||||
|
||||
#include "gold-threads.h"
|
||||
#include "options.h"
|
||||
#include "workqueue.h"
|
||||
#include "dirsearch.h"
|
||||
|
||||
namespace
|
||||
@ -169,11 +171,11 @@ class Dir_cache_task : public gold::Task
|
||||
: 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*);
|
||||
@ -189,19 +191,19 @@ class Dir_cache_task : public gold::Task
|
||||
|
||||
// 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.
|
||||
|
@ -26,12 +26,14 @@
|
||||
#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.
|
||||
@ -40,7 +42,7 @@ class Dirsearch
|
||||
{
|
||||
public:
|
||||
Dirsearch()
|
||||
: directories_(NULL), token_()
|
||||
: directories_(NULL), token_(true)
|
||||
{ }
|
||||
|
||||
// Set the list of directories to search.
|
||||
@ -55,10 +57,10 @@ class Dirsearch
|
||||
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.
|
||||
|
@ -83,7 +83,7 @@ unsigned long long File_read::maximum_mapped_bytes;
|
||||
|
||||
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)
|
||||
@ -98,9 +98,9 @@ File_read::~File_read()
|
||||
// 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;
|
||||
@ -116,7 +116,7 @@ File_read::open(const std::string& name)
|
||||
this->size_ = s.st_size;
|
||||
}
|
||||
|
||||
++this->lock_count_;
|
||||
this->token_.add_writer(task);
|
||||
|
||||
return this->descriptor_ >= 0;
|
||||
}
|
||||
@ -124,46 +124,67 @@ File_read::open(const std::string& name)
|
||||
// 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;
|
||||
}
|
||||
|
||||
void
|
||||
File_read::lock()
|
||||
{
|
||||
++this->lock_count_;
|
||||
}
|
||||
// Release the file. This is called when we are done with the file in
|
||||
// a Task.
|
||||
|
||||
void
|
||||
File_read::unlock()
|
||||
File_read::release()
|
||||
{
|
||||
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->is_locked());
|
||||
|
||||
this->clear_views(false);
|
||||
}
|
||||
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::lock(const Task* task)
|
||||
{
|
||||
gold_assert(this->released_);
|
||||
this->token_.add_writer(task);
|
||||
this->released_ = 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
|
||||
@ -238,7 +259,8 @@ File_read::read(off_t start, off_t size, void* p) const
|
||||
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);
|
||||
|
||||
@ -301,14 +323,11 @@ File_read::find_or_make_view(off_t start, off_t size, bool cache)
|
||||
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());
|
||||
}
|
||||
@ -316,7 +335,6 @@ File_read::get_view(off_t start, off_t size, bool cache)
|
||||
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()));
|
||||
@ -388,13 +406,13 @@ File_view::~File_view()
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -408,7 +426,8 @@ Input_file::Input_file(const char* name, const unsigned char* contents,
|
||||
// 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;
|
||||
|
||||
@ -477,7 +496,7 @@ Input_file::open(const General_options& options, const Dirsearch& dirpath)
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "options.h"
|
||||
#include "token.h"
|
||||
|
||||
namespace gold
|
||||
{
|
||||
@ -45,41 +46,57 @@ class File_read
|
||||
{
|
||||
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
|
||||
@ -118,16 +135,16 @@ class File_read
|
||||
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.
|
||||
@ -227,8 +244,8 @@ class File_read
|
||||
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_
|
||||
@ -240,6 +257,8 @@ class File_read
|
||||
// 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.
|
||||
@ -288,12 +307,13 @@ class Input_file
|
||||
// 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*
|
||||
|
84
gold/gold.cc
84
gold/gold.cc
@ -89,7 +89,7 @@ class Middle_runner : public Task_function_runner
|
||||
{ }
|
||||
|
||||
void
|
||||
run(Workqueue*);
|
||||
run(Workqueue*, const Task*);
|
||||
|
||||
private:
|
||||
const General_options& options_;
|
||||
@ -99,9 +99,9 @@ class Middle_runner : public Task_function_runner
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ Middle_runner::run(Workqueue* 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)
|
||||
@ -131,10 +131,10 @@ queue_initial_tasks(const General_options& options,
|
||||
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;
|
||||
}
|
||||
@ -153,6 +153,7 @@ queue_initial_tasks(const General_options& options,
|
||||
|
||||
void
|
||||
queue_middle_tasks(const General_options& options,
|
||||
const Task* task,
|
||||
const Input_objects* input_objects,
|
||||
Symbol_table* symtab,
|
||||
Layout* layout,
|
||||
@ -187,7 +188,7 @@ queue_middle_tasks(const General_options& options,
|
||||
|
||||
// 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.
|
||||
@ -212,8 +213,8 @@ queue_middle_tasks(const General_options& options,
|
||||
// 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)
|
||||
@ -260,30 +261,20 @@ queue_final_tasks(const General_options& options,
|
||||
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())
|
||||
@ -307,12 +298,43 @@ queue_final_tasks(const General_options& options,
|
||||
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.
|
||||
|
@ -174,6 +174,7 @@ class Input_objects;
|
||||
class Symbol;
|
||||
class Symbol_table;
|
||||
class Layout;
|
||||
class Task;
|
||||
class Workqueue;
|
||||
class Output_file;
|
||||
template<int size, bool big_endian>
|
||||
@ -252,7 +253,7 @@ get_version_string();
|
||||
// Queue up the first set of tasks.
|
||||
extern void
|
||||
queue_initial_tasks(const General_options&,
|
||||
const Dirsearch&,
|
||||
Dirsearch&,
|
||||
const Command_line&,
|
||||
Workqueue*,
|
||||
Input_objects*,
|
||||
@ -262,6 +263,7 @@ queue_initial_tasks(const General_options&,
|
||||
// Queue up the middle set of tasks.
|
||||
extern void
|
||||
queue_middle_tasks(const General_options&,
|
||||
const Task*,
|
||||
const Input_objects*,
|
||||
Symbol_table*,
|
||||
Layout*,
|
||||
|
133
gold/layout.cc
133
gold/layout.cc
@ -44,10 +44,11 @@ namespace gold
|
||||
// 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.
|
||||
@ -72,7 +73,8 @@ Layout::Layout(const General_options& options)
|
||||
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.
|
||||
@ -653,13 +655,14 @@ Layout::find_first_load_seg()
|
||||
// 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);
|
||||
@ -730,7 +733,7 @@ Layout::finalize(const Input_objects* input_objects, Symbol_table* symtab)
|
||||
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);
|
||||
|
||||
@ -747,6 +750,12 @@ Layout::finalize(const Input_objects* input_objects, Symbol_table* symtab)
|
||||
// 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
|
||||
@ -1106,16 +1115,19 @@ Layout::set_section_offsets(off_t off, Layout::Section_offset_pass pass)
|
||||
|
||||
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;
|
||||
@ -1126,7 +1138,7 @@ Layout::set_section_offsets(off_t off, Layout::Section_offset_pass pass)
|
||||
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;
|
||||
@ -1152,7 +1164,8 @@ Layout::set_section_indexes(unsigned int shndx)
|
||||
// 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
|
||||
@ -1177,7 +1190,7 @@ Layout::count_local_symbols(const Input_objects* input_objects)
|
||||
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_);
|
||||
}
|
||||
}
|
||||
@ -1189,6 +1202,7 @@ Layout::count_local_symbols(const Input_objects* input_objects)
|
||||
void
|
||||
Layout::create_symtab_sections(const Input_objects* input_objects,
|
||||
Symbol_table* symtab,
|
||||
const Task* task,
|
||||
off_t* poff)
|
||||
{
|
||||
int symsize;
|
||||
@ -1262,7 +1276,7 @@ Layout::create_symtab_sections(const Input_objects* input_objects,
|
||||
== 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())
|
||||
@ -2004,16 +2018,21 @@ Layout::write_sections_after_input_sections(Output_file* of)
|
||||
// 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();
|
||||
@ -2049,36 +2068,20 @@ Layout::print_stats() const
|
||||
|
||||
// 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
|
||||
void
|
||||
Write_sections_task::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
@ -2093,18 +2096,18 @@ Write_sections_task::run(Workqueue*)
|
||||
|
||||
// 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.
|
||||
@ -2119,18 +2122,18 @@ Write_data_task::run(Workqueue*)
|
||||
|
||||
// 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.
|
||||
@ -2146,20 +2149,20 @@ Write_symbols_task::run(Workqueue*)
|
||||
|
||||
// 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.
|
||||
@ -2175,7 +2178,7 @@ Write_after_input_sections_task::run(Workqueue*)
|
||||
// Run the task--close the file.
|
||||
|
||||
void
|
||||
Close_task_runner::run(Workqueue*)
|
||||
Close_task_runner::run(Workqueue*, const Task*)
|
||||
{
|
||||
this->of_->close();
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ class Layout_task_runner : public Task_function_runner
|
||||
|
||||
// Run the operation.
|
||||
void
|
||||
run(Workqueue*);
|
||||
run(Workqueue*, const Task*);
|
||||
|
||||
private:
|
||||
Layout_task_runner(const Layout_task_runner&);
|
||||
@ -168,7 +168,12 @@ class Layout
|
||||
|
||||
// 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
|
||||
@ -283,11 +288,12 @@ class Layout
|
||||
// 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*
|
||||
@ -368,14 +374,14 @@ class Layout
|
||||
// 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);
|
||||
@ -472,6 +478,8 @@ class Layout
|
||||
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
|
||||
@ -492,11 +500,11 @@ class Write_sections_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -527,11 +535,11 @@ class Write_data_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -562,11 +570,11 @@ class Write_symbols_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -602,11 +610,11 @@ class Write_after_input_sections_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -616,8 +624,6 @@ class Write_after_input_sections_task : public Task
|
||||
{ return "Write_after_input_sections_task"; }
|
||||
|
||||
private:
|
||||
class Write_sections_locker;
|
||||
|
||||
Layout* layout_;
|
||||
Output_file* of_;
|
||||
Task_token* input_sections_blocker_;
|
||||
@ -635,7 +641,7 @@ class Close_task_runner : public Task_function_runner
|
||||
|
||||
// Run the operation.
|
||||
void
|
||||
run(Workqueue*);
|
||||
run(Workqueue*, const Task*);
|
||||
|
||||
private:
|
||||
Output_file* of_;
|
||||
|
@ -94,7 +94,7 @@ main(int argc, char** argv)
|
||||
&symtab, &layout);
|
||||
|
||||
// Run the main task processing loop.
|
||||
workqueue.process();
|
||||
workqueue.process(0);
|
||||
|
||||
if (command_line.options().print_stats())
|
||||
{
|
||||
|
@ -726,8 +726,8 @@ Sized_relobj<size, big_endian>::do_add_symbols(Symbol_table* symtab,
|
||||
}
|
||||
|
||||
// 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>
|
||||
@ -831,7 +831,7 @@ Sized_relobj<size, big_endian>::do_count_local_symbols(Stringpool* pool,
|
||||
|
||||
// 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>
|
||||
@ -987,9 +987,10 @@ Sized_relobj<size, big_endian>::local_value(unsigned int shndx,
|
||||
|
||||
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;
|
||||
|
@ -35,6 +35,7 @@ namespace gold
|
||||
{
|
||||
|
||||
class General_options;
|
||||
class Task;
|
||||
class Layout;
|
||||
class Output_section;
|
||||
class Output_file;
|
||||
@ -165,19 +166,29 @@ class Object
|
||||
|
||||
// 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.
|
||||
@ -322,7 +333,7 @@ class Object
|
||||
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_; }
|
||||
@ -508,7 +519,7 @@ class Relobj : public Object
|
||||
// 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.
|
||||
@ -1101,7 +1112,7 @@ class Sized_relobj : public Relobj
|
||||
// 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
|
||||
|
@ -53,6 +53,7 @@ target-reloc.h
|
||||
target-select.cc
|
||||
target-select.h
|
||||
tls.h
|
||||
token.h
|
||||
version.cc
|
||||
workqueue.cc
|
||||
workqueue.h
|
||||
|
235
gold/po/gold.pot
235
gold/po/gold.pot
@ -8,7 +8,7 @@ msgid ""
|
||||
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"
|
||||
@ -61,11 +61,11 @@ msgstr ""
|
||||
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 ""
|
||||
@ -114,7 +114,7 @@ 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 ""
|
||||
@ -218,42 +218,42 @@ 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 ""
|
||||
@ -265,12 +265,12 @@ 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 ""
|
||||
@ -336,50 +336,50 @@ msgid "pthread_cond_broadcast failed: %s"
|
||||
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 ""
|
||||
|
||||
@ -388,7 +388,7 @@ 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 ""
|
||||
@ -398,117 +398,117 @@ 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
|
||||
#, c-format
|
||||
msgid "unknown section index %u for local symbol %u"
|
||||
msgstr ""
|
||||
|
||||
#: object.cc:807
|
||||
#, c-format
|
||||
msgid "local symbol %u section index %u out of range"
|
||||
msgstr ""
|
||||
|
||||
#: object.cc:839
|
||||
#: object.cc:808
|
||||
#, c-format
|
||||
msgid "local symbol %u section name out of range: %u >= %u"
|
||||
msgstr ""
|
||||
|
||||
#: object.cc:1070
|
||||
#. FIXME: Handle SHN_XINDEX.
|
||||
#: object.cc:865
|
||||
#, c-format
|
||||
msgid "unknown section index %u for local symbol %u"
|
||||
msgstr ""
|
||||
|
||||
#: object.cc:874
|
||||
#, c-format
|
||||
msgid "local symbol %u section index %u out of range"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
@ -832,88 +832,107 @@ msgstr ""
|
||||
msgid "%s: -%c: %s\n"
|
||||
msgstr ""
|
||||
|
||||
#: options.h:372
|
||||
#: options.h:331
|
||||
#, c-format
|
||||
msgid "Unsupported argument to --compress-debug-symbols: %s"
|
||||
msgid "invalid optimization level: %s"
|
||||
msgstr ""
|
||||
|
||||
#: options.h:424
|
||||
#: options.h:377
|
||||
#, c-format
|
||||
msgid "%s: invalid argument to -Ttext: %s\n"
|
||||
msgid "unsupported argument to --compress-debug-sections: %s"
|
||||
msgstr ""
|
||||
|
||||
#: options.h:428
|
||||
#, c-format
|
||||
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 ""
|
||||
@ -948,48 +967,73 @@ 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 "
|
||||
@ -1024,26 +1068,17 @@ msgid ""
|
||||
"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 ""
|
||||
|
119
gold/readsyms.cc
119
gold/readsyms.cc
@ -39,9 +39,9 @@ namespace gold
|
||||
// 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
|
||||
{
|
||||
@ -56,17 +56,17 @@ 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*)
|
||||
@ -93,24 +93,23 @@ Read_symbols::~Read_symbols()
|
||||
// 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.
|
||||
@ -139,7 +138,7 @@ Read_symbols::do_read_symbols(Workqueue* workqueue)
|
||||
}
|
||||
|
||||
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.
|
||||
@ -190,15 +189,23 @@ Read_symbols::do_read_symbols(Workqueue* workqueue)
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -210,14 +217,15 @@ Read_symbols::do_read_symbols(Workqueue* workqueue)
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@ -251,6 +259,7 @@ Read_symbols::do_group(Workqueue* workqueue)
|
||||
|
||||
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)
|
||||
@ -258,7 +267,7 @@ Read_symbols::do_group(Workqueue* workqueue)
|
||||
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_,
|
||||
@ -319,34 +328,21 @@ Add_symbols::~Add_symbols()
|
||||
// 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
|
||||
void
|
||||
Add_symbols::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
@ -363,6 +359,7 @@ Add_symbols::run(Workqueue*)
|
||||
{
|
||||
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;
|
||||
@ -380,18 +377,18 @@ Finish_group::~Finish_group()
|
||||
|
||||
// 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.
|
||||
@ -408,7 +405,7 @@ Finish_group::run(Workqueue*)
|
||||
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_);
|
||||
|
@ -55,7 +55,7 @@ class Read_symbols : public Task
|
||||
// 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),
|
||||
@ -68,11 +68,11 @@ class Read_symbols : public Task
|
||||
|
||||
// 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*);
|
||||
@ -93,7 +93,7 @@ class Read_symbols : public Task
|
||||
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_;
|
||||
@ -123,11 +123,11 @@ class Add_symbols : public Task
|
||||
|
||||
// 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*);
|
||||
@ -137,8 +137,6 @@ class Add_symbols : public Task
|
||||
{ return "Add_symbols " + this->object_->name(); }
|
||||
|
||||
private:
|
||||
class Add_symbols_locker;
|
||||
|
||||
Input_objects* input_objects_;
|
||||
Symbol_table* symtab_;
|
||||
Layout* layout_;
|
||||
@ -199,11 +197,11 @@ class Finish_group : public Task
|
||||
|
||||
// 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*);
|
||||
|
@ -37,18 +37,18 @@ namespace gold
|
||||
// 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.
|
||||
@ -58,6 +58,8 @@ Read_relocs::run(Workqueue* workqueue)
|
||||
{
|
||||
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_));
|
||||
@ -78,37 +80,25 @@ Read_relocs::get_name() const
|
||||
// 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
|
||||
void
|
||||
Scan_relocs::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
@ -118,6 +108,7 @@ Scan_relocs::run(Workqueue*)
|
||||
{
|
||||
this->object_->scan_relocs(this->options_, this->symtab_, this->layout_,
|
||||
this->rd_);
|
||||
this->object_->release();
|
||||
delete this->rd_;
|
||||
this->rd_ = NULL;
|
||||
}
|
||||
@ -134,46 +125,30 @@ Scan_relocs::get_name() const
|
||||
|
||||
// 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
|
||||
void
|
||||
Relocate_task::locks(Task_locker* tl)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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.
|
||||
@ -183,6 +158,7 @@ Relocate_task::run(Workqueue*)
|
||||
{
|
||||
this->object_->relocate(this->options_, this->symtab_, this->layout_,
|
||||
this->of_);
|
||||
this->object_->release();
|
||||
}
|
||||
|
||||
// Return a debugging name for the task.
|
||||
@ -401,10 +377,10 @@ template<int size, bool big_endian>
|
||||
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)
|
||||
@ -521,7 +497,7 @@ Sized_relobj<size, big_endian>::relocate_sections(
|
||||
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;
|
||||
|
29
gold/reloc.h
29
gold/reloc.h
@ -23,6 +23,7 @@
|
||||
#ifndef GOLD_RELOC_H
|
||||
#define GOLD_RELOC_H
|
||||
|
||||
#include <vector>
|
||||
#include <byteswap.h>
|
||||
|
||||
#include "elfcpp.h"
|
||||
@ -69,11 +70,11 @@ class Read_relocs : public Task
|
||||
|
||||
// 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*);
|
||||
@ -107,11 +108,11 @@ class Scan_relocs : public Task
|
||||
|
||||
// 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*);
|
||||
@ -120,8 +121,6 @@ class Scan_relocs : public Task
|
||||
get_name() const;
|
||||
|
||||
private:
|
||||
class Scan_relocs_locker;
|
||||
|
||||
const General_options& options_;
|
||||
Symbol_table* symtab_;
|
||||
Layout* layout_;
|
||||
@ -148,11 +147,11 @@ class Relocate_task : public Task
|
||||
|
||||
// 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*);
|
||||
@ -161,8 +160,6 @@ class Relocate_task : public Task
|
||||
get_name() const;
|
||||
|
||||
private:
|
||||
class Relocate_locker;
|
||||
|
||||
const General_options& options_;
|
||||
const Symbol_table* symtab_;
|
||||
const Layout* layout_;
|
||||
|
@ -797,19 +797,17 @@ class Script_unblock : 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*)
|
||||
@ -826,8 +824,8 @@ class Script_unblock : public Task
|
||||
|
||||
// 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
|
||||
{
|
||||
@ -927,7 +925,7 @@ 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,
|
||||
@ -956,7 +954,7 @@ read_input_script(Workqueue* workqueue, const General_options& options,
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
@ -970,7 +968,7 @@ read_input_script(Workqueue* workqueue, const General_options& options,
|
||||
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,
|
||||
@ -992,17 +990,22 @@ read_commandline_script(const char* filename, Command_line* cmdline)
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -1014,11 +1017,11 @@ read_commandline_script(const char* filename, Command_line* cmdline)
|
||||
&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;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ class Workqueue;
|
||||
|
||||
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);
|
||||
|
@ -1392,8 +1392,8 @@ Symbol_table::set_dynsym_indexes(const Target* target,
|
||||
// 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;
|
||||
@ -1426,7 +1426,7 @@ Symbol_table::finalize(unsigned int index, off_t off, off_t dynoff,
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -1945,7 +1945,8 @@ struct Odr_violation_compare
|
||||
// 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();
|
||||
@ -1961,14 +1962,14 @@ Symbol_table::detect_odr_violations(const char* output_file_name) const
|
||||
++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);
|
||||
}
|
||||
@ -2003,7 +2004,7 @@ Warnings::add_warning(Symbol_table* symtab, const char* name, Object* obj,
|
||||
// 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();
|
||||
@ -2023,7 +2024,7 @@ Warnings::note_warnings(Symbol_table* symtab)
|
||||
// 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,
|
||||
|
@ -905,7 +905,7 @@ class Warnings
|
||||
// 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>
|
||||
@ -1078,7 +1078,7 @@ class Symbol_table
|
||||
// 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.
|
||||
@ -1102,7 +1102,7 @@ class Symbol_table
|
||||
// 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.
|
||||
|
@ -39,22 +39,24 @@ bool
|
||||
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;
|
||||
}
|
||||
|
||||
|
316
gold/token.h
Normal file
316
gold/token.h
Normal file
@ -0,0 +1,316 @@
|
||||
// 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)
|
@ -24,6 +24,7 @@
|
||||
#define GOLD_WORKQUEUE_INTERNAL_H
|
||||
|
||||
#include <queue>
|
||||
#include <csignal>
|
||||
|
||||
#include "gold-threads.h"
|
||||
#include "workqueue.h"
|
||||
@ -36,92 +37,71 @@ namespace gold
|
||||
|
||||
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*);
|
||||
process(int thread_number)
|
||||
{ this->get_workqueue()->process(thread_number); }
|
||||
|
||||
// The Workqueue_thread class calls functions from this and from the
|
||||
// parent Workqueue_runner.
|
||||
friend class Workqueue_thread;
|
||||
private:
|
||||
// This is set if we need to check the thread count.
|
||||
volatile sig_atomic_t check_thread_count_;
|
||||
|
||||
// An entry on the queue of tasks to run.
|
||||
typedef std::pair<Task*, Task_locker*> Task_queue_entry;
|
||||
|
||||
// A queue of tasks to run.
|
||||
typedef std::queue<Task_queue_entry> Task_queue;
|
||||
|
||||
// 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.
|
||||
|
@ -44,7 +44,7 @@ namespace gold
|
||||
class Workqueue_thread
|
||||
{
|
||||
public:
|
||||
Workqueue_thread(Workqueue_runner_threadpool*);
|
||||
Workqueue_thread(Workqueue_threader_threadpool*, int thread_number);
|
||||
|
||||
~Workqueue_thread();
|
||||
|
||||
@ -62,20 +62,19 @@ class 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);
|
||||
@ -114,7 +113,8 @@ void*
|
||||
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;
|
||||
@ -122,144 +122,75 @@ Workqueue_thread::thread_body(void* arg)
|
||||
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();
|
||||
this->get_workqueue()->set_thread_count(0);
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Set the thread count.
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Set the thread count. This is only called in the main thread, and
|
||||
// is only called when there are no threads running.
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
while (this->task_queue_.empty() && this->desired_thread_count_ > 0)
|
||||
{
|
||||
// Wait for a new task to become available.
|
||||
this->task_queue_condvar_.wait();
|
||||
}
|
||||
|
||||
// Check whether we are exiting.
|
||||
if (this->desired_thread_count_ == 0)
|
||||
{
|
||||
gold_assert(this->actual_thread_count_ > 0);
|
||||
--this->actual_thread_count_;
|
||||
return false;
|
||||
}
|
||||
|
||||
*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.
|
||||
|
||||
void
|
||||
Workqueue_runner_threadpool::thread_completed(Task* t, Task_locker* tl)
|
||||
Workqueue_threader_threadpool::set_thread_count(int thread_count)
|
||||
{
|
||||
int create;
|
||||
{
|
||||
Hold_lock hl(this->lock_);
|
||||
gold_assert(this->actual_thread_count_ > 0);
|
||||
gold_assert(this->running_thread_count_ > 0);
|
||||
--this->running_thread_count_;
|
||||
|
||||
this->desired_thread_count_ = thread_count;
|
||||
create = this->desired_thread_count_ - this->threads_;
|
||||
if (create < 0)
|
||||
this->check_thread_count_ = 1;
|
||||
}
|
||||
|
||||
this->completed(t, tl);
|
||||
if (create > 0)
|
||||
{
|
||||
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_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether the current thread should be cancelled.
|
||||
|
||||
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_);
|
||||
if (this->threads_ > this->desired_thread_count_)
|
||||
{
|
||||
--this->threads_;
|
||||
return true;
|
||||
}
|
||||
this->check_thread_count_ = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End namespace gold.
|
||||
|
@ -23,181 +23,99 @@
|
||||
#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)
|
||||
// Add T to the end of the list.
|
||||
|
||||
inline void
|
||||
Task_list::push_back(Task* t)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// For blockers, we just use the readers_ field.
|
||||
|
||||
void
|
||||
Task_token::add_blocker()
|
||||
{
|
||||
if (this->readers_ == 0 && this->writer_ == NULL)
|
||||
this->is_blocker_ = true;
|
||||
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;
|
||||
}
|
||||
|
||||
// The Task_block_token class.
|
||||
|
||||
Task_block_token::Task_block_token(Task_token& token, Workqueue* workqueue)
|
||||
: token_(token), workqueue_(workqueue)
|
||||
{
|
||||
// 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())
|
||||
gold_assert(t->list_next() == NULL);
|
||||
if (this->head_ == 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();
|
||||
this->head_ = t;
|
||||
this->tail_ = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->tail_->set_list_next(t);
|
||||
this->tail_ = t;
|
||||
}
|
||||
}
|
||||
|
||||
// The simple single-threaded implementation of Workqueue_runner.
|
||||
// Remove and return the first Task waiting for this lock to be
|
||||
// released.
|
||||
|
||||
class Workqueue_runner_single : public Workqueue_runner
|
||||
inline Task*
|
||||
Task_list::pop_front()
|
||||
{
|
||||
Task* ret = this->head_;
|
||||
if (ret != NULL)
|
||||
{
|
||||
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_threader.
|
||||
|
||||
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
|
||||
@ -206,10 +124,28 @@ Workqueue::Workqueue(const General_options& options)
|
||||
|
||||
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.
|
||||
@ -217,14 +153,7 @@ Workqueue::~Workqueue()
|
||||
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.
|
||||
@ -232,236 +161,291 @@ Workqueue::queue(Task* t)
|
||||
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();
|
||||
|
||||
Task::Is_runnable_type is_runnable = t->is_runnable(this);
|
||||
if (is_runnable == Task::IS_RUNNABLE)
|
||||
{
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
--this->queued_;
|
||||
}
|
||||
if (token == NULL)
|
||||
return t;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
if (is_runnable != Task::IS_BLOCKED)
|
||||
*all_blocked = false;
|
||||
|
||||
tasks->push_back(t);
|
||||
token->add_waiting(t);
|
||||
++this->waiting_;
|
||||
}
|
||||
while (t != tlast);
|
||||
|
||||
// We couldn't find any runnable task.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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 a runnable task. Return NULL if none could be found. The
|
||||
// workqueue lock must be held when this is called.
|
||||
|
||||
void
|
||||
Workqueue::process()
|
||||
Task*
|
||||
Workqueue::find_runnable()
|
||||
{
|
||||
while (true)
|
||||
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)
|
||||
{
|
||||
Task* t;
|
||||
bool empty;
|
||||
bool all_blocked;
|
||||
|
||||
// Don't start more tasks than desired.
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
|
||||
this->clear_completed();
|
||||
while (this->running_ >= this->desired_thread_count_)
|
||||
{
|
||||
this->completed_condvar_.wait();
|
||||
this->clear_completed();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Hold_lock hl(this->tasks_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;
|
||||
}
|
||||
|
||||
if (t == 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
if (t != NULL)
|
||||
this->run(t);
|
||||
else if (!empty)
|
||||
if (this->running_ == 0
|
||||
&& this->first_tasks_.empty()
|
||||
&& this->tasks_.empty())
|
||||
{
|
||||
// Kick all the threads to make them exit.
|
||||
this->condvar_.broadcast();
|
||||
|
||||
gold_assert(this->waiting_ == 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (this->should_cancel_thread())
|
||||
return NULL;
|
||||
|
||||
gold_debug(DEBUG_TASK, "%3d sleeping", thread_number);
|
||||
|
||||
this->condvar_.wait();
|
||||
|
||||
gold_debug(DEBUG_TASK, "%3d awake", thread_number);
|
||||
|
||||
t = this->find_runnable();
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
bool
|
||||
Workqueue::find_and_run_task(int thread_number)
|
||||
{
|
||||
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)
|
||||
{
|
||||
gold_debug(DEBUG_TASK, "%3d running task %s", thread_number,
|
||||
t->name().c_str());
|
||||
|
||||
t->run(this);
|
||||
|
||||
gold_debug(DEBUG_TASK, "%3d completed task %s", thread_number,
|
||||
t->name().c_str());
|
||||
|
||||
Task* next;
|
||||
{
|
||||
Hold_lock hl(this->lock_);
|
||||
|
||||
--this->running_;
|
||||
|
||||
// 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)
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
tl.clear();
|
||||
next->locks(&tl);
|
||||
|
||||
// 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();
|
||||
}
|
||||
++this->running_;
|
||||
}
|
||||
}
|
||||
|
||||
// We are done with this task.
|
||||
delete t;
|
||||
|
||||
t = next;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle the return value of release_locks, and get tasks ready to
|
||||
// run.
|
||||
|
||||
// 1) If T is not runnable, queue it on the appropriate token.
|
||||
|
||||
// 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.
|
||||
|
||||
// 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.
|
||||
|
||||
// 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)
|
||||
{
|
||||
Task_token* token = t->is_runnable();
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
Task*
|
||||
Workqueue::release_locks(Task* t, Task_locker* tl)
|
||||
{
|
||||
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
|
||||
{
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
token->remove_writer(t);
|
||||
|
||||
// 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();
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Run a task. This is always called in the main thread.
|
||||
// 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::run(Task* t)
|
||||
Workqueue::process(int thread_number)
|
||||
{
|
||||
gold_debug(DEBUG_TASK, "starting task %s", t->name().c_str());
|
||||
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
++this->running_;
|
||||
}
|
||||
this->runner_->run(t, t->locks(this));
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
void
|
||||
Workqueue::completed(Task* t, Task_locker* tl)
|
||||
{
|
||||
gold_debug(DEBUG_TASK, "completed task %s", t->name().c_str());
|
||||
|
||||
{
|
||||
Hold_lock hl(this->completed_lock_);
|
||||
gold_assert(this->running_ > 0);
|
||||
--this->running_;
|
||||
this->completed_.push_back(tl);
|
||||
this->completed_condvar_.signal();
|
||||
}
|
||||
|
||||
delete t;
|
||||
}
|
||||
|
||||
// This is called when the last task for a blocker has completed.
|
||||
// This is always called in the main thread.
|
||||
|
||||
void
|
||||
Workqueue::cleared_blocker()
|
||||
{
|
||||
++this->cleared_blockers_;
|
||||
while (this->find_and_run_task(thread_number))
|
||||
;
|
||||
}
|
||||
|
||||
// Set the number of threads to use for the workqueue, if we are using
|
||||
@ -470,40 +454,11 @@ Workqueue::cleared_blocker()
|
||||
void
|
||||
Workqueue::set_thread_count(int threads)
|
||||
{
|
||||
gold_assert(threads > 0);
|
||||
this->desired_thread_count_ = threads;
|
||||
this->runner_->set_thread_count(threads);
|
||||
}
|
||||
Hold_lock hl(this->lock_);
|
||||
|
||||
// Dump the list of queued tasks and their current state, for
|
||||
// debugging purposes.
|
||||
|
||||
void
|
||||
Workqueue::show_queued_tasks()
|
||||
{
|
||||
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);
|
||||
}
|
||||
this->threader_->set_thread_count(threads);
|
||||
// Wake up all the threads, since something has changed.
|
||||
this->condvar_.broadcast();
|
||||
}
|
||||
|
||||
} // End namespace gold.
|
||||
|
419
gold/workqueue.h
419
gold/workqueue.h
@ -24,250 +24,20 @@
|
||||
// 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.
|
||||
|
||||
@ -275,39 +45,57 @@ class Task
|
||||
{
|
||||
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
|
||||
};
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
// 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&
|
||||
@ -325,15 +113,24 @@ class Task
|
||||
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
|
||||
{
|
||||
@ -342,14 +139,16 @@ 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)
|
||||
@ -364,19 +163,19 @@ class Task_function : public Task
|
||||
// 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
|
||||
@ -392,9 +191,9 @@ class Task_function : public Task
|
||||
const char* name_;
|
||||
};
|
||||
|
||||
// The workqueue
|
||||
// The workqueue itself.
|
||||
|
||||
class Workqueue_runner;
|
||||
class Workqueue_threader;
|
||||
|
||||
class Workqueue
|
||||
{
|
||||
@ -411,15 +210,14 @@ 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);
|
||||
|
||||
@ -428,59 +226,56 @@ class Workqueue
|
||||
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);
|
||||
|
||||
// Lock for access to tasks_ members.
|
||||
Lock tasks_lock_;
|
||||
// Return whether to cancel this thread.
|
||||
bool
|
||||
should_cancel_thread();
|
||||
|
||||
// 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 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_;
|
||||
|
||||
// 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_;
|
||||
// The threading implementation. This is set at construction time
|
||||
// and not changed thereafter.
|
||||
Workqueue_threader* threader_;
|
||||
};
|
||||
|
||||
} // End namespace gold.
|
||||
|
Loading…
x
Reference in New Issue
Block a user