diff --git a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
--- a/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp
@@ -3766,6 +3766,55 @@
   return MadeIRChange;
 }
 
+// Track the scopes used by !alias.scope and !noalias. In a function, a
+// @llvm.experimental.noalias.scope.decl is only useful if that scope is used
+// by both sets. If not, the declaration of the scope can be safely omitted.
+// The MDNode of the scope can be omitted as well for the instructions that are
+// part of this function. We do not do that at this point, as this might become
+// too time consuming to do.
+class AliasScopeTracker {
+  SmallPtrSet<const MDNode *, 8> UsedAliasScopesAndLists;
+  SmallPtrSet<const MDNode *, 8> UsedNoAliasScopesAndLists;
+
+public:
+  void analyse(Instruction *I) {
+    // This seems to be faster than checking 'mayReadOrWriteMemory()'.
+    if (!I->hasMetadataOtherThanDebugLoc())
+      return;
+
+    auto Track = [](Metadata *ScopeList, auto &Container) {
+      const auto *MDScopeList = dyn_cast_or_null<MDNode>(ScopeList);
+      if (!MDScopeList || !Container.insert(MDScopeList).second)
+        return;
+      for (auto &MDOperand : MDScopeList->operands())
+        if (auto *MDScope = dyn_cast<MDNode>(MDOperand))
+          Container.insert(MDScope);
+    };
+
+    Track(I->getMetadata(LLVMContext::MD_alias_scope), UsedAliasScopesAndLists);
+    Track(I->getMetadata(LLVMContext::MD_noalias), UsedNoAliasScopesAndLists);
+  }
+
+  bool isNoAliasScopeDeclDead(Instruction *Inst) {
+    NoAliasScopeDeclInst *Decl = dyn_cast<NoAliasScopeDeclInst>(Inst);
+    if (!Decl)
+      return false;
+
+    assert(Decl->use_empty() &&
+           "llvm.experimental.noalias.scope.decl in use ?");
+    const MDNode *MDSL = Decl->getScopeList();
+    assert(MDSL->getNumOperands() == 1 &&
+           "llvm.experimental.noalias.scope should refer to a single scope");
+    auto &MDOperand = MDSL->getOperand(0);
+    if (auto *MD = dyn_cast<MDNode>(MDOperand))
+      return !UsedAliasScopesAndLists.contains(MD) ||
+             !UsedNoAliasScopesAndLists.contains(MD);
+
+    // Not an MDNode ? throw away.
+    return true;
+  }
+};
+
 /// Populate the IC worklist from a function, by walking it in depth-first
 /// order and adding all reachable code to the worklist.
 ///
@@ -3784,6 +3833,7 @@
 
   SmallVector<Instruction*, 128> InstrsForInstCombineWorklist;
   DenseMap<Constant *, Constant *> FoldedConstants;
+  AliasScopeTracker SeenAliasScopes;
 
   do {
     BasicBlock *BB = Worklist.pop_back_val();
@@ -3830,8 +3880,10 @@
 
       // Skip processing debug intrinsics in InstCombine. Processing these call instructions
       // consumes non-trivial amount of time and provides no value for the optimization.
-      if (!isa<DbgInfoIntrinsic>(Inst))
+      if (!isa<DbgInfoIntrinsic>(Inst)) {
         InstrsForInstCombineWorklist.push_back(Inst);
+        SeenAliasScopes.analyse(Inst);
+      }
     }
 
     // Recursively visit successors.  If this is a branch or switch on a
@@ -3879,7 +3931,8 @@
   for (Instruction *Inst : reverse(InstrsForInstCombineWorklist)) {
     // DCE instruction if trivially dead. As we iterate in reverse program
     // order here, we will clean up whole chains of dead instructions.
-    if (isInstructionTriviallyDead(Inst, TLI)) {
+    if (isInstructionTriviallyDead(Inst, TLI) ||
+        SeenAliasScopes.isNoAliasScopeDeclDead(Inst)) {
       ++NumDeadInst;
       LLVM_DEBUG(dbgs() << "IC: DCE: " << *Inst << '\n');
       salvageDebugInfo(*Inst);
diff --git a/llvm/test/Transforms/Coroutines/coro-retcon-resume-values.ll b/llvm/test/Transforms/Coroutines/coro-retcon-resume-values.ll
--- a/llvm/test/Transforms/Coroutines/coro-retcon-resume-values.ll
+++ b/llvm/test/Transforms/Coroutines/coro-retcon-resume-values.ll
@@ -67,10 +67,7 @@
 ; CHECK-NEXT:  entry:
 ; CHECK:         [[BUFFER:%.*]] = alloca [8 x i8], align 4
 ; CHECK:         [[SLOT:%.*]] = bitcast [8 x i8]* [[BUFFER]] to i32*
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    store i32 7, i32* [[SLOT]], align 4
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    call void @print(i32 7)
 ; CHECK-NEXT:    ret i32 0
 
diff --git a/llvm/test/Transforms/Coroutines/coro-retcon-value.ll b/llvm/test/Transforms/Coroutines/coro-retcon-value.ll
--- a/llvm/test/Transforms/Coroutines/coro-retcon-value.ll
+++ b/llvm/test/Transforms/Coroutines/coro-retcon-value.ll
@@ -79,17 +79,14 @@
 ; CHECK:         [[SLOT:%.*]] = bitcast [8 x i8]* [[BUFFER]] to i32*
 ; CHECK-NEXT:    store i32 4, i32* [[SLOT]], align 4
 ; CHECK-NEXT:    call void @print(i32 4)
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    [[LOAD:%.*]] = load i32, i32* [[SLOT]], align 4
 ; CHECK-NEXT:    [[INC:%.*]] = add i32 [[LOAD]], 1
 ; CHECK-NEXT:    store i32 [[INC]], i32* [[SLOT]], align 4
 ; CHECK-NEXT:    call void @print(i32 [[INC]])
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    [[LOAD:%.*]] = load i32, i32* [[SLOT]], align 4
 ; CHECK-NEXT:    [[INC:%.*]] = add i32 [[LOAD]], 1
 ; CHECK-NEXT:    store i32 [[INC]], i32* [[SLOT]], align 4
 ; CHECK-NEXT:    call void @print(i32 [[INC]])
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    ret i32 0
 
 declare token @llvm.coro.id.retcon(i32, i32, i8*, i8*, i8*, i8*)
diff --git a/llvm/test/Transforms/Coroutines/coro-retcon.ll b/llvm/test/Transforms/Coroutines/coro-retcon.ll
--- a/llvm/test/Transforms/Coroutines/coro-retcon.ll
+++ b/llvm/test/Transforms/Coroutines/coro-retcon.ll
@@ -78,7 +78,6 @@
 ; CHECK-NEXT:    [[LOAD:%.*]] = load i32, i32* [[SLOT]], align 4
 ; CHECK-NEXT:    [[INC:%.*]] = add i32 [[LOAD]], 1
 ; CHECK-NEXT:    call void @print(i32 [[INC]])
-; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT:    ret i32 0
 
 define hidden { i8*, i8* } @g(i8* %buffer, i16* %ptr) {
diff --git a/llvm/test/Transforms/Coroutines/ex2.ll b/llvm/test/Transforms/Coroutines/ex2.ll
--- a/llvm/test/Transforms/Coroutines/ex2.ll
+++ b/llvm/test/Transforms/Coroutines/ex2.ll
@@ -49,9 +49,7 @@
   ret i32 0
 ; CHECK-NOT:  call i8* @CustomAlloc
 ; CHECK:      call void @print(i32 4)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 5)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 6)
 ; CHECK-NEXT: ret i32 0
 }
diff --git a/llvm/test/Transforms/Coroutines/ex3.ll b/llvm/test/Transforms/Coroutines/ex3.ll
--- a/llvm/test/Transforms/Coroutines/ex3.ll
+++ b/llvm/test/Transforms/Coroutines/ex3.ll
@@ -53,9 +53,7 @@
   ret i32 0
 ; CHECK-NOT:  i8* @malloc
 ; CHECK:      call void @print(i32 4)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 -5)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 5)
 ; CHECK:      ret i32 0
 }
diff --git a/llvm/test/Transforms/Coroutines/ex4.ll b/llvm/test/Transforms/Coroutines/ex4.ll
--- a/llvm/test/Transforms/Coroutines/ex4.ll
+++ b/llvm/test/Transforms/Coroutines/ex4.ll
@@ -50,9 +50,7 @@
   call void @llvm.coro.destroy(i8* %hdl)
   ret i32 0
 ; CHECK:      call void @print(i32 4)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 5)
-; CHECK-NEXT: call void @llvm.experimental.noalias.scope.decl
 ; CHECK-NEXT: call void @print(i32 6)
 ; CHECK:      ret i32 0
 }
diff --git a/llvm/test/Transforms/InstCombine/noalias-scope-decl.ll b/llvm/test/Transforms/InstCombine/noalias-scope-decl.ll
new file mode 100644
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/noalias-scope-decl.ll
@@ -0,0 +1,170 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -instcombine -S < %s | FileCheck %s
+
+define void @test01(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test01(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0
+  store i8 43, i8* %ptr1
+  ret void
+}
+
+define void @test02_keep(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test02_keep(
+; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl(metadata !0)
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !0
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !3
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !0
+  store i8 43, i8* %ptr1, !noalias !5
+  ret void
+}
+
+define void @test03(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test03(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !4
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !3
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !4
+  store i8 43, i8* %ptr1, !noalias !5
+  ret void
+}
+
+define void @test04_keep(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test04_keep(
+; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl(metadata !0)
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !3
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !3
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !5
+  store i8 43, i8* %ptr1, !noalias !5
+  ret void
+}
+
+define void @test05_keep(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test05_keep(
+; CHECK-NEXT:    call void @llvm.experimental.noalias.scope.decl(metadata !0)
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !3
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !0
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !5
+  store i8 43, i8* %ptr1, !noalias !0
+  ret void
+}
+
+define void @test06(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test06(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !3
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !5
+  store i8 43, i8* %ptr1, !noalias !4
+  ret void
+}
+
+define void @test07(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test07(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !0
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !0
+  store i8 43, i8* %ptr1, !noalias !4
+  ret void
+}
+
+define void @test08(i8* %ptr0, i8* %ptr1) {
+; CHECK-LABEL: @test08(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !4
+; CHECK-NEXT:    store i8 43, i8* [[PTR1:%.*]], align 1, !noalias !0
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !4
+  store i8 43, i8* %ptr1, !noalias !0
+  ret void
+}
+
+define void @test11(i8* %ptr0) {
+; CHECK-LABEL: @test11(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !0
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !0
+  ret void
+}
+
+define void @test12(i8* %ptr0) {
+; CHECK-LABEL: @test12(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !4
+  ret void
+}
+
+define void @test13(i8* %ptr0) {
+; CHECK-LABEL: @test13(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !alias.scope !3
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !alias.scope !5
+  ret void
+}
+
+define void @test14(i8* %ptr0) {
+; CHECK-LABEL: @test14(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !noalias !0
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !noalias !0
+  ret void
+}
+
+define void @test15(i8* %ptr0) {
+; CHECK-LABEL: @test15(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !noalias !4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !noalias !4
+  ret void
+}
+
+define void @test16(i8* %ptr0) {
+; CHECK-LABEL: @test16(
+; CHECK-NEXT:    store i8 42, i8* [[PTR0:%.*]], align 1, !noalias !3
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.experimental.noalias.scope.decl(metadata !0)
+  store i8 42, i8* %ptr0, !noalias !5
+  ret void
+}
+
+declare void @llvm.experimental.noalias.scope.decl(metadata)
+
+!0 = !{ !1 }
+!1 = distinct !{ !1, !2 }
+!2 = distinct !{ !2 }
+!3 = !{ !4 }
+!4 = distinct !{ !4, !2 }
+!5 = !{ !1, !4 }
diff --git a/llvm/test/Transforms/PhaseOrdering/inlining-alignment-assumptions.ll b/llvm/test/Transforms/PhaseOrdering/inlining-alignment-assumptions.ll
--- a/llvm/test/Transforms/PhaseOrdering/inlining-alignment-assumptions.ll
+++ b/llvm/test/Transforms/PhaseOrdering/inlining-alignment-assumptions.ll
@@ -95,7 +95,6 @@
 
 define amdgpu_kernel void @caller2() {
 ; CHECK-LABEL: @caller2(
-; CHECK-NEXT:    tail call void @llvm.experimental.noalias.scope.decl([[META0:metadata !.*]])
 ; CHECK-NEXT:    ret void
 ;
   %alloca = alloca i64, align 8, addrspace(5)
diff --git a/llvm/test/Transforms/PhaseOrdering/instcombine-sroa-inttoptr.ll b/llvm/test/Transforms/PhaseOrdering/instcombine-sroa-inttoptr.ll
--- a/llvm/test/Transforms/PhaseOrdering/instcombine-sroa-inttoptr.ll
+++ b/llvm/test/Transforms/PhaseOrdering/instcombine-sroa-inttoptr.ll
@@ -70,7 +70,6 @@
 ; CHECK-NEXT:    [[I2:%.*]] = alloca [[TMP0:%.*]], align 8
 ; CHECK-NEXT:    [[I1_SROA_0_0_I5_SROA_IDX:%.*]] = getelementptr inbounds [[TMP0]], %0* [[ARG:%.*]], i64 0, i32 0
 ; CHECK-NEXT:    [[I1_SROA_0_0_COPYLOAD:%.*]] = load i32*, i32** [[I1_SROA_0_0_I5_SROA_IDX]], align 8
-; CHECK-NEXT:    tail call void @llvm.experimental.noalias.scope.decl([[META0:metadata !.*]])
 ; CHECK-NEXT:    [[I_SROA_0_0_I6_SROA_IDX:%.*]] = getelementptr inbounds [[TMP0]], %0* [[I2]], i64 0, i32 0
 ; CHECK-NEXT:    store i32* [[I1_SROA_0_0_COPYLOAD]], i32** [[I_SROA_0_0_I6_SROA_IDX]], align 8
 ; CHECK-NEXT:    tail call void @_Z7escape01S(%0* nonnull byval(%0) align 8 [[I2]])
@@ -110,7 +109,6 @@
 ; CHECK-NEXT:  bb:
 ; CHECK-NEXT:    [[I1_SROA_0_0_I4_SROA_IDX:%.*]] = getelementptr inbounds [[TMP0:%.*]], %0* [[ARG:%.*]], i64 0, i32 0
 ; CHECK-NEXT:    [[I1_SROA_0_0_COPYLOAD:%.*]] = load i32*, i32** [[I1_SROA_0_0_I4_SROA_IDX]], align 8
-; CHECK-NEXT:    tail call void @llvm.experimental.noalias.scope.decl([[META3:metadata !.*]])
 ; CHECK-NEXT:    [[I5:%.*]] = tail call i32 @_Z4condv()
 ; CHECK-NEXT:    [[I6_NOT:%.*]] = icmp eq i32 [[I5]], 0
 ; CHECK-NEXT:    br i1 [[I6_NOT]], label [[BB10:%.*]], label [[BB7:%.*]]