From b37f1aa6398065be806e6605a023189685669885 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Wed, 15 Feb 2017 03:55:15 +0200 Subject: Implement parallel match --- build2/target | 601 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 371 insertions(+), 230 deletions(-) (limited to 'build2/target') diff --git a/build2/target b/build2/target index 27d6947..563e32f 100644 --- a/build2/target +++ b/build2/target @@ -23,11 +23,14 @@ namespace build2 { + class rule; class scope; class target; - target& - search (prerequisite&); // From . + extern size_t current_on; // From . + + const target& + search (const prerequisite&); // From . // Target state. // @@ -106,7 +109,7 @@ namespace build2 // struct group_view { - target* const* members; // NULL means not yet known. + const target* const* members; // NULL means not yet known. size_t count; }; @@ -182,7 +185,7 @@ namespace build2 // special target_state::group state. You would normally also use the // group_recipe for group members. // - const_ptr group = nullptr; + const target* group = nullptr; // What has been described above is a "normal" group. That is, there is @@ -249,7 +252,7 @@ namespace build2 // resolve_group_members() from . // virtual group_view - group_members (action_type); + group_members (action_type) const; // Note that the returned key "tracks" the target (except for the // extension). @@ -293,35 +296,47 @@ namespace build2 // Prerequisites. // + // We use an atomic-empty semantics that allows one to "swap in" a set of + // prerequisites if none were specified. This is used to implement + // "synthesized" dependencies. + // public: - using prerequisites_type = small_vector; - prerequisites_type prerequisites; + using prerequisites_type = build2::prerequisites; - // Targets to which prerequisites resolve for this recipe. Note - // that unlike prerequisite::target, these can be resolved to - // group members. NULL means the target should be skipped (or - // the rule may simply not add such a target to the list). - // - // Note also that it is possible the target can vary from - // action to action, just like recipes. We don't need to keep - // track of the action here since the targets will be updated - // if the recipe is updated, normally as part of rule::apply(). - // - // Note that the recipe may modify (mutable) this list. + const prerequisites_type& + prerequisites () const; + + // Swap-in a list of prerequisites. Return false if unsuccessful (i.e., + // some beat us to it). Note that it can be called on const target. // - using prerequisite_targets_type = vector; - mutable prerequisite_targets_type prerequisite_targets; + bool + prerequisites (prerequisites_type&&) const; - // Check if there are any prerequisites, taking into account - // group prerequisites. + // Check if there are any prerequisites, taking into account group + // prerequisites. // bool has_prerequisites () const { - return !prerequisites.empty () || - (group != nullptr && !group->prerequisites.empty ()); + return !prerequisites ().empty () || + (group != nullptr && !group->prerequisites ().empty ()); } + private: + friend class parser; + + // Note that the state is also used to synchronize the prerequisites + // value so we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + atomic prerequisites_state_ {0}; + prerequisites_type prerequisites_; + + static const prerequisites_type empty_prerequisites_; + // Target-specific variables. // public: @@ -391,7 +406,7 @@ namespace build2 // variable assignment, dependency extraction, etc) is called implied. // // The implied flag should only be cleared during the load phase via the - // target_set::insert(). + // MT-safe target_set::insert(). // public: bool implied; @@ -399,68 +414,136 @@ namespace build2 // Target state. // public: - // Atomic task count that is used during execution to (atomically) track a - // subset of the target's state as well as the number of its sub-tasks - // (execution of prerequisites). + // Atomic task count that is used during match and execution to track the + // target's "meta-state" as well as the number of its sub-tasks (e.g., + // busy+1, busy+2, and so on, for instance, number of prerequisites + // being matched or executed). + // + // For each operation in a meta-operation batch (current_on) we have a + // "band" of counts, [touched, executed], that represent the target + // meta-state. Once the next operation is started, this band "moves" thus + // automatically resetting the target to "not yet touched" state for this + // operation. // - // The count starts unexecuted then transitions executing. Executing - // transitions (via a decrement) to executed. Once it is executed, then - // state_ becomes immutable. + // For match we have a further complication in that we may re-match the + // target and override with a "stronger" recipe thus re-setting the state + // from, say, applied back to touched. // // The target is said to be synchronized (in this thread) if we have - // either observed the task count to reach count_executed or we have - // successfully changed it (via compare_exchange) to count_executing. - // If the target is synchronized, then we can access and modify (second - // case) its state, mtime, etc. + // either observed the task count to reach applied or executed or we have + // successfully changed it (via compare_exchange) to locked or busy. If + // the target is synchronized, then we can access and modify (second case) + // its state etc. // - static const size_t count_unexecuted = 0; - static const size_t count_executed = 1; - static const size_t count_executing = 2; + static const size_t offset_touched = 1; // Has been locked. + static const size_t offset_matched = 2; // Rule has been matched. + static const size_t offset_applied = 3; // Rule has been applied. + static const size_t offset_executed = 4; // Recipe has been executed. + static const size_t offset_locked = 5; // Fast (spin) lock. + static const size_t offset_busy = 6; // Slow (wait) lock. - mutable atomic_count task_count; + static size_t count_base () {return 4 * (current_on - 1);} - // Return the "stapshot" of the target state. That is, unless the target - // has been executed, its state can change asynchronously. If fail is - // true then translate target_state::failed to the failed exception. + static size_t count_touched () {return offset_touched + count_base ();} + static size_t count_matched () {return offset_matched + count_base ();} + static size_t count_applied () {return offset_applied + count_base ();} + static size_t count_executed () {return offset_executed + count_base ();} + static size_t count_locked () {return offset_locked + count_base ();} + static size_t count_busy () {return offset_busy + count_base ();} + + mutable atomic_count task_count {0}; // Start offset_touched - 1. + + // This function can only be called during match if we have observed + // (synchronization-wise) that the this target has been matched (i.e., + // the rule has been applied). // target_state - atomic_state (bool fail = true) const; + matched_state (action a, bool fail = true) const; - // During execution this function can only be called if we have observed + // This function can only be called during execution if we have observed // (synchronization-wise) that this target has been executed. // target_state - synchronized_state (bool fail = true) const; + executed_state (bool fail = true) const; // Number of direct targets that depend on this target in the current - // action. It is incremented during the match phase and then decremented - // during execution, before running the recipe. As a result, the recipe - // can detect the last chance (i.e., last dependent) to execute the - // command (see also the first/last execution modes in ). + // operation. It is incremented during match and then decremented during + // execution, before running the recipe. As a result, the recipe can + // detect the last chance (i.e., last dependent) to execute the command + // (see also the first/last execution modes in ). // - // Note that setting a new recipe (which happens when we match the rule - // and which in turn is triggered by the first dependent) clears this - // counter. However, if the previous action was the same as the current, - // then the existing recipe is reused. In this case, however, the counter - // should have been decremented to 0 naturally, as part of the previous - // action execution. - // - atomic_count dependents; + mutable atomic_count dependents; protected: - friend target_state execute_impl (action, target&) noexcept; - - target_state state_ = target_state::unknown; - // Return fail-untranslated (but group-translated) state assuming the - // target is synchronized. + // target is executed and synchronized. // target_state state () const; - // Auxilary data storage. + // Version that should be used during search & match after the target has + // been matched for this action (see the recipe override). + // + target_state + state (action a) const; + + // Return true if the state comes from the group. Target must be at least + // matched. + // + bool + group_state () const; + + public: + target_state state_; // Raw state, normally not accessed directly. + + // Recipe. // public: + using recipe_type = build2::recipe; + using rule_type = build2::rule; + + action_type action; // Action the rule/recipe is for. + + // Matched rule (pointer to hint_rule_map element). Note that in case of a + // direct recipe assignment we may not have a rule. + // + const pair>* rule; + + // Applied recipe. + // + recipe_type recipe_; + + // Note that the target must be locked in order to set the recipe. + // + void + recipe (recipe_type); + + // After the target has been matched and synchronized, check if the target + // is known to be unchanged. Used for optimizations during search & match. + // + bool + unchanged (action_type a) const + { + return state (a) == target_state::unchanged; + } + + // Targets to which prerequisites resolve for this recipe. Note that + // unlike prerequisite::target, these can be resolved to group members. + // NULL means the target should be skipped (or the rule may simply not add + // such a target to the list). + // + // Note also that it is possible the target can vary from action to + // action, just like recipes. We don't need to keep track of the action + // here since the targets will be updated if the recipe is updated, + // normally as part of rule::apply(). + // + // Note that the recipe may modify this list. + // + using prerequisite_targets_type = vector; + mutable prerequisite_targets_type prerequisite_targets; + + // Auxilary data storage. + // // A rule that matches (i.e., returns true from its match() function) may // use this pad to pass data between its match and apply functions as well // as the recipe. After the recipe is executed, the data is destroyed by @@ -478,17 +561,18 @@ namespace build2 // // Currenly the data is not destroyed until the next match. // - // Note that the recipe may modify (mutable) the data. + // Note that the recipe may modify the data. // static constexpr size_t data_size = sizeof (string) * 8; mutable std::aligned_storage::type data_pad; - mutable void (*data_dtor) (void*) = nullptr; + + mutable void (*data_dtor) (void*) = nullptr; template ::type>::type> typename std::enable_if::value,T&>::type - data (R&& d) + data (R&& d) const { assert (sizeof (T) <= data_size && data_dtor == nullptr); return *new (&data_pad) T (forward (d)); @@ -498,7 +582,7 @@ namespace build2 typename T = typename std::remove_cv< typename std::remove_reference::type>::type> typename std::enable_if::value,T&>::type - data (R&& d) + data (R&& d) const { assert (sizeof (T) <= data_size && data_dtor == nullptr); T& r (*new (&data_pad) T (forward (d))); @@ -520,29 +604,6 @@ namespace build2 } } - // Recipe. - // - public: - using recipe_type = build2::recipe; - - action_type action; // Action this recipe is for. - - const recipe_type& - recipe (action_type a) const {return a > action ? empty_recipe : recipe_;} - - void - recipe (action_type, recipe_type); - - // After the recipe has been set (and target synchronized), check if the - // target is known to be unchanged. Used for various optimizations during - // search & match. - // - bool - unchanged () {return state () == target_state::unchanged;} - - private: - recipe_type recipe_; - // Target type info and casting. // public: @@ -609,6 +670,17 @@ namespace build2 inline ostream& operator<< (ostream& os, const target& t) {return os << t.key ();} + // Sometimes it is handy to "mark" a pointer to a target (for example, in + // prerequisite_targets). We use the last 2 bits in a pointer for that (aka + // the "bit stealing" technique). Note that the pointer needs to be unmarked + // before it can be usable so care must be taken in the face of exceptions, + // etc. + // + void + mark (const target*&, uint8_t = 1); + + uint8_t + unmark (const target*&); // A "range" that presents the prerequisites of a group and one of // its members as one continuous sequence, or, in other words, as @@ -626,35 +698,40 @@ namespace build2 // // For constant iteration use const_group_prerequisites(). // - template - class group_prerequisites_impl + class group_prerequisites { public: explicit - group_prerequisites_impl (T& t) + group_prerequisites (const target& t) : t_ (t), - g_ (t_.group == nullptr || - t_.group->member != nullptr || // Ad hoc group member. - t_.group->prerequisites.empty () + g_ (t_.group == nullptr || + t_.group->member != nullptr || // Ad hoc group member. + t_.group->prerequisites ().empty () ? nullptr : t_.group) {} + using prerequisites_type = target::prerequisites_type; + using base_iterator = prerequisites_type::const_iterator; + struct iterator { - using value_type = typename I::value_type; - using pointer = typename I::pointer; - using reference = typename I::reference; - using difference_type = typename I::difference_type; + using value_type = base_iterator::value_type; + using pointer = base_iterator::pointer; + using reference = base_iterator::reference; + using difference_type = base_iterator::difference_type; using iterator_category = std::bidirectional_iterator_tag; iterator () {} - iterator (T* t, T* g, P* c, I i): t_ (t), g_ (g), c_ (c), i_ (i) {} + iterator (const target* t, + const target* g, + const prerequisites_type* c, + base_iterator i): t_ (t), g_ (g), c_ (c), i_ (i) {} iterator& operator++ () { - if (++i_ == c_->end () && c_ != &t_->prerequisites) + if (++i_ == c_->end () && c_ != &t_->prerequisites ()) { - c_ = &t_->prerequisites; + c_ = &t_->prerequisites (); i_ = c_->begin (); } return *this; @@ -666,9 +743,9 @@ namespace build2 iterator& operator-- () { - if (i_ == c_->begin () && c_ == &t_->prerequisites) + if (i_ == c_->begin () && c_ == &t_->prerequisites ()) { - c_ = &g_->prerequisites; + c_ = &g_->prerequisites (); i_ = c_->end (); } @@ -692,10 +769,10 @@ namespace build2 operator!= (const iterator& x, const iterator& y) {return !(x == y);} private: - T* t_ = nullptr; - T* g_ = nullptr; - P* c_ = nullptr; - I i_; + const target* t_ = nullptr; + const target* g_ = nullptr; + const prerequisites_type* c_ = nullptr; + base_iterator i_; }; using reverse_iterator = std::reverse_iterator; @@ -703,14 +780,14 @@ namespace build2 iterator begin () const { - P& c ((g_ != nullptr ? *g_ : t_).prerequisites); + auto& c ((g_ != nullptr ? *g_ : t_).prerequisites ()); return iterator (&t_, g_, &c, c.begin ()); } iterator end () const { - P& c (t_.prerequisites); + auto& c (t_.prerequisites ()); return iterator (&t_, g_, &c, c.end ()); } @@ -723,25 +800,15 @@ namespace build2 size_t size () const { - return t_.prerequisites.size () + - (g_ != nullptr ? g_->prerequisites.size () : 0); + return t_.prerequisites ().size () + + (g_ != nullptr ? g_->prerequisites ().size () : 0); } private: - T& t_; - T* g_; + const target& t_; + const target* g_; }; - using group_prerequisites = group_prerequisites_impl< - target, - target::prerequisites_type, - target::prerequisites_type::iterator>; - - using const_group_prerequisites = group_prerequisites_impl< - const target, - const target::prerequisites_type, - target::prerequisites_type::const_iterator>; - // A member of a prerequisite. If 'target' is NULL, then this is the // prerequisite itself. Otherwise, it is its member. In this case // 'prerequisite' still refers to the prerequisite. @@ -752,8 +819,8 @@ namespace build2 using prerequisite_type = build2::prerequisite; using target_type_type = build2::target_type; - prerequisite_type& prerequisite; - target_type* target; + const prerequisite_type& prerequisite; + const target_type* target; template bool @@ -800,7 +867,7 @@ namespace build2 : prerequisite.proj; } - target_type& + const target_type& search () const { return target != nullptr ? *target : build2::search (prerequisite); @@ -848,32 +915,32 @@ namespace build2 // group_prerequisite_members (a, t) // reverse_group_prerequisite_members (a, t) // - template + template class prerequisite_members_range; - template - inline prerequisite_members_range - prerequisite_members (slock& ml, action a, T&& x, bool members = true) + template + inline prerequisite_members_range + prerequisite_members (action a, R&& r, bool members = true) { - return prerequisite_members_range (ml, a, forward (x), members); + return prerequisite_members_range (a, forward (r), members); } - template + template class prerequisite_members_range { public: - prerequisite_members_range (slock& l, action a, T&& r, bool m) - : l_ (l), a_ (a), members_ (m), r_ (forward (r)), e_ (r_.end ()) {} + prerequisite_members_range (action a, R&& r, bool m) + : a_ (a), members_ (m), r_ (forward (r)), e_ (r_.end ()) {} - using base_iterator = decltype (declval ().begin ()); + using base_iterator = decltype (declval ().begin ()); struct iterator { - typedef prerequisite_member value_type; - typedef const value_type* pointer; - typedef const value_type& reference; - typedef typename base_iterator::difference_type difference_type; - typedef std::forward_iterator_tag iterator_category; + using value_type = prerequisite_member; + using pointer = const value_type*; + using reference = const value_type&; + using difference_type = typename base_iterator::difference_type; + using iterator_category = std::forward_iterator_tag; iterator (): r_ (nullptr) {} iterator (const prerequisite_members_range* r, const base_iterator& i) @@ -904,8 +971,8 @@ namespace build2 value_type operator* () const { - target* t (k_ != nullptr ? k_: - g_.count != 0 ? g_.members[j_ - 1] : nullptr); + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); return value_type {*i_, t}; } @@ -913,11 +980,11 @@ namespace build2 pointer operator-> () const { static_assert ( - std::is_trivially_destructible::value, + std::is_trivially_destructible::value, "prerequisite_member is not trivially destructible"); - target* t (k_ != nullptr ? k_: - g_.count != 0 ? g_.members[j_ - 1] : nullptr); + const target* t (k_ != nullptr ? k_: + g_.count != 0 ? g_.members[j_ - 1] : nullptr); return new (&m_) value_type {*i_, t}; } @@ -955,10 +1022,10 @@ namespace build2 const prerequisite_members_range* r_; base_iterator i_; group_view g_; - size_t j_; // 1-based index, to support enter_group(). - target* k_; // Current member of ad hoc group or NULL. - mutable std::aligned_storage::type m_; + size_t j_; // 1-based index, to support enter_group(). + const target* k_; // Current member of ad hoc group or NULL. + mutable typename std::aligned_storage::type m_; }; iterator @@ -968,48 +1035,68 @@ namespace build2 end () const {return iterator (this, e_);} private: - slock& l_; action a_; bool members_; // Go into group members by default? - T r_; + R r_; base_iterator e_; }; // prerequisite_members(t.prerequisites) // inline auto - prerequisite_members (slock& ml, action a, target& t, bool members = true) + prerequisite_members (action a, target& t, bool members = true) + { + return prerequisite_members (a, t.prerequisites (), members); + } + + inline auto + prerequisite_members (action a, const target& t, bool members = true) { - return prerequisite_members (ml, a, t.prerequisites, members); + return prerequisite_members (a, t.prerequisites (), members); } // prerequisite_members(reverse_iterate(t.prerequisites)) // inline auto - reverse_prerequisite_members ( - slock& ml, action a, target& t, bool members = true) + reverse_prerequisite_members (action a, target& t, bool m = true) { - return prerequisite_members ( - ml, a, reverse_iterate (t.prerequisites), members); + return prerequisite_members (a, reverse_iterate (t.prerequisites ()), m); + } + + inline auto + reverse_prerequisite_members (action a, const target& t, bool m = true) + { + return prerequisite_members (a, reverse_iterate (t.prerequisites ()), m); } // prerequisite_members(group_prerequisites (t)) // inline auto - group_prerequisite_members ( - slock& ml, action a, target& t, bool members = true) + group_prerequisite_members (action a, target& t, bool m = true) + { + return prerequisite_members (a, group_prerequisites (t), m); + } + + inline auto + group_prerequisite_members (action a, const target& t, bool m = true) { - return prerequisite_members (ml, a, group_prerequisites (t), members); + return prerequisite_members (a, group_prerequisites (t), m); } // prerequisite_members(reverse_iterate (group_prerequisites (t))) // inline auto - reverse_group_prerequisite_members ( - slock& ml, action a, target& t, bool members = true) + reverse_group_prerequisite_members (action a, target& t, bool m = true) { return prerequisite_members ( - ml, a, reverse_iterate (group_prerequisites (t)), members); + a, reverse_iterate (group_prerequisites (t)), m); + } + + inline auto + reverse_group_prerequisite_members (action a, const target& t, bool m = true) + { + return prerequisite_members ( + a, reverse_iterate (group_prerequisites (t)), m); } // A target with an unspecified extension is considered equal to the one @@ -1028,10 +1115,10 @@ namespace build2 // Return existing target or NULL. // - target* + const target* find (const target_key& k, tracer& trace) const; - target* + const target* find (const target_type& type, const dir_path& dir, const dir_path& out, @@ -1043,7 +1130,7 @@ namespace build2 } template - T* + const T* find (const target_type& type, const dir_path& dir, const dir_path& out, @@ -1051,30 +1138,58 @@ namespace build2 const optional& ext, tracer& trace) const { - return static_cast (find (type, dir, out, name, ext, trace)); + return static_cast (find (type, dir, out, name, ext, trace)); } // As above but ignore the extension. // template - T* + const T* find (const dir_path& dir, const dir_path& out, const string& name) const { slock l (mutex_); + auto i ( map_.find ( target_key {&T::static_type, &dir, &out, &name, nullopt})); - return i != map_.end () ? static_cast (i->second.get ()) : nullptr; + + return i != map_.end () + ? static_cast (i->second.get ()) + : nullptr; } + // If the target was inserted, keep the map exclusive-locked and return + // the lock. In this case, the target is effectively still being created + // since nobody can see it until the lock is released. + // + pair + insert_locked (const target_type&, + dir_path dir, + dir_path out, + string name, + optional ext, + bool implied, + tracer&); + pair - insert (const target_type&, + insert (const target_type& tt, dir_path dir, dir_path out, string name, optional ext, bool implied, - tracer&); + tracer& t) + { + auto p (insert_locked (tt, + move (dir), + move (out), + move (name), + move (ext), + implied, + t)); + + return pair (p.first, p.second.owns_lock ()); + } // Note that the following versions always enter implied targets. // @@ -1144,67 +1259,65 @@ namespace build2 public: using target::target; - // Generally, modification time for a target can only be queried after a - // rule has been matched since that's where the path is normally gets - // assigned (if the path was not assigned and no timestamp was set - // manually, this function will return timestamp_unknown). Normally, - // however, it would make sense to first execute the rule to get the - // "updated" timestamp. + // Modification time is an "atomic cash". That is, it can be set at any + // time and we assume everything will be ok regardless of the order in + // which racing updates happen because we do not modify the external state + // (which is the source of timestemps) while updating the internal. + // + // The rule for groups that utilize target_state::group is as follows: if + // it has any members that are mtime_targets, then the group should be + // mtime_target and the members get the mtime from it. // - // The rule for groups that utilize the group state is as follows: - // if it has any members that are mtime_targets, then the group - // should be mtime_target and the members get the mtime from it. + // Note that this function can be called before the target is matched in + // which case the value always comes from the target itself. In other + // words, that group logic only kicks in once the target is matched. // timestamp - mtime (bool load = true) const - { - const mtime_target& t (state_ == target_state::group - ? group->as () - : *this); - - if (load && t.mtime_ == timestamp_unknown) - t.mtime_ = t.load_mtime (); + mtime () const; - return t.mtime_; - } - - // Note that while we can cache the mtime at any time, it may be ignored - // if the target state is group (see the mtime() accessor). + // Note also that while we can cache the mtime, it may be ignored if the + // target state is set to group (see above). // void - mtime (timestamp mt) const - { - mtime_ = mt; - } + mtime (timestamp) const; + + // If the mtime is unknown, then load it from the filesystem also caching + // the result. + // + // Note: can only be called during executing and must not be used if the + // target state is group. + // + timestamp + load_mtime (const path&) const; // Return true if this target is newer than the specified timestamp. // - // Note: can only be called on a synchronized target. + // Note: can only be called during execute on a synchronized target. // bool - newer (timestamp mt) const - { - timestamp mp (mtime ()); + newer (timestamp) const; - // What do we do if timestamps are equal? This can happen, for example, - // on filesystems that don't have subsecond resolution. There is not - // much we can do here except detect the case where the target was - // changed on this run. - // - return mt < mp || (mt == mp && state () == target_state::changed); - } + public: + static const target_type static_type; protected: - // Return timestamp_unknown if the mtime cannot be loaded. + // C++17: // - virtual timestamp - load_mtime () const = 0; + // static_assert (atomic::is_always_lock_free, + // "timestamp is not lock-free on this architecture"); - public: - static const target_type static_type; +#if !defined(ATOMIC_LLONG_LOCK_FREE) || ATOMIC_LLONG_LOCK_FREE != 2 +# error timestamp is not lock-free on this architecture +#endif - private: - mutable timestamp mtime_ {timestamp_unknown}; + // Note that the value is not used to synchronize any other state so we + // use the release-consume ordering (i.e., we are only interested in the + // mtime value being synchronized). + // + // Store it as an underlying representation (normally int64_t) since + // timestamp is not usable with atomic (non-noexcept default ctor). + // + mutable atomic mtime_ {timestamp_unknown_rep}; }; // Filesystem path-based target. @@ -1216,11 +1329,27 @@ namespace build2 typedef build2::path path_type; + // Target path is an "atomic consistent cash". That is, it can be set at + // any time but any subsequent updates must set the same path. Or, in + // other words, once the path is set, it never changes. + // + // A set empty path may signify special unknown/undetermined location (for + // example an installed import library -- we know it's there, just not + // exactly where). In this case you would also normally set its mtime. We + // used to return a pointer to properly distinguish between not set and + // empty but that proved too tedious. Note that this means there could be + // a race between path and mtime (unless you lock the target in some other + // way; see file_rule) so for this case it makes sense to set the + // timestamp first. + // const path_type& - path () const {return path_;} + path () const; - void - path (path_type p) {assert (path_.empty ()); path_ = move (p);} + const path_type& + path (path_type) const; + + timestamp + load_mtime () const {return mtime_target::load_mtime (path ());} // Derive a path from target's dir, name, and, if set, ext. If ext is not // set, try to derive it using the target type extension function and @@ -1254,11 +1383,30 @@ namespace build2 const string& derive_extension (const char* default_ext = nullptr, bool search = false); + // Const versions of the above that can be used on unlocked targets. Note + // that here we don't allow providing any defaults since you probably + // should only use this version if everything comes from the target itself + // (and is therefore atomic). + // + const path_type& + derive_path () const + { + return const_cast (this)->derive_path (); // MT-aware. + } + public: static const target_type static_type; private: - path_type path_; + // Note that the state is also used to synchronize the path value so + // we use the release-acquire ordering. + // + // 0 - absent + // 1 - being set + // 2 - present + // + mutable atomic path_state_ {0}; + mutable path_type path_; }; // File target. @@ -1268,13 +1416,6 @@ namespace build2 public: using path_target::path_target; - protected: - // Note that it is final in order to be consistent with file_rule, - // search_existing_file(). - // - virtual timestamp - load_mtime () const final; - public: static const target_type static_type; virtual const target_type& dynamic_type () const {return static_type;} @@ -1469,13 +1610,13 @@ namespace build2 // The default behavior, that is, look for an existing target in the // prerequisite's directory scope. // - target* + const target* search_target (const prerequisite_key&); // First look for an existing target as above. If not found, then look // for an existing file in the target-type-specific list of paths. // - target* + const target* search_file (const prerequisite_key&); } -- cgit v1.1