diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 771b7dd33cd4..48d1f1faf53f 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -170,7 +170,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
                                /*alignment=*/intAttr,
                                /*mem_order=*/
                                cir::MemOrderAttr{},
-                               /*tbaa=*/mlir::ArrayAttr{});
+                               /*tbaa=*/cir::TBAAAttr{});
   }
 
   mlir::Value createAlignedLoad(mlir::Location loc, mlir::Value ptr,
@@ -357,7 +357,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
         val.getType())
       dst = createPtrBitcast(dst, val.getType());
     return create<cir::StoreOp>(loc, val, dst, _volatile, align, order,
-                                /*tbaa=*/mlir::ArrayAttr{});
+                                /*tbaa=*/cir::TBAAAttr{});
   }
 
   mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
@@ -405,7 +405,7 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
   cir::CopyOp createCopy(mlir::Value dst, mlir::Value src,
                          bool isVolatile = false) {
     return create<cir::CopyOp>(dst.getLoc(), dst, src, isVolatile,
-                               /*tbaa=*/mlir::ArrayAttr{});
+                               /*tbaa=*/cir::TBAAAttr{});
   }
 
   cir::MemCpyOp createMemCpy(mlir::Location loc, mlir::Value dst,
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index e54b52b96c91..e968d4c27fd5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -24,8 +24,9 @@ include "clang/CIR/Interfaces/ASTAttrInterfaces.td"
 // CIR Attrs
 //===----------------------------------------------------------------------===//
 
-class CIR_Attr<string name, string attrMnemonic, list<Trait> traits = []>
-    : AttrDef<CIR_Dialect, name, traits> {
+class CIR_Attr<string name, string attrMnemonic, list<Trait> traits = [],
+               string baseCppClass = "::mlir::Attribute">
+    : AttrDef<CIR_Dialect, name, traits, baseCppClass> {
   let mnemonic = attrMnemonic;
 }
 
@@ -1294,8 +1295,7 @@ def GlobalAnnotationValuesAttr : CIR_Attr<"GlobalAnnotationValues",
   let genVerifyDecl = 1;
 }
 
-def CIR_TBAAAttr : CIR_Attr<"TBAA", "tbaa", []> {
-}
+include "clang/CIR/Dialect/IR/CIRTBAAAttrs.td"
 
 include "clang/CIR/Dialect/IR/CIROpenCLAttrs.td"
 
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index e02194ad15e0..36793ba97d52 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -588,7 +588,7 @@ def LoadOp : CIR_Op<"load", [
                        UnitAttr:$is_volatile,
                        OptionalAttr<I64Attr>:$alignment,
                        OptionalAttr<MemOrder>:$mem_order,
-                       OptionalAttr<ArrayAttr>:$tbaa
+                       OptionalAttr<CIR_AnyTBAAAttr>:$tbaa
                        );
   let results = (outs CIR_AnyType:$result);
 
@@ -657,7 +657,7 @@ def StoreOp : CIR_Op<"store", [
                        UnitAttr:$is_volatile,
                        OptionalAttr<I64Attr>:$alignment,
                        OptionalAttr<MemOrder>:$mem_order,
-                       OptionalAttr<ArrayAttr>:$tbaa);
+                       OptionalAttr<CIR_AnyTBAAAttr>:$tbaa);
 
   let assemblyFormat = [{
     (`volatile` $is_volatile^)?
@@ -4068,7 +4068,7 @@ def CopyOp : CIR_Op<"copy",
   let arguments = (ins Arg<CIR_PointerType, "", [MemWrite]>:$dst,
                        Arg<CIR_PointerType, "", [MemRead]>:$src,
                        UnitAttr:$is_volatile,
-                       OptionalAttr<ArrayAttr>:$tbaa);
+                       OptionalAttr<CIR_TBAAAttr>:$tbaa);
   let summary = "Copies contents from a CIR pointer to another";
   let description = [{
     Given two CIR pointers, `src` and `dst`, `cir.copy` will copy the memory
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTBAAAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRTBAAAttrs.td
new file mode 100644
index 000000000000..d46880e8541e
--- /dev/null
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTBAAAttrs.td
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+// TBAAAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_TBAAAttr : CIR_Attr<"TBAA", "tbaa", []> {
+  let summary = "CIR dialect TBAA base attribute";
+}
+
+//===----------------------------------------------------------------------===//
+// TBAAScalarAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_TBAAScalarAttr : CIR_Attr<"TBAAScalar", "tbaa_scalar", [], "TBAAAttr"> {
+  let summary = "Describes a scalar type in TBAA with an identifier.";
+
+  let parameters = (ins CIR_AnyScalarType : $type);
+
+  let description = [{
+    Define a TBAA attribute.
+
+    Example:
+    ```mlir
+    // CIR_TBAAScalarAttr
+    #tbaa_scalar = #cir.tbaa_scalar<type = !s32i>
+    #tbaa_scalar1 = #cir.tbaa_scalar<type = !u32i>
+    ```
+    
+    See the following link for more details:
+    https://llvm.org/docs/LangRef.html#tbaa-metadata
+  }];
+
+  let assemblyFormat = "`<` struct(params) `>`";
+}
+
+def CIR_AnyTBAAAttr : AnyAttrOf<[
+  CIR_TBAAAttr,
+  CIR_TBAAScalarAttr
+]>;
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index d3f49716301d..68b27a053176 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -74,6 +74,28 @@ def CIR_IntType : CIR_Type<"Int", "int",
     static bool isValidPrimitiveIntBitwidth(unsigned width) {
       return width == 8 || width == 16 || width == 32 || width == 64;
     }
+
+    llvm::StringRef getTBAATypeName() const {
+      switch (getWidth()) {
+        case 1:
+        case 8: {
+          return "omnipotent char";
+        }
+        case 16: {
+          return "short";
+        }
+        case 32: {
+          return "int";
+        }
+        case 64: {
+          return "long";
+        }
+        default: {
+          llvm::errs() << "unknown type: " << *this << "\n";
+          return "unknown";
+        }
+      }
+    }
   }];
   let genVerifyDecl = 1;
 }
@@ -609,4 +631,11 @@ def CIR_AnyType : AnyTypeOf<[
   CIR_ComplexType
 ]>;
 
+def CIR_AnyScalarType : AnyTypeOf<[
+  CIR_IntType, CIR_PointerType, CIR_DataMemberType, CIR_MethodType,
+  CIR_BoolType, CIR_ArrayType, CIR_VectorType, CIR_FuncType, CIR_VoidType,
+  CIR_ExceptionType, CIR_AnyFloat, CIR_FP16, CIR_BFloat16,
+  CIR_ComplexType
+]>;
+
 #endif // MLIR_CIR_DIALECT_CIR_TYPES
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index c0707d687fca..346719691a5d 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -58,7 +58,12 @@ struct MissingFeatures {
   // sanitizer related type check features
   static bool emitTypeCheck() { return false; }
   static bool tbaa() { return false; }
-  static bool tbaa_struct() { return false; }
+  static bool tbaaStruct() { return false; }
+  static bool tbaaTagForStruct() { return false; }
+  static bool tbaaVTablePtr() { return false; }
+  static bool tbaaIncompleteType() { return false; }
+  static bool tbaaMergeTBAAInfo() { return false; }
+  static bool tbaaMayAlias() { return false; }
   static bool cleanups() { return false; }
   static bool emitNullabilityCheck() { return false; }
   static bool ptrAuth() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 28be733f62d7..3019ca8ef62b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -839,7 +839,7 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
     return create<cir::LoadOp>(
         loc, addr.getElementType(), addr.getPointer(), /*isDeref=*/false,
         /*is_volatile=*/isVolatile, /*alignment=*/mlir::IntegerAttr{},
-        /*mem_order=*/cir::MemOrderAttr{}, /*tbaa=*/mlir::ArrayAttr{});
+        /*mem_order=*/cir::MemOrderAttr{}, /*tbaa=*/cir::TBAAAttr{});
   }
 
   mlir::Value createAlignedLoad(mlir::Location loc, mlir::Type ty,
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp
index 46f89bf60d18..df4e97f7a179 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp
@@ -1716,7 +1716,7 @@ void CIRGenFunction::emitAggregateCopy(LValue Dest, LValue Src, QualType Ty,
   // Determine the metadata to describe the position of any padding in this
   // memcpy, as well as the TBAA tags for the members of the struct, in case
   // the optimizer wishes to expand it in to scalar memory operations.
-  assert(!cir::MissingFeatures::tbaa_struct() && "tbaa.struct NYI");
+  assert(!cir::MissingFeatures::tbaaStruct() && "tbaa.struct NYI");
   if (CGM.getCodeGenOpts().NewStructPathTBAA) {
     TBAAAccessInfo TBAAInfo = CGM.mergeTBAAInfoForMemoryTransfer(
         Dest.getTBAAInfo(), Src.getTBAAInfo());
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index b7197afeb896..81d60477cae0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3988,7 +3988,7 @@ cir::TBAAAttr CIRGenModule::getTBAABaseTypeInfo(QualType QTy) {
   return tbaa->getBaseTypeInfo(QTy);
 }
 
-mlir::ArrayAttr CIRGenModule::getTBAAAccessTagInfo(TBAAAccessInfo tbaaInfo) {
+cir::TBAAAttr CIRGenModule::getTBAAAccessTagInfo(TBAAAccessInfo tbaaInfo) {
   if (!tbaa) {
     return nullptr;
   }
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 905754a4ad3a..dd8a0c98b081 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -525,7 +525,7 @@ class CIRGenModule : public CIRGenTypeCache {
   /// type is not suitable for use in TBAA access tags.
   cir::TBAAAttr getTBAABaseTypeInfo(QualType QTy);
 
-  mlir::ArrayAttr getTBAAAccessTagInfo(TBAAAccessInfo tbaaInfo);
+  cir::TBAAAttr getTBAAAccessTagInfo(TBAAAccessInfo tbaaInfo);
 
   /// Get merged TBAA information for the purposes of type casts.
   TBAAAccessInfo mergeTBAAInfoForCast(TBAAAccessInfo SourceInfo,
diff --git a/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp b/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp
index a6efc05e4110..ce2969d130ff 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp
@@ -1,11 +1,12 @@
 #include "CIRGenTBAA.h"
-#include "CIRGenCXXABI.h"
 #include "CIRGenTypes.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/Interfaces/DataLayoutInterfaces.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "clang/CIR/MissingFeatures.h"
 #include "llvm/Support/ErrorHandling.h"
 namespace clang::CIRGen {
 
@@ -21,44 +22,159 @@ CIRGenTBAA::CIRGenTBAA(mlir::MLIRContext *mlirContext,
     : mlirContext(mlirContext), astContext(astContext), types(types),
       moduleOp(moduleOp), codeGenOpts(codeGenOpts), features(features) {}
 
+cir::TBAAAttr CIRGenTBAA::getChar() {
+  return cir::TBAAScalarAttr::get(mlirContext,
+                                  cir::IntType::get(mlirContext, 1, true));
+}
+
+static bool typeHasMayAlias(clang::QualType qty) {
+  // Tagged types have declarations, and therefore may have attributes.
+  if (auto *td = qty->getAsTagDecl())
+    if (td->hasAttr<MayAliasAttr>())
+      return true;
+
+  // Also look for may_alias as a declaration attribute on a typedef.
+  // FIXME: We should follow GCC and model may_alias as a type attribute
+  // rather than as a declaration attribute.
+  while (auto *tt = qty->getAs<TypedefType>()) {
+    if (tt->getDecl()->hasAttr<MayAliasAttr>())
+      return true;
+    qty = tt->desugar();
+  }
+  return false;
+}
+
+/// Check if the given type is a valid base type to be used in access tags.
+static bool isValidBaseType(clang::QualType qty) {
+  if (const clang::RecordType *tty = qty->getAs<clang::RecordType>()) {
+    const clang::RecordDecl *rd = tty->getDecl()->getDefinition();
+    // Incomplete types are not valid base access types.
+    if (!rd)
+      return false;
+    if (rd->hasFlexibleArrayMember())
+      return false;
+    // rd can be struct, union, class, interface or enum.
+    // For now, we only handle struct and class.
+    if (rd->isStruct() || rd->isClass())
+      return true;
+  }
+  return false;
+}
+
 cir::TBAAAttr CIRGenTBAA::getTypeInfo(clang::QualType qty) {
-  return tbaa_NYI(mlirContext);
+  // At -O0 or relaxed aliasing, TBAA is not emitted for regular types.
+  if (codeGenOpts.OptimizationLevel == 0 || codeGenOpts.RelaxedAliasing) {
+    return nullptr;
+  }
+
+  // If the type has the may_alias attribute (even on a typedef), it is
+  // effectively in the general char alias class.
+  if (typeHasMayAlias(qty)) {
+    assert(!cir::MissingFeatures::tbaaMayAlias());
+    return getChar();
+  }
+  // We need this function to not fall back to returning the "omnipotent char"
+  // type node for aggregate and union types. Otherwise, any dereference of an
+  // aggregate will result into the may-alias access descriptor, meaning all
+  // subsequent accesses to direct and indirect members of that aggregate will
+  // be considered may-alias too.
+  // function.
+  if (isValidBaseType(qty)) {
+    // TODO(cir): support TBAA with struct
+    return tbaa_NYI(mlirContext);
+  }
+
+  const clang::Type *ty = astContext.getCanonicalType(qty).getTypePtr();
+  if (metadataCache.contains(ty)) {
+    return metadataCache[ty];
+  }
+
+  // Note that the following helper call is allowed to add new nodes to the
+  // cache, which invalidates all its previously obtained iterators. So we
+  // first generate the node for the type and then add that node to the
+  // cache.
+  auto typeNode = cir::TBAAScalarAttr::get(mlirContext, types.ConvertType(qty));
+  return metadataCache[ty] = typeNode;
 }
 
 TBAAAccessInfo CIRGenTBAA::getAccessInfo(clang::QualType accessType) {
-  return TBAAAccessInfo();
+  // Pointee values may have incomplete types, but they shall never be
+  // dereferenced.
+  if (accessType->isIncompleteType()) {
+    assert(!cir::MissingFeatures::tbaaIncompleteType());
+    return TBAAAccessInfo::getIncompleteInfo();
+  }
+
+  if (typeHasMayAlias(accessType)) {
+    assert(!cir::MissingFeatures::tbaaMayAlias());
+    return TBAAAccessInfo::getMayAliasInfo();
+  }
+
+  uint64_t size = astContext.getTypeSizeInChars(accessType).getQuantity();
+  return TBAAAccessInfo(getTypeInfo(accessType), size);
 }
 
 TBAAAccessInfo CIRGenTBAA::getVTablePtrAccessInfo(mlir::Type vtablePtrType) {
+  // TODO(cir): support vtable ptr
+  assert(!cir::MissingFeatures::tbaaVTablePtr());
   return TBAAAccessInfo();
 }
 
 mlir::ArrayAttr CIRGenTBAA::getTBAAStructInfo(clang::QualType qty) {
-  return mlir::ArrayAttr::get(mlirContext, {});
+  assert(!cir::MissingFeatures::tbaaStruct() && "tbaa.struct NYI");
+  return mlir::ArrayAttr();
 }
 
 cir::TBAAAttr CIRGenTBAA::getBaseTypeInfo(clang::QualType qty) {
   return tbaa_NYI(mlirContext);
 }
 
-mlir::ArrayAttr CIRGenTBAA::getAccessTagInfo(TBAAAccessInfo tbaaInfo) {
-  return mlir::ArrayAttr::get(mlirContext, {tbaa_NYI(mlirContext)});
+cir::TBAAAttr CIRGenTBAA::getAccessTagInfo(TBAAAccessInfo tbaaInfo) {
+  assert(!tbaaInfo.isIncomplete() &&
+         "Access to an object of an incomplete type!");
+
+  if (tbaaInfo.isMayAlias()) {
+    assert(!cir::MissingFeatures::tbaaMayAlias());
+    tbaaInfo = TBAAAccessInfo(getChar(), tbaaInfo.size);
+  }
+  if (!tbaaInfo.accessType) {
+    return nullptr;
+  }
+
+  if (!codeGenOpts.StructPathTBAA)
+    tbaaInfo = TBAAAccessInfo(tbaaInfo.accessType, tbaaInfo.size);
+
+  if (!tbaaInfo.baseType) {
+    tbaaInfo.baseType = tbaaInfo.accessType;
+    assert(!tbaaInfo.offset &&
+           "Nonzero offset for an access with no base type!");
+  }
+  if (codeGenOpts.NewStructPathTBAA) {
+    llvm_unreachable("NYI");
+  }
+  if (tbaaInfo.baseType == tbaaInfo.accessType) {
+    return tbaaInfo.accessType;
+  }
+  return tbaa_NYI(mlirContext);
 }
 
 TBAAAccessInfo CIRGenTBAA::mergeTBAAInfoForCast(TBAAAccessInfo sourceInfo,
                                                 TBAAAccessInfo targetInfo) {
+  assert(!cir::MissingFeatures::tbaaMergeTBAAInfo());
   return TBAAAccessInfo();
 }
 
 TBAAAccessInfo
 CIRGenTBAA::mergeTBAAInfoForConditionalOperator(TBAAAccessInfo infoA,
                                                 TBAAAccessInfo infoB) {
+  assert(!cir::MissingFeatures::tbaaMergeTBAAInfo());
   return TBAAAccessInfo();
 }
 
 TBAAAccessInfo
 CIRGenTBAA::mergeTBAAInfoForMemoryTransfer(TBAAAccessInfo destInfo,
                                            TBAAAccessInfo srcInfo) {
+  assert(!cir::MissingFeatures::tbaaMergeTBAAInfo());
   return TBAAAccessInfo();
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenTBAA.h b/clang/lib/CIR/CodeGen/CIRGenTBAA.h
index 3f59a0e6538b..03b9b75113c9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTBAA.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTBAA.h
@@ -104,6 +104,10 @@ class CIRGenTBAA {
   [[maybe_unused]] const clang::CodeGenOptions &codeGenOpts;
   [[maybe_unused]] const clang::LangOptions &features;
 
+  llvm::DenseMap<const Type *, cir::TBAAAttr> metadataCache;
+
+  cir::TBAAAttr getChar();
+
 public:
   CIRGenTBAA(mlir::MLIRContext *mlirContext, clang::ASTContext &astContext,
              CIRGenTypes &types, mlir::ModuleOp moduleOp,
@@ -129,7 +133,7 @@ class CIRGenTBAA {
   cir::TBAAAttr getBaseTypeInfo(clang::QualType qty);
 
   /// Get TBAA tag for a given memory access.
-  mlir::ArrayAttr getAccessTagInfo(TBAAAccessInfo tbaaInfo);
+  cir::TBAAAttr getAccessTagInfo(TBAAAccessInfo tbaaInfo);
 
   /// Get merged TBAA information for the purpose of type casts.
   TBAAAccessInfo mergeTBAAInfoForCast(TBAAAccessInfo sourceInfo,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 1c5a6467f538..242afd4f00b8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -17,6 +17,7 @@
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 #include "clang/CIR/Interfaces/CIRLoopOpInterface.h"
 #include "clang/CIR/MissingFeatures.h"
+#include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/ErrorHandling.h"
 #include <numeric>
 #include <optional>
@@ -106,12 +107,12 @@ struct CIROpAsmDialectInterface : public OpAsmDialectInterface {
       os << dynCastInfoAttr.getAlias();
       return AliasResult::FinalAlias;
     }
-    if (auto tbaaAttr = mlir::dyn_cast<cir::TBAAAttr>(attr)) {
-      os << tbaaAttr.getMnemonic();
-      return AliasResult::OverridableAlias;
-    }
-
-    return AliasResult::NoAlias;
+    return TypeSwitch<Attribute, AliasResult>(attr)
+        .Case<cir::TBAAAttr, cir::TBAAScalarAttr>([&](auto attr) {
+          os << decltype(attr)::getMnemonic();
+          return AliasResult::OverridableAlias;
+        })
+        .Default([](Attribute) { return AliasResult::NoAlias; });
   }
 };
 } // namespace
diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
index 80963353a304..bb99d53e0ad8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
@@ -151,7 +151,7 @@ DeletionKind cir::CopyOp::removeBlockingUses(
   if (loadsFrom(slot))
     builder.create<cir::StoreOp>(getLoc(), reachingDefinition, getDst(), false,
                                  mlir::IntegerAttr{}, cir::MemOrderAttr(),
-                                 mlir::ArrayAttr{});
+                                 cir::TBAAAttr{});
   return DeletionKind::Delete;
 }
 
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index c0400de4418f..7aa8cfbbc1bf 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -18,6 +18,7 @@
 #include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
 #include "mlir/Dialect/DLTI/DLTI.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
 #include "mlir/IR/Attributes.h"
 #include "mlir/IR/Builders.h"
@@ -41,6 +42,9 @@
 #include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
 #include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
 #include "mlir/Target/LLVMIR/Export.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
 #include "clang/CIR/Dialect/Passes.h"
 #include "clang/CIR/LoweringHelpers.h"
 #include "clang/CIR/MissingFeatures.h"
@@ -51,6 +55,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/ADT/TypeSwitch.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/Support/Casting.h"
@@ -666,6 +671,67 @@ mlir::Value lowerCirAttrAsValue(mlir::Operation *parentOp, mlir::Attribute attr,
   llvm_unreachable("unhandled attribute type");
 }
 
+mlir::LLVM::TBAATypeDescriptorAttr
+createScalarTypeNode(mlir::MLIRContext *ctx, llvm::StringRef typeName,
+                     mlir::LLVM::TBAANodeAttr parent, int64_t offset) {
+  llvm::SmallVector<mlir::LLVM::TBAAMemberAttr, 2> members;
+  members.push_back(mlir::LLVM::TBAAMemberAttr::get(ctx, parent, offset));
+  return mlir::LLVM::TBAATypeDescriptorAttr::get(
+      ctx, typeName, llvm::ArrayRef<mlir::LLVM::TBAAMemberAttr>(members));
+}
+
+mlir::LLVM::TBAARootAttr getRoot(mlir::MLIRContext *ctx) {
+  return mlir::LLVM::TBAARootAttr::get(
+      ctx, mlir::StringAttr::get(ctx, "Simple C/C++ TBAA"));
+}
+
+mlir::LLVM::TBAATypeDescriptorAttr getChar(mlir::MLIRContext *ctx) {
+  return createScalarTypeNode(ctx, "omnipotent char", getRoot(ctx), 0);
+}
+
+// FIXME(cir): This should be moved and use tablegen approach
+// see https://github.com/llvm/clangir/pull/1220#discussion_r1889187867
+StringRef getTypeName(mlir::Type type) {
+  return TypeSwitch<mlir::Type, StringRef>(type)
+      .Case<cir::IntType>([](cir::IntType ty) { return ty.getTBAATypeName(); })
+      .Case<cir::SingleType>([](cir::SingleType) { return "float"; })
+      .Case<cir::DoubleType>([](cir::DoubleType) { return "double"; })
+      .Case<cir::FP80Type>([](cir::FP80Type) { return "f80"; })
+      .Case<cir::FP128Type>([](cir::FP128Type) { return "f128"; })
+      .Case<cir::LongDoubleType>(
+          [](cir::LongDoubleType) { return "long double"; })
+      .Case<cir::BoolType>([](cir::BoolType) { return "bool"; })
+      .Case<cir::PointerType>([](cir::PointerType) { return "any pointer"; })
+      .Default([](auto ty) {
+        llvm::errs() << "unknown type: " << ty << "\n";
+        return "unknown";
+      });
+}
+
+mlir::LLVM::TBAATypeDescriptorAttr
+lowerScalarType(mlir::MLIRContext *ctx, cir::TBAAScalarAttr scalarAttr) {
+  // special handle for omnipotent char
+  if (auto intTy = mlir::dyn_cast_or_null<cir::IntType>(scalarAttr.getType())) {
+    if (intTy.getWidth() == 1 || intTy.getWidth() == 8) {
+      return getChar(ctx);
+    }
+  }
+  auto name = getTypeName(scalarAttr.getType());
+  return createScalarTypeNode(ctx, name, getChar(ctx), 0);
+}
+
+mlir::ArrayAttr lowerCIRTBAAAttr(mlir::Attribute tbaa,
+                                 mlir::ConversionPatternRewriter &rewriter) {
+  auto *ctx = rewriter.getContext();
+  if (auto scalarAttr = mlir::dyn_cast<cir::TBAAScalarAttr>(tbaa)) {
+    auto accessType = lowerScalarType(ctx, scalarAttr);
+    auto tag = mlir::LLVM::TBAATagAttr::get(accessType, accessType, 0);
+    return mlir::ArrayAttr::get(ctx, {tag});
+  }
+  assert(!cir::MissingFeatures::tbaaTagForStruct());
+  return mlir::ArrayAttr();
+}
+
 //===----------------------------------------------------------------------===//
 
 mlir::LLVM::Linkage convertLinkage(cir::GlobalLinkageKind linkage) {
@@ -1512,10 +1578,14 @@ mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
   }
 
   // TODO: nontemporal, syncscope.
-  rewriter.replaceOpWithNewOp<mlir::LLVM::LoadOp>(
-      op, llvmTy, adaptor.getAddr(), /* alignment */ alignment,
+  auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(
+      op->getLoc(), llvmTy, adaptor.getAddr(), /* alignment */ alignment,
       op.getIsVolatile(), /* nontemporal */ false,
       /* invariant */ false, /* invariantGroup */ invariant, ordering);
+  rewriter.replaceOp(op, loadOp);
+  if (auto tbaa = op.getTbaaAttr()) {
+    loadOp.setTBAATags(lowerCIRTBAAAttr(tbaa, rewriter));
+  }
   return mlir::LogicalResult::success();
 }
 
@@ -1547,9 +1617,14 @@ mlir::LogicalResult CIRToLLVMStoreOpLowering::matchAndRewrite(
   }
 
   // TODO: nontemporal, syncscope.
-  rewriter.replaceOpWithNewOp<mlir::LLVM::StoreOp>(
-      op, adaptor.getValue(), adaptor.getAddr(), alignment, op.getIsVolatile(),
+  auto storeOp = rewriter.create<mlir::LLVM::StoreOp>(
+      op->getLoc(), adaptor.getValue(), adaptor.getAddr(), alignment,
+      op.getIsVolatile(),
       /* nontemporal */ false, /* invariantGroup */ invariant, ordering);
+  rewriter.replaceOp(op, storeOp);
+  if (auto tbaa = op.getTbaaAttr()) {
+    storeOp.setTBAATags(lowerCIRTBAAAttr(tbaa, rewriter));
+  }
   return mlir::LogicalResult::success();
 }
 
diff --git a/clang/test/CIR/CodeGen/const-alloca.cpp b/clang/test/CIR/CodeGen/const-alloca.cpp
index 9247b2692474..7cc9a5b57517 100644
--- a/clang/test/CIR/CodeGen/const-alloca.cpp
+++ b/clang/test/CIR/CodeGen/const-alloca.cpp
@@ -66,8 +66,8 @@ int local_const_load_store() {
 
 // LLVM-LABEL: @_Z22local_const_load_storev
 //      LLVM: %[[#INIT:]] = call i32 @_Z11produce_intv()
-// LLVM-NEXT: store i32 %[[#INIT]], ptr %[[#SLOT:]], align 4, !invariant.group !{{.+}}
-// LLVM-NEXT: %{{.+}} = load i32, ptr %[[#SLOT]], align 4, !invariant.group !{{.+}}
+// LLVM-NEXT: store i32 %[[#INIT]], ptr %[[#SLOT:]], align 4, !tbaa !{{.*}}, !invariant.group !{{.+}}
+// LLVM-NEXT: %{{.+}} = load i32, ptr %[[#SLOT]], align 4, !tbaa !{{.*}}, !invariant.group !{{.+}}
 // LLVM: }
 
 int local_const_optimize() {
@@ -80,7 +80,7 @@ int local_const_optimize() {
 // LLVM-LABEL: @_Z20local_const_optimizev()
 // LLVM-NEXT:    %[[#slot:]] = alloca i32, align 4
 // LLVM-NEXT:    %[[#init:]] = tail call i32 @_Z11produce_intv()
-// LLVM-NEXT:    store i32 %[[#init]], ptr %[[#slot]], align 4, !invariant.group !{{.+}}
+// LLVM-NEXT:    store i32 %[[#init]], ptr %[[#slot]], align 4, !tbaa !{{.*}}, !invariant.group !{{.+}}
 // LLVM-NEXT:    call void @_Z8blackboxRKi(ptr nonnull %[[#slot]])
 // LLVM-NEXT:    call void @_Z8blackboxRKi(ptr nonnull %[[#slot]])
 // LLVM-NEXT:    ret i32 %[[#init]]
diff --git a/clang/test/CIR/CodeGen/tbaa-scalar.c b/clang/test/CIR/CodeGen/tbaa-scalar.c
new file mode 100644
index 000000000000..b2f893b4f4ac
--- /dev/null
+++ b/clang/test/CIR/CodeGen/tbaa-scalar.c
@@ -0,0 +1,148 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -O1
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -O1
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -O1 -relaxed-aliasing
+// RUN: FileCheck --check-prefix=NO-TBAA --input-file=%t.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -O0
+// RUN: FileCheck --check-prefix=NO-TBAA --input-file=%t.ll %s
+
+// NO-TBAA-NOT: !tbaa
+
+// CIR: #tbaa[[FLOAT_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!cir.float>>
+// CIR: #tbaa[[FLOAT:.*]] = #cir.tbaa_scalar<type = !cir.float>
+// CIR: #tbaa[[DOUBLE_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!cir.double>>
+// CIR: #tbaa[[DOUBLE:.*]] = #cir.tbaa_scalar<type = !cir.double>
+// CIR: #tbaa[[LONG_DOUBLE_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!cir.long_double<!cir.f80>>>
+// CIR: #tbaa[[LONG_DOUBLE:.*]] = #cir.tbaa_scalar<type = !cir.long_double<!cir.f80>>
+// CIR: #tbaa[[INT:.*]] = #cir.tbaa_scalar<type = !s32i>
+// CIR: #tbaa[[LONG:.*]] = #cir.tbaa_scalar<type = !s64i>
+// CIR: #tbaa[[CHAR:.*]] = #cir.tbaa_scalar<type = !s8i>
+// CIR: #tbaa[[INT_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!s32i>>
+// CIR: #tbaa[[LONG_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!s64i>>
+// CIR: #tbaa[[CHAR_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!s8i>>
+
+void test_int_and_float(int *a, float *b) {
+  // CIR-LABEL: cir.func @test_int_and_float
+  // CIR: cir.scope
+  // CIR: %[[TMP1:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i> tbaa(#tbaa[[INT_PTR]])
+  // CIR: %[[TMP2:.*]] = cir.load %[[TMP1]] : !cir.ptr<!s32i>, !s32i tbaa(#tbaa[[INT]])
+  // CIR: cir.if
+  // CIR: %[[C2:.*]] = cir.const #cir.fp<2
+  // CIR: %[[TMP3:.*]] = cir.load deref %[[ARG_b:.*]] : !cir.ptr<!cir.ptr<!cir.float>>, !cir.ptr<!cir.float> tbaa(#tbaa[[FLOAT_PTR]])
+  // CIR: cir.store %[[C2]], %[[TMP3]] : !cir.float, !cir.ptr<!cir.float> tbaa(#tbaa[[FLOAT]])
+  // CIR: else
+  // CIR: %[[C3:.*]] = cir.const #cir.fp<3
+  // CIR: %[[TMP4:.*]] = cir.load deref %[[ARG_b]] : !cir.ptr<!cir.ptr<!cir.float>>, !cir.ptr<!cir.float> tbaa(#tbaa[[FLOAT_PTR]])
+  // CIR: cir.store %[[C3]], %[[TMP4]] : !cir.float, !cir.ptr<!cir.float> tbaa(#tbaa[[FLOAT]])
+
+  // LLVM-LABEL: void @test_int_and_float
+  // LLVM: %[[ARG_a:.*]] = load i32, ptr %{{.*}}, align 4, !tbaa ![[TBAA_INT:.*]]
+  // LLVM: %[[COND:.*]] = icmp eq i32 %[[ARG_a]], 1
+  // LLVM: %[[RET:.*]] = select i1 %[[COND]], float 2.000000e+00, float 3.000000e+00
+  // LLVM: store float %[[RET]], ptr %{{.*}}, align 4, !tbaa ![[TBAA_FLOAT:.*]]
+  // LLVM: ret void
+  if (*a == 1) {
+    *b = 2.0f;
+  } else {
+    *b = 3.0f;
+  }
+}
+
+void test_long_and_double(long *a, double *b) {
+  // CIR-LABEL: cir.func @test_long_and_double
+  // CIR: cir.scope
+  // CIR: %[[TMP1:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!s64i>>, !cir.ptr<!s64i> tbaa(#tbaa[[LONG_PTR]])
+  // CIR: %[[TMP2:.*]] = cir.load %[[TMP1]] : !cir.ptr<!s64i>, !s64i tbaa(#tbaa[[LONG]])
+  // CIR: cir.if
+  // CIR: %[[C2:.*]] = cir.const #cir.fp<2
+  // CIR: %[[TMP3:.*]] = cir.load deref %[[ARG_b:.*]] : !cir.ptr<!cir.ptr<!cir.double>>, !cir.ptr<!cir.double> tbaa(#tbaa[[DOUBLE_PTR]])
+  // CIR: cir.store %[[C2]], %[[TMP3]] : !cir.double, !cir.ptr<!cir.double> tbaa(#tbaa[[DOUBLE]])
+  // CIR: else
+  // CIR: %[[C3:.*]] = cir.const #cir.fp<3
+  // CIR: %[[TMP4:.*]] = cir.load deref %[[ARG_b]] : !cir.ptr<!cir.ptr<!cir.double>>, !cir.ptr<!cir.double> tbaa(#tbaa[[DOUBLE_PTR]])
+  // CIR: cir.store %[[C3]], %[[TMP4]] : !cir.double, !cir.ptr<!cir.double> tbaa(#tbaa[[DOUBLE]])
+
+  // LLVM-LABEL: void @test_long_and_double
+  // LLVM: %[[ARG_a:.*]] = load i64, ptr %{{.*}}, align 8, !tbaa ![[TBAA_LONG:.*]]
+  // LLVM: %[[COND:.*]] = icmp eq i64 %[[ARG_a]], 1
+  // LLVM: %[[RET:.*]] = select i1 %[[COND]], double 2.000000e+00, double 3.000000e+00
+  // LLVM: store double %[[RET]], ptr %{{.*}}, align 8, !tbaa ![[TBAA_DOUBLE:.*]]
+  // LLVM: ret void
+  if (*a == 1L) {
+    *b = 2.0;
+  } else {
+    *b = 3.0;
+  }
+}
+void test_long_long_and_long_double(long long *a, long double *b) {
+  // CIR-LABEL: cir.func @test_long_long_and_long_double
+  // CIR: cir.scope
+  // CIR: %[[TMP1:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!s64i>>, !cir.ptr<!s64i> tbaa(#tbaa[[LONG_PTR]])
+  // CIR: %[[TMP2:.*]] = cir.load %[[TMP1]] : !cir.ptr<!s64i>, !s64i tbaa(#tbaa[[LONG]])
+  // CIR: cir.if
+  // CIR: %[[C2:.*]] = cir.const #cir.fp<2
+  // CIR: %[[TMP3:.*]] = cir.load deref %[[ARG_b:.*]] : !cir.ptr<!cir.ptr<!cir.long_double<!cir.f80>>>, !cir.ptr<!cir.long_double<!cir.f80>> tbaa(#tbaa[[LONG_DOUBLE_PTR]])
+  // CIR: cir.store %[[C2]], %[[TMP3]] : !cir.long_double<!cir.f80>, !cir.ptr<!cir.long_double<!cir.f80>> tbaa(#tbaa[[LONG_DOUBLE]])
+  // CIR: else
+  // CIR: %[[C3:.*]] = cir.const #cir.fp<3
+  // CIR: %[[TMP4:.*]] = cir.load deref %[[ARG_b]] : !cir.ptr<!cir.ptr<!cir.long_double<!cir.f80>>>, !cir.ptr<!cir.long_double<!cir.f80>> tbaa(#tbaa[[LONG_DOUBLE_PTR]])
+  // CIR: cir.store %[[C3]], %[[TMP4]] : !cir.long_double<!cir.f80>, !cir.ptr<!cir.long_double<!cir.f80>> tbaa(#tbaa[[LONG_DOUBLE]])
+
+  // LLVM-LABEL: void @test_long_long_and_long_double
+  // LLVM: %[[ARG_a:.*]] = load i64, ptr %{{.*}}, align 8, !tbaa ![[TBAA_LONG_LONG:.*]]
+  // LLVM: %[[COND:.*]] = icmp eq i64 %[[ARG_a]], 1
+  // LLVM: %[[RET:.*]] = select i1 %[[COND]], x86_fp80 0xK40008000000000000000, x86_fp80 0xK4000C000000000000000
+  // LLVM: store x86_fp80 %[[RET]], ptr %{{.*}}, align 16, !tbaa ![[TBAA_LONG_DOUBLE:.*]]
+  // LLVM: ret void
+  if (*a == 1L) {
+    *b = 2.0L;
+  } else {
+    *b = 3.0L;
+  }
+}
+
+void test_char(char *a, char* b) {
+  // CIR-LABEL: cir.func @test_char
+  // CIR: cir.scope
+  // CIR: %[[TMP1:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!s8i>>, !cir.ptr<!s8i> tbaa(#tbaa[[CHAR_PTR]])
+  // CIR: %[[TMP2:.*]] = cir.load %[[TMP1]] : !cir.ptr<!s8i>, !s8i tbaa(#tbaa[[CHAR]])
+  // CIR: cir.if
+  // CIR: %[[C2:.*]] = cir.const #cir.int<98> : !s32i
+  // CIR: %[[C2_CHAR:.*]] = cir.cast(integral, %[[C2]] : !s32i), !s8i
+  // CIR: %[[TMP3:.*]] = cir.load deref %[[ARG_b:.*]] : !cir.ptr<!cir.ptr<!s8i>>, !cir.ptr<!s8i> tbaa(#tbaa[[CHAR_PTR]])
+  // CIR: cir.store %[[C2_CHAR]], %[[TMP3]] : !s8i, !cir.ptr<!s8i> tbaa(#tbaa[[CHAR]])
+  // CIR: else
+  // CIR: %[[C3:.*]] = cir.const #cir.int<0> : !s32i
+  // CIR: %[[C3_CHAR:.*]] = cir.cast(integral, %[[C3]] : !s32i), !s8i
+  // CIR: %[[TMP4:.*]] = cir.load deref %[[ARG_b]] : !cir.ptr<!cir.ptr<!s8i>>, !cir.ptr<!s8i> tbaa(#tbaa[[CHAR_PTR]])
+  // CIR: cir.store %[[C3_CHAR]], %[[TMP4]] : !s8i, !cir.ptr<!s8i> tbaa(#tbaa[[CHAR]])
+
+
+  // LLVM-LABEL: void @test_char
+  // LLVM: %[[ARG_a:.*]] = load i8, ptr %{{.*}}, align 1, !tbaa ![[TBAA_CHAR:.*]]
+  // LLVM: %[[COND:.*]] = icmp eq i8 %[[ARG_a]], 97
+  // LLVM: %[[RET:.*]] = select i1 %[[COND]], i8 98, i8 0
+  // LLVM: store i8 %[[RET]], ptr %{{.*}}, align 1, !tbaa ![[TBAA_CHAR]]
+  // LLVM: ret void
+  if (*a == 'a') {
+    *b = 'b';
+  }
+  else {
+    *b = '\0';
+  }
+}
+
+// LLVM: ![[TBAA_INT]] = !{![[TBAA_INT_PARENT:.*]], ![[TBAA_INT_PARENT]], i64 0}
+// LLVM: ![[TBAA_INT_PARENT]] = !{!"int", ![[CHAR:.*]], i64 0}
+// LLVM: ![[CHAR]] = !{!"omnipotent char", ![[ROOT:.*]], i64 0}
+// LLVM: ![[ROOT]] = !{!"Simple C/C++ TBAA"}
+// LLVM: ![[TBAA_FLOAT]] = !{![[TBAA_FLOAT_PARENT:.*]], ![[TBAA_FLOAT_PARENT]], i64 0}
+// LLVM: ![[TBAA_FLOAT_PARENT]] = !{!"float", ![[CHAR]], i64 0}
+// LLVM: ![[TBAA_LONG]] = !{![[TBAA_LONG_PARENT:.*]], ![[TBAA_LONG_PARENT]], i64 0}
+// LLVM: ![[TBAA_LONG_PARENT]] = !{!"long", ![[CHAR]], i64 0}
+// LLVM: ![[TBAA_DOUBLE]] = !{![[TBAA_DOUBLE_PARENT:.*]], ![[TBAA_DOUBLE_PARENT]], i64 0}
+// LLVM: ![[TBAA_DOUBLE_PARENT]] = !{!"double", ![[CHAR]], i64 0}
+// LLVM: ![[TBAA_LONG_DOUBLE]] = !{![[TBAA_LONG_DOUBLE_PARENT:.*]], ![[TBAA_LONG_DOUBLE_PARENT]], i64 0}
+// LLVM: ![[TBAA_LONG_DOUBLE_PARENT]] = !{!"long double", ![[CHAR]], i64 0}
+// LLVM: ![[TBAA_CHAR]] = !{![[CHAR]], ![[CHAR]], i64 0}
diff --git a/clang/test/CIR/CodeGen/tbaa-struct.cpp b/clang/test/CIR/CodeGen/tbaa-struct.cpp
new file mode 100644
index 000000000000..84c49df6b455
--- /dev/null
+++ b/clang/test/CIR/CodeGen/tbaa-struct.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -O1
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+
+// CIR: #tbaa[[tbaa_NYI:.*]] = #cir.tbaa
+// CIR: #tbaa[[INT:.*]] = #cir.tbaa_scalar<type = !u32i>
+// CIR: #tbaa[[INT_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!u32i>>
+// CIR: #tbaa[[StructA_PTR:.*]] = #cir.tbaa_scalar<type = !cir.ptr<!ty_StructA>>
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+typedef struct
+{
+   uint16_t f16;
+   uint32_t f32;
+   uint16_t f16_2;
+   uint32_t f32_2;
+} StructA;
+
+uint32_t g(uint32_t *s, StructA *A) {
+  // CIR-LABEL: cir.func @_Z1g
+  // CIR: %[[INT_1:.*]] = cir.const #cir.int<1> : !s32i
+  // CIR: %[[UINT_1:.*]] = cir.cast(integral, %[[INT_1]] : !s32i), !u32i
+  // CIR: cir.store %[[UINT_1]], %{{.*}} : !u32i, !cir.ptr<!u32i> tbaa(#tbaa[[INT]])
+  // CIR: %[[INT_4:.*]] = cir.const #cir.int<4> : !s32i
+  // CIR: %[[UINT_4:.*]] = cir.cast(integral, %[[INT_4]] : !s32i), !u32i
+  // CIR: %[[pointer_to_StructA:.*]] = cir.load %{{.*}} : !cir.ptr<!cir.ptr<!ty_StructA>>, !cir.ptr<!ty_StructA> tbaa(#tbaa[[StructA_PTR]])
+  // CIR: %[[A_f32:.*]] = cir.get_member %[[pointer_to_StructA]][1] {name = "f32"} : !cir.ptr<!ty_StructA> -> !cir.ptr<!u32i>
+  // CIR: cir.store %[[UINT_4]], %[[A_f32]] : !u32i, !cir.ptr<!u32i> tbaa(#tbaa[[tbaa_NYI]])
+
+  *s = 1;
+  A->f32 = 4;
+  return *s;
+}
diff --git a/clang/test/CIR/CodeGen/tbaa-vptr.cpp b/clang/test/CIR/CodeGen/tbaa-vptr.cpp
new file mode 100644
index 000000000000..dbe28be626a2
--- /dev/null
+++ b/clang/test/CIR/CodeGen/tbaa-vptr.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -O1
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+
+// CIR-NOT: #tbaa
+
+struct Member {
+  ~Member();
+};
+
+struct A {
+  virtual ~A();
+};
+
+struct B : A {
+  Member m;
+  virtual ~B();
+};
+B::~B() { }
diff --git a/clang/test/CIR/CodeGen/tbaa.c b/clang/test/CIR/CodeGen/tbaa.c
deleted file mode 100644
index 43cdde47ecb7..000000000000
--- a/clang/test/CIR/CodeGen/tbaa.c
+++ /dev/null
@@ -1,22 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -O1
-// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
-
-// CIR: #tbaa[[TBAA_NO:.*]] = #cir.tbaa
-void f(int *a, float *b) {
-  // CIR: cir.scope
-  // CIR: %[[TMP1:.*]] = cir.load deref %{{.*}} : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i> tbaa([#tbaa[[TBAA_NO]]])
-  // CIR: %[[TMP2:.*]] = cir.load %[[TMP1]] : !cir.ptr<!s32i>, !s32i tbaa([#tbaa[[TBAA_NO]]])
-  // CIR: cir.if
-  // CIR: %[[C2:.*]] = cir.const #cir.fp<2
-  // CIR: %[[TMP3:.*]] = cir.load deref %[[ARG_b:.*]] : !cir.ptr<!cir.ptr<!cir.float>>, !cir.ptr<!cir.float> tbaa([#tbaa[[TBAA_NO]]])
-  // CIR: cir.store %[[C2]], %[[TMP3]] : !cir.float, !cir.ptr<!cir.float> tbaa([#tbaa[[TBAA_NO]]])
-  // CIR: else
-  // CIR: %[[C3:.*]] = cir.const #cir.fp<3
-  // CIR: %[[TMP4:.*]] = cir.load deref %[[ARG_b]] : !cir.ptr<!cir.ptr<!cir.float>>, !cir.ptr<!cir.float> tbaa([#tbaa[[TBAA_NO]]])
-  // CIR: cir.store %[[C3]], %[[TMP4]] : !cir.float, !cir.ptr<!cir.float> tbaa([#tbaa[[TBAA_NO]]])
-  if (*a == 1) {
-    *b = 2.0f;
-  } else {
-    *b = 3.0f;
-  }
-}