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

Alexey Paznikov via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 8 04:50:58 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.
----------------
apaznikov wrote:

Agreed, in this case the second store is redundant. Left as future work, as you suggest.

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


More information about the llvm-commits mailing list