Skip to content

[Utils][Local] Preserve !nosanitize in combineMetadata when merging instructions #148376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion llvm/lib/Transforms/Utils/Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3381,7 +3381,11 @@ static void combineMetadata(Instruction *K, const Instruction *J,
K->setMetadata(Kind,
MDNode::getMostGenericNoaliasAddrspace(JMD, KMD));
break;
}
case LLVMContext::MD_nosanitize:
// Preserve !nosanitize if both K and J have it.
K->setMetadata(Kind, JMD);
break;
}
}
// Set !invariant.group from J if J has it. If both instructions have it
// then we will just pick it from J - even when they are different.
Expand Down
92 changes: 84 additions & 8 deletions llvm/test/Transforms/GVN/metadata.ll
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ define i32 @test8(ptr %p) {
define i32 @load_noundef_load(ptr %p) {
; CHECK-LABEL: define i32 @load_noundef_load
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !range [[RNG0]], !noundef !6
; CHECK-NEXT: [[A:%.*]] = load i32, ptr [[P]], align 4, !range [[RNG0]], !noundef [[META6:![0-9]+]]
; CHECK-NEXT: [[C:%.*]] = add i32 [[A]], [[A]]
; CHECK-NEXT: ret i32 [[C]]
;
Expand All @@ -138,7 +138,7 @@ define i32 @load_load_noundef(ptr %p) {
define void @load_dereferenceable_dominating(ptr %p) {
; CHECK-LABEL: define void @load_dereferenceable_dominating
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[A:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable !7
; CHECK-NEXT: [[A:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable [[META7:![0-9]+]]
; CHECK-NEXT: call void @use.ptr(ptr [[A]])
; CHECK-NEXT: call void @use.ptr(ptr [[A]])
; CHECK-NEXT: ret void
Expand Down Expand Up @@ -185,7 +185,7 @@ define void @load_ptr_nonnull_to_i64(ptr %p) {
define void @load_ptr_nonnull_noundef_to_i64(ptr %p) {
; CHECK-LABEL: define void @load_ptr_nonnull_noundef_to_i64
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !nonnull !6, !noundef !6
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META6]], !noundef [[META6]]
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
Expand All @@ -202,7 +202,7 @@ define void @load_ptr_nonnull_noundef_to_i64(ptr %p) {
define void @load_ptr_invariant_load_to_i64(ptr %p) {
; CHECK-LABEL: define void @load_ptr_invariant_load_to_i64
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !invariant.load !6
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !invariant.load [[META6]]
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
Expand All @@ -219,7 +219,7 @@ define void @load_ptr_invariant_load_to_i64(ptr %p) {
define void @load_ptr_dereferenceable_to_i64(ptr %p) {
; CHECK-LABEL: define void @load_ptr_dereferenceable_to_i64
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable !7
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable [[META7]]
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
Expand All @@ -236,7 +236,7 @@ define void @load_ptr_dereferenceable_to_i64(ptr %p) {
define void @load_ptr_dereferenceable_or_null_to_i64(ptr %p) {
; CHECK-LABEL: define void @load_ptr_dereferenceable_or_null_to_i64
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable_or_null !7
; CHECK-NEXT: [[VAL:%.*]] = load ptr, ptr [[P]], align 8, !dereferenceable_or_null [[META7]]
; CHECK-NEXT: [[VAL_INT:%.*]] = ptrtoint ptr [[VAL]] to i64
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
; CHECK-NEXT: call void @use.i64(i64 [[VAL_INT]])
Expand Down Expand Up @@ -409,6 +409,82 @@ join:
ret void
}

; We should preserve the !nosanitize if both insns have it.
define void @test_nosanitize1(ptr %p) {
; CHECK-LABEL: define void @test_nosanitize1
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4, !nosanitize [[META6]]
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
; CHECK: if:
; CHECK-NEXT: call void @use.i32(i32 0)
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: ret void
;
%v1 = load i32, ptr %p, !nosanitize !11
%cond = icmp eq i32 %v1, 0
br i1 %cond, label %if, label %join

if:
%v2 = load i32, ptr %p, !nosanitize !11
call void @use.i32(i32 %v2)
br label %join

join:
ret void
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test cases with !nosanitize only on one of the instructions as well.

Copy link
Contributor Author

@Camsyn Camsyn Jul 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imagine such a situation where

  1. a third-party tool instruments a nosanitize instruction;
  2. just after that, there is the same user instruction without nosanitize, and
  3. GVN will delete and replace the second user instruction with the first one.

So in this scenario, I think the nosanitize of the first instruction should be dropped, even if !DoesKMove, to allow the sanitizer to sanitize the user code semantics.

Therefore, I only preserve the nosanitize if both instructions have it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends a bit on the intended purpose of !nosanitize (which is, of course, not documented in LangRef). My initial assumption was that !nosanitize means "this must not be sanitized", but looking at how this actually appears to be used is "sanitization can be omitted for performance reasons". With that in mind, I agree with your interpretation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should still have tests for the other cases though.


define void @test_nosanitize2(ptr %p) {
; CHECK-LABEL: define void @test_nosanitize2
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
; CHECK: if:
; CHECK-NEXT: call void @use.i32(i32 0)
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: ret void
;
%v1 = load i32, ptr %p, !nosanitize !11
%cond = icmp eq i32 %v1, 0
br i1 %cond, label %if, label %join

if:
%v2 = load i32, ptr %p
call void @use.i32(i32 %v2)
br label %join

join:
ret void
}

define void @test_nosanitize3(ptr %p) {
; CHECK-LABEL: define void @test_nosanitize3
; CHECK-SAME: (ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P]], align 4
; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[V1]], 0
; CHECK-NEXT: br i1 [[COND]], label [[IF:%.*]], label [[JOIN:%.*]]
; CHECK: if:
; CHECK-NEXT: call void @use.i32(i32 0)
; CHECK-NEXT: br label [[JOIN]]
; CHECK: join:
; CHECK-NEXT: ret void
;
%v1 = load i32, ptr %p
%cond = icmp eq i32 %v1, 0
br i1 %cond, label %if, label %join

if:
%v2 = load i32, ptr %p, !nosanitize !11
call void @use.i32(i32 %v2)
br label %join

join:
ret void
}

!0 = !{i32 0, i32 2}
!1 = !{i32 3, i32 5}
!2 = !{i32 2, i32 5}
Expand All @@ -430,8 +506,8 @@ join:
; CHECK: [[RNG3]] = !{i32 -5, i32 -2, i32 1, i32 5}
; CHECK: [[RNG4]] = !{i32 10, i32 1}
; CHECK: [[RNG5]] = !{i32 3, i32 4, i32 5, i32 2}
; CHECK: [[META6:![0-9]+]] = !{}
; CHECK: [[META7:![0-9]+]] = !{i64 10}
; CHECK: [[META6]] = !{}
; CHECK: [[META7]] = !{i64 10}
; CHECK: [[RNG8]] = !{i64 0, i64 10}
; CHECK: [[RNG9]] = !{i64 0, i64 10, i64 20, i64 30}
; CHECK: [[RNG10]] = !{i64 10, i64 30}
Expand Down
Loading