[llvm] [TSan] Add dominance-based redundant instrumentation elimination (PR #169897)

Dmitry Vyukov via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 7 07:06:53 PDT 2026


================
@@ -0,0 +1,884 @@
+; RUN: opt < %s -passes=tsan -S | FileCheck %s
+; RUN: opt < %s -passes=tsan -tsan-use-dominance-analysis=false -S | FileCheck %s --check-prefix=NODOM
+; RUN: opt < %s -passes=tsan -tsan-distinguish-volatile -S | FileCheck %s --check-prefix=VOLATILE
+
+; Tests for TSan dominance-based redundant instrumentation elimination.
+; Redundant instrumentation is removed when one access dominates another to
+; the same location with no synchronization on any path between them.
+;
+; Check prefixes:
+;   CHECK   - default run (optimization enabled)
+;   NODOM   - optimization disabled; all accesses must remain instrumented
+;   VOLATILE - -tsan-distinguish-volatile enabled; volatile and non-volatile
+;              accesses emit different runtime calls and must not be merged
+
+target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128"
+
+ at g1 = global i32 0, align 4
+ at g2 = global i32 0, align 4
+ at arr = global [5 x i32] zeroinitializer, align 4
+
+; Unsafe call (no nosync): blocks dominance-based elimination.
+declare void @ext_call()
+; nosync call: safe to cross for dominance-based elimination.
+declare void @nosync_func() #0
+; Unsafe function returning i32, used in loop tests.
+declare i32 @ext_check(...)
+
+; ===========================================================================
+; Intra-block dominance
+; ===========================================================================
+
+; First write dominates second write to the same location.
+define void @intra_block_write_write() nounwind uwtable sanitize_thread {
+entry:
+  store i32 1, ptr @g1, align 4
+  store i32 2, ptr @g1, align 4
+  ret void
+}
+; CHECK-LABEL: define void @intra_block_write_write
+; CHECK:       call void @__tsan_write4(ptr @g1)
+; CHECK-NOT:   call void @__tsan_write4(ptr @g1)
+; CHECK:       ret void
+;
+; NODOM-LABEL: define void @intra_block_write_write
+; NODOM:       call void @__tsan_write4(ptr @g1)
+; NODOM:       call void @__tsan_write4(ptr @g1)
+; NODOM:       ret void
+
+; Write dominates following read: write covers read, so read is removed.
+define void @intra_block_write_read() nounwind uwtable sanitize_thread {
+entry:
+  store i32 1, ptr @g1, align 4
+  %val = load i32, ptr @g1, align 4
+  ret void
+}
+; CHECK-LABEL: define void @intra_block_write_read
+; CHECK:       call void @__tsan_write4(ptr @g1)
+; CHECK-NOT:   call void @__tsan_read4(ptr @g1)
+; CHECK:       ret void
+
+; First read dominates second read to the same location.
+define void @intra_block_read_read() nounwind uwtable sanitize_thread {
+entry:
+  %v1 = load i32, ptr @g1, align 4
+  %v2 = load i32, ptr @g1, align 4
+  ret void
+}
+; CHECK-LABEL: define void @intra_block_read_read
+; CHECK:       call void @__tsan_read4(ptr @g1)
+; CHECK-NOT:   call void @__tsan_read4(ptr @g1)
+; CHECK:       ret void
+
+; A dominating read does NOT eliminate a write on a dominated path.
+; The read-before-write elimination in chooseInstructionsToInstrument only
+; applies within the same basic block, so this uses an inter-block scenario.
+; The write is only on one branch so it is NOT the post-dominator of the
+; read; the dominance check therefore applies in isolation.
+define void @dom_read_does_not_cover_write(i1 %cond) nounwind uwtable sanitize_thread {
+entry:
+  %v = load i32, ptr @g1, align 4
+  br i1 %cond, label %write.path, label %skip
+write.path:
+  store i32 1, ptr @g1, align 4
+  br label %end
+skip:
+  br label %end
+end:
+  ret void
+}
+; CHECK-LABEL: define void @dom_read_does_not_cover_write
+; The read dominates the write, but a read cannot cover write-write races.
+; Both must remain instrumented.
+; CHECK:       call void @__tsan_read4(ptr @g1)
+; CHECK:       write.path:
+; CHECK:       call void @__tsan_write4(ptr @g1)
+; CHECK:       ret void
+
+; ===========================================================================
+; Path safety
+; ===========================================================================
+
+; An inter-thread atomic on the path is a synchronization point: no elimination.
----------------
dvyukov wrote:

Strictly speaking, here we can eliminate second store too. If one races, the other one races too.
But that's an optimization and complication on top of the current also, so no need to implement in this change.

https://github.com/llvm/llvm-project/pull/169897


More information about the llvm-commits mailing list