[Mesa-dev] Static thread safety checking with clang
Chia-I Wu
olvaffe at gmail.com
Tue Nov 10 19:00:16 UTC 2020
On Tue, Nov 10, 2020 at 8:09 AM Kristian Høgsberg <hoegsberg at gmail.com> wrote:
>
> Hi,
>
> I wanted to call attention to
>
> https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/7529
>
> which shows how we can use a new clang __attribute__ to statically
> check locking invariants. It's probably not perfect, but more like
> static type checking - there will always be cases where you can't
> statically determine if the code is right or wrong, but it will be
> useful and correct in most cases (like in the MR). The attributes
> have a few quirks and I suspect that they were mostly designed and
> tested to work for C++ classes, but I found a way to make it work for
> C.
>
> For each type of lock, declare a lock capability:
>
> extern lock_cap_t fd_screen_lock_cap;
>
> Functions that take and release that lock get annotated with:
>
> static inline void
> fd_screen_lock(struct fd_screen *screen)
> acquire_cap(fd_screen_lock_cap);
>
> static inline void
> fd_screen_unlock(struct fd_screen *screen)
> release_cap(fd_screen_lock_cap)
>
> where acquire_cap and release_cap are #defines for an __attribute__.
> One of the quirks is that the function doing the locking triggers a
> warning about how it doesn't actually take the lock, so I silenced
> that by adding a no_thread_safety_analysis in there.
>
> Now whenever we have a function that expects to be called with that
> lock held (we often call them foo_locked) we can add the requires_cap
> annotation:
>
> static struct gmem_key *
> gmem_key_init(struct fd_batch *batch, bool assume_zs, bool no_scis_opt)
> requires_cap(fd_screen_lock_cap)
>
> and calls to this function will warn or error if called from a point
> where it's not statically determinable that you've taken the lock:
>
> ../../master/src/gallium/drivers/freedreno/freedreno_gmem.c:532:25:
> error: calling function 'gmem_key_init' requires holding lock
> 'fd_screen_lock_cap' exclusively [-Werror,-Wthread-safety-analysis]
> struct gmem_key *key = gmem_key_init(batch, assume_zs, no_scis_opt);
> ^
> 1 error generated.
fd_screen_lock_cap seems to be a placeholder here. Is it one of the
quirks? It would be nice if the error message reads "... requiring
hold lock '&screen->lock' exclusively".
>From https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#mutex-h,
wrapping of the threading api is expected. I wonder if we can
annotate simple_mtx_t (and other in-tree locking wrappers), and
perhaps kepp lock_cap_t only for code that uses mtx_t or
pthread_mutex_t directly.
>
> Many functions that assert a lock is taken are better off using the
> requires_cap annotation, which makes the check a compile failure
> instead. For cases where it's not possible to determine statically, we
> can still assert at runtime:
>
> static inline void
> fd_screen_assert_locked(struct fd_screen *screen)
> assert_cap(fd_screen_lock_cap)
>
> which tells the checker to assume the lock is taken. Finally, it's
> possible to annotate struct members:
>
> struct fd_gmem_cache gmem_cache guarded_by(fd_screen_lock_cap);
>
> such that any access to that field can only happen with the lock taken:
>
> ../../master/src/gallium/drivers/freedreno/freedreno_gmem.c:277:20:
> error: reading variable 'gmem_cache' requires holding lock
> 'fd_screen_lock_cap' [-Werror,-Wthread-safety-analysis]
> rzalloc(screen->gmem_cache.ht, struct fd_gmem_stateobj);
> ^
> Having these annotations also helps human readers of the code by
> spelling out the conventions explicitly instead of relying on _locked
> suffix conventions or documentation that call out which parts of a
> struct are protected by which lock. All in all this seems really
> useful.
100% agreed.
>
> Kristian
> _______________________________________________
> mesa-dev mailing list
> mesa-dev at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/mesa-dev
More information about the mesa-dev
mailing list