GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 88.9% 16 / 0 / 18
Functions: 100.0% 6 / 0 / 6
Branches: 75.0% 9 / 0 / 12

src/util/Concurrency.h
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #pragma once
4
5 #include <atomic>
6 #include <mutex>
7
8 namespace spice::compiler {
9
10 /**
11 * Global switch that tells the process-wide caches of the compiler (type registry, function lookup cache, ...) whether
12 * compiler passes are currently executed on more than one thread.
13 *
14 * Those caches sit on the hottest paths of the single-threaded front end and middle end, where taking a lock for every
15 * access would be pure overhead. The back end on the other hand runs one pipeline per source file in parallel (see
16 * SourceFile::runBackEnd), and there the caches do need protection. ParallelSection flips this switch for exactly the
17 * time span in which worker threads are alive.
18 */
19 inline std::atomic concurrentPassesRunning = false;
20
21 /**
22 * RAII marker for a section of the compiler in which passes are executed on multiple threads.
23 *
24 * It has to be entered before the worker threads are handed any work and left only after all of them are joined again,
25 * so that every ConditionalLock taken on a worker thread actually locks.
26 */
27 class ParallelSection final {
28 public:
29 // Constructors
30 2 ParallelSection() { concurrentPassesRunning.store(true, std::memory_order_relaxed); }
31
32 // Prevent copy
33 ParallelSection(const ParallelSection &) = delete;
34 ParallelSection &operator=(const ParallelSection &) = delete;
35
36 // Destructor
37 2 ~ParallelSection() { concurrentPassesRunning.store(false, std::memory_order_relaxed); }
38 };
39
40 /**
41 * RAII lock that is only actually acquired while compiler passes run concurrently.
42 *
43 * Outside of a ParallelSection this degrades to a relaxed atomic load plus a well-predicted branch, which keeps the
44 * single-threaded stages at their current speed.
45 */
46 template <typename MutexT> class ConditionalLock final {
47 public:
48 // Constructors
49
3/4
spice::compiler::ConditionalLock<std::recursive_mutex>::ConditionalLock(std::recursive_mutex&):
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 196202 times.
spice::compiler::ConditionalLock<std::mutex>::ConditionalLock(std::mutex&):
✓ Branch 3 → 4 taken 39673 times.
✓ Branch 3 → 5 taken 14968110 times.
15204123 explicit ConditionalLock(MutexT &mutex) : mutex(concurrentPassesRunning.load(std::memory_order_relaxed) ? &mutex : nullptr) {
50
3/4
spice::compiler::ConditionalLock<std::recursive_mutex>::ConditionalLock(std::recursive_mutex&):
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 196202 times.
spice::compiler::ConditionalLock<std::mutex>::ConditionalLock(std::mutex&):
✓ Branch 6 → 7 taken 39706 times.
✓ Branch 6 → 8 taken 14968077 times.
15203985 if (this->mutex != nullptr)
51 39706 this->mutex->lock();
52 15204280 }
53
54 // Prevent copy
55 ConditionalLock(const ConditionalLock &) = delete;
56 ConditionalLock &operator=(const ConditionalLock &) = delete;
57
58 // Destructor
59 15204349 ~ConditionalLock() {
60
3/4
spice::compiler::ConditionalLock<std::recursive_mutex>::~ConditionalLock():
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 196202 times.
spice::compiler::ConditionalLock<std::mutex>::~ConditionalLock():
✓ Branch 2 → 3 taken 40001 times.
✓ Branch 2 → 4 taken 14968146 times.
15204349 if (mutex != nullptr)
61 40001 mutex->unlock();
62 15204202 }
63
64 private:
65 // Members
66 MutexT *mutex;
67 };
68
69 /**
70 * Guards the process-wide symbol lookup machinery: the lookup caches of FunctionManager, StructManager and
71 * InterfaceManager, plus the manifestation lists and symbol table entries those matchers touch.
72 *
73 * The IR generator lowers struct and interface types through QualType::getStruct/getInterface and looks up implicit
74 * functions (e.g. copy ctors), so all three matchers are reachable from the back-end worker threads. By the time the
75 * back end runs, the type checker has already created every manifestation, so these calls degenerate to cache hits and
76 * the lock is barely contended.
77 *
78 * It is recursive because struct matching recurses into struct and interface matching for field and interface types.
79 */
80 inline std::recursive_mutex symbolRegistryMutex;
81
82 } // namespace spice::compiler
83