| // token.h -- lock tokens for gold -*- C++ -*- | |
| // Copyright (C) 2006-2022 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 head of the list. | |
| void | |
| push_front(Task* t); | |
| // 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; | |
| } | |
| // Add some number of blockers to the token. | |
| void | |
| add_blockers(int c) | |
| { | |
| gold_assert(this->is_blocker_); | |
| this->blockers_ += c; | |
| 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); } | |
| // Add T to the front of the list of tasks waiting for this token to | |
| // be released. | |
| void | |
| add_waiting_front(Task* t) | |
| { this->waiting_.push_front(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) |