[llvm] [TySan] Support the new size-aware TBAA metadata format (PR #226568)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 11:55:16 PDT 2026


https://github.com/OfekShilon created https://github.com/llvm/llvm-project/pull/226568

Fixes #226169

TypeSanitizer only understands the old TBAA type-node layout, where the type name is the first operand. Under `-new-struct-path-tbaa` the first operand is the parent type node and the name moves to operand 2, so `generateBaseTypeDescriptor()` fails to find a name and returns false. Its
caller then abandons instrumentation for the whole function:

```c++
if (!generateTypeDescriptor(MD, TypeDescriptors, TypeNames, M))
  return Res; // Giving up.
```
So -fsanitize=type combined with -new-struct-path-tbaa silently produces no instrumentation at all — no `__tysan_check` calls, no `__tysan_v1_*` descriptors — with no diagnostic, and every type-aliasing violation goes undetected. This affects plain scalar accesses as well as structs, leaving
TySan entirely non-functional in that mode.

Commit 1: "[IR][NFC] Share the TBAA metadata node readers"
The `!tbaa` layout was directly coded in four places: the node wrappers in TypeBasedAliasAnalysis.cpp, the structural checks in Verifier.cpp, createMutableTBAAAccessTag() in MDBuilder.cpp, and TypeSanitizer.cpp. This commit moves TBAANodeImpl, TBAAStructTagNodeImpl, TBAAStructTypeNode and
isNewFormatTypeNode out of the anonymous namespace in TypeBasedAliasAnalysis.cpp into a new header, llvm/IR/TBAAMetadata.h, next to the MDBuilder methods that create these nodes, and uses them from
Verifier.cpp and MDBuilder.cpp.

isNewFormatTypeNode() now tolerates a null node and a null first operand, making it a superset of the copy it replaces in Verifier.cpp.
TBAAStructTypeNode::getFieldOffset() is new; field offsets were previously only reachable through getField()'s offset-walking logic.
Verifier.cpp deliberately keeps its own field-list walk. Its job is to reject malformed metadata, so it cannot use accessors that assume the invariants it exists to check; only the format predicate is shared.
createMutableTBAAAccessTag() had no test coverage anywhere in the tree, and
the immutability flag it looks up sits at a different operand in each format,
so a unit test for the four tag shapes is added alongside.

No functional change intended.

Commit 2 — "[TySan] Support the new size-aware TBAA metadata format"
TySan is the fourth site that directly used `!tbaa` layout, and wasn't addressed in commit 1 because
this isn't NFC: reading through the shared accessors is precisely what teaches it the second layout.

In the old layout a scalar type node's parent occupies the first field slot, so walking the field list covers scalars and structs alike. The new layout keeps the parent in its own operand and gives a scalar no fields at all, so `collectTypeMembers()` has to contribute it explicitly as member 0 — the runtime depends on this, as `getRootTD()` follows `Members[0]` to find the root of the TBAA tree.



>From b85b63ca29a2fb9dff60f91ab39a4bb1269b9ae7 Mon Sep 17 00:00:00 2001
From: Ofek Shilon <ofekshilon at gmail.com>
Date: Fri, 25 Sep 2026 17:05:57 +0300
Subject: [PATCH 1/2] [IR][NFC] Share the TBAA metadata node readers

The !tbaa operand layout is open-coded in four places: the node wrappers
in TypeBasedAliasAnalysis.cpp, the structural checks in Verifier.cpp,
createMutableTBAAAccessTag() in MDBuilder.cpp, and TypeSanitizer.cpp.
Three of those are in llvm/lib/IR, which cannot depend on llvm/Analysis,
so the existing wrappers were unreachable from most of the duplication.

Move TBAANodeImpl, TBAAStructTagNodeImpl, TBAAStructTypeNode and
isNewFormatTypeNode out of the anonymous namespace in
TypeBasedAliasAnalysis.cpp into a new header, llvm/IR/TBAAMetadata.h,
next to the MDBuilder methods that create these nodes, and use them from
Verifier.cpp and MDBuilder.cpp.

  - isNewFormatTypeNode() now tolerates a null node and a null first
    operand, making it a superset of the copy it replaces in Verifier.cpp.
  - TBAAStructTypeNode::getFieldOffset() is new; field offsets were
    previously only reachable through getField()'s offset-walking logic.
  - Verifier.cpp keeps its own field-list walk. Its job is to reject
    malformed metadata, so it cannot use accessors that assume the
    invariants it is there to check; only the format predicate is shared.

createMutableTBAAAccessTag() had no test coverage, and the immutability
flag it looks up is at a different operand in each format, so a unit test
for the four tag shapes is added alongside.

No functional change intended.
---
 llvm/include/llvm/IR/TBAAMetadata.h          | 279 +++++++++++++++++++
 llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp | 234 +---------------
 llvm/lib/IR/MDBuilder.cpp                    |  28 +-
 llvm/lib/IR/Verifier.cpp                     |  12 +-
 llvm/unittests/IR/MDBuilderTest.cpp          |  33 +++
 5 files changed, 325 insertions(+), 261 deletions(-)
 create mode 100644 llvm/include/llvm/IR/TBAAMetadata.h

diff --git a/llvm/include/llvm/IR/TBAAMetadata.h b/llvm/include/llvm/IR/TBAAMetadata.h
new file mode 100644
index 0000000000000..2ca9341923fa4
--- /dev/null
+++ b/llvm/include/llvm/IR/TBAAMetadata.h
@@ -0,0 +1,279 @@
+//===- TBAAMetadata.h - Type-Based Alias Analysis metadata ------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Readers for the !tbaa metadata format described in LangRef.
+///
+/// TBAA metadata exists in two formats, and the operand layout differs between
+/// them:
+///
+///   old scalar:     { name, parent, offset }
+///   old struct:     { name, field, offset, field, offset, ... }
+///   old access tag: { base type, access type, offset }
+///
+///   new scalar:     { parent, size, name }
+///   new struct:     { parent, size, name, field, offset, size, ... }
+///   new access tag: { base type, access type, offset, size }
+///
+/// These wrappers hide that difference so that producers and consumers of TBAA
+/// metadata do not each have to open-code the operand indices. The nodes
+/// themselves are created by the createTBAA*() methods of MDBuilder.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_IR_TBAAMETADATA_H
+#define LLVM_IR_TBAAMETADATA_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/Support/Casting.h"
+#include <cstdint>
+
+namespace llvm {
+
+/// isNewFormatTypeNode - Return true iff the given type node is in the new
+/// size-aware format.
+inline bool isNewFormatTypeNode(const MDNode *N) {
+  if (!N || N->getNumOperands() < 3)
+    return false;
+  // In the new format type nodes have a reference to the parent type as their
+  // first operand; in the old format the first operand is the name string.
+  return isa_and_nonnull<MDNode>(N->getOperand(0));
+}
+
+/// This is a simple wrapper around an MDNode which provides a higher-level
+/// interface by hiding the details of how alias analysis information is encoded
+/// in its operands.
+template<typename MDNodeTy>
+class TBAANodeImpl {
+  MDNodeTy *Node = nullptr;
+
+public:
+  TBAANodeImpl() = default;
+  explicit TBAANodeImpl(MDNodeTy *N) : Node(N) {}
+
+  /// getNode - Get the MDNode for this TBAANode.
+  MDNodeTy *getNode() const { return Node; }
+
+  /// isNewFormat - Return true iff the wrapped type node is in the new
+  /// size-aware format.
+  bool isNewFormat() const { return isNewFormatTypeNode(Node); }
+
+  /// getParent - Get this TBAANode's Alias tree parent.
+  TBAANodeImpl<MDNodeTy> getParent() const {
+    if (isNewFormat())
+      return TBAANodeImpl(cast<MDNodeTy>(Node->getOperand(0)));
+
+    if (Node->getNumOperands() < 2)
+      return TBAANodeImpl<MDNodeTy>();
+    MDNodeTy *P = dyn_cast_or_null<MDNodeTy>(Node->getOperand(1));
+    if (!P)
+      return TBAANodeImpl<MDNodeTy>();
+    // Ok, this node has a valid parent. Return it.
+    return TBAANodeImpl<MDNodeTy>(P);
+  }
+
+  /// Test if this TBAANode represents a type for objects which are
+  /// not modified (by any means) in the context where this
+  /// AliasAnalysis is relevant.
+  bool isTypeImmutable() const {
+    if (Node->getNumOperands() < 3)
+      return false;
+    ConstantInt *CI = mdconst::dyn_extract<ConstantInt>(Node->getOperand(2));
+    if (!CI)
+      return false;
+    return CI->getValue()[0];
+  }
+};
+
+/// \name Specializations of \c TBAANodeImpl for const and non const qualified
+/// \c MDNode.
+/// @{
+using TBAANode = TBAANodeImpl<const MDNode>;
+using MutableTBAANode = TBAANodeImpl<MDNode>;
+/// @}
+
+/// This is a simple wrapper around an MDNode which provides a
+/// higher-level interface by hiding the details of how alias analysis
+/// information is encoded in its operands.
+template<typename MDNodeTy>
+class TBAAStructTagNodeImpl {
+  /// This node should be created with createTBAAAccessTag().
+  MDNodeTy *Node;
+
+public:
+  explicit TBAAStructTagNodeImpl(MDNodeTy *N) : Node(N) {}
+
+  /// Get the MDNode for this TBAAStructTagNode.
+  MDNodeTy *getNode() const { return Node; }
+
+  /// isNewFormat - Return true iff the wrapped access tag is in the new
+  /// size-aware format.
+  bool isNewFormat() const {
+    if (Node->getNumOperands() < 4)
+      return false;
+    if (MDNodeTy *AccessType = getAccessType())
+      if (!TBAANodeImpl<MDNodeTy>(AccessType).isNewFormat())
+        return false;
+    return true;
+  }
+
+  MDNodeTy *getBaseType() const {
+    return dyn_cast_or_null<MDNode>(Node->getOperand(0));
+  }
+
+  MDNodeTy *getAccessType() const {
+    return dyn_cast_or_null<MDNode>(Node->getOperand(1));
+  }
+
+  uint64_t getOffset() const {
+    return mdconst::extract<ConstantInt>(Node->getOperand(2))->getZExtValue();
+  }
+
+  uint64_t getSize() const {
+    if (!isNewFormat())
+      return UINT64_MAX;
+    return mdconst::extract<ConstantInt>(Node->getOperand(3))->getZExtValue();
+  }
+
+  /// Test if this TBAAStructTagNode represents a type for objects
+  /// which are not modified (by any means) in the context where this
+  /// AliasAnalysis is relevant.
+  bool isTypeImmutable() const {
+    unsigned OpNo = isNewFormat() ? 4 : 3;
+    if (Node->getNumOperands() < OpNo + 1)
+      return false;
+    ConstantInt *CI = mdconst::dyn_extract<ConstantInt>(Node->getOperand(OpNo));
+    if (!CI)
+      return false;
+    return CI->getValue()[0];
+  }
+};
+
+/// \name Specializations of \c TBAAStructTagNodeImpl for const and non const
+/// qualified \c MDNods.
+/// @{
+using TBAAStructTagNode = TBAAStructTagNodeImpl<const MDNode>;
+using MutableTBAAStructTagNode = TBAAStructTagNodeImpl<MDNode>;
+/// @}
+
+/// This is a simple wrapper around an MDNode which provides a
+/// higher-level interface by hiding the details of how alias analysis
+/// information is encoded in its operands.
+class TBAAStructTypeNode {
+  /// This node should be created with createTBAATypeNode().
+  const MDNode *Node = nullptr;
+
+public:
+  TBAAStructTypeNode() = default;
+  explicit TBAAStructTypeNode(const MDNode *N) : Node(N) {}
+
+  /// Get the MDNode for this TBAAStructTypeNode.
+  const MDNode *getNode() const { return Node; }
+
+  /// isNewFormat - Return true iff the wrapped type node is in the new
+  /// size-aware format.
+  bool isNewFormat() const { return isNewFormatTypeNode(Node); }
+
+  bool operator==(const TBAAStructTypeNode &Other) const {
+    return getNode() == Other.getNode();
+  }
+
+  /// getId - Return type identifier.
+  Metadata *getId() const {
+    return Node->getOperand(isNewFormat() ? 2 : 0);
+  }
+
+  unsigned getNumFields() const {
+    unsigned FirstFieldOpNo = isNewFormat() ? 3 : 1;
+    unsigned NumOpsPerField = isNewFormat() ? 3 : 2;
+    return (getNode()->getNumOperands() - FirstFieldOpNo) / NumOpsPerField;
+  }
+
+  TBAAStructTypeNode getFieldType(unsigned FieldIndex) const {
+    unsigned FirstFieldOpNo = isNewFormat() ? 3 : 1;
+    unsigned NumOpsPerField = isNewFormat() ? 3 : 2;
+    unsigned OpIndex = FirstFieldOpNo + FieldIndex * NumOpsPerField;
+    auto *TypeNode = cast<MDNode>(getNode()->getOperand(OpIndex));
+    return TBAAStructTypeNode(TypeNode);
+  }
+
+  /// Get the offset of the field at \p FieldIndex within this type.
+  uint64_t getFieldOffset(unsigned FieldIndex) const {
+    unsigned FirstFieldOpNo = isNewFormat() ? 3 : 1;
+    unsigned NumOpsPerField = isNewFormat() ? 3 : 2;
+    unsigned OpIndex = FirstFieldOpNo + FieldIndex * NumOpsPerField;
+    return mdconst::extract<ConstantInt>(getNode()->getOperand(OpIndex + 1))
+        ->getZExtValue();
+  }
+
+  /// Get this TBAAStructTypeNode's field in the type DAG with
+  /// given offset. Update the offset to be relative to the field type.
+  TBAAStructTypeNode getField(uint64_t &Offset) const {
+    bool NewFormat = isNewFormat();
+    const ArrayRef<MDOperand> Operands = Node->operands();
+    const unsigned NumOperands = Operands.size();
+
+    if (NewFormat) {
+      // New-format root and scalar type nodes have no fields.
+      if (NumOperands < 6)
+        return TBAAStructTypeNode();
+    } else {
+      // Parent can be omitted for the root node.
+      if (NumOperands < 2)
+        return TBAAStructTypeNode();
+
+      // Fast path for a scalar type node and a struct type node with a single
+      // field.
+      if (NumOperands <= 3) {
+        uint64_t Cur =
+            NumOperands == 2
+                ? 0
+                : mdconst::extract<ConstantInt>(Operands[2])->getZExtValue();
+        Offset -= Cur;
+        MDNode *P = dyn_cast_or_null<MDNode>(Operands[1]);
+        if (!P)
+          return TBAAStructTypeNode();
+        return TBAAStructTypeNode(P);
+      }
+    }
+
+    // Assume the offsets are in order. We return the previous field if
+    // the current offset is bigger than the given offset.
+    unsigned FirstFieldOpNo = NewFormat ? 3 : 1;
+    unsigned NumOpsPerField = NewFormat ? 3 : 2;
+    unsigned TheIdx = 0;
+
+    for (unsigned Idx = FirstFieldOpNo; Idx < NumOperands;
+         Idx += NumOpsPerField) {
+      uint64_t Cur =
+          mdconst::extract<ConstantInt>(Operands[Idx + 1])->getZExtValue();
+      if (Cur > Offset) {
+        assert(Idx >= FirstFieldOpNo + NumOpsPerField &&
+               "TBAAStructTypeNode::getField should have an offset match!");
+        TheIdx = Idx - NumOpsPerField;
+        break;
+      }
+    }
+    // Move along the last field.
+    if (TheIdx == 0)
+      TheIdx = NumOperands - NumOpsPerField;
+    uint64_t Cur =
+        mdconst::extract<ConstantInt>(Operands[TheIdx + 1])->getZExtValue();
+    Offset -= Cur;
+    MDNode *P = dyn_cast_or_null<MDNode>(Operands[TheIdx]);
+    if (!P)
+      return TBAAStructTypeNode();
+    return TBAAStructTypeNode(P);
+  }
+};
+
+} // end namespace llvm
+
+#endif // LLVM_IR_TBAAMETADATA_H
diff --git a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
index dbe4ccac7801d..75b12d0325b0e 100644
--- a/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/TypeBasedAliasAnalysis.cpp
@@ -110,6 +110,7 @@
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/TBAAMetadata.h"
 #include "llvm/InitializePasses.h"
 #include "llvm/Pass.h"
 #include "llvm/Support/Casting.h"
@@ -125,239 +126,6 @@ using namespace llvm;
 // more convenient.
 static cl::opt<bool> EnableTBAA("enable-tbaa", cl::init(true), cl::Hidden);
 
-namespace {
-
-/// isNewFormatTypeNode - Return true iff the given type node is in the new
-/// size-aware format.
-static bool isNewFormatTypeNode(const MDNode *N) {
-  if (N->getNumOperands() < 3)
-    return false;
-  // In the old format the first operand is a string.
-  if (!isa<MDNode>(N->getOperand(0)))
-    return false;
-  return true;
-}
-
-/// This is a simple wrapper around an MDNode which provides a higher-level
-/// interface by hiding the details of how alias analysis information is encoded
-/// in its operands.
-template<typename MDNodeTy>
-class TBAANodeImpl {
-  MDNodeTy *Node = nullptr;
-
-public:
-  TBAANodeImpl() = default;
-  explicit TBAANodeImpl(MDNodeTy *N) : Node(N) {}
-
-  /// getNode - Get the MDNode for this TBAANode.
-  MDNodeTy *getNode() const { return Node; }
-
-  /// isNewFormat - Return true iff the wrapped type node is in the new
-  /// size-aware format.
-  bool isNewFormat() const { return isNewFormatTypeNode(Node); }
-
-  /// getParent - Get this TBAANode's Alias tree parent.
-  TBAANodeImpl<MDNodeTy> getParent() const {
-    if (isNewFormat())
-      return TBAANodeImpl(cast<MDNodeTy>(Node->getOperand(0)));
-
-    if (Node->getNumOperands() < 2)
-      return TBAANodeImpl<MDNodeTy>();
-    MDNodeTy *P = dyn_cast_or_null<MDNodeTy>(Node->getOperand(1));
-    if (!P)
-      return TBAANodeImpl<MDNodeTy>();
-    // Ok, this node has a valid parent. Return it.
-    return TBAANodeImpl<MDNodeTy>(P);
-  }
-
-  /// Test if this TBAANode represents a type for objects which are
-  /// not modified (by any means) in the context where this
-  /// AliasAnalysis is relevant.
-  bool isTypeImmutable() const {
-    if (Node->getNumOperands() < 3)
-      return false;
-    ConstantInt *CI = mdconst::dyn_extract<ConstantInt>(Node->getOperand(2));
-    if (!CI)
-      return false;
-    return CI->getValue()[0];
-  }
-};
-
-/// \name Specializations of \c TBAANodeImpl for const and non const qualified
-/// \c MDNode.
-/// @{
-using TBAANode = TBAANodeImpl<const MDNode>;
-using MutableTBAANode = TBAANodeImpl<MDNode>;
-/// @}
-
-/// This is a simple wrapper around an MDNode which provides a
-/// higher-level interface by hiding the details of how alias analysis
-/// information is encoded in its operands.
-template<typename MDNodeTy>
-class TBAAStructTagNodeImpl {
-  /// This node should be created with createTBAAAccessTag().
-  MDNodeTy *Node;
-
-public:
-  explicit TBAAStructTagNodeImpl(MDNodeTy *N) : Node(N) {}
-
-  /// Get the MDNode for this TBAAStructTagNode.
-  MDNodeTy *getNode() const { return Node; }
-
-  /// isNewFormat - Return true iff the wrapped access tag is in the new
-  /// size-aware format.
-  bool isNewFormat() const {
-    if (Node->getNumOperands() < 4)
-      return false;
-    if (MDNodeTy *AccessType = getAccessType())
-      if (!TBAANodeImpl<MDNodeTy>(AccessType).isNewFormat())
-        return false;
-    return true;
-  }
-
-  MDNodeTy *getBaseType() const {
-    return dyn_cast_or_null<MDNode>(Node->getOperand(0));
-  }
-
-  MDNodeTy *getAccessType() const {
-    return dyn_cast_or_null<MDNode>(Node->getOperand(1));
-  }
-
-  uint64_t getOffset() const {
-    return mdconst::extract<ConstantInt>(Node->getOperand(2))->getZExtValue();
-  }
-
-  uint64_t getSize() const {
-    if (!isNewFormat())
-      return UINT64_MAX;
-    return mdconst::extract<ConstantInt>(Node->getOperand(3))->getZExtValue();
-  }
-
-  /// Test if this TBAAStructTagNode represents a type for objects
-  /// which are not modified (by any means) in the context where this
-  /// AliasAnalysis is relevant.
-  bool isTypeImmutable() const {
-    unsigned OpNo = isNewFormat() ? 4 : 3;
-    if (Node->getNumOperands() < OpNo + 1)
-      return false;
-    ConstantInt *CI = mdconst::dyn_extract<ConstantInt>(Node->getOperand(OpNo));
-    if (!CI)
-      return false;
-    return CI->getValue()[0];
-  }
-};
-
-/// \name Specializations of \c TBAAStructTagNodeImpl for const and non const
-/// qualified \c MDNods.
-/// @{
-using TBAAStructTagNode = TBAAStructTagNodeImpl<const MDNode>;
-using MutableTBAAStructTagNode = TBAAStructTagNodeImpl<MDNode>;
-/// @}
-
-/// This is a simple wrapper around an MDNode which provides a
-/// higher-level interface by hiding the details of how alias analysis
-/// information is encoded in its operands.
-class TBAAStructTypeNode {
-  /// This node should be created with createTBAATypeNode().
-  const MDNode *Node = nullptr;
-
-public:
-  TBAAStructTypeNode() = default;
-  explicit TBAAStructTypeNode(const MDNode *N) : Node(N) {}
-
-  /// Get the MDNode for this TBAAStructTypeNode.
-  const MDNode *getNode() const { return Node; }
-
-  /// isNewFormat - Return true iff the wrapped type node is in the new
-  /// size-aware format.
-  bool isNewFormat() const { return isNewFormatTypeNode(Node); }
-
-  bool operator==(const TBAAStructTypeNode &Other) const {
-    return getNode() == Other.getNode();
-  }
-
-  /// getId - Return type identifier.
-  Metadata *getId() const {
-    return Node->getOperand(isNewFormat() ? 2 : 0);
-  }
-
-  unsigned getNumFields() const {
-    unsigned FirstFieldOpNo = isNewFormat() ? 3 : 1;
-    unsigned NumOpsPerField = isNewFormat() ? 3 : 2;
-    return (getNode()->getNumOperands() - FirstFieldOpNo) / NumOpsPerField;
-  }
-
-  TBAAStructTypeNode getFieldType(unsigned FieldIndex) const {
-    unsigned FirstFieldOpNo = isNewFormat() ? 3 : 1;
-    unsigned NumOpsPerField = isNewFormat() ? 3 : 2;
-    unsigned OpIndex = FirstFieldOpNo + FieldIndex * NumOpsPerField;
-    auto *TypeNode = cast<MDNode>(getNode()->getOperand(OpIndex));
-    return TBAAStructTypeNode(TypeNode);
-  }
-
-  /// Get this TBAAStructTypeNode's field in the type DAG with
-  /// given offset. Update the offset to be relative to the field type.
-  TBAAStructTypeNode getField(uint64_t &Offset) const {
-    bool NewFormat = isNewFormat();
-    const ArrayRef<MDOperand> Operands = Node->operands();
-    const unsigned NumOperands = Operands.size();
-
-    if (NewFormat) {
-      // New-format root and scalar type nodes have no fields.
-      if (NumOperands < 6)
-        return TBAAStructTypeNode();
-    } else {
-      // Parent can be omitted for the root node.
-      if (NumOperands < 2)
-        return TBAAStructTypeNode();
-
-      // Fast path for a scalar type node and a struct type node with a single
-      // field.
-      if (NumOperands <= 3) {
-        uint64_t Cur =
-            NumOperands == 2
-                ? 0
-                : mdconst::extract<ConstantInt>(Operands[2])->getZExtValue();
-        Offset -= Cur;
-        MDNode *P = dyn_cast_or_null<MDNode>(Operands[1]);
-        if (!P)
-          return TBAAStructTypeNode();
-        return TBAAStructTypeNode(P);
-      }
-    }
-
-    // Assume the offsets are in order. We return the previous field if
-    // the current offset is bigger than the given offset.
-    unsigned FirstFieldOpNo = NewFormat ? 3 : 1;
-    unsigned NumOpsPerField = NewFormat ? 3 : 2;
-    unsigned TheIdx = 0;
-
-    for (unsigned Idx = FirstFieldOpNo; Idx < NumOperands;
-         Idx += NumOpsPerField) {
-      uint64_t Cur =
-          mdconst::extract<ConstantInt>(Operands[Idx + 1])->getZExtValue();
-      if (Cur > Offset) {
-        assert(Idx >= FirstFieldOpNo + NumOpsPerField &&
-               "TBAAStructTypeNode::getField should have an offset match!");
-        TheIdx = Idx - NumOpsPerField;
-        break;
-      }
-    }
-    // Move along the last field.
-    if (TheIdx == 0)
-      TheIdx = NumOperands - NumOpsPerField;
-    uint64_t Cur =
-        mdconst::extract<ConstantInt>(Operands[TheIdx + 1])->getZExtValue();
-    Offset -= Cur;
-    MDNode *P = dyn_cast_or_null<MDNode>(Operands[TheIdx]);
-    if (!P)
-      return TBAAStructTypeNode();
-    return TBAAStructTypeNode(P);
-  }
-};
-
-} // end anonymous namespace
-
 AliasResult TypeBasedAAResult::alias(const MemoryLocation &LocA,
                                      const MemoryLocation &LocB,
                                      AAQueryInfo &AAQI, const Instruction *) {
diff --git a/llvm/lib/IR/MDBuilder.cpp b/llvm/lib/IR/MDBuilder.cpp
index 9a8e417b43ba7..102ad2a65fc1c 100644
--- a/llvm/lib/IR/MDBuilder.cpp
+++ b/llvm/lib/IR/MDBuilder.cpp
@@ -15,6 +15,7 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/Metadata.h"
+#include "llvm/IR/TBAAMetadata.h"
 #include "llvm/IR/ProfDataUtils.h"
 using namespace llvm;
 
@@ -314,30 +315,21 @@ MDNode *MDBuilder::createTBAAAccessTag(MDNode *BaseType, MDNode *AccessType,
 }
 
 MDNode *MDBuilder::createMutableTBAAAccessTag(MDNode *Tag) {
-  MDNode *BaseType = cast<MDNode>(Tag->getOperand(0));
-  MDNode *AccessType = cast<MDNode>(Tag->getOperand(1));
-  Metadata *OffsetNode = Tag->getOperand(2);
-  uint64_t Offset = mdconst::extract<ConstantInt>(OffsetNode)->getZExtValue();
-
-  bool NewFormat = isa<MDNode>(AccessType->getOperand(0));
-
-  // See if the tag is already mutable.
-  unsigned ImmutabilityFlagOp = NewFormat ? 4 : 3;
-  if (Tag->getNumOperands() <= ImmutabilityFlagOp)
-    return Tag;
-
-  // If Tag is already mutable then return it.
-  Metadata *ImmutabilityFlagNode = Tag->getOperand(ImmutabilityFlagOp);
-  if (!mdconst::extract<ConstantInt>(ImmutabilityFlagNode)->getValue())
+  MutableTBAAStructTagNode TagNode(Tag);
+  MDNode *BaseType = TagNode.getBaseType();
+  MDNode *AccessType = TagNode.getAccessType();
+  uint64_t Offset = TagNode.getOffset();
+  bool NewFormat = isNewFormatTypeNode(AccessType);
+
+  // If the tag has no immutability flag, or is already mutable, return it.
+  if (!TagNode.isTypeImmutable())
     return Tag;
 
   // Otherwise, create another node.
   if (!NewFormat)
     return createTBAAStructTagNode(BaseType, AccessType, Offset);
 
-  Metadata *SizeNode = Tag->getOperand(3);
-  uint64_t Size = mdconst::extract<ConstantInt>(SizeNode)->getZExtValue();
-  return createTBAAAccessTag(BaseType, AccessType, Offset, Size);
+  return createTBAAAccessTag(BaseType, AccessType, Offset, TagNode.getSize());
 }
 
 MDNode *MDBuilder::createIrrLoopHeaderWeight(uint64_t Weight) {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 1440b95896474..765749100dc5f 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -109,6 +109,7 @@
 #include "llvm/IR/PassManager.h"
 #include "llvm/IR/ProfDataUtils.h"
 #include "llvm/IR/Statepoint.h"
+#include "llvm/IR/TBAAMetadata.h"
 #include "llvm/IR/Type.h"
 #include "llvm/IR/Use.h"
 #include "llvm/IR/User.h"
@@ -8230,15 +8231,6 @@ MDNode *TBAAVerifier::getFieldNodeFromTBAABaseNode(const Instruction *I,
   return cast<MDNode>(BaseNode->getOperand(LastIdx));
 }
 
-static bool isNewFormatTBAATypeNode(llvm::MDNode *Type) {
-  if (!Type || Type->getNumOperands() < 3)
-    return false;
-
-  // In the new format type nodes shall have a reference to the parent type as
-  // its first operand.
-  return isa_and_nonnull<MDNode>(Type->getOperand(0));
-}
-
 bool TBAAVerifier::visitTBAAMetadata(const Instruction *I, const MDNode *MD) {
   CheckTBAA(MD->getNumOperands() > 0, "TBAA metadata cannot have 0 operands", I,
             MD);
@@ -8259,7 +8251,7 @@ bool TBAAVerifier::visitTBAAMetadata(const Instruction *I, const MDNode *MD) {
   auto *BaseNode = dyn_cast_or_null<MDNode>(MD->getOperand(0));
   auto *AccessType = dyn_cast_or_null<MDNode>(MD->getOperand(1));
 
-  bool IsNewFormat = isNewFormatTBAATypeNode(AccessType);
+  bool IsNewFormat = isNewFormatTypeNode(AccessType);
 
   if (IsNewFormat) {
     CheckTBAA(MD->getNumOperands() == 4 || MD->getNumOperands() == 5,
diff --git a/llvm/unittests/IR/MDBuilderTest.cpp b/llvm/unittests/IR/MDBuilderTest.cpp
index a923418a05d64..89000332d2a6a 100644
--- a/llvm/unittests/IR/MDBuilderTest.cpp
+++ b/llvm/unittests/IR/MDBuilderTest.cpp
@@ -105,6 +105,39 @@ TEST_F(MDBuilderTest, createTBAANode) {
   EXPECT_EQ(mdconst::extract<ConstantInt>(N2->getOperand(2))->getZExtValue(),
             1U);
 }
+TEST_F(MDBuilderTest, createMutableTBAAAccessTag) {
+  MDBuilder MDHelper(Context);
+  MDNode *Root = MDHelper.createTBAARoot("Root");
+
+  // Old-format scalar type node: { name, parent, offset }.
+  MDNode *OldTy = MDHelper.createTBAANode("Scalar", Root);
+  // New-format scalar type node: { parent, size, name }.
+  MDNode *NewTy = MDHelper.createTBAATypeNode(Root, 4, MDHelper.createString("Scalar"));
+
+  // A tag with no immutability flag is already mutable and is returned as is.
+  MDNode *OldMutable = MDHelper.createTBAAStructTagNode(OldTy, OldTy, 0);
+  EXPECT_EQ(MDHelper.createMutableTBAAAccessTag(OldMutable), OldMutable);
+  MDNode *NewMutable = MDHelper.createTBAAAccessTag(NewTy, NewTy, 0, 4);
+  EXPECT_EQ(MDHelper.createMutableTBAAAccessTag(NewMutable), NewMutable);
+
+  // An immutable tag is rebuilt as the corresponding mutable one, in the same
+  // format. The immutability flag sits at operand 3 in the old format and at
+  // operand 4 in the new one, so this is where the two layouts diverge.
+  MDNode *OldImmutable =
+      MDHelper.createTBAAStructTagNode(OldTy, OldTy, 0, /*IsConstant=*/true);
+  ASSERT_EQ(OldImmutable->getNumOperands(), 4U);
+  MDNode *OldResult = MDHelper.createMutableTBAAAccessTag(OldImmutable);
+  EXPECT_NE(OldResult, OldImmutable);
+  EXPECT_EQ(OldResult, OldMutable);
+
+  MDNode *NewImmutable =
+      MDHelper.createTBAAAccessTag(NewTy, NewTy, 0, 4, /*IsImmutable=*/true);
+  ASSERT_EQ(NewImmutable->getNumOperands(), 5U);
+  MDNode *NewResult = MDHelper.createMutableTBAAAccessTag(NewImmutable);
+  EXPECT_NE(NewResult, NewImmutable);
+  EXPECT_EQ(NewResult, NewMutable);
+}
+
 TEST_F(MDBuilderTest, createPCSections) {
   MDBuilder MDHelper(Context);
   ConstantInt *C1 = ConstantInt::get(Context, APInt(8, 1));

>From 0fae124bf156da20e93e000447f6114ba7d99a17 Mon Sep 17 00:00:00 2001
From: Ofek Shilon <ofekshilon at gmail.com>
Date: Fri, 25 Sep 2026 17:06:09 +0300
Subject: [PATCH 2/2] [TySan] Support the new size-aware TBAA metadata format

TypeSanitizer only understood the old TBAA type-node layout, in which the
type name is the first operand. Under -new-struct-path-tbaa the first
operand is the parent type node and the name moves to operand 2, so
generateBaseTypeDescriptor() failed to find a name and returned false. Its
caller then abandoned instrumentation for the entire function:

    if (!generateTypeDescriptor(MD, TypeDescriptors, TypeNames, M))
      return Res; // Giving up.

As a result, -fsanitize=type combined with -new-struct-path-tbaa silently
produced no instrumentation at all -- no __tysan_check calls and no
__tysan_v1_* type descriptors -- with no diagnostic, so every
type-aliasing violation went undetected. This affected plain scalar
accesses as well as structs, leaving TySan entirely non-functional in that
mode.

Read the metadata through the shared TBAAStructTypeNode accessors instead
of open-coding the old operand layout.

Note that in the old format a scalar type node's parent occupies the first
field slot, so scalars and structs are both covered by the field list. The
new format keeps the parent in a separate operand and gives a scalar no
fields at all, so it has to be added explicitly. The runtime depends on
this: getRootTD() follows Members[0] to find the root of the TBAA tree.

The emitted descriptor layout is unchanged, so no compiler-rt change is
needed: for a given type hierarchy both metadata formats now produce
byte-identical __tysan_v1_* descriptors. The new test asserts exactly the
same descriptor globals as basic.ll, which encodes the same hierarchy in
the old format.

Fixes #226169
---
 .../Instrumentation/TypeSanitizer.cpp         |  72 +++--
 .../TypeSanitizer/new-struct-path-tbaa.ll     | 265 ++++++++++++++++++
 2 files changed, 316 insertions(+), 21 deletions(-)
 create mode 100644 llvm/test/Instrumentation/TypeSanitizer/new-struct-path-tbaa.ll

diff --git a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
index cea6e9e316c1d..2c35c0d04fba5 100644
--- a/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/TypeSanitizer.cpp
@@ -29,6 +29,7 @@
 #include "llvm/IR/MDBuilder.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/TBAAMetadata.h"
 #include "llvm/IR/Type.h"
 #include "llvm/ProfileData/InstrProf.h"
 #include "llvm/Support/CommandLine.h"
@@ -260,24 +261,61 @@ static std::string encodeName(StringRef Name) {
   return Output;
 }
 
+using TBAAMemberList = SmallVectorImpl<std::pair<const MDNode *, uint64_t>>;
+
+/// Return the MDString naming the type described by \p N, or nullptr if \p N
+/// is not a well-formed type node.
+static MDString *getTypeNameNode(const MDNode *N) {
+  if (!N->getNumOperands())
+    return nullptr;
+  return dyn_cast<MDString>(TBAAStructTypeNode(N).getId());
+}
+
+/// Collect the (member type node, offset) pairs that \p N contributes to its
+/// TySan type descriptor, appending them to \p Members.
+///
+/// In the old format a scalar type node's parent occupies the first field
+/// slot, so scalars and structs are both covered by the field list. The new
+/// format keeps the parent in a separate operand and gives a scalar no fields
+/// at all, so it is added explicitly here. The runtime depends on this:
+/// getRootTD() follows Members[0] to find the root of the TBAA tree.
+static bool collectTypeMembers(const MDNode *N, TBAAMemberList &Members) {
+  TBAAStructTypeNode TypeNode(N);
+  unsigned NumFields = TypeNode.getNumFields();
+
+  if (TypeNode.isNewFormat() && NumFields == 0) {
+    // A scalar, or a struct with no fields. Either way the parent is the only
+    // edge towards the root.
+    const auto *Parent = dyn_cast<MDNode>(N->getOperand(0));
+    if (!Parent)
+      return false;
+    Members.emplace_back(Parent, 0);
+    return true;
+  }
+
+  for (unsigned I = 0; I != NumFields; ++I)
+    Members.emplace_back(TypeNode.getFieldType(I).getNode(),
+                         TypeNode.getFieldOffset(I));
+
+  return true;
+}
+
 std::string
 TypeSanitizer::getAnonymousStructIdentifier(const MDNode *MD,
                                             TypeNameMapTy &TypeNames) {
   MD5 Hash;
 
-  for (int i = 1, e = MD->getNumOperands(); i < e; i += 2) {
-    const MDNode *MemberNode = dyn_cast<MDNode>(MD->getOperand(i));
-    if (!MemberNode)
-      return "";
+  SmallVector<std::pair<const MDNode *, uint64_t>> Members;
+  if (!collectTypeMembers(MD, Members))
+    return "";
 
+  for (const auto &[MemberNode, Offset] : Members) {
     auto TNI = TypeNames.find(MemberNode);
     std::string MemberName;
     if (TNI != TypeNames.end()) {
       MemberName = TNI->second;
     } else {
-      if (MemberNode->getNumOperands() < 1)
-        return "";
-      MDString *MemberNameNode = dyn_cast<MDString>(MemberNode->getOperand(0));
+      MDString *MemberNameNode = getTypeNameNode(MemberNode);
       if (!MemberNameNode)
         return "";
       MemberName = MemberNameNode->getString().str();
@@ -291,8 +329,6 @@ TypeSanitizer::getAnonymousStructIdentifier(const MDNode *MD,
     Hash.update(MemberName);
     Hash.update("\0");
 
-    uint64_t Offset =
-        mdconst::extract<ConstantInt>(MD->getOperand(i + 1))->getZExtValue();
     Hash.update(utostr(Offset));
     Hash.update("\0");
   }
@@ -305,10 +341,7 @@ TypeSanitizer::getAnonymousStructIdentifier(const MDNode *MD,
 bool TypeSanitizer::generateBaseTypeDescriptor(
     const MDNode *MD, TypeDescriptorsMapTy &TypeDescriptors,
     TypeNameMapTy &TypeNames, Module &M) {
-  if (MD->getNumOperands() < 1)
-    return false;
-
-  MDString *NameNode = dyn_cast<MDString>(MD->getOperand(0));
+  MDString *NameNode = getTypeNameNode(MD);
   if (!NameNode)
     return false;
 
@@ -327,12 +360,12 @@ bool TypeSanitizer::generateBaseTypeDescriptor(
     return true;
   }
 
-  SmallVector<std::pair<Constant *, uint64_t>> Members;
-  for (int i = 1, e = MD->getNumOperands(); i < e; i += 2) {
-    const MDNode *MemberNode = dyn_cast<MDNode>(MD->getOperand(i));
-    if (!MemberNode)
-      return false;
+  SmallVector<std::pair<const MDNode *, uint64_t>> MemberNodes;
+  if (!collectTypeMembers(MD, MemberNodes))
+    return false;
 
+  SmallVector<std::pair<Constant *, uint64_t>> Members;
+  for (const auto &[MemberNode, Offset] : MemberNodes) {
     Constant *Member;
     auto TDI = TypeDescriptors.find(MemberNode);
     if (TDI != TypeDescriptors.end()) {
@@ -345,9 +378,6 @@ bool TypeSanitizer::generateBaseTypeDescriptor(
       Member = TypeDescriptors[MemberNode];
     }
 
-    uint64_t Offset =
-        mdconst::extract<ConstantInt>(MD->getOperand(i + 1))->getZExtValue();
-
     Members.push_back(std::make_pair(Member, Offset));
   }
 
diff --git a/llvm/test/Instrumentation/TypeSanitizer/new-struct-path-tbaa.ll b/llvm/test/Instrumentation/TypeSanitizer/new-struct-path-tbaa.ll
new file mode 100644
index 0000000000000..011e76bb8989f
--- /dev/null
+++ b/llvm/test/Instrumentation/TypeSanitizer/new-struct-path-tbaa.ll
@@ -0,0 +1,265 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals all --version 6
+; Test that type sanitizer instrumentation is also generated for TBAA metadata
+; in the new, size-aware format (clang's -new-struct-path-tbaa).
+;
+; This mirrors basic.ll, with the same type hierarchy expressed in the new
+; format:
+;   old scalar: { name, parent, offset }   new scalar: { parent, size, name }
+;   old struct: { name, field, offset, ... }
+;   new struct: { parent, size, name, field, offset, size, ... }
+;
+; RUN: opt -passes='tysan' -tysan-outline-instrumentation=false -S %s | FileCheck %s --check-prefix=CHECK-INLINE
+; RUN: opt -passes='tysan' -S %s | FileCheck %s --check-prefix=CHECK-OUTLINE
+
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+
+;.
+; CHECK-INLINE: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
+; CHECK-INLINE: @__tysan_v1_Simple_20C_2b_2b_20TBAA = linkonce_odr constant { i64, i64, [16 x i8] } { i64 2, i64 0, [16 x i8] c"Simple C++ TBAA\00" }, comdat
+; CHECK-INLINE: @__tysan_v1_omnipotent_20char = linkonce_odr constant { i64, i64, ptr, i64, [16 x i8] } { i64 2, i64 1, ptr @__tysan_v1_Simple_20C_2b_2b_20TBAA, i64 0, [16 x i8] c"omnipotent char\00" }, comdat
+; CHECK-INLINE: @__tysan_v1_int = linkonce_odr constant { i64, i64, ptr, i64, [4 x i8] } { i64 2, i64 1, ptr @__tysan_v1_omnipotent_20char, i64 0, [4 x i8] c"int\00" }, comdat
+; CHECK-INLINE: @__tysan_v1_int_o_0 = linkonce_odr constant { i64, ptr, ptr, i64 } { i64 1, ptr @__tysan_v1_int, ptr @__tysan_v1_int, i64 0 }, comdat
+; CHECK-INLINE: @__tysan_shadow_memory_address = external global i64
+; CHECK-INLINE: @__tysan_app_memory_mask = external global i64
+; CHECK-INLINE: @__tysan_v1___ZTS1x = linkonce_odr constant { i64, i64, ptr, i64, ptr, i64, [7 x i8] } { i64 2, i64 2, ptr @__tysan_v1_int, i64 0, ptr @__tysan_v1_int, i64 4, [7 x i8] c"_ZTS1x\00" }, comdat
+; CHECK-INLINE: @__tysan_v1___ZTS1v = linkonce_odr constant { i64, i64, ptr, i64, ptr, i64, ptr, i64, [7 x i8] } { i64 2, i64 3, ptr @__tysan_v1_int, i64 8, ptr @__tysan_v1_int, i64 12, ptr @__tysan_v1___ZTS1x, i64 16, [7 x i8] c"_ZTS1v\00" }, comdat
+; CHECK-INLINE: @__tysan_v1___ZTS1v_o_12 = linkonce_odr constant { i64, ptr, ptr, i64 } { i64 1, ptr @__tysan_v1___ZTS1v, ptr @__tysan_v1_int, i64 12 }, comdat
+; CHECK-INLINE: @llvm.used = appending global [8 x ptr] [ptr @tysan.module_ctor, ptr @__tysan_v1_Simple_20C_2b_2b_20TBAA, ptr @__tysan_v1_omnipotent_20char, ptr @__tysan_v1_int, ptr @__tysan_v1_int_o_0, ptr @__tysan_v1___ZTS1x, ptr @__tysan_v1___ZTS1v, ptr @__tysan_v1___ZTS1v_o_12], section "llvm.metadata"
+;.
+; CHECK-OUTLINE: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @tysan.module_ctor, ptr null }]
+; CHECK-OUTLINE: @__tysan_v1_Simple_20C_2b_2b_20TBAA = linkonce_odr constant { i64, i64, [16 x i8] } { i64 2, i64 0, [16 x i8] c"Simple C++ TBAA\00" }, comdat
+; CHECK-OUTLINE: @__tysan_v1_omnipotent_20char = linkonce_odr constant { i64, i64, ptr, i64, [16 x i8] } { i64 2, i64 1, ptr @__tysan_v1_Simple_20C_2b_2b_20TBAA, i64 0, [16 x i8] c"omnipotent char\00" }, comdat
+; CHECK-OUTLINE: @__tysan_v1_int = linkonce_odr constant { i64, i64, ptr, i64, [4 x i8] } { i64 2, i64 1, ptr @__tysan_v1_omnipotent_20char, i64 0, [4 x i8] c"int\00" }, comdat
+; CHECK-OUTLINE: @__tysan_v1_int_o_0 = linkonce_odr constant { i64, ptr, ptr, i64 } { i64 1, ptr @__tysan_v1_int, ptr @__tysan_v1_int, i64 0 }, comdat
+; CHECK-OUTLINE: @__tysan_shadow_memory_address = external global i64
+; CHECK-OUTLINE: @__tysan_app_memory_mask = external global i64
+; CHECK-OUTLINE: @__tysan_v1___ZTS1x = linkonce_odr constant { i64, i64, ptr, i64, ptr, i64, [7 x i8] } { i64 2, i64 2, ptr @__tysan_v1_int, i64 0, ptr @__tysan_v1_int, i64 4, [7 x i8] c"_ZTS1x\00" }, comdat
+; CHECK-OUTLINE: @__tysan_v1___ZTS1v = linkonce_odr constant { i64, i64, ptr, i64, ptr, i64, ptr, i64, [7 x i8] } { i64 2, i64 3, ptr @__tysan_v1_int, i64 8, ptr @__tysan_v1_int, i64 12, ptr @__tysan_v1___ZTS1x, i64 16, [7 x i8] c"_ZTS1v\00" }, comdat
+; CHECK-OUTLINE: @__tysan_v1___ZTS1v_o_12 = linkonce_odr constant { i64, ptr, ptr, i64 } { i64 1, ptr @__tysan_v1___ZTS1v, ptr @__tysan_v1_int, i64 12 }, comdat
+; CHECK-OUTLINE: @llvm.used = appending global [8 x ptr] [ptr @tysan.module_ctor, ptr @__tysan_v1_Simple_20C_2b_2b_20TBAA, ptr @__tysan_v1_omnipotent_20char, ptr @__tysan_v1_int, ptr @__tysan_v1_int_o_0, ptr @__tysan_v1___ZTS1x, ptr @__tysan_v1___ZTS1v, ptr @__tysan_v1___ZTS1v_o_12], section "llvm.metadata"
+;.
+define i32 @test_load(ptr %a) sanitize_type {
+; CHECK-INLINE-LABEL: define i32 @test_load(
+; CHECK-INLINE-SAME: ptr [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-INLINE-NEXT:  [[ENTRY:.*:]]
+; CHECK-INLINE-NEXT:    [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
+; CHECK-INLINE-NEXT:    [[APP_PTR_INT:%.*]] = ptrtoint ptr [[A]] to i64
+; CHECK-INLINE-NEXT:    [[APP_PTR_MASKED:%.*]] = and i64 [[APP_PTR_INT]], [[APP_MEM_MASK]]
+; CHECK-INLINE-NEXT:    [[APP_PTR_SHIFTED:%.*]] = shl i64 [[APP_PTR_MASKED]], 3
+; CHECK-INLINE-NEXT:    [[SHADOW_PTR_INT:%.*]] = add i64 [[APP_PTR_SHIFTED]], [[SHADOW_BASE]]
+; CHECK-INLINE-NEXT:    [[SHADOW_PTR:%.*]] = inttoptr i64 [[SHADOW_PTR_INT]] to ptr
+; CHECK-INLINE-NEXT:    [[SHADOW_DESC:%.*]] = load ptr, ptr [[SHADOW_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[BAD_DESC:%.*]] = icmp ne ptr [[SHADOW_DESC]], @__tysan_v1_int_o_0
+; CHECK-INLINE-NEXT:    br i1 [[BAD_DESC]], label %[[BB0:.*]], label %[[BB22:.*]], !prof [[PROF0:![0-9]+]]
+; CHECK-INLINE:       [[BB0]]:
+; CHECK-INLINE-NEXT:    [[TMP1:%.*]] = icmp eq ptr [[SHADOW_DESC]], null
+; CHECK-INLINE-NEXT:    br i1 [[TMP1]], label %[[BB2:.*]], label %[[BB20:.*]]
+; CHECK-INLINE:       [[BB2]]:
+; CHECK-INLINE-NEXT:    [[TMP3:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[TMP4:%.*]] = inttoptr i64 [[TMP3]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP5:%.*]] = load ptr, ptr [[TMP4]], align 8
+; CHECK-INLINE-NEXT:    [[TMP6:%.*]] = icmp ne ptr [[TMP5]], null
+; CHECK-INLINE-NEXT:    [[TMP7:%.*]] = or i1 false, [[TMP6]]
+; CHECK-INLINE-NEXT:    [[TMP8:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[TMP9:%.*]] = inttoptr i64 [[TMP8]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP10:%.*]] = load ptr, ptr [[TMP9]], align 8
+; CHECK-INLINE-NEXT:    [[TMP11:%.*]] = icmp ne ptr [[TMP10]], null
+; CHECK-INLINE-NEXT:    [[TMP12:%.*]] = or i1 [[TMP7]], [[TMP11]]
+; CHECK-INLINE-NEXT:    [[TMP13:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[TMP14:%.*]] = inttoptr i64 [[TMP13]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP15:%.*]] = load ptr, ptr [[TMP14]], align 8
+; CHECK-INLINE-NEXT:    [[TMP16:%.*]] = icmp ne ptr [[TMP15]], null
+; CHECK-INLINE-NEXT:    [[TMP17:%.*]] = or i1 [[TMP12]], [[TMP16]]
+; CHECK-INLINE-NEXT:    br i1 [[TMP17]], label %[[BB18:.*]], label %[[BB19:.*]], !prof [[PROF0]]
+; CHECK-INLINE:       [[BB18]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1_int_o_0, i32 1)
+; CHECK-INLINE-NEXT:    br label %[[BB19]]
+; CHECK-INLINE:       [[BB19]]:
+; CHECK-INLINE-NEXT:    store ptr @__tysan_v1_int_o_0, ptr [[SHADOW_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_1_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_1_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_1_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -1 to ptr), ptr [[SHADOW_BYTE_1_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_2_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_2_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_2_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -2 to ptr), ptr [[SHADOW_BYTE_2_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_3_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_3_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_3_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -3 to ptr), ptr [[SHADOW_BYTE_3_PTR]], align 8
+; CHECK-INLINE-NEXT:    br label %[[BB21:.*]]
+; CHECK-INLINE:       [[BB20]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1_int_o_0, i32 1)
+; CHECK-INLINE-NEXT:    br label %[[BB21]]
+; CHECK-INLINE:       [[BB21]]:
+; CHECK-INLINE-NEXT:    br label %[[BB43:.*]]
+; CHECK-INLINE:       [[BB22]]:
+; CHECK-INLINE-NEXT:    [[TMP23:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[TMP24:%.*]] = inttoptr i64 [[TMP23]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP25:%.*]] = load ptr, ptr [[TMP24]], align 8
+; CHECK-INLINE-NEXT:    [[TMP26:%.*]] = ptrtoint ptr [[TMP25]] to i64
+; CHECK-INLINE-NEXT:    [[TMP27:%.*]] = icmp sge i64 [[TMP26]], 0
+; CHECK-INLINE-NEXT:    [[TMP28:%.*]] = or i1 false, [[TMP27]]
+; CHECK-INLINE-NEXT:    [[TMP29:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[TMP30:%.*]] = inttoptr i64 [[TMP29]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP31:%.*]] = load ptr, ptr [[TMP30]], align 8
+; CHECK-INLINE-NEXT:    [[TMP32:%.*]] = ptrtoint ptr [[TMP31]] to i64
+; CHECK-INLINE-NEXT:    [[TMP33:%.*]] = icmp sge i64 [[TMP32]], 0
+; CHECK-INLINE-NEXT:    [[TMP34:%.*]] = or i1 [[TMP28]], [[TMP33]]
+; CHECK-INLINE-NEXT:    [[TMP35:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[TMP36:%.*]] = inttoptr i64 [[TMP35]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP37:%.*]] = load ptr, ptr [[TMP36]], align 8
+; CHECK-INLINE-NEXT:    [[TMP38:%.*]] = ptrtoint ptr [[TMP37]] to i64
+; CHECK-INLINE-NEXT:    [[TMP39:%.*]] = icmp sge i64 [[TMP38]], 0
+; CHECK-INLINE-NEXT:    [[TMP40:%.*]] = or i1 [[TMP34]], [[TMP39]]
+; CHECK-INLINE-NEXT:    br i1 [[TMP40]], label %[[BB41:.*]], label %[[BB42:.*]], !prof [[PROF0]]
+; CHECK-INLINE:       [[BB41]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1_int_o_0, i32 1)
+; CHECK-INLINE-NEXT:    br label %[[BB42]]
+; CHECK-INLINE:       [[BB42]]:
+; CHECK-INLINE-NEXT:    br label %[[BB43]]
+; CHECK-INLINE:       [[BB43]]:
+; CHECK-INLINE-NEXT:    [[TMP1:%.*]] = load i32, ptr [[A]], align 4, !tbaa [[TBAA1:![0-9]+]]
+; CHECK-INLINE-NEXT:    ret i32 [[TMP1]]
+;
+; CHECK-OUTLINE-LABEL: define i32 @test_load(
+; CHECK-OUTLINE-SAME: ptr [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-OUTLINE-NEXT:  [[ENTRY:.*:]]
+; CHECK-OUTLINE-NEXT:    [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
+; CHECK-OUTLINE-NEXT:    [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
+; CHECK-OUTLINE-NEXT:    call void @__tysan_instrument_with_shadow_update(ptr [[A]], ptr @__tysan_v1_int_o_0, i1 true, i64 4, i32 1)
+; CHECK-OUTLINE-NEXT:    [[TMP1:%.*]] = load i32, ptr [[A]], align 4, !tbaa [[TBAA0:![0-9]+]]
+; CHECK-OUTLINE-NEXT:    ret i32 [[TMP1]]
+;
+entry:
+  %tmp1 = load i32, ptr %a, align 4, !tbaa !3
+  ret i32 %tmp1
+}
+
+define void @test_store(ptr %a) sanitize_type {
+; CHECK-INLINE-LABEL: define void @test_store(
+; CHECK-INLINE-SAME: ptr [[A:%.*]]) #[[ATTR0]] {
+; CHECK-INLINE-NEXT:  [[ENTRY:.*:]]
+; CHECK-INLINE-NEXT:    [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
+; CHECK-INLINE-NEXT:    [[APP_PTR_INT:%.*]] = ptrtoint ptr [[A]] to i64
+; CHECK-INLINE-NEXT:    [[APP_PTR_MASKED:%.*]] = and i64 [[APP_PTR_INT]], [[APP_MEM_MASK]]
+; CHECK-INLINE-NEXT:    [[APP_PTR_SHIFTED:%.*]] = shl i64 [[APP_PTR_MASKED]], 3
+; CHECK-INLINE-NEXT:    [[SHADOW_PTR_INT:%.*]] = add i64 [[APP_PTR_SHIFTED]], [[SHADOW_BASE]]
+; CHECK-INLINE-NEXT:    [[SHADOW_PTR:%.*]] = inttoptr i64 [[SHADOW_PTR_INT]] to ptr
+; CHECK-INLINE-NEXT:    [[SHADOW_DESC:%.*]] = load ptr, ptr [[SHADOW_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[BAD_DESC:%.*]] = icmp ne ptr [[SHADOW_DESC]], @__tysan_v1___ZTS1v_o_12
+; CHECK-INLINE-NEXT:    br i1 [[BAD_DESC]], label %[[BB0:.*]], label %[[BB22:.*]], !prof [[PROF0]]
+; CHECK-INLINE:       [[BB0]]:
+; CHECK-INLINE-NEXT:    [[TMP1:%.*]] = icmp eq ptr [[SHADOW_DESC]], null
+; CHECK-INLINE-NEXT:    br i1 [[TMP1]], label %[[BB2:.*]], label %[[BB20:.*]]
+; CHECK-INLINE:       [[BB2]]:
+; CHECK-INLINE-NEXT:    [[TMP3:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[TMP4:%.*]] = inttoptr i64 [[TMP3]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP5:%.*]] = load ptr, ptr [[TMP4]], align 8
+; CHECK-INLINE-NEXT:    [[TMP6:%.*]] = icmp ne ptr [[TMP5]], null
+; CHECK-INLINE-NEXT:    [[TMP7:%.*]] = or i1 false, [[TMP6]]
+; CHECK-INLINE-NEXT:    [[TMP8:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[TMP9:%.*]] = inttoptr i64 [[TMP8]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP10:%.*]] = load ptr, ptr [[TMP9]], align 8
+; CHECK-INLINE-NEXT:    [[TMP11:%.*]] = icmp ne ptr [[TMP10]], null
+; CHECK-INLINE-NEXT:    [[TMP12:%.*]] = or i1 [[TMP7]], [[TMP11]]
+; CHECK-INLINE-NEXT:    [[TMP13:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[TMP14:%.*]] = inttoptr i64 [[TMP13]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP15:%.*]] = load ptr, ptr [[TMP14]], align 8
+; CHECK-INLINE-NEXT:    [[TMP16:%.*]] = icmp ne ptr [[TMP15]], null
+; CHECK-INLINE-NEXT:    [[TMP17:%.*]] = or i1 [[TMP12]], [[TMP16]]
+; CHECK-INLINE-NEXT:    br i1 [[TMP17]], label %[[BB18:.*]], label %[[BB19:.*]], !prof [[PROF0]]
+; CHECK-INLINE:       [[BB18]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1___ZTS1v_o_12, i32 2)
+; CHECK-INLINE-NEXT:    br label %[[BB19]]
+; CHECK-INLINE:       [[BB19]]:
+; CHECK-INLINE-NEXT:    store ptr @__tysan_v1___ZTS1v_o_12, ptr [[SHADOW_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_1_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_1_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_1_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -1 to ptr), ptr [[SHADOW_BYTE_1_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_2_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_2_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_2_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -2 to ptr), ptr [[SHADOW_BYTE_2_PTR]], align 8
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_3_OFFSET:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[SHADOW_BYTE_3_PTR:%.*]] = inttoptr i64 [[SHADOW_BYTE_3_OFFSET]] to ptr
+; CHECK-INLINE-NEXT:    store ptr inttoptr (i64 -3 to ptr), ptr [[SHADOW_BYTE_3_PTR]], align 8
+; CHECK-INLINE-NEXT:    br label %[[BB21:.*]]
+; CHECK-INLINE:       [[BB20]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1___ZTS1v_o_12, i32 2)
+; CHECK-INLINE-NEXT:    br label %[[BB21]]
+; CHECK-INLINE:       [[BB21]]:
+; CHECK-INLINE-NEXT:    br label %[[BB43:.*]]
+; CHECK-INLINE:       [[BB22]]:
+; CHECK-INLINE-NEXT:    [[TMP23:%.*]] = add i64 [[SHADOW_PTR_INT]], 8
+; CHECK-INLINE-NEXT:    [[TMP24:%.*]] = inttoptr i64 [[TMP23]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP25:%.*]] = load ptr, ptr [[TMP24]], align 8
+; CHECK-INLINE-NEXT:    [[TMP26:%.*]] = ptrtoint ptr [[TMP25]] to i64
+; CHECK-INLINE-NEXT:    [[TMP27:%.*]] = icmp sge i64 [[TMP26]], 0
+; CHECK-INLINE-NEXT:    [[TMP28:%.*]] = or i1 false, [[TMP27]]
+; CHECK-INLINE-NEXT:    [[TMP29:%.*]] = add i64 [[SHADOW_PTR_INT]], 16
+; CHECK-INLINE-NEXT:    [[TMP30:%.*]] = inttoptr i64 [[TMP29]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP31:%.*]] = load ptr, ptr [[TMP30]], align 8
+; CHECK-INLINE-NEXT:    [[TMP32:%.*]] = ptrtoint ptr [[TMP31]] to i64
+; CHECK-INLINE-NEXT:    [[TMP33:%.*]] = icmp sge i64 [[TMP32]], 0
+; CHECK-INLINE-NEXT:    [[TMP34:%.*]] = or i1 [[TMP28]], [[TMP33]]
+; CHECK-INLINE-NEXT:    [[TMP35:%.*]] = add i64 [[SHADOW_PTR_INT]], 24
+; CHECK-INLINE-NEXT:    [[TMP36:%.*]] = inttoptr i64 [[TMP35]] to ptr
+; CHECK-INLINE-NEXT:    [[TMP37:%.*]] = load ptr, ptr [[TMP36]], align 8
+; CHECK-INLINE-NEXT:    [[TMP38:%.*]] = ptrtoint ptr [[TMP37]] to i64
+; CHECK-INLINE-NEXT:    [[TMP39:%.*]] = icmp sge i64 [[TMP38]], 0
+; CHECK-INLINE-NEXT:    [[TMP40:%.*]] = or i1 [[TMP34]], [[TMP39]]
+; CHECK-INLINE-NEXT:    br i1 [[TMP40]], label %[[BB41:.*]], label %[[BB42:.*]], !prof [[PROF0]]
+; CHECK-INLINE:       [[BB41]]:
+; CHECK-INLINE-NEXT:    call void @__tysan_check(ptr [[A]], i32 4, ptr @__tysan_v1___ZTS1v_o_12, i32 2)
+; CHECK-INLINE-NEXT:    br label %[[BB42]]
+; CHECK-INLINE:       [[BB42]]:
+; CHECK-INLINE-NEXT:    br label %[[BB43]]
+; CHECK-INLINE:       [[BB43]]:
+; CHECK-INLINE-NEXT:    store i32 42, ptr [[A]], align 4, !tbaa [[TBAA5:![0-9]+]]
+; CHECK-INLINE-NEXT:    ret void
+;
+; CHECK-OUTLINE-LABEL: define void @test_store(
+; CHECK-OUTLINE-SAME: ptr [[A:%.*]]) #[[ATTR0]] {
+; CHECK-OUTLINE-NEXT:  [[ENTRY:.*:]]
+; CHECK-OUTLINE-NEXT:    [[APP_MEM_MASK:%.*]] = load i64, ptr @__tysan_app_memory_mask, align 8
+; CHECK-OUTLINE-NEXT:    [[SHADOW_BASE:%.*]] = load i64, ptr @__tysan_shadow_memory_address, align 8
+; CHECK-OUTLINE-NEXT:    call void @__tysan_instrument_with_shadow_update(ptr [[A]], ptr @__tysan_v1___ZTS1v_o_12, i1 true, i64 4, i32 2)
+; CHECK-OUTLINE-NEXT:    store i32 42, ptr [[A]], align 4, !tbaa [[TBAA4:![0-9]+]]
+; CHECK-OUTLINE-NEXT:    ret void
+;
+entry:
+  store i32 42, ptr %a, align 4, !tbaa !6
+  ret void
+}
+
+!0 = !{!"Simple C++ TBAA"}
+!1 = !{!0, i64 1, !"omnipotent char"}
+!2 = !{!1, i64 4, !"int"}
+!3 = !{!2, !2, i64 0, i64 4}
+!4 = !{!1, i64 8, !"_ZTS1x", !2, i64 0, i64 4, !2, i64 4, i64 4}
+!5 = !{!1, i64 24, !"_ZTS1v", !2, i64 8, i64 4, !2, i64 12, i64 4, !4, i64 16, i64 8}
+!6 = !{!5, !2, i64 12, i64 4}
+;.
+; CHECK-INLINE: attributes #[[ATTR0]] = { sanitize_type }
+; CHECK-INLINE: attributes #[[ATTR1:[0-9]+]] = { nounwind }
+;.
+; CHECK-OUTLINE: attributes #[[ATTR0]] = { sanitize_type }
+; CHECK-OUTLINE: attributes #[[ATTR1:[0-9]+]] = { nounwind }
+;.
+; CHECK-INLINE: [[PROF0]] = !{!"branch_weights", i32 1, i32 100000}
+; CHECK-INLINE: [[TBAA1]] = !{[[META2:![0-9]+]], [[META2]], i64 0, i64 4}
+; CHECK-INLINE: [[META2]] = !{[[META3:![0-9]+]], i64 4, !"int"}
+; CHECK-INLINE: [[META3]] = !{[[META4:![0-9]+]], i64 1, !"omnipotent char"}
+; CHECK-INLINE: [[META4]] = !{!"Simple C++ TBAA"}
+; CHECK-INLINE: [[TBAA5]] = !{[[META6:![0-9]+]], [[META2]], i64 12, i64 4}
+; CHECK-INLINE: [[META6]] = !{[[META3]], i64 24, !"_ZTS1v", [[META2]], i64 8, i64 4, [[META2]], i64 12, i64 4, [[META7:![0-9]+]], i64 16, i64 8}
+; CHECK-INLINE: [[META7]] = !{[[META3]], i64 8, !"_ZTS1x", [[META2]], i64 0, i64 4, [[META2]], i64 4, i64 4}
+;.
+; CHECK-OUTLINE: [[TBAA0]] = !{[[META1:![0-9]+]], [[META1]], i64 0, i64 4}
+; CHECK-OUTLINE: [[META1]] = !{[[META2:![0-9]+]], i64 4, !"int"}
+; CHECK-OUTLINE: [[META2]] = !{[[META3:![0-9]+]], i64 1, !"omnipotent char"}
+; CHECK-OUTLINE: [[META3]] = !{!"Simple C++ TBAA"}
+; CHECK-OUTLINE: [[TBAA4]] = !{[[META5:![0-9]+]], [[META1]], i64 12, i64 4}
+; CHECK-OUTLINE: [[META5]] = !{[[META2]], i64 24, !"_ZTS1v", [[META1]], i64 8, i64 4, [[META1]], i64 12, i64 4, [[META6:![0-9]+]], i64 16, i64 8}
+; CHECK-OUTLINE: [[META6]] = !{[[META2]], i64 8, !"_ZTS1x", [[META1]], i64 0, i64 4, [[META1]], i64 4, i64 4}
+;.



More information about the llvm-commits mailing list