[clang] [llvm] [LLVM][Clang] Diagnose uninitialized record fields in IR (PR #227723)

via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 02:13:41 PDT 2026


https://github.com/XinlongZHANG-Bob updated https://github.com/llvm/llvm-project/pull/227723

>From 337ed983d059d63a825a235cdbf34cd281f5cf4e Mon Sep 17 00:00:00 2001
From: XinlongZHANG-Bob <zhangxinlong.bob at bytedance.com>
Date: Thu, 1 Oct 2026 17:12:23 +0800
Subject: [PATCH] [LLVM][Clang] Diagnose uninitialized record fields in IR

Add MemorySSA-based early and late passes for diagnosing scalar field
loads from uninitialized local record objects.

The early pass handles local stack objects, including field-sensitive
stores, control-flow merges, aggregate copies, and bounded callee
summaries. The late pass runs after inlining and additionally handles
fresh stack and heap objects exposed at call sites.

Report definite uses through -Wuninitialized and path-dependent uses
through -Wconditional-uninitialized. Unknown calls, escaped objects,
unsupported memory operations, and analysis limits remain conservative
and do not produce diagnostics.

Preserve source locations with LocTrackingOnly when either warning is
enabled, including -g0 builds.
---
 .../clang/Basic/DiagnosticFrontendKinds.td    |   7 +
 .../CodeGenUtils/BackendDiagnosticHandler.h   |   2 +
 clang/lib/CodeGen/BackendUtil.cpp             |  19 +
 .../CodeGenUtils/BackendDiagnosticHandler.cpp |  31 +-
 .../CodeGenCXX/warn-uninitialized-fields.cpp  |  75 ++
 .../CodeGenCXX/warn-uninitialized-late.cpp    | 111 +++
 llvm/include/llvm/IR/DiagnosticInfo.h         |  21 +
 .../Transforms/Scalar/WarnUninitialized.h     |  50 ++
 llvm/lib/IR/DiagnosticInfo.cpp                |  11 +
 llvm/lib/Passes/PassBuilder.cpp               |   1 +
 llvm/lib/Passes/PassRegistry.def              |   2 +
 llvm/lib/Transforms/Scalar/CMakeLists.txt     |   1 +
 .../Transforms/Scalar/WarnUninitialized.cpp   | 753 ++++++++++++++++++
 .../Transforms/WarnUninitialized/basic.ll     | 318 ++++++++
 .../test/Transforms/WarnUninitialized/late.ll | 181 +++++
 15 files changed, 1575 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
 create mode 100644 clang/test/CodeGenCXX/warn-uninitialized-late.cpp
 create mode 100644 llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
 create mode 100644 llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
 create mode 100644 llvm/test/Transforms/WarnUninitialized/basic.ll
 create mode 100644 llvm/test/Transforms/WarnUninitialized/late.ll

diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 031858610ede2..1b8bd08059ffb 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -561,4 +561,11 @@ def warn_dyndbg_unable_to_create_target : Warning<
 
 def err_dyndbg_no_instrumentation : Error<
     "'-fdynamic-debugging' unsupported with instrumentation (PGO/code coverage)">;
+
+def warn_fe_backend_uninitialized : Warning<
+  "field is uninitialized when used here">,
+  InGroup<Uninitialized>, DefaultIgnore;
+def warn_fe_backend_maybe_uninitialized : Warning<
+  "field may be uninitialized when used here">,
+  InGroup<UninitializedMaybe>, DefaultIgnore;
 }
diff --git a/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h b/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
index 484b5c6859495..7ed839562524d 100644
--- a/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
+++ b/clang/include/clang/CodeGenUtils/BackendDiagnosticHandler.h
@@ -37,6 +37,7 @@ class DiagnosticInfoOptimizationFailure;
 class DiagnosticInfoResourceLimit;
 class DiagnosticInfoSrcMgr;
 class DiagnosticInfoStackSize;
+class DiagnosticInfoUninitialized;
 class DiagnosticInfoUnsupported;
 class DiagnosticInfoUnsupportedTargetIntrinsic;
 class DiagnosticInfoWithLocationBase;
@@ -134,6 +135,7 @@ class BackendDiagnosticConsumer {
   /// Specialized handler for misexpect warnings.
   /// Note that misexpect remarks are emitted through ORE
   void MisExpectDiagHandler(const llvm::DiagnosticInfoMisExpect &D);
+  void UninitializedDiagHandler(const llvm::DiagnosticInfoUninitialized &D);
 
   DiagnosticsEngine &Diags;
   const CodeGenOptions &CodeGenOpts;
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c754c75aa59de..6cc50b49c9f9d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -95,6 +95,7 @@
 #include "llvm/Transforms/Scalar/EarlyCSE.h"
 #include "llvm/Transforms/Scalar/GVN.h"
 #include "llvm/Transforms/Scalar/JumpThreading.h"
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
 #include "llvm/Transforms/Utils/AssignGUID.h"
 #include "llvm/Transforms/Utils/Debugify.h"
 #include "llvm/Transforms/Utils/DynamicDebugging.h"
@@ -898,6 +899,24 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
   SI.registerCallbacks(PIC, &MAM);
   PassBuilder PB(TM.get(), PTO, PGOOpt, &PIC, CI.getVirtualFileSystemPtr());
 
+  if (!CI.getDiagnostics().isIgnored(diag::warn_fe_backend_uninitialized,
+                                     SourceLocation()) ||
+      !CI.getDiagnostics().isIgnored(diag::warn_fe_backend_maybe_uninitialized,
+                                     SourceLocation())) {
+    auto DiagnosticState = createWarnUninitializedDiagnosticState();
+    PB.registerPipelineStartEPCallback(
+        [DiagnosticState](ModulePassManager &MPM, OptimizationLevel) {
+          MPM.addPass(createModuleToFunctionPassAdaptor(
+              WarnUninitializedEarlyPass(DiagnosticState)));
+        });
+    PB.registerCGSCCOptimizerLateEPCallback(
+        [DiagnosticState](CGSCCPassManager &CGPM, OptimizationLevel Level) {
+          if (Level != OptimizationLevel::O0)
+            CGPM.addPass(createCGSCCToFunctionPassAdaptor(
+                WarnUninitializedLatePass(DiagnosticState)));
+        });
+  }
+
   // Handle the assignment tracking feature options.
   switch (CodeGenOpts.getAssignmentTrackingMode()) {
   case CodeGenOptions::AssignmentTrackingOpts::Forced:
diff --git a/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp b/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
index 667a7948fa18f..4c9ac2b44d67a 100644
--- a/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
+++ b/clang/lib/CodeGenUtils/BackendDiagnosticHandler.cpp
@@ -178,14 +178,6 @@ const FullSourceLoc BackendDiagnosticConsumer::getBestLocationFromDebugLoc(
       Loc = *MaybeLoc;
   }
 
-  if (DILoc.isInvalid() && D.isLocationAvailable())
-    // If we were not able to translate the file:line:col information
-    // back to a SourceLocation, at least emit a note stating that
-    // we could not translate this location. This can happen in the
-    // case of #line directives.
-    Diags.Report(Loc, diag::note_fe_backend_invalid_loc)
-        << Filename << Line << Column;
-
   return Loc;
 }
 
@@ -549,6 +541,26 @@ void BackendDiagnosticConsumer::MisExpectDiagHandler(
         << Filename << Line << Column;
 }
 
+void BackendDiagnosticConsumer::UninitializedDiagHandler(
+    const llvm::DiagnosticInfoUninitialized &D) {
+  unsigned DiagID = D.isMaybe() ? diag::warn_fe_backend_maybe_uninitialized
+                                : diag::warn_fe_backend_uninitialized;
+  StringRef Filename;
+  unsigned Line, Column;
+  bool BadDebugInfo = false;
+  FullSourceLoc Loc;
+  if (SM)
+    Loc = getBestLocationFromDebugLoc(D, BadDebugInfo, Filename, Line, Column);
+
+  if (Diags.isIgnored(DiagID, Loc))
+    return;
+  Diags.Report(Loc, DiagID);
+
+  if (BadDebugInfo)
+    Diags.Report(Loc, diag::note_fe_backend_invalid_loc)
+        << Filename << Line << Column;
+}
+
 void BackendDiagnosticConsumer::handleDiagnostics(const DiagnosticInfo &DI) {
   unsigned DiagID = diag::err_fe_inline_asm;
   llvm::DiagnosticSeverity Severity = DI.getSeverity();
@@ -633,6 +645,9 @@ void BackendDiagnosticConsumer::handleDiagnostics(const DiagnosticInfo &DI) {
   case llvm::DK_MisExpect:
     MisExpectDiagHandler(cast<DiagnosticInfoMisExpect>(DI));
     return;
+  case llvm::DK_Uninitialized:
+    UninitializedDiagHandler(cast<DiagnosticInfoUninitialized>(DI));
+    return;
   default:
     // Plugin IDs are not bound to any value as they are set dynamically.
     ComputeDiagRemarkID(Severity, backend_plugin, DiagID);
diff --git a/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp b/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
new file mode 100644
index 0000000000000..f7b83d8366d74
--- /dev/null
+++ b/clang/test/CodeGenCXX/warn-uninitialized-fields.cpp
@@ -0,0 +1,75 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wall -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wuninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -Wconditional-uninitialized -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn,late,maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -Wconditional-uninitialized -emit-obj -o /dev/null -verify=imprecise %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized -emit-llvm -o - %s 2>/dev/null | FileCheck %s --check-prefix=NO-DEBUG-IR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wno-uninitialized -emit-obj -o /dev/null -verify=disabled %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -emit-obj -o /dev/null -verify=disabled %s
+// disabled-no-diagnostics
+// NO-DEBUG-IR: define
+// NO-DEBUG-IR-NOT: !dbg
+
+void consume(int);
+
+struct C {
+  int i;
+  int j;
+  C() {}
+  void set_i() { i = 1; }
+  void set_j() { j = 1; }
+  void inspect() const { consume(j); } // late-warning {{field is uninitialized when used here}}
+};
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void no_write() { C c; consume(c.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void same_field_write() { C c; c.set_i(); consume(c.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void sibling_field_write() { C c; c.set_j(); consume(c.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void unknown(C &);
+void unknown_call() { C c; unknown(c); consume(c.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void readonly_call() { C c; c.inspect(); consume(c.i); } // imprecise-warning 2 {{field is uninitialized when used here}}
+
+void conditional_write(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+  C c;
+  if (condition)
+    c.i = 1;
+  // maybe-warning at +1 {{field may be uninitialized when used here}}
+  consume(c.i);
+}
+
+void conditional_sibling_write(bool condition) { // imprecise-warning {{field is uninitialized when used here}}
+  C c;
+  if (condition)
+    c.j = 1;
+  // warn-warning at +1 {{field is uninitialized when used here}}
+  consume(c.i);
+}
+
+struct B {
+  int i;
+  int j;
+};
+
+struct F {
+  int padding;
+  B b;
+};
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void copy_uninitialized() { B b; F f; f.b = b; consume(f.b.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+void copy_initialized() { B b; b.i = 1; F f; f.b = b; consume(f.b.i); }
+
+// warn-warning at +1 {{field is uninitialized when used here}}
+void copy_sibling_initialized() { B b; b.j = 1; F f; f.b = b; consume(f.b.i); } // imprecise-warning {{field is uninitialized when used here}}
+
+C *escaped;
+void escaped_address() { C c; escaped = &c; consume(c.i); }
diff --git a/clang/test/CodeGenCXX/warn-uninitialized-late.cpp b/clang/test/CodeGenCXX/warn-uninitialized-late.cpp
new file mode 100644
index 0000000000000..376e45cc25771
--- /dev/null
+++ b/clang/test/CodeGenCXX/warn-uninitialized-late.cpp
@@ -0,0 +1,111 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized \
+// RUN:   -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall \
+// RUN:   -debug-info-kind=line-tables-only -emit-obj -o /dev/null -verify=warn %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 \
+// RUN:   -Wconditional-uninitialized -debug-info-kind=line-tables-only \
+// RUN:   -emit-obj -o /dev/null -verify=maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wuninitialized \
+// RUN:   -Wconditional-uninitialized -debug-info-kind=line-tables-only \
+// RUN:   -emit-obj -o /dev/null -verify=warn,maybe %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -Wuninitialized \
+// RUN:   -Wconditional-uninitialized -emit-obj -o /dev/null \
+// RUN:   -verify=imprecise %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 -Wuninitialized \
+// RUN:   -emit-obj -o /dev/null -verify=disabled %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wno-uninitialized \
+// RUN:   -emit-obj -o /dev/null -verify=disabled %s
+// disabled-no-diagnostics
+
+void consume(int);
+
+struct C {
+  int i;
+  C() {}
+  void set(int value) { i = value; }
+  void use() {
+    // warn-warning at +2 4 {{field is uninitialized when used here}}
+    // maybe-warning at +1 2 {{field may be uninitialized when used here}}
+    consume(i);
+  }
+};
+
+void unknown(C &);
+
+void stack_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+  C c;
+  c.use();
+}
+
+void stack_initialized() {
+  C c;
+  c.set(1);
+  c.use();
+}
+
+void stack_conditionally_initialized(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+  C c;
+  if (condition)
+    c.set(1);
+  c.use();
+}
+
+void heap_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+  C *c = new C;
+  c->use();
+}
+
+void heap_initialized() {
+  C *c = new C;
+  c->set(1);
+  c->use();
+}
+
+void heap_conditionally_initialized(bool condition) { // imprecise-warning {{field may be uninitialized when used here}}
+  C *c = new C;
+  if (condition)
+    c->set(1);
+  c->use();
+}
+
+void heap_unknown_call() {
+  C *c = new C;
+  unknown(*c);
+  c->use();
+}
+
+struct Owner {
+  C *pointer;
+  C *operator->() { return pointer; }
+};
+
+void owner_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+  Owner owner{new C};
+  owner->use();
+}
+
+struct Box {
+  long control[2];
+  C object;
+};
+
+void nonzero_offset_uninitialized() { // imprecise-warning {{field is uninitialized when used here}}
+  Box *box = new Box;
+  box->object.use();
+}
+
+enum class Cache : unsigned char { No, Yes, Unknown };
+
+struct BitFields {
+  unsigned precedence : 6;
+  Cache rhs : 2;
+  Cache array : 2;
+  Cache function : 2;
+
+  BitFields(unsigned p, Cache r, Cache a, Cache f)
+      : precedence(p), rhs(r), array(a), function(f) {}
+};
+
+BitFields *initialize_bit_fields(unsigned p, Cache r, Cache a, Cache f) {
+  return new BitFields(p, r, a, f);
+}
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index da62b62bd8c74..17fa699fb2525 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -93,6 +93,7 @@ enum DiagnosticKind {
   DK_SrcMgr,
   DK_DontCall,
   DK_MisExpect,
+  DK_Uninitialized,
   DK_FirstPluginKind // Must be last value to work with
                      // getNextAvailablePluginDiagnosticKind
 };
@@ -1171,6 +1172,26 @@ class LLVM_ABI DiagnosticInfoMisExpect : public DiagnosticInfoWithLocationBase {
   const Twine &Msg;
 };
 
+/// Diagnostic information for an uninitialized load.
+class LLVM_ABI DiagnosticInfoUninitialized
+    : public DiagnosticInfoWithLocationBase {
+public:
+  explicit DiagnosticInfoUninitialized(const Instruction *Inst,
+                                       bool Maybe = false);
+
+  /// \see DiagnosticInfo::print.
+  void print(DiagnosticPrinter &DP) const override;
+
+  static bool classof(const DiagnosticInfo *DI) {
+    return DI->getKind() == DK_Uninitialized;
+  }
+
+  bool isMaybe() const { return Maybe; }
+
+private:
+  bool Maybe;
+};
+
 static DiagnosticSeverity getDiagnosticSeverity(SourceMgr::DiagKind DK) {
   switch (DK) {
   case llvm::SourceMgr::DK_Error:
diff --git a/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h b/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
new file mode 100644
index 0000000000000..5eaa314a174a1
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Scalar/WarnUninitialized.h
@@ -0,0 +1,50 @@
+//===- WarnUninitialized.h - Warn about uninitialized loads -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
+#define LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
+
+#include "llvm/IR/PassManager.h"
+#include <memory>
+
+namespace llvm {
+
+class WarnUninitializedDiagnosticState;
+
+LLVM_ABI std::shared_ptr<WarnUninitializedDiagnosticState>
+createWarnUninitializedDiagnosticState();
+
+class WarnUninitializedEarlyPass
+    : public RequiredPassInfoMixin<WarnUninitializedEarlyPass> {
+public:
+  explicit WarnUninitializedEarlyPass(
+      std::shared_ptr<WarnUninitializedDiagnosticState> State = nullptr)
+      : State(State) {}
+
+  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+private:
+  std::shared_ptr<WarnUninitializedDiagnosticState> State;
+};
+
+class WarnUninitializedLatePass
+    : public RequiredPassInfoMixin<WarnUninitializedLatePass> {
+public:
+  explicit WarnUninitializedLatePass(
+      std::shared_ptr<WarnUninitializedDiagnosticState> State = nullptr)
+      : State(State) {}
+
+  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
+
+private:
+  std::shared_ptr<WarnUninitializedDiagnosticState> State;
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_SCALAR_WARNUNINITIALIZED_H
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index a24b6d0935008..895fadb309fcf 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -495,6 +495,17 @@ void DiagnosticInfoMisExpect::print(DiagnosticPrinter &DP) const {
   DP << getLocationStr() << ": " << getMsg();
 }
 
+DiagnosticInfoUninitialized::DiagnosticInfoUninitialized(
+    const Instruction *Inst, bool Maybe)
+    : DiagnosticInfoWithLocationBase(DK_Uninitialized, DS_Warning,
+                                     *Inst->getFunction(), Inst->getDebugLoc()),
+      Maybe(Maybe) {}
+
+void DiagnosticInfoUninitialized::print(DiagnosticPrinter &DP) const {
+  DP << getLocationStr() << ": field " << (Maybe ? "may be" : "is")
+     << " uninitialized when used here";
+}
+
 void OptimizationRemarkAnalysisFPCommute::anchor() {}
 void OptimizationRemarkAnalysisAliasing::anchor() {}
 
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 30a6f75a1b86d..a86118a13a8e5 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -378,6 +378,7 @@
 #include "llvm/Transforms/Scalar/StructurizeCFG.h"
 #include "llvm/Transforms/Scalar/TailRecursionElimination.h"
 #include "llvm/Transforms/Scalar/WarnMissedTransforms.h"
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
 #include "llvm/Transforms/Utils/AddDiscriminators.h"
 #include "llvm/Transforms/Utils/AssignGUID.h"
 #include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index af4ce5029551d..22aa8b9abf1e2 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -554,6 +554,8 @@ FUNCTION_PASS("strip-gc-relocates", StripGCRelocates())
 FUNCTION_PASS("tailcallelim", TailCallElimPass())
 FUNCTION_PASS("transform-warning", WarnMissedTransformationsPass())
 FUNCTION_PASS("trigger-crash-function", TriggerCrashFunctionPass())
+FUNCTION_PASS("warn-uninitialized-early", WarnUninitializedEarlyPass())
+FUNCTION_PASS("warn-uninitialized-late", WarnUninitializedLatePass())
 FUNCTION_PASS("trigger-verifier-error", TriggerVerifierErrorPass())
 FUNCTION_PASS("tsan", ThreadSanitizerPass())
 FUNCTION_PASS("typepromotion", TypePromotionPass(*TM))
diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt
index c92fd202af968..67f8b0e7c5217 100644
--- a/llvm/lib/Transforms/Scalar/CMakeLists.txt
+++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt
@@ -82,6 +82,7 @@ add_llvm_component_library(LLVMScalarOpts
   StructurizeCFG.cpp
   TailRecursionElimination.cpp
   WarnMissedTransforms.cpp
+  WarnUninitialized.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms
diff --git a/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp b/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
new file mode 100644
index 0000000000000..11833d92f98d2
--- /dev/null
+++ b/llvm/lib/Transforms/Scalar/WarnUninitialized.cpp
@@ -0,0 +1,753 @@
+//===- WarnUninitialized.cpp - Warn about uninitialized loads -------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// These passes classify scalar field loads from local records as initialized,
+/// uninitialized, conditionally uninitialized, or unknown by walking MemorySSA.
+/// The late pass also handles fresh heap allocations exposed by inlining. Calls
+/// are barriers unless a bounded scan of a visible callee proves that it does
+/// not modify the queried byte range.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Scalar/WarnUninitialized.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/AliasAnalysis.h"
+#include "llvm/Analysis/MemoryBuiltins.h"
+#include "llvm/Analysis/MemoryLocation.h"
+#include "llvm/Analysis/MemorySSA.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/Dominators.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/ValueHandle.h"
+#include "llvm/Support/MathExtras.h"
+#include <limits>
+#include <optional>
+
+using namespace llvm;
+
+class llvm::WarnUninitializedDiagnosticState {
+  SmallVector<WeakTrackingVH, 8> DiagnosedLoads;
+
+public:
+  bool contains(const Instruction *I) const {
+    for (const WeakTrackingVH &VH : DiagnosedLoads)
+      if (static_cast<Value *>(VH) == I)
+        return true;
+    return false;
+  }
+
+  void insert(Instruction *I) { DiagnosedLoads.emplace_back(I); }
+};
+
+std::shared_ptr<WarnUninitializedDiagnosticState>
+llvm::createWarnUninitializedDiagnosticState() {
+  return std::make_shared<WarnUninitializedDiagnosticState>();
+}
+
+namespace {
+
+struct ByteRange {
+  int64_t Offset;
+  uint64_t Size;
+};
+
+static std::optional<int64_t> getEnd(ByteRange Range) {
+  if (Range.Size > uint64_t(std::numeric_limits<int64_t>::max()))
+    return std::nullopt;
+  int64_t End;
+  if (AddOverflow(Range.Offset, int64_t(Range.Size), End))
+    return std::nullopt;
+  return End;
+}
+
+static bool rangesOverlap(ByteRange LHS, ByteRange RHS) {
+  std::optional<int64_t> LHSEnd = getEnd(LHS);
+  std::optional<int64_t> RHSEnd = getEnd(RHS);
+  return !LHSEnd || !RHSEnd || (LHS.Offset < *RHSEnd && RHS.Offset < *LHSEnd);
+}
+
+static bool rangeContains(ByteRange Outer, ByteRange Inner) {
+  std::optional<int64_t> OuterEnd = getEnd(Outer);
+  std::optional<int64_t> InnerEnd = getEnd(Inner);
+  return OuterEnd && InnerEnd && Outer.Offset <= Inner.Offset &&
+         *InnerEnd <= *OuterEnd;
+}
+
+static bool isMaskedReadModifyWrite(const LoadInst &LI) {
+  const Value *Current = &LI;
+  bool SawMask = false;
+
+  while (Current->hasOneUse()) {
+    const User *OnlyUser = *Current->user_begin();
+    if (const auto *SI = dyn_cast<StoreInst>(OnlyUser))
+      return SawMask && SI->isSimple() && SI->getValueOperand() == Current &&
+             SI->getPointerOperand()->stripPointerCasts() ==
+                 LI.getPointerOperand()->stripPointerCasts();
+
+    const auto *BO = dyn_cast<BinaryOperator>(OnlyUser);
+    if (!BO)
+      return false;
+
+    const Value *Other =
+        BO->getOperand(0) == Current ? BO->getOperand(1) : BO->getOperand(0);
+    if (BO->getOpcode() == Instruction::And) {
+      const auto *Mask = dyn_cast<ConstantInt>(Other);
+      if (SawMask || !Mask || Mask->isMinusOne())
+        return false;
+      SawMask = true;
+    } else if (BO->getOpcode() != Instruction::Or || !SawMask) {
+      return false;
+    }
+    Current = BO;
+  }
+  return false;
+}
+
+struct Query {
+  const Value *Object;
+  ByteRange Range;
+  MemoryLocation Location;
+};
+
+struct RootAndOffset {
+  const Value *Root;
+  int64_t Offset;
+};
+
+enum class AnalysisStage { Early, Late };
+
+enum class InitializationState {
+  Initialized,
+  Uninitialized,
+  MaybeUninitialized,
+  Unknown
+};
+
+struct SummaryKey {
+  const Function *F;
+  unsigned ArgNo;
+  ByteRange Range;
+};
+
+class UninitializedUseAnalyzer {
+  static constexpr unsigned MaxMemoryAccesses = 128;
+  static constexpr unsigned MaxSummaryDepth = 8;
+  static constexpr unsigned MaxSummaryInstructions = 1024;
+
+  const DataLayout &DL;
+  MemorySSA &MSSA;
+  MemorySSAWalker *Walker;
+  BatchAAResults BatchAA;
+  const TargetLibraryInfo *TLI;
+  AnalysisStage Stage;
+  SmallVector<SummaryKey, 8> SummaryStack;
+
+  std::optional<int64_t>
+  getOffsetFromRootImpl(const Value *Ptr, const Value *Root,
+                        SmallPtrSetImpl<const Value *> &Visited) const {
+    if (!Ptr->getType()->isPointerTy() || !Visited.insert(Ptr).second)
+      return std::nullopt;
+    scope_exit RemoveVisited([&] { Visited.erase(Ptr); });
+
+    int64_t OuterOffset = 0;
+    const Value *Base = GetPointerBaseWithConstantOffset(Ptr, OuterOffset, DL);
+    if (Base == Root)
+      return OuterOffset;
+
+    if (const auto *LI = dyn_cast<LoadInst>(Base)) {
+      int64_t SpillOffset = 0;
+      const auto *Spill = dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(
+          LI->getPointerOperand(), SpillOffset, DL));
+      if (!Spill || SpillOffset != 0 ||
+          !Spill->getAllocatedType()->isPointerTy())
+        return std::nullopt;
+
+      const StoreInst *Def = nullptr;
+      for (const User *U : Spill->users()) {
+        if (const auto *SI = dyn_cast<StoreInst>(U)) {
+          int64_t Offset = 0;
+          if (GetPointerBaseWithConstantOffset(SI->getPointerOperand(), Offset,
+                                               DL) != Spill ||
+              Offset != 0)
+            return std::nullopt;
+          if (Def)
+            return std::nullopt;
+          Def = SI;
+          continue;
+        }
+        if (isa<LoadInst>(U))
+          continue;
+        const auto *I = dyn_cast<Instruction>(U);
+        if (!I || (!I->isLifetimeStartOrEnd() && !I->isDroppable()))
+          return std::nullopt;
+      }
+
+      if (!Def || Def->getParent() != LI->getParent() || !Def->comesBefore(LI))
+        return std::nullopt;
+      std::optional<int64_t> StoredOffset =
+          getOffsetFromRootImpl(Def->getValueOperand(), Root, Visited);
+      if (!StoredOffset)
+        return std::nullopt;
+      int64_t Result;
+      if (AddOverflow(*StoredOffset, OuterOffset, Result))
+        return std::nullopt;
+      return Result;
+    }
+
+    auto MergeOffset = [&](auto Values) -> std::optional<int64_t> {
+      std::optional<int64_t> Common;
+      for (const Value *V : Values) {
+        std::optional<int64_t> Offset = getOffsetFromRootImpl(V, Root, Visited);
+        if (!Offset)
+          return std::nullopt;
+        if (Common && *Common != *Offset)
+          return std::nullopt;
+        Common = Offset;
+      }
+      if (!Common)
+        return std::nullopt;
+      int64_t Result;
+      if (AddOverflow(*Common, OuterOffset, Result))
+        return std::nullopt;
+      return Result;
+    };
+
+    if (const auto *PN = dyn_cast<PHINode>(Base))
+      return MergeOffset(PN->incoming_values());
+    if (const auto *SI = dyn_cast<SelectInst>(Base))
+      return MergeOffset(
+          ArrayRef<const Value *>{SI->getTrueValue(), SI->getFalseValue()});
+    return std::nullopt;
+  }
+
+  std::optional<int64_t> getOffsetFromRoot(const Value *Ptr,
+                                           const Value *Root) const {
+    SmallPtrSet<const Value *, 16> Visited;
+    return getOffsetFromRootImpl(Ptr, Root, Visited);
+  }
+
+  std::optional<RootAndOffset>
+  getRootAndOffsetImpl(const Value *Ptr,
+                       SmallPtrSetImpl<const Value *> &Visited) {
+    if (!Ptr->getType()->isPointerTy() || !Visited.insert(Ptr).second)
+      return std::nullopt;
+    scope_exit RemoveVisited([&] { Visited.erase(Ptr); });
+
+    int64_t OuterOffset = 0;
+    const Value *Base = GetPointerBaseWithConstantOffset(Ptr, OuterOffset, DL);
+    if (isa<AllocaInst, CallBase>(Base))
+      return RootAndOffset{Base, OuterOffset};
+
+    const auto *LI = dyn_cast<LoadInst>(Base);
+    if (!LI || !LI->isSimple())
+      return std::nullopt;
+
+    MemoryAccess *Use = MSSA.getMemoryAccess(LI);
+    if (!Use)
+      return std::nullopt;
+    auto *Def =
+        dyn_cast<MemoryDef>(Walker->getClobberingMemoryAccess(Use, BatchAA));
+    if (!Def || !Def->getMemoryInst())
+      return std::nullopt;
+    const auto *SI = dyn_cast<StoreInst>(Def->getMemoryInst());
+    if (!SI || !SI->getValueOperand()->getType()->isPointerTy() ||
+        BatchAA.alias(MemoryLocation::get(LI), MemoryLocation::get(SI)) !=
+            AliasResult::MustAlias)
+      return std::nullopt;
+
+    std::optional<RootAndOffset> Stored =
+        getRootAndOffsetImpl(SI->getValueOperand(), Visited);
+    if (!Stored)
+      return std::nullopt;
+    int64_t Offset;
+    if (AddOverflow(Stored->Offset, OuterOffset, Offset))
+      return std::nullopt;
+    Stored->Offset = Offset;
+    return Stored;
+  }
+
+  std::optional<RootAndOffset> getRootAndOffset(const Value *Ptr) {
+    SmallPtrSet<const Value *, 16> Visited;
+    return getRootAndOffsetImpl(Ptr, Visited);
+  }
+
+  bool isSeparateObject(const Value *Ptr, const Value *Root) const {
+    const Value *Object = getUnderlyingObject(Ptr);
+    return Object != Root &&
+           (isa<AllocaInst>(Object) || isa<GlobalValue>(Object));
+  }
+
+  bool isBenignPointerSpill(const Value *Ptr) const {
+    int64_t Offset = 0;
+    const auto *AI =
+        dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(Ptr, Offset, DL));
+    if (!AI || Offset != 0 || !AI->getAllocatedType()->isPointerTy())
+      return false;
+
+    for (const User *U : AI->users()) {
+      if (const auto *SI = dyn_cast<StoreInst>(U)) {
+        int64_t StoreOffset = 0;
+        if (GetPointerBaseWithConstantOffset(SI->getPointerOperand(),
+                                             StoreOffset, DL) != AI ||
+            StoreOffset != 0)
+          return false;
+        continue;
+      }
+      if (isa<LoadInst>(U))
+        continue;
+      const auto *I = dyn_cast<Instruction>(U);
+      if (!I || (!I->isLifetimeStartOrEnd() && !I->isDroppable()))
+        return false;
+    }
+    return true;
+  }
+
+  bool hasNonCallEscape(const Value *Root) const {
+    SmallVector<const Value *, 16> Worklist(1, Root);
+    SmallPtrSet<const Value *, 16> Visited;
+    while (!Worklist.empty()) {
+      const Value *V = Worklist.pop_back_val();
+      if (!Visited.insert(V).second)
+        continue;
+
+      for (const User *U : V->users()) {
+        const auto *I = dyn_cast<Instruction>(U);
+        if (!I)
+          return true;
+        if (isa<CallBase>(I) || I->isDroppable())
+          continue;
+        if (const auto *SI = dyn_cast<StoreInst>(I)) {
+          if (SI->getValueOperand() == V)
+            return true;
+          continue;
+        }
+        if (isa<LoadInst, ICmpInst>(I))
+          continue;
+        if (I->getType()->isPointerTy() &&
+            isa<GetElementPtrInst, BitCastInst, AddrSpaceCastInst, PHINode,
+                SelectInst, FreezeInst>(I)) {
+          Worklist.push_back(I);
+          continue;
+        }
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool writeDoesNotOverlap(const Value *Ptr, uint64_t Size, const Value *Root,
+                           ByteRange Target) const {
+    if (std::optional<int64_t> Offset = getOffsetFromRoot(Ptr, Root))
+      return !rangesOverlap({*Offset, Size}, Target);
+    return isSeparateObject(Ptr, Root);
+  }
+
+  std::optional<InitializationState> classifyWrite(const Value *Ptr,
+                                                   uint64_t Size,
+                                                   const Query &Q,
+                                                   bool Initializes) const {
+    std::optional<int64_t> Offset = getOffsetFromRoot(Ptr, Q.Object);
+    if (!Offset) {
+      if (isSeparateObject(Ptr, Q.Object))
+        return std::nullopt;
+      return InitializationState::Unknown;
+    }
+
+    ByteRange WriteRange{*Offset, Size};
+    if (!rangesOverlap(WriteRange, Q.Range))
+      return std::nullopt;
+    if (Initializes && rangeContains(WriteRange, Q.Range))
+      return InitializationState::Initialized;
+    return InitializationState::Unknown;
+  }
+
+  bool calleeDoesNotModify(const Function &Callee, unsigned ArgNo,
+                           ByteRange Target, unsigned Depth) {
+    if (Callee.isDeclaration() || ArgNo >= Callee.arg_size() ||
+        Depth >= MaxSummaryDepth ||
+        Callee.getInstructionCount() > MaxSummaryInstructions)
+      return false;
+
+    for (const SummaryKey &Key : SummaryStack)
+      if (Key.F == &Callee && Key.ArgNo == ArgNo &&
+          Key.Range.Offset == Target.Offset && Key.Range.Size == Target.Size)
+        return false;
+
+    SummaryStack.push_back({&Callee, ArgNo, Target});
+    scope_exit PopStack([&] { SummaryStack.pop_back(); });
+    const Argument *Root = Callee.getArg(ArgNo);
+
+    for (const Instruction &I : instructions(Callee)) {
+      if (const auto *SI = dyn_cast<StoreInst>(&I)) {
+        TypeSize Size = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+        if (Size.isScalable() ||
+            !writeDoesNotOverlap(SI->getPointerOperand(), Size.getFixedValue(),
+                                 Root, Target))
+          return false;
+
+        if (SI->getValueOperand()->getType()->isPointerTy() &&
+            getOffsetFromRoot(SI->getValueOperand(), Root) &&
+            !isBenignPointerSpill(SI->getPointerOperand()))
+          return false;
+        continue;
+      }
+
+      if (const auto *MI = dyn_cast<MemIntrinsic>(&I)) {
+        const auto *Length = dyn_cast<ConstantInt>(MI->getLength());
+        if (!Length || !writeDoesNotOverlap(
+                           MI->getDest(), Length->getZExtValue(), Root, Target))
+          return false;
+        continue;
+      }
+
+      if (const auto *RMW = dyn_cast<AtomicRMWInst>(&I)) {
+        TypeSize Size = DL.getTypeStoreSize(RMW->getValOperand()->getType());
+        if (Size.isScalable() ||
+            !writeDoesNotOverlap(RMW->getPointerOperand(), Size.getFixedValue(),
+                                 Root, Target))
+          return false;
+        continue;
+      }
+
+      if (const auto *CX = dyn_cast<AtomicCmpXchgInst>(&I)) {
+        TypeSize Size = DL.getTypeStoreSize(CX->getCompareOperand()->getType());
+        if (Size.isScalable() ||
+            !writeDoesNotOverlap(CX->getPointerOperand(), Size.getFixedValue(),
+                                 Root, Target))
+          return false;
+        continue;
+      }
+
+      if (const auto *CB = dyn_cast<CallBase>(&I)) {
+        if (CB->isLifetimeStartOrEnd() || CB->onlyReadsMemory())
+          continue;
+
+        const Function *Nested = CB->getCalledFunction();
+        for (unsigned I = 0; I < CB->arg_size(); ++I) {
+          const Value *Arg = CB->getArgOperand(I);
+          if (!Arg->getType()->isPointerTy())
+            continue;
+          std::optional<int64_t> Offset = getOffsetFromRoot(Arg, Root);
+          if (!Offset) {
+            if (!isa<ConstantPointerNull>(Arg) && !isSeparateObject(Arg, Root))
+              return false;
+            continue;
+          }
+          int64_t RelativeOffset;
+          if (!Nested || I >= Nested->arg_size() ||
+              SubOverflow(Target.Offset, *Offset, RelativeOffset) ||
+              !calleeDoesNotModify(*Nested, I, {RelativeOffset, Target.Size},
+                                   Depth + 1))
+            return false;
+        }
+        continue;
+      }
+
+      if (const auto *PTI = dyn_cast<PtrToIntInst>(&I)) {
+        if (getOffsetFromRoot(PTI->getPointerOperand(), Root))
+          return false;
+      }
+
+      if (const auto *RI = dyn_cast<ReturnInst>(&I)) {
+        const Value *ReturnValue = RI->getReturnValue();
+        if (ReturnValue && ReturnValue->getType()->isPointerTy() &&
+            getOffsetFromRoot(ReturnValue, Root))
+          return false;
+      }
+
+      if (I.mayWriteToMemory())
+        return false;
+    }
+    return true;
+  }
+
+  bool callDoesNotModify(const CallBase &CB, const Query &Q) {
+    if (CB.onlyReadsMemory())
+      return true;
+    const Function *Callee = CB.getCalledFunction();
+    if (!Callee)
+      return false;
+
+    bool FoundObjectArgument = false;
+    for (unsigned I = 0; I < CB.arg_size(); ++I) {
+      const Value *Arg = CB.getArgOperand(I);
+      if (!Arg->getType()->isPointerTy())
+        continue;
+      std::optional<int64_t> ArgOffset = getOffsetFromRoot(Arg, Q.Object);
+      if (!ArgOffset)
+        continue;
+      FoundObjectArgument = true;
+      int64_t RelativeOffset;
+      if (I >= Callee->arg_size() ||
+          SubOverflow(Q.Range.Offset, *ArgOffset, RelativeOffset) ||
+          !calleeDoesNotModify(*Callee, I, {RelativeOffset, Q.Range.Size}, 0))
+        return false;
+    }
+    return FoundObjectArgument;
+  }
+
+  std::optional<Query> getMemcpySourceQuery(const MemCpyInst &Copy,
+                                            const Query &Q) const {
+    const auto *Length = dyn_cast<ConstantInt>(Copy.getLength());
+    std::optional<int64_t> DestOffset =
+        getOffsetFromRoot(Copy.getDest(), Q.Object);
+    if (!Length || !DestOffset ||
+        !rangeContains({*DestOffset, Length->getZExtValue()}, Q.Range))
+      return std::nullopt;
+
+    int64_t OffsetInCopy;
+    if (SubOverflow(Q.Range.Offset, *DestOffset, OffsetInCopy))
+      return std::nullopt;
+
+    int64_t SourceBaseOffset = 0;
+    auto *SourceObject = dyn_cast<AllocaInst>(GetPointerBaseWithConstantOffset(
+        Copy.getSource(), SourceBaseOffset, DL));
+    if (!SourceObject || !SourceObject->isStaticAlloca() ||
+        SourceObject->isArrayAllocation() ||
+        !SourceObject->getAllocatedType()->isStructTy() ||
+        hasNonCallEscape(SourceObject))
+      return std::nullopt;
+
+    int64_t SourceOffset;
+    if (AddOverflow(SourceBaseOffset, OffsetInCopy, SourceOffset))
+      return std::nullopt;
+    TypeSize ObjectSize = DL.getTypeAllocSize(SourceObject->getAllocatedType());
+    if (ObjectSize.isScalable() ||
+        !rangeContains({0, ObjectSize.getFixedValue()},
+                       {SourceOffset, Q.Range.Size}))
+      return std::nullopt;
+
+    return Query{SourceObject,
+                 {SourceOffset, Q.Range.Size},
+                 MemoryLocation(SourceObject, ObjectSize)};
+  }
+
+  std::optional<InitializationState> getMemoryDefState(const Instruction &I,
+                                                       const Query &Q) {
+    if (I.isLifetimeStartOrEnd())
+      return std::nullopt;
+
+    if (const auto *SI = dyn_cast<StoreInst>(&I)) {
+      TypeSize Size = DL.getTypeStoreSize(SI->getValueOperand()->getType());
+      if (Size.isScalable())
+        return InitializationState::Unknown;
+      return classifyWrite(SI->getPointerOperand(), Size.getFixedValue(), Q,
+                           /*Initializes=*/true);
+    }
+
+    if (const auto *MI = dyn_cast<MemIntrinsic>(&I)) {
+      const auto *Length = dyn_cast<ConstantInt>(MI->getLength());
+      if (!Length)
+        return InitializationState::Unknown;
+      return classifyWrite(MI->getDest(), Length->getZExtValue(), Q,
+                           /*Initializes=*/isa<MemSetInst>(MI));
+    }
+
+    if (const auto *CB = dyn_cast<CallBase>(&I)) {
+      if (callDoesNotModify(*CB, Q))
+        return std::nullopt;
+      return InitializationState::Unknown;
+    }
+
+    return InitializationState::Unknown;
+  }
+
+  InitializationState
+  getInitializationState(MemoryAccess *Access, const Query &Q,
+                         SmallPtrSetImpl<MemoryAccess *> &Active,
+                         unsigned &NumAccesses) {
+    if (++NumAccesses > MaxMemoryAccesses || !Active.insert(Access).second)
+      return InitializationState::Unknown;
+    scope_exit RemoveActive([&] { Active.erase(Access); });
+
+    if (MSSA.isLiveOnEntryDef(Access))
+      return InitializationState::Uninitialized;
+
+    if (auto *Phi = dyn_cast<MemoryPhi>(Access)) {
+      if (Phi->getNumIncomingValues() == 0)
+        return InitializationState::Unknown;
+      std::optional<InitializationState> Merged;
+      for (unsigned I = 0; I < Phi->getNumIncomingValues(); ++I) {
+        MemoryAccess *Incoming = Phi->getIncomingValue(I);
+        MemoryAccess *Clobber =
+            Walker->getClobberingMemoryAccess(Incoming, Q.Location, BatchAA);
+        InitializationState State =
+            getInitializationState(Clobber, Q, Active, NumAccesses);
+        if (State == InitializationState::Unknown)
+          return State;
+        if (!Merged)
+          Merged = State;
+        else if (*Merged != State)
+          Merged = InitializationState::MaybeUninitialized;
+      }
+      return *Merged;
+    }
+
+    auto *Def = dyn_cast<MemoryDef>(Access);
+    if (!Def)
+      return InitializationState::Unknown;
+
+    if (Def->getMemoryInst() == Q.Object)
+      return InitializationState::Uninitialized;
+
+    if (const auto *Copy = dyn_cast<MemCpyInst>(Def->getMemoryInst())) {
+      if (std::optional<Query> Source = getMemcpySourceQuery(*Copy, Q)) {
+        MemoryAccess *SourceClobber = Walker->getClobberingMemoryAccess(
+            Def->getDefiningAccess(), Source->Location, BatchAA);
+        return getInitializationState(SourceClobber, *Source, Active,
+                                      NumAccesses);
+      }
+    }
+
+    std::optional<InitializationState> State =
+        getMemoryDefState(*Def->getMemoryInst(), Q);
+    if (State)
+      return *State;
+
+    MemoryAccess *Clobber = Walker->getClobberingMemoryAccess(
+        Def->getDefiningAccess(), Q.Location, BatchAA);
+    return getInitializationState(Clobber, Q, Active, NumAccesses);
+  }
+
+public:
+  UninitializedUseAnalyzer(Function &F, AAResults &AA, MemorySSA &MSSA,
+                           const TargetLibraryInfo *TLI, AnalysisStage Stage)
+      : DL(F.getDataLayout()), MSSA(MSSA), Walker(MSSA.getWalker()),
+        BatchAA(AA), TLI(TLI), Stage(Stage) {}
+
+  InitializationState getInitializationState(LoadInst &LI) {
+    if (!LI.isSimple() ||
+        !(LI.getType()->isIntegerTy() || LI.getType()->isFloatingPointTy() ||
+          LI.getType()->isPointerTy()))
+      return InitializationState::Unknown;
+
+    if (Stage == AnalysisStage::Early && isMaskedReadModifyWrite(LI))
+      return InitializationState::Unknown;
+
+    TypeSize Size = DL.getTypeStoreSize(LI.getType());
+    if (Size.isScalable())
+      return InitializationState::Unknown;
+
+    const Value *Object;
+    int64_t Offset;
+    if (Stage == AnalysisStage::Early) {
+      const Value *AccessPtr = LI.getPointerOperand();
+      if (!isa<GetElementPtrInst>(AccessPtr))
+        return InitializationState::Unknown;
+
+      auto *AI = dyn_cast<AllocaInst>(
+          GetPointerBaseWithConstantOffset(AccessPtr, Offset, DL));
+      if (!AI || !AI->isStaticAlloca() || AI->isArrayAllocation() ||
+          !AI->getAllocatedType()->isStructTy() || hasNonCallEscape(AI))
+        return InitializationState::Unknown;
+      Object = AI;
+    } else {
+      if (LI.getDebugLoc() && !LI.getDebugLoc().getInlinedAt())
+        return InitializationState::Unknown;
+
+      std::optional<RootAndOffset> Root =
+          getRootAndOffset(LI.getPointerOperand());
+      if (!Root)
+        return InitializationState::Unknown;
+      Object = Root->Root;
+      Offset = Root->Offset;
+
+      uint64_t ObjectSize;
+      if (const auto *AI = dyn_cast<AllocaInst>(Object)) {
+        TypeSize Size = DL.getTypeAllocSize(AI->getAllocatedType());
+        if (!AI->isStaticAlloca() || AI->isArrayAllocation() ||
+            !AI->getAllocatedType()->isStructTy() || Size.isScalable() ||
+            hasNonCallEscape(AI))
+          return InitializationState::Unknown;
+        ObjectSize = Size.getFixedValue();
+      } else {
+        const auto *Alloc = dyn_cast<CallBase>(Object);
+        if (!Alloc || !TLI ||
+            !isa_and_nonnull<UndefValue>(
+                getInitialValueOfAllocation(Alloc, TLI, LI.getType())))
+          return InitializationState::Unknown;
+        std::optional<APInt> Size = getAllocSize(Alloc, TLI);
+        std::optional<uint64_t> FixedSize =
+            Size ? Size->tryZExtValue() : std::nullopt;
+        if (!FixedSize)
+          return InitializationState::Unknown;
+        ObjectSize = *FixedSize;
+      }
+
+      if (!rangeContains({0, ObjectSize}, {Offset, Size.getFixedValue()}))
+        return InitializationState::Unknown;
+    }
+
+    MemoryAccess *Use = MSSA.getMemoryAccess(&LI);
+    if (!Use)
+      return InitializationState::Unknown;
+    Query Q{Object, {Offset, Size.getFixedValue()}, MemoryLocation::get(&LI)};
+    MemoryAccess *Clobber = Walker->getClobberingMemoryAccess(Use, BatchAA);
+    SmallPtrSet<MemoryAccess *, 16> Active;
+    unsigned NumAccesses = 0;
+    return getInitializationState(Clobber, Q, Active, NumAccesses);
+  }
+};
+
+} // namespace
+
+static PreservedAnalyses
+runUninitializedAnalysis(Function &F, FunctionAnalysisManager &AM,
+                         AnalysisStage Stage,
+                         WarnUninitializedDiagnosticState *DiagnosticState) {
+  if (F.isDeclaration())
+    return PreservedAnalyses::all();
+
+  AAResults &AA = AM.getResult<AAManager>(F);
+  DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
+  MemorySSA &MSSA = AM.getResult<MemorySSAAnalysis>(F).getMSSA();
+  const TargetLibraryInfo *TLI = Stage == AnalysisStage::Late
+                                     ? &AM.getResult<TargetLibraryAnalysis>(F)
+                                     : nullptr;
+  UninitializedUseAnalyzer Analyzer(F, AA, MSSA, TLI, Stage);
+  for (BasicBlock &BB : F) {
+    if (!DT.isReachableFromEntry(&BB))
+      continue;
+    for (Instruction &I : BB) {
+      auto *LI = dyn_cast<LoadInst>(&I);
+      // Post-inlining IR may contain speculative loads whose undef or poison
+      // result is masked before it can trigger undefined behavior.
+      if (!LI || (DiagnosticState && DiagnosticState->contains(LI)) ||
+          (Stage == AnalysisStage::Late &&
+           !programUndefinedIfUndefOrPoison(LI)))
+        continue;
+      InitializationState State = Analyzer.getInitializationState(*LI);
+      if (State == InitializationState::Uninitialized ||
+          State == InitializationState::MaybeUninitialized) {
+        if (DiagnosticState)
+          DiagnosticState->insert(LI);
+        F.getContext().diagnose(DiagnosticInfoUninitialized(
+            LI, State == InitializationState::MaybeUninitialized));
+      }
+    }
+  }
+
+  return PreservedAnalyses::all();
+}
+
+PreservedAnalyses WarnUninitializedEarlyPass::run(Function &F,
+                                                  FunctionAnalysisManager &AM) {
+  return runUninitializedAnalysis(F, AM, AnalysisStage::Early, State.get());
+}
+
+PreservedAnalyses WarnUninitializedLatePass::run(Function &F,
+                                                 FunctionAnalysisManager &AM) {
+  return runUninitializedAnalysis(F, AM, AnalysisStage::Late, State.get());
+}
diff --git a/llvm/test/Transforms/WarnUninitialized/basic.ll b/llvm/test/Transforms/WarnUninitialized/basic.ll
new file mode 100644
index 0000000000000..6a2114e47ffba
--- /dev/null
+++ b/llvm/test/Transforms/WarnUninitialized/basic.ll
@@ -0,0 +1,318 @@
+; RUN: opt -passes=warn-uninitialized-early -disable-output %s 2>&1 | FileCheck %s
+; RUN: opt -passes=strip -S %s | opt -passes=warn-uninitialized-early \
+; RUN:   -disable-output - 2>&1 | FileCheck %s --check-prefix=NO-DEBUG
+
+; NO-DEBUG: warning: <unknown>:0:0: field is uninitialized when used here
+
+%pair = type { i32, i32 }
+
+ at escaped = global ptr null
+
+declare void @opaque(ptr)
+declare void @readonly(ptr) memory(read)
+declare void @consume(i32)
+declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)
+
+define void @no_write() !dbg !5 {
+entry:
+  %p = alloca %pair, align 4
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !20
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:10:7: field is uninitialized when used here
+
+define void @same_field_write() !dbg !6 {
+entry:
+  %p = alloca %pair, align 4
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  store i32 1, ptr %field, align 4
+  %value = load i32, ptr %field, align 4, !dbg !21
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:20:7
+
+define void @sibling_field_write() !dbg !7 {
+entry:
+  %p = alloca %pair, align 4
+  %sibling = getelementptr inbounds %pair, ptr %p, i64 0, i32 1
+  store i32 1, ptr %sibling, align 4
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !22
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:30:7: field is uninitialized when used here
+
+define void @unknown_call() !dbg !8 {
+entry:
+  %p = alloca %pair, align 4
+  call void @opaque(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !23
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:40:7
+
+define void @readonly_call() !dbg !9 {
+entry:
+  %p = alloca %pair, align 4
+  call void @readonly(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !24
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:50:7: field is uninitialized when used here
+
+define void @conditional_write(i1 %condition) !dbg !10 {
+entry:
+  %p = alloca %pair, align 4
+  br i1 %condition, label %init, label %merge
+
+init:
+  %init.field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  store i32 1, ptr %init.field, align 4
+  br label %merge
+
+merge:
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !25
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:60:7: field may be uninitialized when used here
+
+define void @conditional_sibling_write(i1 %condition) !dbg !11 {
+entry:
+  %p = alloca %pair, align 4
+  br i1 %condition, label %init, label %merge
+
+init:
+  %sibling = getelementptr inbounds %pair, ptr %p, i64 0, i32 1
+  store i32 1, ptr %sibling, align 4
+  br label %merge
+
+merge:
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !26
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:70:7: field is uninitialized when used here
+
+define internal void @empty(ptr %this) {
+entry:
+  %slot = alloca ptr, align 8
+  store ptr %this, ptr %slot, align 8
+  %reload = load ptr, ptr %slot, align 8
+  ret void
+}
+
+define internal void @write_sibling(ptr %this) {
+entry:
+  %slot = alloca ptr, align 8
+  store ptr %this, ptr %slot, align 8
+  %reload = load ptr, ptr %slot, align 8
+  %sibling = getelementptr inbounds %pair, ptr %reload, i64 0, i32 1
+  store i32 1, ptr %sibling, align 4
+  ret void
+}
+
+define internal void @write_field(ptr %this) {
+entry:
+  %slot = alloca ptr, align 8
+  store ptr %this, ptr %slot, align 8
+  %reload = load ptr, ptr %slot, align 8
+  %field = getelementptr inbounds %pair, ptr %reload, i64 0, i32 0
+  store i32 1, ptr %field, align 4
+  ret void
+}
+
+define void @empty_callee() !dbg !12 {
+entry:
+  %p = alloca %pair, align 4
+  call void @empty(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !27
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:80:7: field is uninitialized when used here
+
+define void @sibling_callee() !dbg !13 {
+entry:
+  %p = alloca %pair, align 4
+  call void @write_sibling(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !28
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:90:7: field is uninitialized when used here
+
+define void @field_callee() !dbg !14 {
+entry:
+  %p = alloca %pair, align 4
+  call void @write_field(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !29
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:100:7
+
+
+define void @copy_uninitialized() !dbg !15 {
+entry:
+  %source = alloca %pair, align 4
+  %dest = alloca %pair, align 4
+  call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+  %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !30
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:110:7: field is uninitialized when used here
+
+define void @copy_initialized() !dbg !16 {
+entry:
+  %source = alloca %pair, align 4
+  %dest = alloca %pair, align 4
+  %source.field = getelementptr inbounds %pair, ptr %source, i64 0, i32 0
+  store i32 1, ptr %source.field, align 4
+  call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+  %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !31
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:120:7
+
+define void @copy_sibling_initialized() !dbg !17 {
+entry:
+  %source = alloca %pair, align 4
+  %dest = alloca %pair, align 4
+  %source.sibling = getelementptr inbounds %pair, ptr %source, i64 0, i32 1
+  store i32 1, ptr %source.sibling, align 4
+  call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %source, i64 8, i1 false)
+  %field = getelementptr inbounds %pair, ptr %dest, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !32
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:130:7: field is uninitialized when used here
+
+define void @escaped_address() !dbg !18 {
+entry:
+  %p = alloca %pair, align 4
+  store ptr %p, ptr @escaped, align 8
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !33
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:140:7
+
+define internal ptr @return_pointer(ptr %pointer) {
+entry:
+  ret ptr %pointer
+}
+
+define void @returned_address() !dbg !19 {
+entry:
+  %p = alloca %pair, align 4
+  %escaped = call ptr @return_pointer(ptr %p)
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !34
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:150:7
+
+define void @unreachable_block() !dbg !35 {
+entry:
+  %p = alloca %pair, align 4
+  ret void
+
+dead:
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %value = load i32, ptr %field, align 4, !dbg !36
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:160:7
+
+define void @masked_read_modify_write() !dbg !37 {
+entry:
+  %p = alloca %pair, align 4
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %old = load i32, ptr %field, align 4, !dbg !38
+  %preserved = and i32 %old, -8
+  %new = or i32 %preserved, 3
+  store i32 %new, ptr %field, align 4
+  ret void
+}
+
+; CHECK-NOT: warn-uninitialized.cpp:170:7
+
+define void @masked_read_modify_write_with_use() !dbg !39 {
+entry:
+  %p = alloca %pair, align 4
+  %field = getelementptr inbounds %pair, ptr %p, i64 0, i32 0
+  %old = load i32, ptr %field, align 4, !dbg !40
+  %preserved = and i32 %old, -8
+  %new = or i32 %preserved, 3
+  store i32 %new, ptr %field, align 4
+  call void @consume(i32 %old)
+  ret void
+}
+
+; CHECK: warning: warn-uninitialized.cpp:180:7: field is uninitialized when used here
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
+!1 = !DIFile(filename: "warn-uninitialized.cpp", directory: "")
+!2 = !DISubroutineType(types: !4)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{}
+!5 = distinct !DISubprogram(name: "no_write", scope: !1, file: !1, line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!6 = distinct !DISubprogram(name: "same_field_write", scope: !1, file: !1, line: 2, type: !2, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "sibling_field_write", scope: !1, file: !1, line: 3, type: !2, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)
+!8 = distinct !DISubprogram(name: "unknown_call", scope: !1, file: !1, line: 4, type: !2, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0)
+!9 = distinct !DISubprogram(name: "readonly_call", scope: !1, file: !1, line: 5, type: !2, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!10 = distinct !DISubprogram(name: "conditional_write", scope: !1, file: !1, line: 6, type: !2, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!11 = distinct !DISubprogram(name: "conditional_sibling_write", scope: !1, file: !1, line: 7, type: !2, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!12 = distinct !DISubprogram(name: "empty_callee", scope: !1, file: !1, line: 8, type: !2, scopeLine: 8, spFlags: DISPFlagDefinition, unit: !0)
+!13 = distinct !DISubprogram(name: "sibling_callee", scope: !1, file: !1, line: 9, type: !2, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0)
+!14 = distinct !DISubprogram(name: "field_callee", scope: !1, file: !1, line: 10, type: !2, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !0)
+!15 = distinct !DISubprogram(name: "copy_uninitialized", scope: !1, file: !1, line: 11, type: !2, scopeLine: 11, spFlags: DISPFlagDefinition, unit: !0)
+!16 = distinct !DISubprogram(name: "copy_initialized", scope: !1, file: !1, line: 12, type: !2, scopeLine: 12, spFlags: DISPFlagDefinition, unit: !0)
+!17 = distinct !DISubprogram(name: "copy_sibling_initialized", scope: !1, file: !1, line: 13, type: !2, scopeLine: 13, spFlags: DISPFlagDefinition, unit: !0)
+!18 = distinct !DISubprogram(name: "escaped_address", scope: !1, file: !1, line: 14, type: !2, scopeLine: 14, spFlags: DISPFlagDefinition, unit: !0)
+!19 = distinct !DISubprogram(name: "returned_address", scope: !1, file: !1, line: 15, type: !2, scopeLine: 15, spFlags: DISPFlagDefinition, unit: !0)
+!20 = !DILocation(line: 10, column: 7, scope: !5)
+!21 = !DILocation(line: 20, column: 7, scope: !6)
+!22 = !DILocation(line: 30, column: 7, scope: !7)
+!23 = !DILocation(line: 40, column: 7, scope: !8)
+!24 = !DILocation(line: 50, column: 7, scope: !9)
+!25 = !DILocation(line: 60, column: 7, scope: !10)
+!26 = !DILocation(line: 70, column: 7, scope: !11)
+!27 = !DILocation(line: 80, column: 7, scope: !12)
+!28 = !DILocation(line: 90, column: 7, scope: !13)
+!29 = !DILocation(line: 100, column: 7, scope: !14)
+!30 = !DILocation(line: 110, column: 7, scope: !15)
+!31 = !DILocation(line: 120, column: 7, scope: !16)
+!32 = !DILocation(line: 130, column: 7, scope: !17)
+!33 = !DILocation(line: 140, column: 7, scope: !18)
+!34 = !DILocation(line: 150, column: 7, scope: !19)
+!35 = distinct !DISubprogram(name: "unreachable_block", scope: !1, file: !1, line: 16, type: !2, scopeLine: 16, spFlags: DISPFlagDefinition, unit: !0)
+!36 = !DILocation(line: 160, column: 7, scope: !35)
+!37 = distinct !DISubprogram(name: "masked_read_modify_write", scope: !1, file: !1, line: 17, type: !2, scopeLine: 17, spFlags: DISPFlagDefinition, unit: !0)
+!38 = !DILocation(line: 170, column: 7, scope: !37)
+!39 = distinct !DISubprogram(name: "masked_read_modify_write_with_use", scope: !1, file: !1, line: 18, type: !2, scopeLine: 18, spFlags: DISPFlagDefinition, unit: !0)
+!40 = !DILocation(line: 180, column: 7, scope: !39)
diff --git a/llvm/test/Transforms/WarnUninitialized/late.ll b/llvm/test/Transforms/WarnUninitialized/late.ll
new file mode 100644
index 0000000000000..de7eb124d959f
--- /dev/null
+++ b/llvm/test/Transforms/WarnUninitialized/late.ll
@@ -0,0 +1,181 @@
+; RUN: opt -passes=warn-uninitialized-late -disable-output %s 2>&1 | FileCheck %s
+; RUN: opt -passes=strip -S %s | opt -passes=warn-uninitialized-late \
+; RUN:   -disable-output - 2>&1 | FileCheck %s --check-prefix=NO-DEBUG
+
+; NO-DEBUG: warning: <unknown>:0:0: field is uninitialized when used here
+
+target triple = "x86_64-unknown-linux-gnu"
+
+%pair = type { i32, i32 }
+
+declare noalias ptr @malloc(i64) nounwind allockind("alloc,uninitialized") allocsize(0)
+declare noalias ptr @calloc(i64, i64) nounwind allockind("alloc,zeroed") allocsize(0,1)
+declare void @opaque(ptr)
+declare void @consume(i32 noundef)
+
+define void @stack_uninitialized() !dbg !5 {
+entry:
+  %object = alloca %pair, align 4
+  %value = load i32, ptr %object, align 4, !dbg !20
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK: warning: late.cpp:10:7: field is uninitialized when used here
+
+define void @heap_uninitialized() !dbg !6 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  %value = load i32, ptr %object, align 4, !dbg !21
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK: warning: late.cpp:20:7: field is uninitialized when used here
+
+define void @heap_sibling_initialized() !dbg !7 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  %sibling = getelementptr i8, ptr %object, i64 4
+  store i32 1, ptr %sibling, align 4
+  %value = load i32, ptr %object, align 4, !dbg !22
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK: warning: late.cpp:30:7: field is uninitialized when used here
+
+define void @heap_pointer_spill() !dbg !8 {
+entry:
+  %slot = alloca ptr, align 8
+  %allocation = call ptr @malloc(i64 24)
+  %object = getelementptr i8, ptr %allocation, i64 16
+  store ptr %object, ptr %slot, align 8
+  %restored = load ptr, ptr %slot, align 8
+  %value = load i32, ptr %restored, align 4, !dbg !23
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK: warning: late.cpp:40:7: field is uninitialized when used here
+
+define void @heap_initialized() !dbg !9 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  store i32 1, ptr %object, align 4
+  %value = load i32, ptr %object, align 4, !dbg !24
+  ret void
+}
+; CHECK-NOT: late.cpp:50:7
+
+define void @heap_conditionally_initialized(i1 %condition) !dbg !10 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  br i1 %condition, label %initialize, label %merge
+initialize:
+  store i32 1, ptr %object, align 4
+  br label %merge
+merge:
+  %value = load i32, ptr %object, align 4, !dbg !25
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK: warning: late.cpp:60:7: field may be uninitialized when used here
+
+define void @heap_unknown_call() !dbg !11 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  call void @opaque(ptr %object)
+  %value = load i32, ptr %object, align 4, !dbg !26
+  ret void
+}
+; CHECK-NOT: late.cpp:70:7
+
+define void @zeroed_allocation() !dbg !12 {
+entry:
+  %object = call ptr @calloc(i64 1, i64 8)
+  %value = load i32, ptr %object, align 4, !dbg !27
+  ret void
+}
+; CHECK-NOT: late.cpp:80:7
+
+define void @not_inlined() !dbg !13 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  %value = load i32, ptr %object, align 4, !dbg !28
+  ret void
+}
+; CHECK-NOT: late.cpp:90:7
+
+define void @unreachable_block() !dbg !15 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  ret void
+
+dead:
+  %value = load i32, ptr %object, align 4, !dbg !40
+  call void @consume(i32 %value)
+  ret void
+}
+; CHECK-NOT: late.cpp:100:7
+
+define void @bitfield_read_modify_write() !dbg !16 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  %old = load i32, ptr %object, align 4, !dbg !41
+  %preserved = and i32 %old, -16
+  %new = or i32 %preserved, 7
+  store i32 %new, ptr %object, align 4
+  ret void
+}
+; CHECK-NOT: late.cpp:110:7
+
+define void @masked_load() !dbg !17 {
+entry:
+  %object = call ptr @malloc(i64 8)
+  %value = load i32, ptr %object, align 4, !dbg !42
+  %selected = select i1 false, i32 %value, i32 0
+  call void @consume(i32 %selected)
+  ret void
+}
+; CHECK-NOT: late.cpp:120:7
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "test", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
+!1 = !DIFile(filename: "late.cpp", directory: "")
+!2 = !DISubroutineType(types: !4)
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{}
+!5 = distinct !DISubprogram(name: "stack_uninitialized", scope: !1, file: !1, line: 1, type: !2, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!6 = distinct !DISubprogram(name: "heap_uninitialized", scope: !1, file: !1, line: 2, type: !2, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "heap_sibling_initialized", scope: !1, file: !1, line: 3, type: !2, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)
+!8 = distinct !DISubprogram(name: "heap_pointer_spill", scope: !1, file: !1, line: 4, type: !2, scopeLine: 4, spFlags: DISPFlagDefinition, unit: !0)
+!9 = distinct !DISubprogram(name: "heap_initialized", scope: !1, file: !1, line: 5, type: !2, scopeLine: 5, spFlags: DISPFlagDefinition, unit: !0)
+!10 = distinct !DISubprogram(name: "heap_conditionally_initialized", scope: !1, file: !1, line: 6, type: !2, scopeLine: 6, spFlags: DISPFlagDefinition, unit: !0)
+!11 = distinct !DISubprogram(name: "heap_unknown_call", scope: !1, file: !1, line: 7, type: !2, scopeLine: 7, spFlags: DISPFlagDefinition, unit: !0)
+!12 = distinct !DISubprogram(name: "zeroed_allocation", scope: !1, file: !1, line: 8, type: !2, scopeLine: 8, spFlags: DISPFlagDefinition, unit: !0)
+!13 = distinct !DISubprogram(name: "not_inlined", scope: !1, file: !1, line: 9, type: !2, scopeLine: 9, spFlags: DISPFlagDefinition, unit: !0)
+!14 = distinct !DISubprogram(name: "use", scope: !1, file: !1, line: 100, type: !2, scopeLine: 100, spFlags: DISPFlagDefinition, unit: !0)
+!15 = distinct !DISubprogram(name: "unreachable_block", scope: !1, file: !1, line: 10, type: !2, scopeLine: 10, spFlags: DISPFlagDefinition, unit: !0)
+!16 = distinct !DISubprogram(name: "bitfield_read_modify_write", scope: !1, file: !1, line: 11, type: !2, scopeLine: 11, spFlags: DISPFlagDefinition, unit: !0)
+!17 = distinct !DISubprogram(name: "masked_load", scope: !1, file: !1, line: 12, type: !2, scopeLine: 12, spFlags: DISPFlagDefinition, unit: !0)
+!20 = !DILocation(line: 10, column: 7, scope: !14, inlinedAt: !30)
+!21 = !DILocation(line: 20, column: 7, scope: !14, inlinedAt: !31)
+!22 = !DILocation(line: 30, column: 7, scope: !14, inlinedAt: !32)
+!23 = !DILocation(line: 40, column: 7, scope: !14, inlinedAt: !33)
+!24 = !DILocation(line: 50, column: 7, scope: !14, inlinedAt: !34)
+!25 = !DILocation(line: 60, column: 7, scope: !14, inlinedAt: !35)
+!26 = !DILocation(line: 70, column: 7, scope: !14, inlinedAt: !36)
+!27 = !DILocation(line: 80, column: 7, scope: !14, inlinedAt: !37)
+!28 = !DILocation(line: 90, column: 7, scope: !13)
+!30 = !DILocation(line: 1, column: 1, scope: !5)
+!31 = !DILocation(line: 2, column: 1, scope: !6)
+!32 = !DILocation(line: 3, column: 1, scope: !7)
+!33 = !DILocation(line: 4, column: 1, scope: !8)
+!34 = !DILocation(line: 5, column: 1, scope: !9)
+!35 = !DILocation(line: 6, column: 1, scope: !10)
+!36 = !DILocation(line: 7, column: 1, scope: !11)
+!37 = !DILocation(line: 8, column: 1, scope: !12)
+!40 = !DILocation(line: 100, column: 7, scope: !14, inlinedAt: !43)
+!41 = !DILocation(line: 110, column: 7, scope: !14, inlinedAt: !44)
+!42 = !DILocation(line: 120, column: 7, scope: !14, inlinedAt: !45)
+!43 = !DILocation(line: 10, column: 1, scope: !15)
+!44 = !DILocation(line: 11, column: 1, scope: !16)
+!45 = !DILocation(line: 12, column: 1, scope: !17)



More information about the llvm-commits mailing list