[Mlir-commits] [mlir] [mlir] Migrate users to explicitly discardable/inherent attribute APIs (PR #217233)

Mehdi Amini llvmlistbot at llvm.org
Wed Aug 19 01:02:45 PDT 2026


https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/217233

Start to require explicit handling of discardable attributes. Use operation-specific accessors for attributes stored as properties.

This is both more efficient (no need to materialize the combine dictionnary) and a step in the direction of deprecating these combined APIs.

Assisted-by: Codex

>From c5196f0c3b74db0aed0622ecc7a4521a9293e3d2 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 19 Aug 2026 00:55:40 -0700
Subject: [PATCH] [mlir] Migrate users to explicitly discardable/inherent
 attribute APIs

Start to require explicit handling of discardable attributes.
Use operation-specific accessors for attributes stored as properties.

This is both more efficient (no need to materialize the combine dictionnary)
and a step in the direction of deprecating these combined APIs.

Assisted-by: Codex
---
 mlir/examples/toy/Ch2/mlir/Dialect.cpp        |   6 +-
 mlir/examples/toy/Ch3/mlir/Dialect.cpp        |   6 +-
 mlir/examples/toy/Ch4/mlir/Dialect.cpp        |  10 +-
 mlir/examples/toy/Ch5/mlir/Dialect.cpp        |  10 +-
 mlir/examples/toy/Ch6/mlir/Dialect.cpp        |  10 +-
 mlir/examples/toy/Ch7/mlir/Dialect.cpp        |  10 +-
 .../ArithCommon/AttrToLLVMConverter.h         |  17 ++-
 .../mlir/Conversion/LLVMCommon/Pattern.h      |   5 +-
 .../Conversion/LLVMCommon/VectorPattern.h     |   7 +-
 .../mlir/Dialect/ArmSME/IR/ArmSMEOps.td       |   4 +-
 .../include/mlir/Dialect/Async/IR/AsyncOps.td |   4 +-
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   |   4 +-
 mlir/include/mlir/Dialect/Func/IR/FuncOps.td  |   4 +-
 mlir/include/mlir/Dialect/GPU/IR/GPUOps.td    |   6 +-
 .../LLVMIR/BasicPtxBuilderInterface.td        |  17 ++-
 .../mlir/Dialect/LLVMIR/LLVMInterfaces.td     |  11 ++
 mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td   |   3 +-
 mlir/include/mlir/Dialect/OpenACC/OpenACC.h   |   5 +-
 .../Interfaces/AtomicInterfaces.td            |   9 +-
 .../Dialect/OpenMP/OpenMPOpsInterfaces.td     |  45 ++++----
 .../mlir/Dialect/Shard/Transforms/Simplify.h  |   5 +-
 .../mlir/Dialect/Tosa/Utils/ConversionUtils.h |   8 +-
 mlir/include/mlir/IR/Matchers.h               |  13 ++-
 mlir/include/mlir/IR/Operation.h              | 100 +++++++++++++++---
 mlir/include/mlir/IR/SymbolInterfaces.td      |   7 +-
 mlir/include/mlir/Target/SMTLIB/Namespace.h   |   5 +-
 mlir/lib/Analysis/CallGraph.cpp               |  13 ++-
 .../DataFlow/ConstantPropagationAnalysis.cpp  |   8 +-
 mlir/lib/Bytecode/Writer/BytecodeWriter.cpp   |  11 +-
 mlir/lib/Bytecode/Writer/IRNumbering.cpp      |  12 ++-
 mlir/lib/CAPI/Dialect/Linalg.cpp              |   5 +-
 mlir/lib/CAPI/IR/IR.cpp                       |   8 +-
 .../AMDGPUToROCDL/AMDGPUToROCDL.cpp           |   6 +-
 .../Conversion/ArithToLLVM/ArithToLLVM.cpp    |   8 +-
 .../Conversion/ArithToSPIRV/ArithToSPIRV.cpp  |  12 ++-
 .../ComplexToLLVM/ComplexToLLVM.cpp           |   4 +-
 .../ControlFlowToLLVM/ControlFlowToLLVM.cpp   |   4 +-
 .../Conversion/FuncToEmitC/FuncToEmitC.cpp    |  21 +++-
 mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp |  44 ++++----
 .../Conversion/FuncToSPIRV/FuncToSPIRV.cpp    |  12 ++-
 .../Conversion/GPUCommon/GPUOpsLowering.cpp   |  14 +--
 .../GPUCommon/IndexIntrinsicsOpLowering.h     |   2 +-
 .../GPUToNVVM/LowerGpuOpsToNVVMOps.cpp        |   4 +-
 .../GPUToROCDL/LowerGpuOpsToROCDLOps.cpp      |  11 +-
 mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp |  41 +++++--
 .../Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp  |   4 +-
 .../LowerFunctionDiscardablesToLLVM.cpp       |   3 +-
 mlir/lib/Conversion/LLVMCommon/Pattern.cpp    |   2 +-
 .../LinalgToStandard/LinalgToStandard.cpp     |   4 +-
 .../Conversion/MathToFuncs/MathToFuncs.cpp    |   6 +-
 mlir/lib/Conversion/MathToLibm/MathToLibm.cpp |   4 +-
 mlir/lib/Conversion/MathToXeVM/MathToXeVM.cpp |   3 +-
 .../MapMemRefStorageClassPass.cpp             |   6 +-
 .../Conversion/NVGPUToNVVM/NVGPUToNVVM.cpp    |   4 +-
 .../Conversion/OpenMPToLLVM/OpenMPToLLVM.cpp  |  11 +-
 .../Conversion/RaiseWasm/RaiseWasmMLIR.cpp    |   8 +-
 .../SCFToControlFlow/SCFToControlFlow.cpp     |   4 +-
 mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp     |  19 ++--
 mlir/lib/Conversion/SCFToSPIRV/SCFToSPIRV.cpp |  11 +-
 .../ConvertLaunchFuncToLLVMCalls.cpp          |  29 ++---
 .../Conversion/SPIRVToLLVM/SPIRVToLLVM.cpp    |  37 ++++---
 .../Conversion/TosaToLinalg/TosaToLinalg.cpp  |  13 +--
 .../TosaToLinalg/TosaToLinalgNamed.cpp        |   6 +-
 .../TosaToSPIRVTosa/TosaToSPIRVTosa.cpp       |   4 +-
 .../TosaToSPIRVTosaConstants.cpp              |   5 +-
 .../TosaToSPIRVTosa/TosaToSPIRVTosaPass.cpp   |   4 +-
 .../VectorToArmSME/VectorToArmSME.cpp         |   6 +-
 .../Conversion/VectorToSCF/VectorToSCF.cpp    |  10 +-
 .../Conversion/XeGPUToXeVM/XeGPUToXeVM.cpp    |   4 +-
 mlir/lib/Conversion/XeVMToLLVM/XeVMToLLVM.cpp |  29 +++--
 .../AMDGPU/Transforms/EmulateAtomics.cpp      |   3 +-
 .../AMDGPU/Transforms/MaskedloadToLoad.cpp    |   4 +-
 mlir/lib/Dialect/Affine/Analysis/Utils.cpp    |   3 +-
 mlir/lib/Dialect/Affine/IR/AffineOps.cpp      | 100 ++++++++----------
 .../Transforms/PipelineDataTransfer.cpp       |   2 +-
 .../Transforms/SimplifyAffineStructures.cpp   |   9 +-
 .../Affine/Transforms/SuperVectorize.cpp      |   8 +-
 mlir/lib/Dialect/Affine/Utils/Utils.cpp       |  19 ++--
 mlir/lib/Dialect/Arith/IR/ArithOps.cpp        |  13 ++-
 .../Transforms/EmulateUnsupportedFloats.cpp   |   8 +-
 .../Transforms/IntRangeOptimizations.cpp      |   5 +-
 .../Transforms/UnsignedWhenEquivalent.cpp     |   5 +-
 .../ArmSME/Transforms/EnableArmStreaming.cpp  |   6 +-
 .../Transforms/LegalizeVectorStorage.cpp      |   4 +-
 mlir/lib/Dialect/Async/IR/Async.cpp           |   7 +-
 .../Async/Transforms/AsyncToAsyncRuntime.cpp  |  11 +-
 .../Bufferization/IR/BufferizationOps.cpp     |   7 +-
 .../FuncBufferizableOpInterfaceImpl.cpp       |   6 +-
 .../Transforms/OneShotAnalysis.cpp            |  26 +++--
 .../Transforms/OneShotModuleBufferize.cpp     |   6 +-
 .../OwnershipBasedBufferDeallocation.cpp      |  12 +--
 mlir/lib/Dialect/DLTI/DLTI.cpp                |   2 +-
 mlir/lib/Dialect/DLTI/Traits.cpp              |   4 +-
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |   9 +-
 .../EmitC/Transforms/MLGOAddReflectionMap.cpp |   3 +-
 mlir/lib/Dialect/Func/IR/FuncOps.cpp          |   8 +-
 .../DuplicateFunctionElimination.cpp          |   3 +-
 mlir/lib/Dialect/GPU/IR/GPUDialect.cpp        |  44 ++++----
 .../GPU/IR/InferIntRangeInterfaceImpls.cpp    |   4 +-
 .../GPU/Transforms/EliminateBarriers.cpp      |   2 +-
 .../GPU/Transforms/KernelOutlining.cpp        |   8 +-
 .../GPU/Transforms/ParallelLoopMapper.cpp     |   7 +-
 mlir/lib/Dialect/IRDL/IRDLLoading.cpp         |   4 +-
 mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp      |   4 +-
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    |  35 +++---
 mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp    |  31 ++++--
 .../Transforms/InlinerInterfaceImpl.cpp       |   2 +-
 .../LLVMIR/Transforms/RequestCWrappers.cpp    |   5 +-
 .../Dialect/Linalg/IR/LinalgInterfaces.cpp    |   8 +-
 mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp      |  65 +++++++-----
 .../TransformOps/LinalgTransformOps.cpp       |  15 ++-
 .../BufferizableOpInterfaceImpl.cpp           |  17 ++-
 .../Linalg/Transforms/DropUnitDims.cpp        |   4 +-
 .../Linalg/Transforms/ElementwiseToLinalg.cpp |   9 +-
 .../EraseUnusedOperandsAndResults.cpp         |   4 +-
 .../Linalg/Transforms/FoldIntoElementwise.cpp |   3 +-
 .../Transforms/SimplifyDepthwiseConv.cpp      |   2 +-
 .../Linalg/Transforms/TilingInterfaceImpl.cpp |  26 +++--
 .../Linalg/Transforms/Vectorization.cpp       |  18 ++--
 .../Transforms/PolynomialApproximation.cpp    |   4 +-
 mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp      |  14 +--
 .../Transforms/AllocationOpInterfaceImpl.cpp  |  11 +-
 .../Dialect/MemRef/Transforms/MultiBuffer.cpp |   5 +-
 .../MemRef/Transforms/NormalizeMemRefs.cpp    |   3 +-
 mlir/lib/Dialect/NVGPU/IR/NVGPUDialect.cpp    |   5 +-
 .../NVGPU/Transforms/MmaSyncTF32Transform.cpp |   2 +-
 mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp       |   2 +-
 mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp     |  18 ++--
 .../OpenACC/Transforms/ACCBindRoutine.cpp     |   8 +-
 .../Dialect/OpenACC/Transforms/ACCCGToGPU.cpp |  10 +-
 .../OpenACC/Transforms/ACCComputeLowering.cpp |   2 +-
 .../ACCDeclareGPUModuleInsertion.cpp          |  21 ++--
 .../OpenACC/Transforms/ACCEmitRemarksData.cpp |   2 +-
 .../OpenACC/Transforms/ACCEmitRemarksLoop.cpp |   2 +-
 .../OpenACC/Transforms/ACCImplicitData.cpp    |   4 +-
 .../OpenACC/Transforms/ACCImplicitDeclare.cpp |   9 +-
 .../OpenACC/Transforms/ACCImplicitRoutine.cpp |   4 +-
 .../Transforms/ACCRecipeMaterialization.cpp   |  16 +--
 .../OpenACC/Transforms/ACCRoutineLowering.cpp |  16 +--
 .../Transforms/ACCRoutineToGPUFunc.cpp        |  16 +--
 .../Dialect/OpenACC/Utils/OpenACCUtils.cpp    |  16 +--
 .../Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp  |  25 +++--
 .../Dialect/OpenACC/Utils/OpenACCUtilsGPU.cpp |   4 +-
 .../OpenACC/Utils/OpenACCUtilsLoop.cpp        |   8 +-
 mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp  |  24 +++--
 mlir/lib/Dialect/OpenMP/Utils/Utils.cpp       |   8 +-
 mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp   |   2 +-
 .../Quant/Transforms/NormalizeQuantTypes.cpp  |   3 +-
 mlir/lib/Dialect/SCF/IR/SCF.cpp               |  49 +++++----
 .../BufferizableOpInterfaceImpl.cpp           |   3 +-
 .../lib/Dialect/SCF/Transforms/ForToWhile.cpp |   5 +-
 .../SCF/Transforms/ForallToParallel.cpp       |   2 +-
 .../SCF/Transforms/LoopSpecialization.cpp     |  16 +--
 .../Transforms/StructuralTypeConversions.cpp  |   4 +-
 mlir/lib/Dialect/SMT/IR/SMTOps.cpp            |  12 ++-
 mlir/lib/Dialect/SPIRV/IR/AtomicOps.cpp       |   6 +-
 mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp  |   4 +-
 mlir/lib/Dialect/SPIRV/IR/DotProductOps.cpp   |  14 +--
 mlir/lib/Dialect/SPIRV/IR/GroupOps.cpp        |   6 +-
 mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp       |  33 +++---
 .../SPIRV/IR/SPIRVCanonicalization.cpp        |   9 +-
 mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp        |  30 +++---
 mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp    |   4 +-
 .../Linking/ModuleCombiner/ModuleCombiner.cpp |   9 +-
 .../DecorateCompositeTypeLayoutPass.cpp       |  11 +-
 .../Transforms/LowerABIAttributesPass.cpp     |  12 ++-
 .../SPIRV/Transforms/SPIRVConversion.cpp      |  26 +++--
 .../Transforms/UnifyAliasedResourcePass.cpp   |  10 +-
 .../SPIRV/Transforms/UpdateVCEPass.cpp        |   2 +-
 mlir/lib/Dialect/Shape/IR/Shape.cpp           |  20 ++--
 .../SparseTensor/IR/SparseTensorDialect.cpp   |   2 +-
 .../Transforms/SparseAssembler.cpp            |   9 +-
 .../Transforms/SparseGPUCodegen.cpp           |   7 +-
 .../Transforms/SparseReinterpretMap.cpp       |   4 +-
 .../Transforms/SparseVectorization.cpp        |   9 +-
 .../Transforms/Sparsification.cpp             |   4 +-
 .../Transforms/Utils/CodegenUtils.cpp         |   4 +-
 .../Transforms/Utils/LoopEmitter.h            |   3 +-
 .../Transforms/ScalarizeFunctionResult.cpp    |   3 +-
 mlir/lib/Dialect/Tosa/IR/TargetEnv.cpp        |   3 +-
 mlir/lib/Dialect/Tosa/IR/TosaOps.cpp          |  21 ++--
 .../Tosa/Transforms/TosaAttachTarget.cpp      |   2 +-
 .../TosaConvertIntegerTypeToSignless.cpp      |   3 +-
 .../Tosa/Transforms/TosaNarrowTypes.cpp       |   6 +-
 .../Tosa/Transforms/TosaReduceTransposes.cpp  |  14 +--
 .../Tosa/Transforms/TosaValidation.cpp        |   6 +-
 .../Dialect/Tosa/Utils/ConversionUtils.cpp    |   3 +-
 .../Dialect/Transform/IR/TransformDialect.cpp |   4 +-
 .../lib/Dialect/Transform/IR/TransformOps.cpp |  11 +-
 .../Interfaces/TransformInterfaces.cpp        |   4 +-
 .../Transform/Transforms/InterpreterPass.cpp  |   2 +-
 .../Transforms/TransformInterpreterUtils.cpp  |   4 +-
 mlir/lib/Dialect/Utils/StructuredOpsUtils.cpp |   8 +-
 mlir/lib/Dialect/Vector/IR/VectorOps.cpp      |  30 +++---
 .../Vector/Transforms/VectorDistribute.cpp    |   3 +-
 .../Transforms/VectorDropLeadUnitDim.cpp      |  20 ++--
 .../Vector/Transforms/VectorLinearize.cpp     |   8 +-
 .../VectorTransferSplitRewritePatterns.cpp    |   8 +-
 .../Vector/Transforms/VectorTransforms.cpp    |  20 ++--
 .../Vector/Transforms/VectorUnroll.cpp        |   6 +-
 .../XeGPU/Transforms/XeGPUBlocking.cpp        |  20 ++--
 .../XeGPU/Transforms/XeGPULayoutImpl.cpp      |  10 +-
 .../XeGPU/Transforms/XeGPUPropagateLayout.cpp |  10 +-
 .../Transforms/XeGPUSgToLaneDistribute.cpp    |  13 ++-
 .../Dialect/XeGPU/Transforms/XeGPUUnroll.cpp  |  30 +++---
 .../Transforms/XeGPUWgToSgDistribute.cpp      |   5 +-
 mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp   |  27 ++---
 mlir/lib/IR/AsmPrinter.cpp                    |  13 ++-
 mlir/lib/IR/AttrTypeSubElements.cpp           |  13 ++-
 mlir/lib/IR/BuiltinAttributes.cpp             |   7 +-
 mlir/lib/IR/BuiltinDialect.cpp                |  11 +-
 mlir/lib/IR/Operation.cpp                     |   7 +-
 mlir/lib/IR/OperationSupport.cpp              |   8 +-
 mlir/lib/IR/PatternMatch.cpp                  |   4 +-
 mlir/lib/IR/SymbolTable.cpp                   |  84 ++++++++++-----
 .../lib/Interfaces/FunctionImplementation.cpp |  18 ++--
 mlir/lib/Interfaces/Utils/MemorySlotUtils.cpp |   3 +-
 mlir/lib/Pass/IRPrinting.cpp                  |  11 +-
 mlir/lib/Rewrite/ByteCode.cpp                 |  10 +-
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        |   6 +-
 mlir/lib/Target/LLVM/XeVM/Target.cpp          |   3 +-
 .../LLVMIR/LLVMIRToLLVMTranslation.cpp        |   2 +-
 .../LLVMIR/LLVMToLLVMIRTranslation.cpp        |   2 +-
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |   5 +-
 .../ROCDL/ROCDLToLLVMIRTranslation.cpp        |   8 +-
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       |  27 ++---
 mlir/lib/Target/LLVMIR/ModuleTranslation.cpp  |  12 ++-
 .../LLVMIR/Transforms/TargetToDataLayout.cpp  |   9 +-
 .../Transforms/TargetToTargetFeatures.cpp     |   5 +-
 .../SPIRV/Deserialization/Deserializer.cpp    |  33 +++---
 .../SPIRV/Serialization/SerializeOps.cpp      |  44 ++++----
 .../Target/SPIRV/Serialization/Serializer.cpp |  15 +--
 mlir/lib/Target/Wasm/TranslateFromWasm.cpp    |   6 +-
 .../Transforms/Utils/DialectConversion.cpp    |  17 +--
 mlir/lib/Transforms/ViewOpGraph.cpp           |  10 +-
 mlir/python/mlir/dialects/gpu/__init__.py     |  10 +-
 .../DataFlow/TestDeadCodeAnalysis.cpp         |   2 +-
 .../TestDenseBackwardDataFlowAnalysis.cpp     |  13 +--
 .../DataFlow/TestDenseDataFlowAnalysis.h      |   2 +-
 .../TestDenseForwardDataFlowAnalysis.cpp      |   5 +-
 .../TestIntegerDivisibilityAnalysis.cpp       |   5 +-
 .../DataFlow/TestLivenessAnalysis.cpp         |   2 +-
 .../TestSparseBackwardDataFlowAnalysis.cpp    |   6 +-
 mlir/test/lib/Analysis/TestAliasAnalysis.cpp  |   5 +-
 .../lib/Analysis/TestDataFlowFramework.cpp    |  15 +--
 mlir/test/lib/Analysis/TestSlice.cpp          |   4 +-
 .../test/lib/Analysis/TestTopologicalSort.cpp |   8 +-
 .../TestTensorLikeAndBufferLike.cpp           |   2 +-
 .../lib/Dialect/DLTI/TestDataLayoutQuery.cpp  |   2 +-
 mlir/test/lib/Dialect/LLVM/TestPatterns.cpp   |   5 +-
 .../Dialect/OpenACC/TestOpenACCSupport.cpp    |   7 +-
 .../OpenACC/TestPointerLikeTypeInterface.cpp  |  18 ++--
 .../Dialect/OpenACC/TestRecipePopulate.cpp    |   2 +-
 .../lib/Dialect/SCF/TestLoopUnrolling.cpp     |   2 +-
 .../Dialect/SCF/TestParallelLoopUnrolling.cpp |   2 +-
 mlir/test/lib/Dialect/SCF/TestSCFUtils.cpp    |  25 +++--
 .../lib/Dialect/SPIRV/TestAvailability.cpp    |   5 +-
 .../lib/Dialect/SPIRV/TestEntryPointAbi.cpp   |  14 +--
 .../lib/Dialect/Shape/TestShapeFunctions.cpp  |   2 +-
 .../Dialect/Tensor/TestTensorTransforms.cpp   |   6 +-
 mlir/test/lib/Dialect/Test/TestDialect.cpp    |   6 +-
 .../Dialect/Test/TestDialectInterfaces.cpp    |   5 +-
 mlir/test/lib/Dialect/Test/TestOpDefs.cpp     |  29 +++--
 mlir/test/lib/Dialect/Test/TestOps.td         |  23 ++--
 mlir/test/lib/Dialect/Test/TestOpsSyntax.cpp  |   3 +-
 mlir/test/lib/Dialect/Test/TestPatterns.cpp   |  83 ++++++++-------
 .../TestTransformDialectExtension.cpp         |  13 +--
 .../lib/Dialect/XeGPU/TestXeGPUTransforms.cpp |   5 +-
 mlir/test/lib/IR/TestAffineWalk.cpp           |   2 +-
 .../lib/IR/TestBuiltinAttributeInterfaces.cpp |   5 +-
 .../lib/IR/TestBuiltinDistinctAttributes.cpp  |   8 +-
 mlir/test/lib/IR/TestDiagnostics.cpp          |   3 +-
 mlir/test/lib/IR/TestDiagnosticsMetadata.cpp  |   3 +-
 mlir/test/lib/IR/TestDominance.cpp            |   5 +-
 mlir/test/lib/IR/TestFunc.cpp                 |  13 ++-
 mlir/test/lib/IR/TestOperationEquals.cpp      |   6 +-
 mlir/test/lib/IR/TestPrintNesting.cpp         |   7 +-
 mlir/test/lib/IR/TestSymbolUses.cpp           |   3 +-
 mlir/test/lib/IR/TestVisitors.cpp             |   8 +-
 mlir/test/lib/IR/TestVisitorsGeneric.cpp      |  20 ++--
 .../lib/Transforms/TestControlFlowSink.cpp    |   4 +-
 .../test-linalg-ods-yaml-gen.yaml             |   2 +-
 .../mlir-linalg-ods-yaml-gen.cpp              |   8 +-
 mlir/tools/mlir-tblgen/DialectGen.cpp         |  10 +-
 mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp   |  11 +-
 mlir/tools/mlir-tblgen/OpFormatGen.cpp        |  24 +++--
 mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp      |  13 ++-
 mlir/unittests/Bytecode/BytecodeTest.cpp      |   4 +-
 .../Dialect/OpenACC/OpenACCUtilsCGTest.cpp    |   2 +-
 .../Dialect/OpenACC/OpenACCUtilsGPUTest.cpp   |   4 +-
 .../Dialect/OpenACC/OpenACCUtilsLoopTest.cpp  |   4 +-
 .../Dialect/OpenACC/OpenACCUtilsTest.cpp      |  15 +--
 .../Dialect/SPIRV/SerializationTest.cpp       |   2 +-
 mlir/unittests/IR/BlobManagerTest.cpp         |   3 +-
 mlir/unittests/IR/OpPropertiesTest.cpp        |  10 +-
 mlir/unittests/IR/OperationSupportTest.cpp    |   2 +-
 .../Interfaces/DataLayoutInterfacesTest.cpp   |  12 ++-
 mlir/unittests/Pass/PassManagerTest.cpp       |  12 +--
 mlir/unittests/TableGen/OpBuildGen.cpp        |  23 ++--
 .../Target/LLVM/SerializeToLLVMBitcode.cpp    |   5 +-
 300 files changed, 1955 insertions(+), 1341 deletions(-)

diff --git a/mlir/examples/toy/Ch2/mlir/Dialect.cpp b/mlir/examples/toy/Ch2/mlir/Dialect.cpp
index 489f348c8be52..41c451e94d322 100644
--- a/mlir/examples/toy/Ch2/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch2/mlir/Dialect.cpp
@@ -84,7 +84,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -135,7 +135,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
diff --git a/mlir/examples/toy/Ch3/mlir/Dialect.cpp b/mlir/examples/toy/Ch3/mlir/Dialect.cpp
index 708855f18cf45..bfd4480db5702 100644
--- a/mlir/examples/toy/Ch3/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch3/mlir/Dialect.cpp
@@ -84,7 +84,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -135,7 +135,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
diff --git a/mlir/examples/toy/Ch4/mlir/Dialect.cpp b/mlir/examples/toy/Ch4/mlir/Dialect.cpp
index 1e5e67296a753..298491ad3476e 100644
--- a/mlir/examples/toy/Ch4/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch4/mlir/Dialect.cpp
@@ -146,7 +146,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -197,7 +197,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
@@ -328,13 +330,13 @@ void GenericCallOp::build(mlir::OpBuilder &builder, mlir::OperationState &state,
 /// Return the callee of the generic call operation, this is required by the
 /// call interface.
 CallInterfaceCallable GenericCallOp::getCallableForCallee() {
-  return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+  return getCalleeAttr();
 }
 
 /// Set the callee for the generic call operation, this is required by the call
 /// interface.
 void GenericCallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
-  (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+  setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
 }
 
 /// Get the argument operands to the called function, this is required by the
diff --git a/mlir/examples/toy/Ch5/mlir/Dialect.cpp b/mlir/examples/toy/Ch5/mlir/Dialect.cpp
index 69fb69fd6e7ba..0f9c24eba0cc7 100644
--- a/mlir/examples/toy/Ch5/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch5/mlir/Dialect.cpp
@@ -146,7 +146,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -197,7 +197,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
@@ -328,13 +330,13 @@ void GenericCallOp::build(mlir::OpBuilder &builder, mlir::OperationState &state,
 /// Return the callee of the generic call operation, this is required by the
 /// call interface.
 CallInterfaceCallable GenericCallOp::getCallableForCallee() {
-  return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+  return getCalleeAttr();
 }
 
 /// Set the callee for the generic call operation, this is required by the call
 /// interface.
 void GenericCallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
-  (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+  setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
 }
 
 /// Get the argument operands to the called function, this is required by the
diff --git a/mlir/examples/toy/Ch6/mlir/Dialect.cpp b/mlir/examples/toy/Ch6/mlir/Dialect.cpp
index 69fb69fd6e7ba..0f9c24eba0cc7 100644
--- a/mlir/examples/toy/Ch6/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch6/mlir/Dialect.cpp
@@ -146,7 +146,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -197,7 +197,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
@@ -328,13 +330,13 @@ void GenericCallOp::build(mlir::OpBuilder &builder, mlir::OperationState &state,
 /// Return the callee of the generic call operation, this is required by the
 /// call interface.
 CallInterfaceCallable GenericCallOp::getCallableForCallee() {
-  return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+  return getCalleeAttr();
 }
 
 /// Set the callee for the generic call operation, this is required by the call
 /// interface.
 void GenericCallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
-  (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+  setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
 }
 
 /// Get the argument operands to the called function, this is required by the
diff --git a/mlir/examples/toy/Ch7/mlir/Dialect.cpp b/mlir/examples/toy/Ch7/mlir/Dialect.cpp
index 4d2f063afd0fb..db0364307af94 100644
--- a/mlir/examples/toy/Ch7/mlir/Dialect.cpp
+++ b/mlir/examples/toy/Ch7/mlir/Dialect.cpp
@@ -138,7 +138,7 @@ static mlir::ParseResult parseBinaryOp(mlir::OpAsmParser &parser,
 /// forms depending on if all of the types match.
 static void printBinaryOp(mlir::OpAsmPrinter &printer, mlir::Operation *op) {
   printer << " " << op->getOperands();
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer << " : ";
 
   // If all of the types are the same, print the type directly.
@@ -189,7 +189,9 @@ mlir::ParseResult ConstantOp::parse(mlir::OpAsmParser &parser,
 /// strings, attributes, operands, types, etc.
 void ConstantOp::print(mlir::OpAsmPrinter &printer) {
   printer << " ";
-  printer.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      /*elidedAttrs=*/{"value"});
   printer << getValue();
 }
 
@@ -361,13 +363,13 @@ void GenericCallOp::build(mlir::OpBuilder &builder, mlir::OperationState &state,
 /// Return the callee of the generic call operation, this is required by the
 /// call interface.
 CallInterfaceCallable GenericCallOp::getCallableForCallee() {
-  return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+  return getCalleeAttr();
 }
 
 /// Set the callee for the generic call operation, this is required by the call
 /// interface.
 void GenericCallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
-  (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+  setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
 }
 
 /// Get the argument operands to the called function, this is required by the
diff --git a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
index feb74c86e349f..497ab3b7152da 100644
--- a/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
+++ b/mlir/include/mlir/Conversion/ArithCommon/AttrToLLVMConverter.h
@@ -18,6 +18,15 @@
 
 namespace mlir {
 namespace arith {
+
+template <typename SourceOp>
+static NamedAttrList getAttrsWithProperties(SourceOp srcOp) {
+  NamedAttrList attrs(srcOp->getDiscardableAttrDictionary());
+  if (auto properties =
+          dyn_cast_or_null<DictionaryAttr>(srcOp->getPropertiesAsAttribute()))
+    attrs.append(properties.getValue());
+  return attrs;
+}
 /// Maps arithmetic fastmath enum values to LLVM enum values.
 LLVM::FastmathFlags
 convertArithFastMathFlagsToLLVM(arith::FastMathFlags arithFMF);
@@ -53,7 +62,7 @@ class AttrConvertFastMathToLLVM {
 public:
   AttrConvertFastMathToLLVM(SourceOp srcOp) {
     // Copy the source attributes.
-    convertedAttr = NamedAttrList{srcOp->getAttrs()};
+    convertedAttr = getAttrsWithProperties(srcOp);
     // Get the name of the arith fastmath attribute.
     StringRef arithFMFAttrName = SourceOp::getFastMathAttrName();
     // Remove the source fastmath attribute.
@@ -82,7 +91,7 @@ class AttrConvertOverflowToLLVM {
     using IntegerOverflowFlagsAttr = LLVM::IntegerOverflowFlagsAttr;
 
     // Copy the source attributes.
-    convertedAttr = NamedAttrList{srcOp->getAttrs()};
+    convertedAttr = getAttrsWithProperties(srcOp);
     // Get the name of the arith overflow attribute.
     StringRef arithAttrName = SourceOp::getIntegerOverflowAttrName();
     // Remove the source overflow attribute from the set that will be present
@@ -118,7 +127,7 @@ template <typename SourceOp, typename TargetOp>
 class AttrConvertNonNegToLLVM {
 public:
   AttrConvertNonNegToLLVM(SourceOp srcOp) {
-    convertedAttr = NamedAttrList{srcOp->getAttrs()};
+    convertedAttr = getAttrsWithProperties(srcOp);
     if (!convertedAttr.erase("nonNeg"))
       return;
     MLIRContext *ctx = srcOp.getOperation()->getContext();
@@ -144,7 +153,7 @@ class AttrConverterConstrainedFPToLLVM {
 public:
   AttrConverterConstrainedFPToLLVM(SourceOp srcOp) {
     // Copy the source attributes.
-    convertedAttr = NamedAttrList{srcOp->getAttrs()};
+    convertedAttr = getAttrsWithProperties(srcOp);
 
     if constexpr (TargetOp::template hasTrait<
                       LLVM::RoundingModeOpInterface::Trait>()) {
diff --git a/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h b/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h
index 2f468458addd3..b50aeb1e274b3 100644
--- a/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h
+++ b/mlir/include/mlir/Conversion/LLVMCommon/Pattern.h
@@ -338,8 +338,9 @@ class OneToOneConvertToLLVMPattern : public ConvertOpToLLVMPattern<SourceOp> {
   matchAndRewrite(SourceOp op, typename SourceOp::Adaptor adaptor,
                   ConversionPatternRewriter &rewriter) const override {
     return LLVM::detail::oneToOneRewrite(
-        op, TargetOp::getOperationName(), adaptor.getOperands(), op->getAttrs(),
-        /*propertiesAttr=*/Attribute{}, *this->getTypeConverter(), rewriter);
+        op, TargetOp::getOperationName(), adaptor.getOperands(),
+        op->getDiscardableAttrDictionary().getValue(),
+        op->getPropertiesAsAttribute(), *this->getTypeConverter(), rewriter);
   }
 };
 
diff --git a/mlir/include/mlir/Conversion/LLVMCommon/VectorPattern.h b/mlir/include/mlir/Conversion/LLVMCommon/VectorPattern.h
index 65988a2466318..fdc787d84383e 100644
--- a/mlir/include/mlir/Conversion/LLVMCommon/VectorPattern.h
+++ b/mlir/include/mlir/Conversion/LLVMCommon/VectorPattern.h
@@ -70,13 +70,16 @@ LogicalResult vectorOneToOneRewrite(Operation *op, StringRef targetOp,
 template <typename SourceOp, typename TargetOp>
 class AttrConvertPassThrough {
 public:
-  AttrConvertPassThrough(SourceOp srcOp) : srcAttrs(srcOp->getAttrs()) {}
+  AttrConvertPassThrough(SourceOp srcOp)
+      : srcAttrs(srcOp->getDiscardableAttrDictionary().getValue()),
+        propertiesAttr(srcOp->getPropertiesAsAttribute()) {}
 
   ArrayRef<NamedAttribute> getAttrs() const { return srcAttrs; }
-  Attribute getPropAttr() const { return {}; }
+  Attribute getPropAttr() const { return propertiesAttr; }
 
 private:
   ArrayRef<NamedAttribute> srcAttrs;
+  Attribute propertiesAttr;
 };
 
 /// Basic lowering implementation to rewrite Ops with just one result to the
diff --git a/mlir/include/mlir/Dialect/ArmSME/IR/ArmSMEOps.td b/mlir/include/mlir/Dialect/ArmSME/IR/ArmSMEOps.td
index 264c3969a1152..22ed496a9f48f 100644
--- a/mlir/include/mlir/Dialect/ArmSME/IR/ArmSMEOps.td
+++ b/mlir/include/mlir/Dialect/ArmSME/IR/ArmSMEOps.td
@@ -63,7 +63,7 @@ def ArmSMETileOpInterface : OpInterface<"ArmSMETileOpInterface"> {
         if (!tileId)
           return;
         ::mlir::Operation* op = this->getOperation();
-        op->setAttr("tile_id", tileId);
+        op->setDiscardableAttr("tile_id", tileId);
       }]
     >,
     InterfaceMethod<
@@ -77,7 +77,7 @@ def ArmSMETileOpInterface : OpInterface<"ArmSMETileOpInterface"> {
       /*methodBody=*/[{}],
       /*defaultImpl=*/ [{
         ::mlir::Operation* op = this->getOperation();
-        return op->getAttrOfType<mlir::IntegerAttr>("tile_id");
+        return op->getDiscardableAttrOfType<mlir::IntegerAttr>("tile_id");
       }]
     >,
     InterfaceMethod<
diff --git a/mlir/include/mlir/Dialect/Async/IR/AsyncOps.td b/mlir/include/mlir/Dialect/Async/IR/AsyncOps.td
index 722370b8f3e29..0368bb55ca271 100644
--- a/mlir/include/mlir/Dialect/Async/IR/AsyncOps.td
+++ b/mlir/include/mlir/Dialect/Async/IR/AsyncOps.td
@@ -261,12 +261,12 @@ def Async_CallOp : Async_Op<"call",
 
     /// Return the callee of this operation.
     CallInterfaceCallable getCallableForCallee() {
-      return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+      return getCalleeAttr();
     }
 
     /// Set the callee for this operation.
     void setCalleeFromCallable(CallInterfaceCallable callee) {
-      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+      setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
     }
   }];
 
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 49412d1dfb01c..0e7ea45d6d091 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -794,12 +794,12 @@ def EmitC_CallOp : EmitC_Op<"call",
 
     /// Return the callee of this operation.
     CallInterfaceCallable getCallableForCallee() {
-      return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+      return getCalleeAttr();
     }
 
     /// Set the callee for this operation.
     void setCalleeFromCallable(CallInterfaceCallable callee) {
-      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+      setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
     }
 
     bool hasSideEffects() {
diff --git a/mlir/include/mlir/Dialect/Func/IR/FuncOps.td b/mlir/include/mlir/Dialect/Func/IR/FuncOps.td
index a31b860276099..f86dac3a772f1 100644
--- a/mlir/include/mlir/Dialect/Func/IR/FuncOps.td
+++ b/mlir/include/mlir/Dialect/Func/IR/FuncOps.td
@@ -110,12 +110,12 @@ def CallOp : Func_Op<"call",
 
     /// Return the callee of this operation.
     CallInterfaceCallable getCallableForCallee() {
-      return (*this)->getAttrOfType<SymbolRefAttr>("callee");
+      return getCalleeAttr();
     }
 
     /// Set the callee for this operation.
     void setCalleeFromCallable(CallInterfaceCallable callee) {
-      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+      setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
     }
   }];
 
diff --git a/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td b/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
index de145960692ad..7dbce7bed893a 100644
--- a/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
+++ b/mlir/include/mlir/Dialect/GPU/IR/GPUOps.td
@@ -419,7 +419,9 @@ def GPU_GPUFuncOp : GPU_Op<"func", [
     attribution.
   }];
 
-  let arguments = (ins TypeAttrOf<FunctionType>:$function_type,
+  let arguments = (ins SymbolNameAttr:$sym_name,
+                       OptionalAttr<StrAttr>:$sym_visibility,
+                       TypeAttrOf<FunctionType>:$function_type,
                        OptionalAttr<DictArrayAttr>:$arg_attrs,
                        OptionalAttr<DictArrayAttr>:$res_attrs,
                        OptionalAttr<DictArrayAttr>:$workgroup_attrib_attrs,
@@ -447,7 +449,7 @@ def GPU_GPUFuncOp : GPU_Op<"func", [
     bool isKernel() {
       if (getKernel())
         return true;
-      return (*this)->getAttrOfType<UnitAttr>(
+      return (*this)->getDiscardableAttrOfType<UnitAttr>(
           GPUDialect::getKernelFuncAttrName()) != nullptr;
     }
 
diff --git a/mlir/include/mlir/Dialect/LLVMIR/BasicPtxBuilderInterface.td b/mlir/include/mlir/Dialect/LLVMIR/BasicPtxBuilderInterface.td
index 2834ae160595d..b631ec169f943 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/BasicPtxBuilderInterface.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/BasicPtxBuilderInterface.td
@@ -157,11 +157,18 @@ def BasicPtxBuilderOpInterface : OpInterface<"BasicPtxBuilderInterface"> {
            for (auto val : op->getOperands()) 
             asmValues.push_back({val, mlir::NVVM::PTXRegisterMod::Read});
            
-           // Step 3. Add attributes
-           for (auto attr : op->getAttrs()) {
-            if (auto intAttr = dyn_cast<mlir::IntegerAttr>(attr.getValue())) {
-             ::mlir::Value val = makeConstantI32(rewriter, intAttr.getInt());
-             asmValues.push_back({val, mlir::NVVM::PTXRegisterMod::Read});
+           // Step 3. Add inherent attributes.
+           auto properties = llvm::dyn_cast_or_null<mlir::DictionaryAttr>(
+               op->getPropertiesAsAttribute());
+           if (properties) {
+             for (auto attr : properties) {
+               if (auto intAttr =
+                       dyn_cast<mlir::IntegerAttr>(attr.getValue())) {
+                 ::mlir::Value val =
+                     makeConstantI32(rewriter, intAttr.getInt());
+                 asmValues.push_back(
+                     {val, mlir::NVVM::PTXRegisterMod::Read});
+               }
              }
            }
            return false; // No manual mapping needed
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td
index c59b38c416956..66a1fe0ce251a 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMInterfaces.td
@@ -35,6 +35,17 @@ def FastmathFlagsInterface : OpInterface<"FastmathFlagsInterface"> {
         return op.getFastmathFlagsAttr();
       }]
       >,
+    InterfaceMethod<
+      /*desc=*/        "Sets the FastmathFlagsAttr attribute for the operation",
+      /*returnType=*/  "void",
+      /*methodName=*/  "setFastmathAttr",
+      /*args=*/        (ins "::mlir::LLVM::FastmathFlagsAttr":$attr),
+      /*methodBody=*/  [{}],
+      /*defaultImpl=*/ [{
+        auto op = cast<ConcreteOp>(this->getOperation());
+        op.setFastmathFlagsAttr(attr);
+      }]
+      >,
     StaticInterfaceMethod<
       /*desc=*/        [{Returns the name of the FastmathFlagsAttr attribute
                          for the operation}],
diff --git a/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td
index 6241606122f43..e1d3a1940a223 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td
@@ -277,7 +277,8 @@ class NVVM_PureSpecialRangeableRegisterOp<string mnemonic, list<Trait> traits =
     void $cppClass::inferResultRanges(
         ArrayRef<::mlir::ConstantIntRanges> argRanges,
         SetIntRangeFn setResultRanges) {
-        nvvmInferResultRanges(getOperation(), getResult(), argRanges, setResultRanges);
+        nvvmInferResultRanges(getRange(), getResult(), argRanges,
+                              setResultRanges);
     }
 
     // Verify the range attribute satisfies LLVM ConstantRange constructor requirements.
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index 4abc3971f12c0..ff455fa2b7b20 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -193,13 +193,14 @@ static constexpr StringLiteral getSpecializedRoutineAttrName() {
 /// Used to check whether the current operation is marked with
 /// `acc routine`. The operation passed in should be a function.
 inline bool isAccRoutine(mlir::Operation *op) {
-  return op && op->hasAttr(mlir::acc::getRoutineInfoAttrName());
+  return op && op->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName());
 }
 
 /// Used to check whether this is a specialized accelerator version of
 /// `acc routine` function.
 inline bool isSpecializedAccRoutine(mlir::Operation *op) {
-  return op && op->hasAttr(mlir::acc::getSpecializedRoutineAttrName());
+  return op &&
+         op->hasDiscardableAttr(mlir::acc::getSpecializedRoutineAttrName());
 }
 
 static constexpr StringLiteral getFromDefaultClauseAttrName() {
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index a61c43e2fd9cd..90c6a5fba8429 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -445,7 +445,8 @@ def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
           llvm::StringRef opName = op.getName().getStringRef();
           if (opName == "arith.cmpi" || opName == "llvm.icmp") {
             foundComparison = true;
-            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            auto predAttr = llvm::dyn_cast_or_null<mlir::IntegerAttr>(
+                op.getInherentAttr("predicate").value_or(mlir::Attribute{}));
             if (predAttr) {
 	      auto predName = mlir::arith::stringifyCmpIPredicate(
                   static_cast<mlir::arith::CmpIPredicate>(predAttr.getInt()));
@@ -460,7 +461,8 @@ def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
             break;
           } else if (opName == "arith.cmpf" || opName == "llvm.fcmp") {
             foundComparison = true;
-            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            auto predAttr = llvm::dyn_cast_or_null<mlir::IntegerAttr>(
+                op.getInherentAttr("predicate").value_or(mlir::Attribute{}));
             if (predAttr) {
               auto predName = mlir::arith::stringifyCmpFPredicate(
                   static_cast<mlir::arith::CmpFPredicate>(predAttr.getInt()));
@@ -475,7 +477,8 @@ def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
             break;
           } else if (opName == "fir.cmpc") {
             foundComparison = true;
-            auto predAttr = op.getAttrOfType<mlir::IntegerAttr>("predicate");
+            auto predAttr = llvm::dyn_cast_or_null<mlir::IntegerAttr>(
+                op.getInherentAttr("predicate").value_or(mlir::Attribute{}));
             if (predAttr) {
               auto predName = mlir::arith::stringifyCmpFPredicate(
                   static_cast<mlir::arith::CmpFPredicate>(predAttr.getInt()));
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
index 51f925b17f47e..6b66ba6a7022d 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOpsInterfaces.td
@@ -157,6 +157,13 @@ def BlockArgOpenMPOpInterface : OpInterface<"BlockArgOpenMPOpInterface"> {
                 "iface." # clause.blockArgsMethod.name # "()))" # [{
             pairs.emplace_back(var, arg);
         } }]), "\n")
+      >,
+      InterfaceMethod<
+        "Get the symbols referenced by the private clause.",
+        "::mlir::ArrayAttr", "getPrivateSymsAttribute", (ins), [{}], [{
+          auto attr = $_op->getInherentAttr("private_syms");
+          return ::llvm::cast<::mlir::ArrayAttr>(attr.value());
+        }]
       >
     ]
   );
@@ -297,7 +304,7 @@ def ComposableOpInterface : OpInterface<"ComposableOpInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"isComposite",
       (ins ), [{}], [{
-        return $_op->hasAttr("omp.composite");
+        return $_op->hasDiscardableAttr("omp.composite");
       }]
     >,
     InterfaceMethod<
@@ -322,7 +329,7 @@ def ComposableOpInterface : OpInterface<"ComposableOpInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"isCombined",
       (ins ), [{}], [{
-        return $_op->hasAttr("omp.combined");
+        return $_op->hasDiscardableAttr("omp.combined");
       }]
     >,
     InterfaceMethod<
@@ -377,7 +384,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       (ins "mlir::omp::DeclareTargetDeviceType":$deviceType,
            "mlir::omp::DeclareTargetCaptureClause":$captureClause,
            "bool":$automap), [{}], [{
-        $_op->setAttr("omp.declare_target",
+        $_op->setDiscardableAttr("omp.declare_target",
                   mlir::omp::DeclareTargetAttr::get(
                       $_op->getContext(),
                       mlir::omp::DeclareTargetDeviceTypeAttr::get(
@@ -395,7 +402,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"isDeclareTarget",
       (ins), [{}], [{
-        return $_op->hasAttr("omp.declare_target");
+        return $_op->hasDiscardableAttr("omp.declare_target");
       }]>,
       InterfaceMethod<
       /*description=*/[{
@@ -405,7 +412,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       /*retTy=*/"mlir::omp::DeclareTargetDeviceType",
       /*methodName=*/"getDeclareTargetDeviceType",
       (ins), [{}], [{
-        if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target"))
+        if (mlir::Attribute dTar = $_op->getDiscardableAttr("omp.declare_target"))
           if (auto dAttr = llvm::dyn_cast_or_null<mlir::omp::DeclareTargetAttr>(dTar))
             return dAttr.getDeviceType().getValue();
         return {};
@@ -418,7 +425,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       /*retTy=*/"mlir::omp::DeclareTargetCaptureClause",
       /*methodName=*/"getDeclareTargetCaptureClause",
       (ins), [{}], [{
-        if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target"))
+        if (mlir::Attribute dTar = $_op->getDiscardableAttr("omp.declare_target"))
           if (auto dAttr = llvm::dyn_cast_or_null<mlir::omp::DeclareTargetAttr>(dTar))
             return dAttr.getCaptureClause().getValue();
         return {};
@@ -430,7 +437,7 @@ def DeclareTargetInterface : OpInterface<"DeclareTargetInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"getDeclareTargetAutomap",
       (ins), [{}], [{
-        if (mlir::Attribute dTar = $_op->getAttr("omp.declare_target"))
+        if (mlir::Attribute dTar = $_op->getDiscardableAttr("omp.declare_target"))
           if (auto dAttr = llvm::dyn_cast_or_null<mlir::omp::DeclareTargetAttr>(dTar))
             if (auto autoVal = dAttr.getAutomap())
               return autoVal.getValue();
@@ -456,7 +463,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"void",
       /*methodName=*/"setIsTargetDevice",
       (ins "bool":$isTargetDevice), [{}], [{
-        $_op->setAttr(
+        $_op->setDiscardableAttr(
           mlir::StringAttr::get($_op->getContext(), llvm::Twine{"omp.is_target_device"}),
             mlir::BoolAttr::get($_op->getContext(), isTargetDevice));
       }]>,
@@ -468,7 +475,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"getIsTargetDevice",
       (ins), [{}], [{
-        if (Attribute isTargetDevice = $_op->getAttr("omp.is_target_device"))
+        if (Attribute isTargetDevice = $_op->getDiscardableAttr("omp.is_target_device"))
           if (::llvm::isa<mlir::BoolAttr>(isTargetDevice))
            return ::llvm::dyn_cast<BoolAttr>(isTargetDevice).getValue();
         return false;
@@ -481,7 +488,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"void",
       /*methodName=*/"setIsGPU",
       (ins "bool":$isGPU), [{}], [{
-        $_op->setAttr(
+        $_op->setDiscardableAttr(
           mlir::StringAttr::get($_op->getContext(), "omp.is_gpu"),
             mlir::BoolAttr::get($_op->getContext(), isGPU));
       }]>,
@@ -493,7 +500,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"bool",
       /*methodName=*/"getIsGPU",
       (ins), [{}], [{
-        if (Attribute isTargetCGAttr = $_op->getAttr("omp.is_gpu"))
+        if (Attribute isTargetCGAttr = $_op->getDiscardableAttr("omp.is_gpu"))
           if (auto isTargetCGVal = ::llvm::dyn_cast<BoolAttr>(isTargetCGAttr))
            return isTargetCGVal.getValue();
         return false;
@@ -506,7 +513,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"mlir::omp::FlagsAttr",
       /*methodName=*/"getFlags",
       (ins), [{}], [{
-        if (Attribute flags = $_op->getAttr("omp.flags"))
+        if (Attribute flags = $_op->getDiscardableAttr("omp.flags"))
           return ::llvm::dyn_cast_or_null<mlir::omp::FlagsAttr>(flags);
         return nullptr;
       }]>,
@@ -524,7 +531,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
             "bool":$assumeNoNestedParallelism,
             "uint32_t":$openmpDeviceVersion,
             "bool":$noGPULib), [{}], [{
-        $_op->setAttr(("omp." + mlir::omp::FlagsAttr::getMnemonic()).str(),
+        $_op->setDiscardableAttr(("omp." + mlir::omp::FlagsAttr::getMnemonic()).str(),
                   mlir::omp::FlagsAttr::get($_op->getContext(), debugKind,
                       assumeTeamsOversubscription, assumeThreadsOversubscription,
                       assumeNoThreadState, assumeNoNestedParallelism, noGPULib, openmpDeviceVersion));
@@ -538,7 +545,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"void",
       /*methodName=*/"setHostIRFilePath",
       (ins "std::string":$hostIRFilePath), [{}], [{
-        $_op->setAttr(
+        $_op->setDiscardableAttr(
           mlir::StringAttr::get($_op->getContext(), llvm::Twine{"omp.host_ir_filepath"}),
             mlir::StringAttr::get($_op->getContext(), hostIRFilePath));
        }]>,
@@ -552,7 +559,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"llvm::StringRef",
       /*methodName=*/"getHostIRFilePath",
       (ins), [{}], [{
-        if (Attribute filepath = $_op->getAttr("omp.host_ir_filepath"))
+        if (Attribute filepath = $_op->getDiscardableAttr("omp.host_ir_filepath"))
           if (::llvm::isa<mlir::StringAttr>(filepath))
             return ::llvm::dyn_cast<mlir::StringAttr>(filepath).getValue();
         return {};
@@ -566,7 +573,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"::mlir::omp::ClauseRequires",
       /*methodName=*/"getRequires",
       (ins), [{}], [{
-        if (Attribute requiresAttr = $_op->getAttr("omp.requires"))
+        if (Attribute requiresAttr = $_op->getDiscardableAttr("omp.requires"))
           if (auto requiresVal = ::llvm::dyn_cast<mlir::omp::ClauseRequiresAttr>(requiresAttr))
             return requiresVal.getValue();
         return mlir::omp::ClauseRequires::none;
@@ -578,7 +585,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"void",
       /*methodName=*/"setRequires",
       (ins "::mlir::omp::ClauseRequires":$clauses), [{}], [{
-        $_op->setAttr(mlir::StringAttr::get($_op->getContext(), "omp.requires"),
+        $_op->setDiscardableAttr(mlir::StringAttr::get($_op->getContext(), "omp.requires"),
           mlir::omp::ClauseRequiresAttr::get($_op->getContext(), clauses));
       }]>,
     InterfaceMethod<
@@ -589,7 +596,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
       /*retTy=*/"::llvm::ArrayRef<::mlir::Attribute>",
       /*methodName=*/"getTargetTriples",
       (ins), [{}], [{
-        if (Attribute triplesAttr = $_op->getAttr("omp.target_triples"))
+        if (Attribute triplesAttr = $_op->getDiscardableAttr("omp.target_triples"))
           if (auto triples = ::llvm::dyn_cast<::mlir::ArrayAttr>(triplesAttr))
             return triples.getValue();
         return {};
@@ -605,7 +612,7 @@ def OffloadModuleInterface : OpInterface<"OffloadModuleInterface"> {
             targetTriples, [&](::std::string str) -> ::mlir::Attribute {
               return mlir::StringAttr::get($_op->getContext(), str);
             }));
-        $_op->setAttr(
+        $_op->setDiscardableAttr(
             ::mlir::StringAttr::get($_op->getContext(), "omp.target_triples"),
             ::mlir::ArrayAttr::get($_op->getContext(), names));
       }]>
diff --git a/mlir/include/mlir/Dialect/Shard/Transforms/Simplify.h b/mlir/include/mlir/Dialect/Shard/Transforms/Simplify.h
index f3f4feffd8a71..5f961743015f9 100644
--- a/mlir/include/mlir/Dialect/Shard/Transforms/Simplify.h
+++ b/mlir/include/mlir/Dialect/Shard/Transforms/Simplify.h
@@ -84,7 +84,10 @@ void populateAllReduceEndomorphismSimplifyPatterns(RewritePatternSet &patterns,
 
     auto refAllReduceOp = llvm::dyn_cast<AllReduceOp>(referenceOp.value());
     auto refType = cast<ShapedType>(refAllReduceOp.getResult().getType());
-    return refAllReduceOp->getAttrs() == allReduceOp->getAttrs() &&
+    return refAllReduceOp.getGridAttr() == allReduceOp.getGridAttr() &&
+           refAllReduceOp.getGridAxesAttr() == allReduceOp.getGridAxesAttr() &&
+           refAllReduceOp->getDiscardableAttrDictionary() ==
+               allReduceOp->getDiscardableAttrDictionary() &&
            inType.getElementType() == refType.getElementType();
   };
   auto isAlgebraicOp = [](Operation *op) { return isa<AlgebraicOp>(op); };
diff --git a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
index df4dabc2afda8..15b91bb529390 100644
--- a/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
+++ b/mlir/include/mlir/Dialect/Tosa/Utils/ConversionUtils.h
@@ -101,10 +101,10 @@ TosaOp createOpAndInferShape(ImplicitLocOpBuilder &builder, Type resultTy,
 
   SmallVector<ShapedTypeComponents> returnedShapes;
   if (shapeInterface
-          .inferReturnTypeComponents(op.getContext(), builder.getLoc(),
-                                     op->getOperands(), op->getAttrDictionary(),
-                                     op->getPropertiesStorage(),
-                                     op->getRegions(), returnedShapes)
+          .inferReturnTypeComponents(
+              op.getContext(), builder.getLoc(), op->getOperands(),
+              op->getDiscardableAttrDictionary(), op->getPropertiesStorage(),
+              op->getRegions(), returnedShapes)
           .failed())
     return op;
 
diff --git a/mlir/include/mlir/IR/Matchers.h b/mlir/include/mlir/IR/Matchers.h
index e577909621cb8..c2169c3d19238 100644
--- a/mlir/include/mlir/IR/Matchers.h
+++ b/mlir/include/mlir/IR/Matchers.h
@@ -65,7 +65,13 @@ struct NameOpMatcher {
 /// The matcher that matches operations that have the specified attribute name.
 struct AttrOpMatcher {
   AttrOpMatcher(StringRef attrName) : attrName(attrName) {}
-  bool match(Operation *op) { return op->hasAttr(attrName); }
+  bool match(Operation *op) {
+    std::optional<Attribute> inherent;
+    if (op->getPropertiesStorageSize())
+      inherent = op->getInherentAttr(attrName);
+    return inherent.has_value() ? static_cast<bool>(*inherent)
+                                : op->hasDiscardableAttr(attrName);
+  }
 
   StringRef attrName;
 };
@@ -146,7 +152,10 @@ struct AttrOpBinder {
   AttrOpBinder(StringRef attrName) : attrName(attrName), bindValue(nullptr) {}
 
   bool match(Operation *op) {
-    if (auto attr = op->getAttrOfType<AttrT>(attrName)) {
+    Attribute rawAttr = op->getDiscardableAttr(attrName);
+    if (op->getPropertiesStorageSize())
+      rawAttr = op->getInherentAttr(attrName).value_or(rawAttr);
+    if (auto attr = dyn_cast_or_null<AttrT>(rawAttr)) {
       if (bindValue)
         *bindValue = attr;
       return true;
diff --git a/mlir/include/mlir/IR/Operation.h b/mlir/include/mlir/IR/Operation.h
index 793c046fbf2e5..03f0afdf64b25 100644
--- a/mlir/include/mlir/IR/Operation.h
+++ b/mlir/include/mlir/IR/Operation.h
@@ -481,6 +481,26 @@ class alignas(8) Operation final
   /// discardable attribute does not exist.
   Attribute getDiscardableAttr(StringAttr name) { return attrs.get(name); }
 
+  /// Access a discardable attribute by name and cast it to `AttrClass`.
+  template <typename AttrClass>
+  AttrClass getDiscardableAttrOfType(StringRef name) {
+    return llvm::dyn_cast_or_null<AttrClass>(getDiscardableAttr(name));
+  }
+  template <typename AttrClass>
+  AttrClass getDiscardableAttrOfType(StringAttr name) {
+    return llvm::dyn_cast_or_null<AttrClass>(getDiscardableAttr(name));
+  }
+
+  /// Return true if this operation has a discardable attribute with the
+  /// provided name.
+  bool hasDiscardableAttr(StringRef name) { return bool(attrs.get(name)); }
+  bool hasDiscardableAttr(StringAttr name) { return bool(attrs.get(name)); }
+  template <typename AttrClass, typename NameT>
+  bool hasDiscardableAttrOfType(NameT &&name) {
+    return static_cast<bool>(
+        getDiscardableAttrOfType<AttrClass>(std::forward<NameT>(name)));
+  }
+
   /// Set a discardable attribute by name.
   void setDiscardableAttr(StringAttr name, Attribute value) {
     NamedAttrList attributes(attrs);
@@ -534,15 +554,25 @@ class alignas(8) Operation final
   DictionaryAttr getRawDictionaryAttrs() { return attrs; }
 
   /// Return all of the attributes on this operation.
-  ArrayRef<NamedAttribute> getAttrs() { return getAttrDictionary().getValue(); }
+  [[deprecated("use getDiscardableAttrs() and operation-specific accessors "
+               "instead")]]
+  ArrayRef<NamedAttribute> getAttrs() {
+    return getAttrDictionary().getValue();
+  }
 
   /// Return all of the attributes on this operation as a DictionaryAttr.
+  [[deprecated("use getDiscardableAttrDictionary() and operation-specific "
+               "accessors instead")]]
   DictionaryAttr getAttrDictionary();
 
   /// Set the attributes from a dictionary on this operation.
   /// These methods are expensive: if the dictionary only contains discardable
   /// attributes, `setDiscardableAttrs` is more efficient.
+  [[deprecated("use setDiscardableAttrs() and operation-specific mutators "
+               "instead")]]
   void setAttrs(DictionaryAttr newAttrs);
+  [[deprecated("use setDiscardableAttrs() and operation-specific mutators "
+               "instead")]]
   void setAttrs(ArrayRef<NamedAttribute> newAttrs);
   /// Set the discardable attribute dictionary on this operation.
   void setDiscardableAttrs(DictionaryAttr newAttrs) {
@@ -556,6 +586,8 @@ class alignas(8) Operation final
   /// Return the specified attribute if present, null otherwise.
   /// These methods are expensive: if the dictionary only contains discardable
   /// attributes, `getDiscardableAttr` is more efficient.
+  [[deprecated("use getDiscardableAttr() or an operation-specific accessor "
+               "instead")]]
   Attribute getAttr(StringAttr name) {
     if (getPropertiesStorageSize()) {
       if (std::optional<Attribute> inherentAttr = getInherentAttr(name))
@@ -563,6 +595,8 @@ class alignas(8) Operation final
     }
     return attrs.get(name);
   }
+  [[deprecated("use getDiscardableAttr() or an operation-specific accessor "
+               "instead")]]
   Attribute getAttr(StringRef name) {
     if (getPropertiesStorageSize()) {
       if (std::optional<Attribute> inherentAttr = getInherentAttr(name))
@@ -572,16 +606,22 @@ class alignas(8) Operation final
   }
 
   template <typename AttrClass>
+  [[deprecated("use getDiscardableAttrOfType() or an operation-specific "
+               "accessor instead")]]
   AttrClass getAttrOfType(StringAttr name) {
     return llvm::dyn_cast_or_null<AttrClass>(getAttr(name));
   }
   template <typename AttrClass>
+  [[deprecated("use getDiscardableAttrOfType() or an operation-specific "
+               "accessor instead")]]
   AttrClass getAttrOfType(StringRef name) {
     return llvm::dyn_cast_or_null<AttrClass>(getAttr(name));
   }
 
   /// Return true if the operation has an attribute with the provided name,
   /// false otherwise.
+  [[deprecated("use hasDiscardableAttr() or an operation-specific accessor "
+               "instead")]]
   bool hasAttr(StringAttr name) {
     if (getPropertiesStorageSize()) {
       if (std::optional<Attribute> inherentAttr = getInherentAttr(name))
@@ -589,6 +629,8 @@ class alignas(8) Operation final
     }
     return attrs.contains(name);
   }
+  [[deprecated("use hasDiscardableAttr() or an operation-specific accessor "
+               "instead")]]
   bool hasAttr(StringRef name) {
     if (getPropertiesStorageSize()) {
       if (std::optional<Attribute> inherentAttr = getInherentAttr(name))
@@ -597,6 +639,8 @@ class alignas(8) Operation final
     return attrs.contains(name);
   }
   template <typename AttrClass, typename NameT>
+  [[deprecated("use hasDiscardableAttrOfType() or an operation-specific "
+               "accessor instead")]]
   bool hasAttrOfType(NameT &&name) {
     return static_cast<bool>(
         getAttrOfType<AttrClass>(std::forward<NameT>(name)));
@@ -604,6 +648,8 @@ class alignas(8) Operation final
 
   /// If the an attribute exists with the specified name, change it to the new
   /// value. Otherwise, add a new attribute with the specified name/value.
+  [[deprecated("use setDiscardableAttr() or an operation-specific mutator "
+               "instead")]]
   void setAttr(StringAttr name, Attribute value) {
     if (getPropertiesStorageSize()) {
       if (getInherentAttr(name)) {
@@ -615,6 +661,8 @@ class alignas(8) Operation final
     if (attributes.set(name, value) != value)
       attrs = attributes.getDictionary(getContext());
   }
+  [[deprecated("use setDiscardableAttr() or an operation-specific mutator "
+               "instead")]]
   void setAttr(StringRef name, Attribute value) {
     setAttr(StringAttr::get(getContext(), name), value);
   }
@@ -622,6 +670,8 @@ class alignas(8) Operation final
   /// Remove the attribute with the specified name if it exists. Return the
   /// attribute that was erased, or nullptr if there was no attribute with such
   /// name.
+  [[deprecated("use removeDiscardableAttr() or an operation-specific mutator "
+               "instead")]]
   Attribute removeAttr(StringAttr name) {
     if (getPropertiesStorageSize()) {
       if (std::optional<Attribute> inherentAttr = getInherentAttr(name)) {
@@ -635,6 +685,8 @@ class alignas(8) Operation final
       attrs = attributes.getDictionary(getContext());
     return removedAttr;
   }
+  [[deprecated("use removeDiscardableAttr() or an operation-specific mutator "
+               "instead")]]
   Attribute removeAttr(StringRef name) {
     return removeAttr(StringAttr::get(getContext(), name));
   }
@@ -660,35 +712,51 @@ class alignas(8) Operation final
 
   /// Return a range corresponding to the dialect attributes for this operation.
   dialect_attr_range getDialectAttrs() {
-    auto attrs = getAttrs();
-    return {dialect_attr_iterator(attrs.begin(), attrs.end()),
-            dialect_attr_iterator(attrs.end(), attrs.end())};
+    ArrayRef<NamedAttribute> rawAttrs = attrs.getValue();
+    return {dialect_attr_iterator(rawAttrs.begin(), rawAttrs.end()),
+            dialect_attr_iterator(rawAttrs.end(), rawAttrs.end())};
   }
   dialect_attr_iterator dialect_attr_begin() {
-    auto attrs = getAttrs();
-    return dialect_attr_iterator(attrs.begin(), attrs.end());
+    ArrayRef<NamedAttribute> rawAttrs = attrs.getValue();
+    return dialect_attr_iterator(rawAttrs.begin(), rawAttrs.end());
   }
   dialect_attr_iterator dialect_attr_end() {
-    auto attrs = getAttrs();
-    return dialect_attr_iterator(attrs.end(), attrs.end());
+    ArrayRef<NamedAttribute> rawAttrs = attrs.getValue();
+    return dialect_attr_iterator(rawAttrs.end(), rawAttrs.end());
   }
 
   /// Set the dialect attributes for this operation, and preserve all inherent.
   template <typename DialectAttrT>
   void setDialectAttrs(DialectAttrT &&dialectAttrs) {
-    NamedAttrList attrs;
-    attrs.append(std::begin(dialectAttrs), std::end(dialectAttrs));
-    for (auto attr : getAttrs())
+    NamedAttrList newAttrs;
+    newAttrs.append(std::begin(dialectAttrs), std::end(dialectAttrs));
+    for (auto attr : attrs)
       if (!attr.getName().strref().contains('.'))
-        attrs.push_back(attr);
-    setAttrs(attrs.getDictionary(getContext()));
+        newAttrs.push_back(attr);
+    setDiscardableAttrs(newAttrs.getDictionary(getContext()));
   }
 
   /// Sets default attributes on unset attributes.
   void populateDefaultAttrs() {
-    NamedAttrList attrs(getAttrDictionary());
-    name.populateDefaultAttrs(attrs);
-    setAttrs(attrs.getDictionary(getContext()));
+    NamedAttrList allAttrs;
+    if (getPropertiesStorageSize())
+      name.populateInherentAttrs(this, allAttrs);
+    allAttrs.append(getDiscardableAttrDictionary().getValue());
+    name.populateDefaultAttrs(allAttrs);
+
+    if (!getPropertiesStorageSize()) {
+      setDiscardableAttrs(allAttrs.getDictionary(getContext()));
+      return;
+    }
+
+    NamedAttrList discardableAttrs;
+    for (NamedAttribute attr : allAttrs) {
+      if (getInherentAttr(attr.getName()).has_value())
+        setInherentAttr(attr.getName(), attr.getValue());
+      else
+        discardableAttrs.append(attr);
+    }
+    setDiscardableAttrs(discardableAttrs.getDictionary(getContext()));
   }
 
   //===--------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/IR/SymbolInterfaces.td b/mlir/include/mlir/IR/SymbolInterfaces.td
index 292c355cbe157..473aa0a1b85ed 100644
--- a/mlir/include/mlir/IR/SymbolInterfaces.td
+++ b/mlir/include/mlir/IR/SymbolInterfaces.td
@@ -42,8 +42,7 @@ def Symbol : OpInterface<"SymbolOpInterface"> {
     InterfaceMethod<"Sets the name of this symbol.",
       "void", "setName", (ins "::mlir::StringAttr":$name), [{}],
       /*defaultImplementation=*/[{
-        this->getOperation()->setAttr(
-            mlir::SymbolTable::getSymbolAttrName(), name);
+        mlir::SymbolTable::setSymbolName(this->getOperation(), name);
       }]
     >,
     InterfaceMethod<"Gets the visibility of this symbol.",
@@ -197,7 +196,9 @@ def Symbol : OpInterface<"SymbolOpInterface"> {
 
   // Add additional classof checks to properly handle "optional" symbols.
   let extraClassOf = [{
-    return $_op->hasAttr(::mlir::SymbolTable::getSymbolAttrName());
+    return static_cast<bool>(
+        $_op->getInherentAttr(::mlir::SymbolTable::getSymbolAttrName())
+            .value_or(::mlir::Attribute{}));
   }];
 }
 
diff --git a/mlir/include/mlir/Target/SMTLIB/Namespace.h b/mlir/include/mlir/Target/SMTLIB/Namespace.h
index 09bd5cd2d407b..8032292cddd38 100644
--- a/mlir/include/mlir/Target/SMTLIB/Namespace.h
+++ b/mlir/include/mlir/Target/SMTLIB/Namespace.h
@@ -47,8 +47,9 @@ class Namespace {
   void add(mlir::ModuleOp module) {
     assert(module->getNumRegions() == 1);
     for (auto &op : module.getBody(0)->getOperations())
-      if (auto symbol = op.getAttrOfType<mlir::StringAttr>(
-              mlir::SymbolTable::getSymbolAttrName()))
+      if (auto symbol = llvm::dyn_cast_or_null<mlir::StringAttr>(
+              op.getInherentAttr(mlir::SymbolTable::getSymbolAttrName())
+                  .value_or(mlir::Attribute{})))
         nextIndex.insert({symbol.getValue(), 0});
   }
 
diff --git a/mlir/lib/Analysis/CallGraph.cpp b/mlir/lib/Analysis/CallGraph.cpp
index d6fe62d8e58d6..a09c913029e57 100644
--- a/mlir/lib/Analysis/CallGraph.cpp
+++ b/mlir/lib/Analysis/CallGraph.cpp
@@ -195,9 +195,16 @@ void CallGraph::print(raw_ostream &os) const {
     auto *parentOp = callableRegion->getParentOp();
     os << "'" << callableRegion->getParentOp()->getName() << "' - Region #"
        << callableRegion->getRegionNumber();
-    auto attrs = parentOp->getAttrDictionary();
-    if (!attrs.empty())
-      os << " : " << attrs;
+    NamedAttrList attrs(parentOp->getDiscardableAttrDictionary());
+    parentOp->getName().populateInherentAttrs(parentOp, attrs);
+    if (!attrs.empty()) {
+      os << " : { ";
+      llvm::interleaveComma(attrs, os, [&](NamedAttribute attr) {
+        os << attr.getName().getValue() << " = ";
+        attr.getValue().print(os);
+      });
+      os << " }";
+    }
   };
 
   for (auto &nodeIt : nodes) {
diff --git a/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp b/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
index e6e7491b041ee..d067365a65c6c 100644
--- a/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
+++ b/mlir/lib/Analysis/DataFlow/ConstantPropagationAnalysis.cpp
@@ -70,7 +70,8 @@ LogicalResult SparseConstantPropagation::visitOperation(
   // folds in-place. The constant passed in may not correspond to the real
   // runtime value, so in-place updates are not allowed.
   SmallVector<Value, 8> originalOperands(op->getOperands());
-  DictionaryAttr originalAttrs = op->getAttrDictionary();
+  DictionaryAttr originalAttrs = op->getDiscardableAttrDictionary();
+  Attribute originalProperties = op->getPropertiesAsAttribute();
 
   // Simulate the result of folding this operation to a constant.
   SmallVector<OpFoldResult, 8> foldResults;
@@ -83,7 +84,10 @@ LogicalResult SparseConstantPropagation::visitOperation(
   // relinks use-lists even for identical values.
   if (!llvm::equal(op->getOperands(), originalOperands))
     op->setOperands(originalOperands);
-  op->setAttrs(originalAttrs);
+  op->setDiscardableAttrs(originalAttrs);
+  if (originalProperties)
+    (void)op->setPropertiesFromAttribute(originalProperties,
+                                         /*emitError=*/nullptr);
 
   // If folding failed or was in-place, mark the results as overdefined. We
   // don't allow in-place folds here: the goal is simulated execution, not
diff --git a/mlir/lib/Bytecode/Writer/BytecodeWriter.cpp b/mlir/lib/Bytecode/Writer/BytecodeWriter.cpp
index 90a1de6691d99..15d41acea84d2 100644
--- a/mlir/lib/Bytecode/Writer/BytecodeWriter.cpp
+++ b/mlir/lib/Bytecode/Writer/BytecodeWriter.cpp
@@ -1015,14 +1015,17 @@ LogicalResult BytecodeWriter::writeOp(EncodingEmitter &emitter, Operation *op) {
   emitter.emitVarInt(numberingState.getNumber(op->getLoc()), "op location");
 
   // Emit the attributes of this operation.
-  DictionaryAttr attrs = op->getDiscardableAttrDictionary();
+  DictionaryAttr attrs = op->getRawDictionaryAttrs();
   // Allow deployment to version <kNativePropertiesEncoding by merging inherent
   // attribute with the discardable ones. We should fail if there are any
   // conflicts. When properties are not used by the op, also store everything as
   // attributes.
-  if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding ||
-      !op->getPropertiesStorage()) {
-    attrs = op->getAttrDictionary();
+  if (config.bytecodeVersion < bytecode::kNativePropertiesEncoding &&
+      op->getPropertiesStorage()) {
+    NamedAttrList allAttrs;
+    op->getName().populateInherentAttrs(op, allAttrs);
+    allAttrs.append(op->getDiscardableAttrDictionary().getValue());
+    attrs = allAttrs.getDictionary(op->getContext());
   }
   if (!attrs.empty()) {
     opEncodingMask |= bytecode::OpEncodingMask::kHasAttrs;
diff --git a/mlir/lib/Bytecode/Writer/IRNumbering.cpp b/mlir/lib/Bytecode/Writer/IRNumbering.cpp
index 04625628fa5a6..fa6580a76d0e1 100644
--- a/mlir/lib/Bytecode/Writer/IRNumbering.cpp
+++ b/mlir/lib/Bytecode/Writer/IRNumbering.cpp
@@ -446,10 +446,16 @@ void IRNumberingState::number(Operation &op) {
   // not used, we need to number also the merged dictionary containing both the
   // inherent and discardable attribute.
   DictionaryAttr dictAttr;
-  if (config.getDesiredBytecodeVersion() >= bytecode::kNativePropertiesEncoding)
+  if (config.getDesiredBytecodeVersion() >=
+          bytecode::kNativePropertiesEncoding ||
+      !op.getPropertiesStorage())
     dictAttr = op.getRawDictionaryAttrs();
-  else
-    dictAttr = op.getAttrDictionary();
+  else {
+    NamedAttrList attrs;
+    op.getName().populateInherentAttrs(&op, attrs);
+    attrs.append(op.getDiscardableAttrDictionary().getValue());
+    dictAttr = attrs.getDictionary(op.getContext());
+  }
   // Only number the operation's dictionary if it isn't empty.
   if (!dictAttr.empty())
     number(dictAttr);
diff --git a/mlir/lib/CAPI/Dialect/Linalg.cpp b/mlir/lib/CAPI/Dialect/Linalg.cpp
index 92ead3eed9a95..3ddb76d865246 100644
--- a/mlir/lib/CAPI/Dialect/Linalg.cpp
+++ b/mlir/lib/CAPI/Dialect/Linalg.cpp
@@ -39,7 +39,10 @@ void mlirLinalgFillBuiltinNamedOpRegion(MlirOperation mlirOp) {
   Region &region = op->getRegion(0);
   Block *body = b.createBlock(&region, /*insertPt=*/{}, argTypes, argLocs);
   b.setInsertionPointToStart(body);
-  fun(b, *body, op->getAttrs(), /*emitError=*/{});
+  NamedAttrList attrs;
+  op->getName().populateInherentAttrs(op, attrs);
+  attrs.append(op->getDiscardableAttrDictionary().getValue());
+  fun(b, *body, attrs, /*emitError=*/{});
 }
 
 MLIR_CAPI_EXPORTED bool mlirLinalgIsAContractionOp(MlirOperation op) {
diff --git a/mlir/lib/CAPI/IR/IR.cpp b/mlir/lib/CAPI/IR/IR.cpp
index ef730b26cdd5d..0cc65e4fa7989 100644
--- a/mlir/lib/CAPI/IR/IR.cpp
+++ b/mlir/lib/CAPI/IR/IR.cpp
@@ -806,13 +806,13 @@ void mlirOperationSetInherentAttributeByName(MlirOperation op,
 
 intptr_t mlirOperationGetNumDiscardableAttributes(MlirOperation op) {
   return static_cast<intptr_t>(
-      llvm::range_size(unwrap(op)->getDiscardableAttrs()));
+      llvm::range_size(unwrap(op)->getDiscardableAttrDictionary().getValue()));
 }
 
 MlirNamedAttribute mlirOperationGetDiscardableAttribute(MlirOperation op,
                                                         intptr_t pos) {
-  NamedAttribute attr =
-      *std::next(unwrap(op)->getDiscardableAttrs().begin(), pos);
+  NamedAttribute attr = *std::next(
+      unwrap(op)->getDiscardableAttrDictionary().getValue().begin(), pos);
   return MlirNamedAttribute{wrap(attr.getName()), wrap(attr.getValue())};
 }
 
@@ -837,6 +837,7 @@ void mlirOperationSetSuccessor(MlirOperation op, intptr_t pos,
   unwrap(op)->setSuccessor(unwrap(block), static_cast<unsigned>(pos));
 }
 
+LLVM_SUPPRESS_DEPRECATED_DECLARATIONS_PUSH
 intptr_t mlirOperationGetNumAttributes(MlirOperation op) {
   return static_cast<intptr_t>(unwrap(op)->getAttrs().size());
 }
@@ -859,6 +860,7 @@ void mlirOperationSetAttributeByName(MlirOperation op, MlirStringRef name,
 bool mlirOperationRemoveAttributeByName(MlirOperation op, MlirStringRef name) {
   return !!unwrap(op)->removeAttr(unwrap(name));
 }
+LLVM_SUPPRESS_DEPRECATED_DECLARATIONS_POP
 
 void mlirOperationPrint(MlirOperation op, MlirStringCallback callback,
                         void *userData) {
diff --git a/mlir/lib/Conversion/AMDGPUToROCDL/AMDGPUToROCDL.cpp b/mlir/lib/Conversion/AMDGPUToROCDL/AMDGPUToROCDL.cpp
index e8f77d6c9f435..fe160132f40f1 100644
--- a/mlir/lib/Conversion/AMDGPUToROCDL/AMDGPUToROCDL.cpp
+++ b/mlir/lib/Conversion/AMDGPUToROCDL/AMDGPUToROCDL.cpp
@@ -3219,9 +3219,9 @@ struct AMDGPUDPPLowering : public ConvertOpToLLVMPattern<DPPOp> {
 
     // Check for row_mask, bank_mask, bound_ctrl if they exist and create
     // constants
-    auto rowMask = DppOp->getAttrOfType<IntegerAttr>("row_mask").getInt();
-    auto bankMask = DppOp->getAttrOfType<IntegerAttr>("bank_mask").getInt();
-    bool boundCtrl = DppOp->getAttrOfType<BoolAttr>("bound_ctrl").getValue();
+    auto rowMask = DppOp.getRowMask();
+    auto bankMask = DppOp.getBankMask();
+    bool boundCtrl = DppOp.getBoundCtrl();
 
     // create a ROCDL_DPPMovOp instruction with the appropriate attributes
     auto dppMovOp =
diff --git a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
index 4e8ef252a10c9..d766c47a817f5 100644
--- a/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
+++ b/mlir/lib/Conversion/ArithToLLVM/ArithToLLVM.cpp
@@ -374,10 +374,10 @@ struct SelectOpOneToNLowering : public ConvertOpToLLVMPattern<arith::SelectOp> {
 LogicalResult
 ConstantOpLowering::matchAndRewrite(arith::ConstantOp op, OpAdaptor adaptor,
                                     ConversionPatternRewriter &rewriter) const {
-  return LLVM::detail::oneToOneRewrite(op, LLVM::ConstantOp::getOperationName(),
-                                       adaptor.getOperands(), op->getAttrs(),
-                                       /*propAttr=*/Attribute{},
-                                       *getTypeConverter(), rewriter);
+  return LLVM::detail::oneToOneRewrite(
+      op, LLVM::ConstantOp::getOperationName(), adaptor.getOperands(),
+      op->getDiscardableAttrDictionary().getValue(),
+      op->getPropertiesAsAttribute(), *getTypeConverter(), rewriter);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
index 614ac2b43b5f2..6478c188d7d2a 100644
--- a/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
+++ b/mlir/lib/Conversion/ArithToSPIRV/ArithToSPIRV.cpp
@@ -214,12 +214,14 @@ struct ElementwiseArithOpPattern final : OpConversionPattern<Op> {
         op, dstType, adaptor.getOperands());
 
     if (bitEnumContainsAny(overflowFlags, arith::IntegerOverflowFlags::nsw))
-      newOp->setAttr(getDecorationString(spirv::Decoration::NoSignedWrap),
-                     rewriter.getUnitAttr());
+      newOp->setDiscardableAttr(
+          getDecorationString(spirv::Decoration::NoSignedWrap),
+          rewriter.getUnitAttr());
 
     if (bitEnumContainsAny(overflowFlags, arith::IntegerOverflowFlags::nuw))
-      newOp->setAttr(getDecorationString(spirv::Decoration::NoUnsignedWrap),
-                     rewriter.getUnitAttr());
+      newOp->setDiscardableAttr(
+          getDecorationString(spirv::Decoration::NoUnsignedWrap),
+          rewriter.getUnitAttr());
 
     return success();
   }
@@ -1059,7 +1061,7 @@ struct TypeCastingOpPattern final : public OpConversionPattern<Op> {
       auto newOp = rewriter.template replaceOpWithNewOp<SPIRVOp>(
           op, dstType, adaptor.getOperands());
       if (rm) {
-        newOp->setAttr(
+        newOp->setDiscardableAttr(
             getDecorationString(spirv::Decoration::FPRoundingMode),
             spirv::FPRoundingModeAttr::get(rewriter.getContext(), *rm));
       }
diff --git a/mlir/lib/Conversion/ComplexToLLVM/ComplexToLLVM.cpp b/mlir/lib/Conversion/ComplexToLLVM/ComplexToLLVM.cpp
index ceda3888360ce..74acbe325c656 100644
--- a/mlir/lib/Conversion/ComplexToLLVM/ComplexToLLVM.cpp
+++ b/mlir/lib/Conversion/ComplexToLLVM/ComplexToLLVM.cpp
@@ -96,8 +96,8 @@ struct ConstantOpLowering : public ConvertOpToLLVMPattern<complex::ConstantOp> {
                   ConversionPatternRewriter &rewriter) const override {
     return LLVM::detail::oneToOneRewrite(
         op, LLVM::ConstantOp::getOperationName(), adaptor.getOperands(),
-        op->getAttrs(), /*propAttr=*/Attribute{}, *getTypeConverter(),
-        rewriter);
+        op->getDiscardableAttrDictionary().getValue(),
+        op->getPropertiesAsAttribute(), *getTypeConverter(), rewriter);
   }
 };
 
diff --git a/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp b/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
index fef78a46d69fc..bf82d30d527ad 100644
--- a/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
+++ b/mlir/lib/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.cpp
@@ -148,12 +148,12 @@ struct BranchOpLowering : public ConvertOpToLLVMPattern<cf::BranchOp> {
                           TypeRange(ValueRange(flattenedAdaptor)));
     if (failed(convertedBlock))
       return failure();
-    DictionaryAttr attrs = op->getAttrDictionary();
+    DictionaryAttr attrs = op->getDiscardableAttrDictionary();
     Operation *newOp = rewriter.replaceOpWithNewOp<LLVM::BrOp>(
         op, flattenedAdaptor, *convertedBlock);
     // TODO: We should not just forward all attributes like that. But there are
     // existing Flang tests that depend on this behavior.
-    newOp->setAttrs(attrs);
+    newOp->setDiscardableAttrs(attrs);
     return success();
   }
 };
diff --git a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
index 81d15be7d548e..cec82782fc802 100644
--- a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
+++ b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
@@ -188,9 +188,12 @@ class CallOpConversion final : public OpConversionPattern<func::CallOp> {
     }
 
     if (callOp.getNumResults() <= 1) {
-      rewriter.replaceOpWithNewOp<emitc::CallOp>(callOp, convertedResultTypes,
-                                                 adaptor.getOperands(),
-                                                 callOp->getAttrs());
+      auto newCall = rewriter.replaceOpWithNewOp<emitc::CallOp>(
+          callOp, callOp.getCalleeAttr(), convertedResultTypes,
+          adaptor.getOperands());
+      newCall.setArgAttrsAttr(callOp.getArgAttrsAttr());
+      newCall.setResAttrsAttr(callOp.getResAttrsAttr());
+      newCall->setDiscardableAttrs(callOp->getDiscardableAttrDictionary());
       return success();
     }
 
@@ -292,11 +295,19 @@ class FuncOpConversion final : public OpConversionPattern<func::FuncOp> {
                           signatureConverter.getConvertedTypes(),
                           resultType ? TypeRange(resultType) : TypeRange()));
 
+    newFuncOp.setArgAttrsAttr(funcOp.getArgAttrsAttr());
+    newFuncOp.setResAttrsAttr(funcOp.getResAttrsAttr());
+    if (StringAttr visibility = funcOp.getSymVisibilityAttr())
+      newFuncOp->setDiscardableAttr(SymbolTable::getVisibilityAttrName(),
+                                    visibility);
+
     // Copy over all attributes other than the function name and type.
-    for (const auto &namedAttr : funcOp->getAttrs()) {
+    for (const auto &namedAttr :
+         funcOp->getDiscardableAttrDictionary().getValue()) {
       if (namedAttr.getName() != funcOp.getFunctionTypeAttrName() &&
           namedAttr.getName() != SymbolTable::getSymbolAttrName())
-        newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+        newFuncOp->setDiscardableAttr(namedAttr.getName(),
+                                      namedAttr.getValue());
     }
 
     // Add `extern` to specifiers if `func.func` is declaration only.
diff --git a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
index 2686158f67e76..d531d6d7c18f1 100644
--- a/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
+++ b/mlir/lib/Conversion/FuncToLLVM/FuncToLLVM.cpp
@@ -58,7 +58,7 @@ static constexpr StringRef barePtrAttrName = "llvm.bareptr";
 /// Return `true` if the `op` should use bare pointer calling convention.
 static bool shouldUseBarePtrCallConv(Operation *op,
                                      const LLVMTypeConverter *typeConverter) {
-  return (op && op->hasAttr(barePtrAttrName)) ||
+  return (op && op->hasDiscardableAttr(barePtrAttrName)) ||
          typeConverter->getOptions().useBarePtrCallConv;
 }
 
@@ -71,7 +71,8 @@ static bool isDiscardableAttr(StringRef name) {
 /// `LLVMFuncOp::build`.
 static void filterFuncAttributes(FunctionOpInterface func,
                                  SmallVectorImpl<NamedAttribute> &result) {
-  for (const NamedAttribute &attr : func->getDiscardableAttrs()) {
+  for (const NamedAttribute &attr :
+       func->getDiscardableAttrDictionary().getValue()) {
     if (isDiscardableAttr(attr.getName().strref()))
       continue;
     result.push_back(attr);
@@ -298,7 +299,8 @@ static FailureOr<LLVM::LLVMFunctionType> convertFuncSignature(
     FunctionOpInterface funcOp, const LLVMTypeConverter &converter,
     bool useBarePtrCallConv, TypeConverter::SignatureConversion &result,
     SmallVectorImpl<std::optional<NamedAttribute>> &byValRefNonPtrAttrs) {
-  auto varargsAttr = funcOp->getAttrOfType<BoolAttr>(varargsAttrName);
+  auto varargsAttr =
+      funcOp->getDiscardableAttrOfType<BoolAttr>(varargsAttrName);
   auto llvmType = dyn_cast_or_null<LLVM::LLVMFunctionType>(
       converter.convertFunctionSignature(
           funcOp, varargsAttr && varargsAttr.getValue(), useBarePtrCallConv,
@@ -334,7 +336,7 @@ static LLVM::LLVMFuncOp createLLVMFuncOp(FunctionOpInterface funcOp,
       .setVisibility(funcOp.getVisibility());
 
   // Set readnone memory effects
-  if (funcOp->hasAttr(LLVM::LLVMDialect::getReadnoneAttrName())) {
+  if (funcOp->hasDiscardableAttr(LLVM::LLVMDialect::getReadnoneAttrName())) {
     auto memoryAttr = LLVM::MemoryEffectsAttr::get(
         rewriter.getContext(), {/*other=*/LLVM::ModRefInfo::NoModRef,
                                 /*argMem=*/LLVM::ModRefInfo::NoModRef,
@@ -466,7 +468,7 @@ FailureOr<LLVM::LLVMFuncOp> mlir::convertFuncOpToLLVMFuncOp(
     return rewriter.notifyMatchFailure(funcOp, "signature conversion failed");
 
   // Validate C wrapper varargs constraint
-  bool emitCWrapper = funcOp->hasAttrOfType<UnitAttr>(
+  bool emitCWrapper = funcOp->hasDiscardableAttrOfType<UnitAttr>(
       LLVM::LLVMDialect::getEmitCWrapperAttrName());
   if (!useBarePtrCallConv && emitCWrapper && llvmType->isVarArg())
     return funcOp.emitError("C interface for variadic functions is not "
@@ -549,10 +551,11 @@ struct ConstantOpLowering : public ConvertOpToLLVMPattern<func::ConstantOp> {
 
     auto newOp =
         LLVM::AddressOfOp::create(rewriter, op.getLoc(), type, op.getValue());
-    for (const NamedAttribute &attr : op->getAttrs()) {
+    for (const NamedAttribute &attr :
+         op->getDiscardableAttrDictionary().getValue()) {
       if (attr.getName().strref() == "value")
         continue;
-      newOp->setAttr(attr.getName(), attr.getValue());
+      newOp->setDiscardableAttr(attr.getName(), attr.getValue());
     }
     rewriter.replaceOp(op, newOp->getResults());
     return success();
@@ -597,10 +600,12 @@ struct CallOpInterfaceLowering : public ConvertOpToLLVMPattern<CallOpType> {
     auto promoted = this->getTypeConverter()->promoteOperands(
         callOp.getLoc(), /*opOperands=*/callOp->getOperands(),
         adaptor.getOperands(), rewriter, useBarePtrCallConv);
-    auto newOp = LLVM::CallOp::create(rewriter, callOp.getLoc(),
-                                      packedResult ? TypeRange(packedResult)
-                                                   : TypeRange(),
-                                      promoted, callOp->getAttrs());
+    auto newOp = LLVM::CallOp::create(
+        rewriter, callOp.getLoc(),
+        packedResult ? TypeRange(packedResult) : TypeRange(), promoted,
+        callOp->getDiscardableAttrDictionary().getValue());
+    if constexpr (std::is_same_v<CallOpType, func::CallOp>)
+      newOp.setCalleeAttr(callOp.getCalleeAttr());
 
     newOp.getProperties().operandSegmentSizes = {
         static_cast<int32_t>(promoted.size()), 0};
@@ -687,13 +692,13 @@ class CallOpLowering : public CallOpInterfaceLowering<func::CallOp> {
       Operation *callee =
           symbolTables->lookupNearestSymbolFrom(callOp, callOp.getCalleeAttr());
       useBarePtrCallConv =
-          callee != nullptr && callee->hasAttr(barePtrAttrName);
+          callee != nullptr && callee->hasDiscardableAttr(barePtrAttrName);
     } else {
       // Warning: This is a linear lookup.
       Operation *callee =
           SymbolTable::lookupNearestSymbolFrom(callOp, callOp.getCalleeAttr());
       useBarePtrCallConv =
-          callee != nullptr && callee->hasAttr(barePtrAttrName);
+          callee != nullptr && callee->hasDiscardableAttr(barePtrAttrName);
     }
     return matchAndRewriteImpl(callOp, adaptor, rewriter, useBarePtrCallConv);
   }
@@ -795,7 +800,8 @@ struct ReturnOpLowering : public ConvertOpToLLVMPattern<func::ReturnOp> {
     // If ReturnOp has 0 or 1 operand, create it and return immediately.
     if (updatedOperands.size() <= 1) {
       rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(
-          op, TypeRange(), updatedOperands, op->getAttrs());
+          op, TypeRange(), updatedOperands,
+          op->getDiscardableAttrDictionary().getValue());
       return success();
     }
 
@@ -811,8 +817,8 @@ struct ReturnOpLowering : public ConvertOpToLLVMPattern<func::ReturnOp> {
     for (auto [idx, operand] : llvm::enumerate(updatedOperands)) {
       packed = LLVM::InsertValueOp::create(rewriter, loc, packed, operand, idx);
     }
-    rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, TypeRange(), packed,
-                                                op->getAttrs());
+    rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(
+        op, TypeRange(), packed, op->getDiscardableAttrDictionary().getValue());
     return success();
   }
 };
@@ -845,7 +851,7 @@ struct ConvertFuncToLLVMPass
     ModuleOp m = getOperation();
     StringRef dataLayout;
     auto dataLayoutAttr = dyn_cast_or_null<StringAttr>(
-        m->getAttr(LLVM::LLVMDialect::getDataLayoutAttrName()));
+        m->getDiscardableAttr(LLVM::LLVMDialect::getDataLayoutAttrName()));
     if (dataLayoutAttr)
       dataLayout = dataLayoutAttr.getValue();
 
@@ -896,8 +902,8 @@ struct SetLLVMModuleDataLayoutPass
       return;
     }
     ModuleOp m = getOperation();
-    m->setAttr(LLVM::LLVMDialect::getDataLayoutAttrName(),
-               StringAttr::get(m.getContext(), this->dataLayout));
+    m->setDiscardableAttr(LLVM::LLVMDialect::getDataLayoutAttrName(),
+                          StringAttr::get(m.getContext(), this->dataLayout));
   }
 };
 } // namespace
diff --git a/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRV.cpp b/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRV.cpp
index 7ceab394a67ff..7956e07b012fb 100644
--- a/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRV.cpp
+++ b/mlir/lib/Conversion/FuncToSPIRV/FuncToSPIRV.cpp
@@ -68,11 +68,15 @@ class CallOpPattern final : public OpConversionPattern<func::CallOp> {
           getTypeConverter()->convertType(callOp.getResult(0).getType());
       if (!resultType)
         return failure();
-      rewriter.replaceOpWithNewOp<spirv::FunctionCallOp>(
-          callOp, resultType, adaptor.getOperands(), callOp->getAttrs());
+      auto newCall = rewriter.replaceOpWithNewOp<spirv::FunctionCallOp>(
+          callOp, resultType, callOp.getCalleeAttr(), adaptor.getOperands(),
+          callOp.getArgAttrsAttr(), callOp.getResAttrsAttr());
+      newCall->setDiscardableAttrs(callOp->getDiscardableAttrDictionary());
     } else {
-      rewriter.replaceOpWithNewOp<spirv::FunctionCallOp>(
-          callOp, TypeRange(), adaptor.getOperands(), callOp->getAttrs());
+      auto newCall = rewriter.replaceOpWithNewOp<spirv::FunctionCallOp>(
+          callOp, TypeRange(), callOp.getCalleeAttr(), adaptor.getOperands(),
+          callOp.getArgAttrsAttr(), callOp.getResAttrsAttr());
+      newCall->setDiscardableAttrs(callOp->getDiscardableAttrDictionary());
     }
     return success();
   }
diff --git a/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp b/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
index eeb90a7ff8150..3ce209213f2a0 100644
--- a/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
+++ b/mlir/lib/Conversion/GPUCommon/GPUOpsLowering.cpp
@@ -630,7 +630,6 @@ static Value scalarizeVectorOpHelper(Operation *op, ValueRange operands,
   Location loc = op->getLoc();
   Value result = LLVM::PoisonOp::create(rewriter, loc, vectorType);
   Type indexType = converter.convertType(rewriter.getIndexType());
-  StringAttr name = op->getName().getIdentifier();
   Type elementType = vectorType.getElementType();
 
   for (int64_t i = 0; i < vectorType.getNumElements(); ++i) {
@@ -641,8 +640,10 @@ static Value scalarizeVectorOpHelper(Operation *op, ValueRange operands,
       return LLVM::ExtractElementOp::create(rewriter, loc, operand, index);
     };
     auto scalarOperands = llvm::map_to_vector(operands, extractElement);
-    Operation *scalarOp =
-        rewriter.create(loc, name, scalarOperands, elementType, op->getAttrs());
+    OperationState state(loc, op->getName(), scalarOperands, elementType,
+                         op->getDiscardableAttrDictionary().getValue());
+    state.propertiesAttr = op->getPropertiesAsAttribute();
+    Operation *scalarOp = rewriter.create(state);
     result = LLVM::InsertElementOp::create(rewriter, loc, result,
                                            scalarOp->getResult(0), index);
   }
@@ -808,7 +809,8 @@ LogicalResult GPUReturnOpLowering::matchAndRewrite(
   // If ReturnOp has 0 or 1 operand, create it and return immediately.
   if (numArguments <= 1) {
     rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(
-        op, TypeRange(), updatedOperands, op->getAttrs());
+        op, TypeRange(), updatedOperands,
+        op->getDiscardableAttrDictionary().getValue());
     return success();
   }
 
@@ -824,8 +826,8 @@ LogicalResult GPUReturnOpLowering::matchAndRewrite(
   for (auto [idx, operand] : llvm::enumerate(updatedOperands)) {
     packed = LLVM::InsertValueOp::create(rewriter, loc, packed, operand, idx);
   }
-  rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(op, TypeRange(), packed,
-                                              op->getAttrs());
+  rewriter.replaceOpWithNewOp<LLVM::ReturnOp>(
+      op, TypeRange(), packed, op->getDiscardableAttrDictionary().getValue());
   return success();
 }
 
diff --git a/mlir/lib/Conversion/GPUCommon/IndexIntrinsicsOpLowering.h b/mlir/lib/Conversion/GPUCommon/IndexIntrinsicsOpLowering.h
index 777f3b92b941c..2a6036ab97bb6 100644
--- a/mlir/lib/Conversion/GPUCommon/IndexIntrinsicsOpLowering.h
+++ b/mlir/lib/Conversion/GPUCommon/IndexIntrinsicsOpLowering.h
@@ -87,7 +87,7 @@ struct OpLowering : public ConvertOpToLLVMPattern<Op> {
       opBound = static_cast<uint32_t>(bound->getZExtValue());
     if (auto range = getIndexOpRange(op, op.getDimension(), opBound, indexKind,
                                      intrType, /*bitWidth=*/32))
-      newOp->setAttr("range", range);
+      newOp->setInherentAttr(rewriter.getStringAttr("range"), range);
 
     if (indexBitwidth > 32) {
       newOp = LLVM::SExtOp::create(rewriter, loc,
diff --git a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
index 80420c26537c3..2b57e90efe7a5 100644
--- a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
+++ b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
@@ -513,8 +513,8 @@ struct LowerGpuOpsToNVVMOpsPass final
 
     // Request C wrapper emission.
     for (auto func : m.getOps<func::FuncOp>()) {
-      func->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                    UnitAttr::get(&getContext()));
+      func->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                               UnitAttr::get(&getContext()));
     }
 
     // Customize the bitwidth used for the device side index computations.
diff --git a/mlir/lib/Conversion/GPUToROCDL/LowerGpuOpsToROCDLOps.cpp b/mlir/lib/Conversion/GPUToROCDL/LowerGpuOpsToROCDLOps.cpp
index a3819df4f8a84..ab7666bc5d530 100644
--- a/mlir/lib/Conversion/GPUToROCDL/LowerGpuOpsToROCDLOps.cpp
+++ b/mlir/lib/Conversion/GPUToROCDL/LowerGpuOpsToROCDLOps.cpp
@@ -302,7 +302,7 @@ struct GPUSubgroupIdOpToROCDL : ConvertOpToLLVMPattern<gpu::SubgroupIdOp> {
                     op, dim, std::nullopt,
                     gpu::index_lowering::IndexKind::Block,
                     gpu::index_lowering::IntrType::Id, 32))
-          tidOp->setAttr("range", range);
+          tidOp->setInherentAttr(rewriter.getStringAttr("range"), range);
       };
       setBoundFromContext(tidX, gpu::Dimension::x);
       setBoundFromContext(tidY, gpu::Dimension::y);
@@ -737,16 +737,17 @@ struct LowerGpuOpsToROCDLOpsPass final
     gpu::GPUModuleOp m = getOperation();
     MLIRContext *ctx = m.getContext();
 
-    auto llvmDataLayout = m->getAttrOfType<StringAttr>(
+    auto llvmDataLayout = m->getDiscardableAttrOfType<StringAttr>(
         LLVM::LLVMDialect::getDataLayoutAttrName());
     if (!llvmDataLayout) {
       llvmDataLayout = StringAttr::get(ctx, amdgcnDataLayout);
-      m->setAttr(LLVM::LLVMDialect::getDataLayoutAttrName(), llvmDataLayout);
+      m->setDiscardableAttr(LLVM::LLVMDialect::getDataLayoutAttrName(),
+                            llvmDataLayout);
     }
     // Request C wrapper emission.
     for (auto func : m.getOps<func::FuncOp>()) {
-      func->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                    UnitAttr::get(ctx));
+      func->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                               UnitAttr::get(ctx));
     }
 
     FailureOr<amdgpu::Chipset> maybeChipset = amdgpu::Chipset::parse(chipset);
diff --git a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
index 609dbe57a9a46..1ebf2daf557d3 100644
--- a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
+++ b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRV.cpp
@@ -294,11 +294,34 @@ lowerAsEntryFunction(gpu::GPUFuncOp funcOp, const TypeConverter &typeConverter,
   auto newFuncOp = spirv::FuncOp::create(
       rewriter, funcOp.getLoc(), funcOp.getName(),
       rewriter.getFunctionType(signatureConverter.getConvertedTypes(), {}));
-  for (const auto &namedAttr : funcOp->getAttrs()) {
+  newFuncOp.setArgAttrsAttr(funcOp.getArgAttrsAttr());
+  newFuncOp.setResAttrsAttr(funcOp.getResAttrsAttr());
+  cast<SymbolOpInterface>(newFuncOp.getOperation())
+      .setVisibility(
+          cast<SymbolOpInterface>(funcOp.getOperation()).getVisibility());
+
+  auto copyGPUProperty = [&](StringAttr name, Attribute value) {
+    if (value)
+      newFuncOp->setDiscardableAttr(name, value);
+  };
+  copyGPUProperty(funcOp.getWorkgroupAttribAttrsAttrName(),
+                  funcOp.getWorkgroupAttribAttrsAttr());
+  copyGPUProperty(funcOp.getPrivateAttribAttrsAttrName(),
+                  funcOp.getPrivateAttribAttrsAttr());
+  copyGPUProperty(funcOp.getKnownBlockSizeAttrName(),
+                  funcOp.getKnownBlockSizeAttr());
+  copyGPUProperty(funcOp.getKnownGridSizeAttrName(),
+                  funcOp.getKnownGridSizeAttr());
+  copyGPUProperty(funcOp.getKnownClusterSizeAttrName(),
+                  funcOp.getKnownClusterSizeAttr());
+  copyGPUProperty(funcOp.getWorkgroupAttributionsAttrName(),
+                  funcOp.getWorkgroupAttributionsAttr());
+  for (const auto &namedAttr :
+       funcOp->getDiscardableAttrDictionary().getValue()) {
     if (namedAttr.getName() == funcOp.getFunctionTypeAttrName() ||
         namedAttr.getName() == SymbolTable::getSymbolAttrName())
       continue;
-    newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+    newFuncOp->setDiscardableAttr(namedAttr.getName(), namedAttr.getValue());
   }
 
   rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
@@ -313,7 +336,8 @@ lowerAsEntryFunction(gpu::GPUFuncOp funcOp, const TypeConverter &typeConverter,
   for (auto argIndex : llvm::seq<unsigned>(0, argABIInfo.size())) {
     newFuncOp.setArgAttr(argIndex, argABIAttrName, argABIInfo[argIndex]);
   }
-  newFuncOp->setAttr(spirv::getEntryPointABIAttrName(), entryPointInfo);
+  newFuncOp->setDiscardableAttr(spirv::getEntryPointABIAttrName(),
+                                entryPointInfo);
 
   return newFuncOp;
 }
@@ -378,7 +402,7 @@ LogicalResult GPUFuncOpConversion::matchAndRewrite(
       funcOp, *getTypeConverter(), rewriter, entryPointAttr, argABI);
   if (!newFuncOp)
     return failure();
-  newFuncOp->removeAttr(
+  newFuncOp->removeDiscardableAttr(
       rewriter.getStringAttr(gpu::GPUDialect::getKernelFuncAttrName()));
   return success();
 }
@@ -416,14 +440,15 @@ LogicalResult GPUModuleConversion::matchAndRewrite(
   // will fail if called after GPUModuleConversion and we don't preserve
   // `TargetEnv` attribute.
   // Copy TargetEnvAttr only if it is attached directly to the GPUModuleOp.
-  if (auto attr = moduleOp->getAttrOfType<spirv::TargetEnvAttr>(
+  if (auto attr = moduleOp->getDiscardableAttrOfType<spirv::TargetEnvAttr>(
           spirv::getTargetEnvAttrName()))
-    spvModule->setAttr(spirv::getTargetEnvAttrName(), attr);
+    spvModule->setDiscardableAttr(spirv::getTargetEnvAttrName(), attr);
   if (ArrayAttr targets = moduleOp.getTargetsAttr()) {
     for (Attribute targetAttr : targets)
       if (auto spirvTargetEnvAttr =
               dyn_cast<spirv::TargetEnvAttr>(targetAttr)) {
-        spvModule->setAttr(spirv::getTargetEnvAttrName(), spirvTargetEnvAttr);
+        spvModule->setDiscardableAttr(spirv::getTargetEnvAttrName(),
+                                      spirvTargetEnvAttr);
         break;
       }
   }
@@ -986,7 +1011,7 @@ LogicalResult GPUPrintfConversion::matchAndRewrite(
         rewriter, loc, ptrType, globalVarName,
         FlatSymbolRefAttr::get(specCstComposite));
 
-    globalVar->setAttr("Constant", rewriter.getUnitAttr());
+    globalVar->setDiscardableAttr("Constant", rewriter.getUnitAttr());
   }
   // Get SSA value of Global variable and create pointer to i8 to point to
   // the format string.
diff --git a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp
index 1b49e9d6305f1..b687283da0477 100644
--- a/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp
+++ b/mlir/lib/Conversion/GPUToSPIRV/GPUToSPIRVPass.cpp
@@ -177,8 +177,8 @@ void GPUToSPIRVPass::runOnOperation() {
         auto entryBlock = newFuncOp.addEntryBlock();
         builder.setInsertionPointToEnd(entryBlock);
         func::ReturnOp::create(builder, funcOp.getLoc());
-        newFuncOp->setAttr(gpu::GPUDialect::getKernelFuncAttrName(),
-                           builder.getUnitAttr());
+        newFuncOp->setDiscardableAttr(gpu::GPUDialect::getKernelFuncAttrName(),
+                                      builder.getUnitAttr());
         funcOp.erase();
       });
     }
diff --git a/mlir/lib/Conversion/LLVMCommon/LowerFunctionDiscardablesToLLVM.cpp b/mlir/lib/Conversion/LLVMCommon/LowerFunctionDiscardablesToLLVM.cpp
index 5427877fdfd8d..859d96d7e2377 100644
--- a/mlir/lib/Conversion/LLVMCommon/LowerFunctionDiscardablesToLLVM.cpp
+++ b/mlir/lib/Conversion/LLVMCommon/LowerFunctionDiscardablesToLLVM.cpp
@@ -29,7 +29,8 @@ mlir::lowerDiscardableAttrsForLLVMFunc(FunctionOpInterface funcOp,
 
   NamedAttrList inherentAttrs;
 
-  for (const NamedAttribute &attr : funcOp->getDiscardableAttrs()) {
+  for (const NamedAttribute &attr :
+       funcOp->getDiscardableAttrDictionary().getValue()) {
     StringRef attrName = attr.getName().strref();
 
     if (odsAttrNames.contains(attrName)) {
diff --git a/mlir/lib/Conversion/LLVMCommon/Pattern.cpp b/mlir/lib/Conversion/LLVMCommon/Pattern.cpp
index 2e0d92c3ba847..91ef201ea4065 100644
--- a/mlir/lib/Conversion/LLVMCommon/Pattern.cpp
+++ b/mlir/lib/Conversion/LLVMCommon/Pattern.cpp
@@ -367,7 +367,7 @@ LogicalResult LLVM::detail::intrinsicRewrite(
   auto callIntrOp = LLVM::CallIntrinsicOp::create(
       rewriter, loc, resType, rewriter.getStringAttr(intrinsic), operands);
   // Propagate attributes.
-  callIntrOp->setAttrs(op->getAttrDictionary());
+  callIntrOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
 
   if (numResults <= 1) {
     // Directly replace the original op.
diff --git a/mlir/lib/Conversion/LinalgToStandard/LinalgToStandard.cpp b/mlir/lib/Conversion/LinalgToStandard/LinalgToStandard.cpp
index 54c554eb6bd93..b5b2b0ba2c277 100644
--- a/mlir/lib/Conversion/LinalgToStandard/LinalgToStandard.cpp
+++ b/mlir/lib/Conversion/LinalgToStandard/LinalgToStandard.cpp
@@ -83,8 +83,8 @@ getLibraryCallSymbolRef(Operation *op, PatternRewriter &rewriter) {
   // Insert a function attribute that will trigger the emission of the
   // corresponding `_mlir_ciface_xxx` interface so that external libraries see
   // a normalized ABI. This interface is added during std to llvm conversion.
-  funcOp->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                  UnitAttr::get(op->getContext()));
+  funcOp->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                             UnitAttr::get(op->getContext()));
   funcOp.setPrivate();
   return fnNameAttr;
 }
diff --git a/mlir/lib/Conversion/MathToFuncs/MathToFuncs.cpp b/mlir/lib/Conversion/MathToFuncs/MathToFuncs.cpp
index 2774adb071c91..9ff2e3e4fdf7f 100644
--- a/mlir/lib/Conversion/MathToFuncs/MathToFuncs.cpp
+++ b/mlir/lib/Conversion/MathToFuncs/MathToFuncs.cpp
@@ -197,7 +197,7 @@ static func::FuncOp createElementIPowIFunc(ModuleOp *module, Type elementType) {
   LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
   Attribute linkage =
       LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
-  funcOp->setAttr("llvm.linkage", linkage);
+  funcOp->setDiscardableAttr("llvm.linkage", linkage);
   funcOp.setPrivate();
 
   Block *entryBlock = funcOp.addEntryBlock();
@@ -424,7 +424,7 @@ static func::FuncOp createElementFPowIFunc(ModuleOp *module,
   LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
   Attribute linkage =
       LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
-  funcOp->setAttr("llvm.linkage", linkage);
+  funcOp->setDiscardableAttr("llvm.linkage", linkage);
   funcOp.setPrivate();
 
   Block *entryBlock = funcOp.addEntryBlock();
@@ -674,7 +674,7 @@ static func::FuncOp createCtlzFunc(ModuleOp *module, Type elementType) {
   LLVM::linkage::Linkage inlineLinkage = LLVM::linkage::Linkage::LinkonceODR;
   Attribute linkage =
       LLVM::LinkageAttr::get(builder.getContext(), inlineLinkage);
-  funcOp->setAttr("llvm.linkage", linkage);
+  funcOp->setDiscardableAttr("llvm.linkage", linkage);
   funcOp.setPrivate();
 
   // set the insertion point to the start of the function
diff --git a/mlir/lib/Conversion/MathToLibm/MathToLibm.cpp b/mlir/lib/Conversion/MathToLibm/MathToLibm.cpp
index d43b70467cd04..1f8020152807a 100644
--- a/mlir/lib/Conversion/MathToLibm/MathToLibm.cpp
+++ b/mlir/lib/Conversion/MathToLibm/MathToLibm.cpp
@@ -150,8 +150,8 @@ ScalarOpToLibmCall<Op>::matchAndRewrite(Op op,
     // optimization opportunities (e.g. LICM) for backends targeting LLVM IR.
     // This will have to be changed, when strict FP behavior is supported
     // by Math dialect.
-    opFunc->setAttr(LLVM::LLVMDialect::getReadnoneAttrName(),
-                    UnitAttr::get(rewriter.getContext()));
+    opFunc->setDiscardableAttr(LLVM::LLVMDialect::getReadnoneAttrName(),
+                               UnitAttr::get(rewriter.getContext()));
   }
   assert(isa<FunctionOpInterface>(SymbolTable::lookupSymbolIn(module, name)));
 
diff --git a/mlir/lib/Conversion/MathToXeVM/MathToXeVM.cpp b/mlir/lib/Conversion/MathToXeVM/MathToXeVM.cpp
index 7143dcc19ff10..4b29df764be71 100644
--- a/mlir/lib/Conversion/MathToXeVM/MathToXeVM.cpp
+++ b/mlir/lib/Conversion/MathToXeVM/MathToXeVM.cpp
@@ -145,7 +145,8 @@ struct ConvertNativeFuncPattern final : public OpConversionPattern<Op> {
     // convert arith fastmath attrs into attrs recognized by llvm.
     arith::AttrConvertFastMathToLLVM<Op, LLVM::CallOp> fastAttrConverter(op);
     mlir::NamedAttribute fastAttr = fastAttrConverter.getAttrs()[0];
-    callOp->setAttr(fastAttr.getName(), fastAttr.getValue());
+    callOp.setFastmathFlagsAttr(
+        cast<LLVM::FastmathFlagsAttr>(fastAttr.getValue()));
 
     if (unwrapSizeOneVec) {
       // Re-wrap the scalar result back into a size-1 vector to preserve types.
diff --git a/mlir/lib/Conversion/MemRefToSPIRV/MapMemRefStorageClassPass.cpp b/mlir/lib/Conversion/MemRefToSPIRV/MapMemRefStorageClassPass.cpp
index 1fbc5a03987e8..6bc49a538bc9e 100644
--- a/mlir/lib/Conversion/MemRefToSPIRV/MapMemRefStorageClassPass.cpp
+++ b/mlir/lib/Conversion/MemRefToSPIRV/MapMemRefStorageClassPass.cpp
@@ -230,9 +230,9 @@ static bool isLegalOp(Operation *op) {
                         isLegalType);
   }
 
-  auto attrs = llvm::map_range(op->getAttrs(), [](const NamedAttribute &attr) {
-    return attr.getValue();
-  });
+  auto attrs = llvm::map_range(
+      op->getDiscardableAttrDictionary().getValue(),
+      [](const NamedAttribute &attr) { return attr.getValue(); });
 
   return llvm::all_of(op->getOperandTypes(), isLegalType) &&
          llvm::all_of(op->getResultTypes(), isLegalType) &&
diff --git a/mlir/lib/Conversion/NVGPUToNVVM/NVGPUToNVVM.cpp b/mlir/lib/Conversion/NVGPUToNVVM/NVGPUToNVVM.cpp
index b155505a2dae1..ae3a5f7e8e97a 100644
--- a/mlir/lib/Conversion/NVGPUToNVVM/NVGPUToNVVM.cpp
+++ b/mlir/lib/Conversion/NVGPUToNVVM/NVGPUToNVVM.cpp
@@ -353,7 +353,7 @@ struct MmaSyncOptoNVVM : public ConvertOpToLLVMPattern<nvgpu::MmaSyncOp> {
     std::array<int64_t, 3> gemmShape = op.getMmaShapeAsArray();
 
     // Tensor Cores (mma.sync) on F32 works only with TensorFloat32 (TF32).
-    bool tf32Enabled = op->hasAttr(op.getTf32EnabledAttrName());
+    bool tf32Enabled = op.getTf32Enabled().value_or(false);
     if (aType.getElementType().isF32() && !tf32Enabled)
       return failure();
 
@@ -599,7 +599,7 @@ struct NVGPUMmaSparseSyncLowering
           "could not infer the PTX type for the accumulator/result");
 
     // Same as `mma.sync`, F32 works only with TensorFloat32 (TF32).
-    bool tf32Enabled = op->hasAttr(op.getTf32EnabledAttrName());
+    bool tf32Enabled = op.getTf32Enabled().value_or(false);
     if (aType.getElementType().isF32() && !tf32Enabled)
       return failure();
 
diff --git a/mlir/lib/Conversion/OpenMPToLLVM/OpenMPToLLVM.cpp b/mlir/lib/Conversion/OpenMPToLLVM/OpenMPToLLVM.cpp
index 2ab1575a510ac..69d5b57609edd 100644
--- a/mlir/lib/Conversion/OpenMPToLLVM/OpenMPToLLVM.cpp
+++ b/mlir/lib/Conversion/OpenMPToLLVM/OpenMPToLLVM.cpp
@@ -28,6 +28,13 @@ using namespace mlir;
 
 namespace {
 
+static NamedAttrList getAttrsForConversion(Operation *op) {
+  NamedAttrList attrs;
+  op->getName().populateInherentAttrs(op, attrs);
+  attrs.append(op->getDiscardableAttrDictionary().getValue());
+  return attrs;
+}
+
 /// A pattern that converts the result and operand types, attributes, and region
 /// arguments of an OpenMP operation to the LLVM dialect.
 ///
@@ -63,7 +70,7 @@ struct OpenMPOpConversion : public ConvertOpToLLVMPattern<T> {
     // Translate type attributes.
     // They are kept unmodified except if they are type attributes.
     SmallVector<NamedAttribute> convertedAttrs;
-    for (NamedAttribute attr : op->getAttrs()) {
+    for (NamedAttribute attr : getAttrsForConversion(op)) {
       if (auto typeAttr = dyn_cast<TypeAttr>(attr.getValue())) {
         Type convertedType = converter->convertType(typeAttr.getValue());
         if (!convertedType)
@@ -131,7 +138,7 @@ void mlir::configureOpenMPToLLVMConversionLegality(
                         [&](Region &region) {
                           return typeConverter.isLegal(&region);
                         }) &&
-           llvm::all_of(op->getAttrs(), [&](NamedAttribute attr) {
+           llvm::all_of(getAttrsForConversion(op), [&](NamedAttribute attr) {
              auto typeAttr = dyn_cast<TypeAttr>(attr.getValue());
              return !typeAttr || typeConverter.isLegal(typeAttr.getValue());
            });
diff --git a/mlir/lib/Conversion/RaiseWasm/RaiseWasmMLIR.cpp b/mlir/lib/Conversion/RaiseWasm/RaiseWasmMLIR.cpp
index c26a3b038e918..f6c1c748b2e89 100644
--- a/mlir/lib/Conversion/RaiseWasm/RaiseWasmMLIR.cpp
+++ b/mlir/lib/Conversion/RaiseWasm/RaiseWasmMLIR.cpp
@@ -649,8 +649,8 @@ struct WasmGlobalWithGetGlobalInitConversion
     auto globalInitializer =
         func::FuncOp::create(rewriter, loc, initializerName,
                              FunctionType::get(getContext(), {}, {}));
-    globalInitializer->setAttr(rewriter.getStringAttr("initializer"),
-                               rewriter.getUnitAttr());
+    globalInitializer->setDiscardableAttr(rewriter.getStringAttr("initializer"),
+                                          rewriter.getUnitAttr());
     auto *initializerBody = globalInitializer.addEntryBlock();
     auto sip = rewriter.saveInsertionPoint();
     rewriter.setInsertionPointToStart(initializerBody);
@@ -717,8 +717,8 @@ struct WasmMemoryOpConversion : OpConversionPattern<MemOp> {
     auto memInitializer =
         func::FuncOp::create(rewriter, loc, initializerName,
                              FunctionType::get(getContext(), {}, {}));
-    memInitializer->setAttr(rewriter.getStringAttr("initializer"),
-                            rewriter.getUnitAttr());
+    memInitializer->setDiscardableAttr(rewriter.getStringAttr("initializer"),
+                                       rewriter.getUnitAttr());
     auto *initializerBody = memInitializer.addEntryBlock();
     auto sip = rewriter.saveInsertionPoint();
     rewriter.setInsertionPointToStart(initializerBody);
diff --git a/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp b/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
index 2972d79c4302f..eba6ef8c48fa2 100644
--- a/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
+++ b/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
@@ -319,8 +319,8 @@ static void propagateLoopAttrs(Operation *scfOp, Operation *brOp) {
   // LLVM requires the loop metadata to be attached on the "latch" block. Which
   // is the back-edge to the header block (conditionBlock)
   SmallVector<NamedAttribute> llvmAttrs;
-  llvm::copy_if(scfOp->getAttrs(), std::back_inserter(llvmAttrs),
-                [](auto attr) {
+  llvm::copy_if(scfOp->getDiscardableAttrDictionary().getValue(),
+                std::back_inserter(llvmAttrs), [](auto attr) {
                   return isa<LLVM::LLVMDialect>(attr.getValue().getDialect());
                 });
   brOp->setDiscardableAttrs(llvmAttrs);
diff --git a/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp b/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
index 370457c85e797..26b2f537e91a7 100644
--- a/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
+++ b/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
@@ -41,7 +41,7 @@ using namespace mlir::scf;
 // Name of internal attribute to mark visited operations during conversion.
 //
 // NOTE: The conversion originally used the following legality criteria:
-//   `!parallelOp->hasAttr(gpu::getMappingAttrName())`
+//   `!parallelOp->hasDiscardableAttr(gpu::getMappingAttrName())`
 // But the provided pattern might reject some cases based on more detailed
 // analysis of the `mapping` attribute.
 // To avoid dialect conversion failure due to non-converted illegal operation
@@ -408,8 +408,8 @@ static LogicalResult processParallelLoop(
     DenseMap<gpu::Processor, Value> &bounds, PatternRewriter &rewriter) {
   // TODO: Verify that this is a valid GPU mapping.
   // processor ids: 0-2 block [x/y/z], 3-5 -> thread [x/y/z], 6-> sequential
-  ArrayAttr mapping =
-      parallelOp->getAttrOfType<ArrayAttr>(gpu::getMappingAttrName());
+  ArrayAttr mapping = parallelOp->getDiscardableAttrOfType<ArrayAttr>(
+      gpu::getMappingAttrName());
 
   // TODO: Support multiple reductions.
   if (!mapping || parallelOp.getNumResults() > 1)
@@ -562,11 +562,12 @@ static LogicalResult processParallelLoop(
 
   // Propagate custom user defined optional attributes, that can be used at
   // later stage, such as extension data for GPU kernel dispatch
-  for (const auto &namedAttr : parallelOp->getAttrs()) {
+  for (const auto &namedAttr :
+       parallelOp->getDiscardableAttrDictionary().getValue()) {
     if (namedAttr.getName() == gpu::getMappingAttrName() ||
         namedAttr.getName() == ParallelOp::getOperandSegmentSizeAttr())
       continue;
-    launchOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+    launchOp->setDiscardableAttr(namedAttr.getName(), namedAttr.getValue());
   }
 
   Block *body = parallelOp.getBody();
@@ -614,7 +615,7 @@ LogicalResult
 ParallelToGpuLaunchLowering::matchAndRewrite(ParallelOp parallelOp,
                                              PatternRewriter &rewriter) const {
   // Mark the operation as visited for recursive legality check.
-  parallelOp->setAttr(kVisitedAttrName, rewriter.getUnitAttr());
+  parallelOp->setDiscardableAttr(kVisitedAttrName, rewriter.getUnitAttr());
 
   // We can only transform starting at the outer-most loop. Launches inside of
   // parallel loops are not supported.
@@ -775,13 +776,13 @@ void mlir::populateParallelLoopToGPUPatterns(RewritePatternSet &patterns) {
 void mlir::configureParallelLoopToGPULegality(ConversionTarget &target) {
   target.addLegalDialect<memref::MemRefDialect>();
   target.addDynamicallyLegalOp<scf::ParallelOp>([](scf::ParallelOp parallelOp) {
-    return !parallelOp->hasAttr(gpu::getMappingAttrName()) ||
-           parallelOp->hasAttr(kVisitedAttrName);
+    return !parallelOp->hasDiscardableAttr(gpu::getMappingAttrName()) ||
+           parallelOp->hasDiscardableAttr(kVisitedAttrName);
   });
 }
 
 void mlir::finalizeParallelLoopToGPUConversion(Operation *op) {
   op->walk([](scf::ParallelOp parallelOp) {
-    parallelOp->removeAttr(kVisitedAttrName);
+    parallelOp->removeDiscardableAttr(kVisitedAttrName);
   });
 }
diff --git a/mlir/lib/Conversion/SCFToSPIRV/SCFToSPIRV.cpp b/mlir/lib/Conversion/SCFToSPIRV/SCFToSPIRV.cpp
index fc6b37798e42d..0821e8e96a9d0 100644
--- a/mlir/lib/Conversion/SCFToSPIRV/SCFToSPIRV.cpp
+++ b/mlir/lib/Conversion/SCFToSPIRV/SCFToSPIRV.cpp
@@ -137,7 +137,7 @@ struct ForOpConversion final : SCFToSPIRVPattern<scf::ForOp> {
     // from header to merge.
     auto loc = forOp.getLoc();
     auto loopControl = spirv::LoopControl::None;
-    if (auto attr = forOp->getAttrOfType<spirv::LoopControlAttr>(
+    if (auto attr = forOp->getDiscardableAttrOfType<spirv::LoopControlAttr>(
             spirv::getLoopControlAttrName()))
       loopControl = attr.getValue();
     auto loopOp = spirv::LoopOp::create(rewriter, loc, loopControl);
@@ -260,7 +260,7 @@ struct IfOpConversion : SCFToSPIRVPattern<scf::IfOp> {
     // Create `spirv.selection` operation, selection header block and merge
     // block.
     auto selectionControl = spirv::SelectionControl::None;
-    if (auto attr = ifOp->getAttrOfType<spirv::SelectionControlAttr>(
+    if (auto attr = ifOp->getDiscardableAttrOfType<spirv::SelectionControlAttr>(
             spirv::getSelectionControlAttrName()))
       selectionControl = attr.getValue();
     auto selectionOp =
@@ -339,8 +339,9 @@ struct IndexSwitchOpConversion final : SCFToSPIRVPattern<scf::IndexSwitchOp> {
 
     // Create the `spirv.mlir.selection` op, its header block, and merge block.
     auto selectionControl = spirv::SelectionControl::None;
-    if (auto attr = switchOp->getAttrOfType<spirv::SelectionControlAttr>(
-            spirv::getSelectionControlAttrName()))
+    if (auto attr =
+            switchOp->getDiscardableAttrOfType<spirv::SelectionControlAttr>(
+                spirv::getSelectionControlAttrName()))
       selectionControl = attr.getValue();
     auto selectionOp =
         spirv::SelectionOp::create(rewriter, loc, selectionControl);
@@ -451,7 +452,7 @@ struct WhileOpConversion final : SCFToSPIRVPattern<scf::WhileOp> {
                   ConversionPatternRewriter &rewriter) const override {
     auto loc = whileOp.getLoc();
     auto loopControl = spirv::LoopControl::None;
-    if (auto attr = whileOp->getAttrOfType<spirv::LoopControlAttr>(
+    if (auto attr = whileOp->getDiscardableAttrOfType<spirv::LoopControlAttr>(
             spirv::getLoopControlAttrName()))
       loopControl = attr.getValue();
     auto loopOp = spirv::LoopOp::create(rewriter, loc, loopControl);
diff --git a/mlir/lib/Conversion/SPIRVToLLVM/ConvertLaunchFuncToLLVMCalls.cpp b/mlir/lib/Conversion/SPIRVToLLVM/ConvertLaunchFuncToLLVMCalls.cpp
index 2491c7cbd3d22..54a24004cfc75 100644
--- a/mlir/lib/Conversion/SPIRVToLLVM/ConvertLaunchFuncToLLVMCalls.cpp
+++ b/mlir/lib/Conversion/SPIRVToLLVM/ConvertLaunchFuncToLLVMCalls.cpp
@@ -44,24 +44,13 @@ static constexpr const char kSPIRVModule[] = "__spv__";
 // Utility functions
 //===----------------------------------------------------------------------===//
 
-/// Returns the string name of the `DescriptorSet` decoration.
-static std::string descriptorSetName() {
-  return spirv::getDecorationString(spirv::Decoration::DescriptorSet);
-}
-
-/// Returns the string name of the `Binding` decoration.
-static std::string bindingName() {
-  return spirv::getDecorationString(spirv::Decoration::Binding);
-}
-
 /// Calculates the index of the kernel's operand that is represented by the
 /// given global variable with the `bind` attribute. We assume that the index of
 /// each kernel's operand is mapped to (descriptorSet, binding) by the map:
 ///   i -> (0, i)
 /// which is implemented under `LowerABIAttributesPass`.
 static unsigned calculateGlobalIndex(spirv::GlobalVariableOp op) {
-  IntegerAttr binding = op->getAttrOfType<IntegerAttr>(bindingName());
-  return binding.getInt();
+  return *op.getBinding();
 }
 
 /// Copies the given number of bytes from src to dst pointers.
@@ -78,22 +67,16 @@ static void copy(Location loc, Value dst, Value src, Value size,
 static std::string
 createGlobalVariableWithBindName(spirv::GlobalVariableOp op,
                                  StringRef kernelModuleName) {
-  IntegerAttr descriptorSet =
-      op->getAttrOfType<IntegerAttr>(descriptorSetName());
-  IntegerAttr binding = op->getAttrOfType<IntegerAttr>(bindingName());
   return llvm::formatv("{0}_{1}_descriptor_set{2}_binding{3}",
                        kernelModuleName.str(), op.getSymName().str(),
-                       std::to_string(descriptorSet.getInt()),
-                       std::to_string(binding.getInt()));
+                       std::to_string(*op.getDescriptorSet()),
+                       std::to_string(*op.getBinding()));
 }
 
 /// Returns true if the given global variable has both a descriptor set number
 /// and a binding number.
 static bool hasDescriptorSetAndBinding(spirv::GlobalVariableOp op) {
-  IntegerAttr descriptorSet =
-      op->getAttrOfType<IntegerAttr>(descriptorSetName());
-  IntegerAttr binding = op->getAttrOfType<IntegerAttr>(bindingName());
-  return descriptorSet && binding;
+  return op.getDescriptorSetAttr() && op.getBindingAttr();
 }
 
 /// Fills `globalVariableMap` with SPIR-V global variables that represent kernel
@@ -289,8 +272,8 @@ class LowerHostCodeToLLVM
 
     // Request C wrapper emission.
     for (auto func : module.getOps<func::FuncOp>()) {
-      func->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                    UnitAttr::get(&getContext()));
+      func->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                               UnitAttr::get(&getContext()));
     }
 
     // Specify options to lower to LLVM and pull in the conversion patterns.
diff --git a/mlir/lib/Conversion/SPIRVToLLVM/SPIRVToLLVM.cpp b/mlir/lib/Conversion/SPIRVToLLVM/SPIRVToLLVM.cpp
index 63f9188a4e464..5b74f4e1bb5e9 100644
--- a/mlir/lib/Conversion/SPIRVToLLVM/SPIRVToLLVM.cpp
+++ b/mlir/lib/Conversion/SPIRVToLLVM/SPIRVToLLVM.cpp
@@ -303,6 +303,15 @@ static Type convertStructType(spirv::StructType type,
 
 namespace {
 
+template <typename OpTy>
+static NamedAttrList collectAttrsForConversion(OpTy op) {
+  NamedAttrList attrs(op->getDiscardableAttrDictionary());
+  if (auto properties =
+          dyn_cast_or_null<DictionaryAttr>(op->getPropertiesAsAttribute()))
+    attrs.append(properties.getValue());
+  return attrs;
+}
+
 class AccessChainPattern : public SPIRVToLLVMConversion<spirv::AccessChainOp> {
 public:
   using SPIRVToLLVMConversion<spirv::AccessChainOp>::SPIRVToLLVMConversion;
@@ -434,7 +443,8 @@ class ConstantScalarAndVectorPattern
       return success();
     }
     rewriter.replaceOpWithNewOp<LLVM::ConstantOp>(
-        constOp, dstType, adaptor.getOperands(), constOp->getAttrs());
+        constOp, dstType, adaptor.getOperands(),
+        collectAttrsForConversion(constOp));
     return success();
   }
 };
@@ -644,7 +654,7 @@ class DirectConversionPattern : public SPIRVToLLVMConversion<SPIRVOp> {
     if (!dstType)
       return rewriter.notifyMatchFailure(op, "type conversion failed");
     rewriter.template replaceOpWithNewOp<LLVMOp>(
-        op, dstType, adaptor.getOperands(), op->getAttrs());
+        op, dstType, adaptor.getOperands(), collectAttrsForConversion(op));
     return success();
   }
 };
@@ -828,7 +838,7 @@ class GlobalVariablePattern
 
     // Attach location attribute if applicable
     if (locationAttr)
-      newGlobalOp->setAttr(locationAttrName, locationAttr);
+      newGlobalOp->setDiscardableAttr(locationAttrName, locationAttr);
 
     return success();
   }
@@ -879,7 +889,8 @@ class FunctionCallPattern
                   ConversionPatternRewriter &rewriter) const override {
     if (callOp.getNumResults() == 0) {
       auto newOp = rewriter.replaceOpWithNewOp<LLVM::CallOp>(
-          callOp, TypeRange(), adaptor.getOperands(), callOp->getAttrs());
+          callOp, TypeRange(), adaptor.getOperands(),
+          collectAttrsForConversion(callOp));
       newOp.getProperties().operandSegmentSizes = {
           static_cast<int32_t>(adaptor.getOperands().size()), 0};
       newOp.getProperties().op_bundle_sizes = rewriter.getDenseI32ArrayAttr({});
@@ -891,7 +902,8 @@ class FunctionCallPattern
     if (!dstType)
       return rewriter.notifyMatchFailure(callOp, "type conversion failed");
     auto newOp = rewriter.replaceOpWithNewOp<LLVM::CallOp>(
-        callOp, dstType, adaptor.getOperands(), callOp->getAttrs());
+        callOp, dstType, adaptor.getOperands(),
+        collectAttrsForConversion(callOp));
     newOp.getProperties().operandSegmentSizes = {
         static_cast<int32_t>(adaptor.getOperands().size()), 0};
     newOp.getProperties().op_bundle_sizes = rewriter.getDenseI32ArrayAttr({});
@@ -1940,7 +1952,8 @@ class BitcastConversionPattern
     }
 
     rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(
-        bitcastOp, dstType, adaptor.getOperands(), bitcastOp->getAttrs());
+        bitcastOp, dstType, adaptor.getOperands(),
+        collectAttrsForConversion(bitcastOp));
     return success();
   }
 };
@@ -1986,7 +1999,8 @@ class FuncConversionPattern : public SPIRVToLLVMConversion<spirv::FuncOp> {
 
 #define DISPATCH(functionControl, llvmAttr)                                    \
   case functionControl:                                                        \
-    newFuncOp->setAttr("passthrough", ArrayAttr::get(context, {llvmAttr}));    \
+    newFuncOp->setDiscardableAttr("passthrough",                               \
+                                  ArrayAttr::get(context, {llvmAttr}));        \
     break;
 
       DISPATCH(spirv::FunctionControl::Pure,
@@ -2388,9 +2402,8 @@ void mlir::encodeBindAttribute(ModuleOp module) {
   auto spvModules = module.getOps<spirv::ModuleOp>();
   for (auto spvModule : spvModules) {
     spvModule.walk([&](spirv::GlobalVariableOp op) {
-      IntegerAttr descriptorSet =
-          op->getAttrOfType<IntegerAttr>(kDescriptorSet);
-      IntegerAttr binding = op->getAttrOfType<IntegerAttr>(kBinding);
+      IntegerAttr descriptorSet = op.getDescriptorSetAttr();
+      IntegerAttr binding = op.getBindingAttr();
       // For every global variable in the module, get the ones with descriptor
       // set and binding numbers.
       if (descriptorSet && binding) {
@@ -2411,8 +2424,8 @@ void mlir::encodeBindAttribute(ModuleOp module) {
         if (failed(SymbolTable::replaceAllSymbolUses(op, nameAttr, spvModule)))
           op.emitError("unable to replace all symbol uses for ") << name;
         SymbolTable::setSymbolName(op, nameAttr);
-        op->removeAttr(kDescriptorSet);
-        op->removeAttr(kBinding);
+        op.removeDescriptorSetAttr();
+        op.removeBindingAttr();
       }
     });
   }
diff --git a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
index e3f40c57eb312..b7eb0a3aed546 100644
--- a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
+++ b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalg.cpp
@@ -291,7 +291,7 @@ static Value createLinalgBodyCalculationForElementwiseOp(
   // tosa::ArithmeticRightShiftOp
   if (isa<tosa::ArithmeticRightShiftOp>(op) && isa<IntegerType>(elementTy)) {
     auto result = arith::ShRSIOp::create(rewriter, loc, resultTypes, args);
-    auto round = cast<BoolAttr>(op->getAttr("round")).getValue();
+    bool round = cast<tosa::ArithmeticRightShiftOp>(op).getRound();
     if (!round) {
       return result;
     }
@@ -451,8 +451,9 @@ static Value createLinalgBodyCalculationForElementwiseOp(
   // tosa::ClampOp
   if (isa<tosa::ClampOp>(op) && isa<FloatType>(elementTy)) {
     bool losesInfo = false;
-    APFloat minApf = cast<FloatAttr>(op->getAttr("min_val")).getValue();
-    APFloat maxApf = cast<FloatAttr>(op->getAttr("max_val")).getValue();
+    auto clampOp = cast<tosa::ClampOp>(op);
+    APFloat minApf = cast<FloatAttr>(clampOp.getMinValAttr()).getValue();
+    APFloat maxApf = cast<FloatAttr>(clampOp.getMaxValAttr()).getValue();
     minApf.convert(cast<FloatType>(elementTy).getFloatSemantics(),
                    APFloat::rmNearestTiesToEven, &losesInfo);
     maxApf.convert(cast<FloatType>(elementTy).getFloatSemantics(),
@@ -463,7 +464,6 @@ static Value createLinalgBodyCalculationForElementwiseOp(
         rewriter, loc, elementTy, rewriter.getFloatAttr(elementTy, maxApf));
     auto result = clampFloatHelper(loc, args[0], min, max, rewriter);
 
-    auto clampOp = llvm::cast<tosa::ClampOp>(op);
     const auto nanMode = clampOp.getNanMode();
 
     // NaN propagation has no meaning for non floating point types.
@@ -495,10 +495,11 @@ static Value createLinalgBodyCalculationForElementwiseOp(
 
   if (isa<tosa::ClampOp>(op) && isa<IntegerType>(elementTy)) {
     auto intTy = cast<IntegerType>(elementTy);
+    auto clampOp = cast<tosa::ClampOp>(op);
     int64_t min =
-        cast<IntegerAttr>(op->getAttr("min_val")).getValue().getSExtValue();
+        cast<IntegerAttr>(clampOp.getMinValAttr()).getValue().getSExtValue();
     int64_t max =
-        cast<IntegerAttr>(op->getAttr("max_val")).getValue().getSExtValue();
+        cast<IntegerAttr>(clampOp.getMaxValAttr()).getValue().getSExtValue();
 
     int64_t minRepresentable = std::numeric_limits<int64_t>::min();
     int64_t maxRepresentable = std::numeric_limits<int64_t>::max();
diff --git a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgNamed.cpp b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgNamed.cpp
index 058b3de3a3788..ee57a5a781c5f 100644
--- a/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgNamed.cpp
+++ b/mlir/lib/Conversion/TosaToLinalg/TosaToLinalgNamed.cpp
@@ -445,9 +445,9 @@ class DepthwiseConvConverter
     Type inputETy = inputTy.getElementType();
     Type resultETy = resultTy.getElementType();
 
-    auto padAttr = cast<DenseI64ArrayAttr>(op->getAttr("pad"));
-    auto strideTosaAttr = cast<DenseI64ArrayAttr>(op->getAttr("stride"));
-    auto dilationTosaAttr = cast<DenseI64ArrayAttr>(op->getAttr("dilation"));
+    auto padAttr = op.getPadAttr();
+    auto strideTosaAttr = op.getStrideAttr();
+    auto dilationTosaAttr = op.getDilationAttr();
 
     Type accETy = op.getAccType();
 
diff --git a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosa.cpp b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosa.cpp
index 3ef57c1e2cd22..45a7b0e68b793 100644
--- a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosa.cpp
+++ b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosa.cpp
@@ -34,7 +34,7 @@ void copyFuncAttrsToGraph(func::FuncOp funcOp, func::FuncOpAdaptor adaptor,
                            attrName))
       continue;
 
-    graphOp->setAttr(attr.getName(), attr.getValue());
+    graphOp->setDiscardableAttr(attr.getName(), attr.getValue());
   }
 }
 
@@ -109,7 +109,7 @@ struct FuncGraphConvert final : OpConversionPattern<func::FuncOp> {
         rewriter, funcOp.getLoc(), spirv::AddressingModel::Logical,
         spirv::MemoryModel::Vulkan, std::nullopt,
         ("_spirv_tosa_" + name).str());
-    spvModule->setAttr(spirv::getTargetEnvAttrName(), targetAttr);
+    spvModule->setDiscardableAttr(spirv::getTargetEnvAttrName(), targetAttr);
 
     rewriter.setInsertionPoint(spvModule.getBody(), spvModule.begin());
 
diff --git a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaConstants.cpp b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaConstants.cpp
index c7174fd930782..b1496a776fa89 100644
--- a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaConstants.cpp
+++ b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaConstants.cpp
@@ -51,7 +51,8 @@ bool shouldMarkGraphConstant(Operation *op) {
 
 void setGraphConstantId(Operation *op, uint32_t id) {
   auto i32Type = IntegerType::get(op->getContext(), 32);
-  op->setAttr(graphARMGraphConstantIdAttrName, IntegerAttr::get(i32Type, id));
+  op->setDiscardableAttr(graphARMGraphConstantIdAttrName,
+                         IntegerAttr::get(i32Type, id));
 }
 
 struct TosaToSPIRVTosaMarkGraphConstants final
@@ -64,7 +65,7 @@ struct TosaToSPIRVTosaMarkGraphConstants final
           if (!isa<tosa::ConstOp, tosa::ConstShapeOp>(op))
             return WalkResult::advance();
 
-          if (op->hasAttr(graphARMGraphConstantIdAttrName)) {
+          if (op->hasDiscardableAttr(graphARMGraphConstantIdAttrName)) {
             op->emitOpError()
                 << "already has `" << graphARMGraphConstantIdAttrName
                 << "`; this pass assigns graph constant IDs automatically and "
diff --git a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaPass.cpp b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaPass.cpp
index 815f2db74ffa6..937d1d0e6f2ee 100644
--- a/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaPass.cpp
+++ b/mlir/lib/Conversion/TosaToSPIRVTosa/TosaToSPIRVTosaPass.cpp
@@ -145,8 +145,8 @@ LogicalResult verifyGraphConstantIdAttrs(Operation *op) {
     if (!isa<tosa::ConstOp, tosa::ConstShapeOp>(op))
       return WalkResult::advance();
 
-    auto graphConstantId =
-        op->getAttrOfType<IntegerAttr>(graphARMGraphConstantIdAttrName);
+    auto graphConstantId = op->getDiscardableAttrOfType<IntegerAttr>(
+        graphARMGraphConstantIdAttrName);
     if (!graphConstantId)
       return WalkResult::advance();
 
diff --git a/mlir/lib/Conversion/VectorToArmSME/VectorToArmSME.cpp b/mlir/lib/Conversion/VectorToArmSME/VectorToArmSME.cpp
index 778c616f1bf44..6ec404e982577 100644
--- a/mlir/lib/Conversion/VectorToArmSME/VectorToArmSME.cpp
+++ b/mlir/lib/Conversion/VectorToArmSME/VectorToArmSME.cpp
@@ -300,9 +300,9 @@ struct TransposeOpToArmSMELowering
       // Fold transpose into transfer_read to enable in-flight transpose when
       // converting to arm_sme.tile_load.
       rewriter.modifyOpInPlace(xferOp, [&]() {
-        xferOp->setAttr(xferOp.getPermutationMapAttrName(),
-                        AffineMapAttr::get(AffineMap::getPermutationMap(
-                            permutation, transposeOp.getContext())));
+        xferOp.setPermutationMapAttr(
+            AffineMapAttr::get(AffineMap::getPermutationMap(
+                permutation, transposeOp.getContext())));
       });
       rewriter.replaceOp(transposeOp, xferOp);
       return success();
diff --git a/mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp b/mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp
index c9eba6962e6a4..3a48e2e6abf6a 100644
--- a/mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp
+++ b/mlir/lib/Conversion/VectorToSCF/VectorToSCF.cpp
@@ -272,7 +272,7 @@ template <typename OpTy>
 static void maybeApplyPassLabel(OpBuilder &b, OpTy newXferOp,
                                 unsigned targetRank) {
   if (newXferOp.getVectorType().getRank() > targetRank)
-    newXferOp->setAttr(kPassLabel, b.getUnitAttr());
+    newXferOp->setDiscardableAttr(kPassLabel, b.getUnitAttr());
 }
 
 namespace lowering_n_d {
@@ -550,7 +550,7 @@ struct Strategy<TransferWriteOp> {
 template <typename OpTy>
 static LogicalResult checkPrepareXferOp(OpTy xferOp, PatternRewriter &rewriter,
                                         VectorTransferToSCFOptions options) {
-  if (xferOp->hasAttr(kPassLabel))
+  if (xferOp->hasDiscardableAttr(kPassLabel))
     return rewriter.notifyMatchFailure(
         xferOp, "kPassLabel is present (vector-to-scf lowering in progress)");
   if (xferOp.getVectorType().getRank() <= options.targetRank)
@@ -606,7 +606,7 @@ struct PrepareTransferReadConversion
 
     auto buffers = allocBuffers(rewriter, xferOp);
     auto *newXfer = rewriter.clone(*xferOp.getOperation());
-    newXfer->setAttr(kPassLabel, rewriter.getUnitAttr());
+    newXfer->setDiscardableAttr(kPassLabel, rewriter.getUnitAttr());
     if (xferOp.getMask()) {
       dyn_cast<TransferReadOp>(newXfer).getMaskMutable().assign(
           buffers.maskBuffer);
@@ -661,7 +661,7 @@ struct PrepareTransferWriteConversion
     auto loadedVec = memref::LoadOp::create(rewriter, loc, buffers.dataBuffer);
     rewriter.modifyOpInPlace(xferOp, [&]() {
       xferOp.getValueToStoreMutable().assign(loadedVec);
-      xferOp->setAttr(kPassLabel, rewriter.getUnitAttr());
+      xferOp->setDiscardableAttr(kPassLabel, rewriter.getUnitAttr());
     });
 
     if (xferOp.getMask()) {
@@ -906,7 +906,7 @@ struct TransferOpConversion : public VectorToSCFPattern<OpTy> {
 
   LogicalResult matchAndRewrite(OpTy xferOp,
                                 PatternRewriter &rewriter) const override {
-    if (!xferOp->hasAttr(kPassLabel))
+    if (!xferOp->hasDiscardableAttr(kPassLabel))
       return rewriter.notifyMatchFailure(
           xferOp, "kPassLabel is present (progressing lowering in progress)");
 
diff --git a/mlir/lib/Conversion/XeGPUToXeVM/XeGPUToXeVM.cpp b/mlir/lib/Conversion/XeGPUToXeVM/XeGPUToXeVM.cpp
index 78d99cf88b768..c1c7ddfbdd237 100644
--- a/mlir/lib/Conversion/XeGPUToXeVM/XeGPUToXeVM.cpp
+++ b/mlir/lib/Conversion/XeGPUToXeVM/XeGPUToXeVM.cpp
@@ -678,7 +678,7 @@ class LoadStoreToXeVMPattern : public OpConversionPattern<OpType> {
       Value loaded =
           LLVM::LoadOp::create(rewriter, loc, valOrResTy, basePtrLLVM);
       // Set cache control attribute on the load operation.
-      loaded.getDefiningOp()->setAttr(
+      loaded.getDefiningOp()->setDiscardableAttr(
           "cache_control", xevm::LoadCacheControlAttr::get(
                                ctxt, translateLoadXeGPUCacheHint(
                                          op.getL1Hint(), op.getL3Hint())));
@@ -706,7 +706,7 @@ class LoadStoreToXeVMPattern : public OpConversionPattern<OpType> {
       auto storeOp =
           LLVM::StoreOp::create(rewriter, loc, adaptor.getValue(), basePtrLLVM);
       // Set cache control attribute on the store operation.
-      storeOp.getOperation()->setAttr(
+      storeOp.getOperation()->setDiscardableAttr(
           "cache_control", xevm::StoreCacheControlAttr::get(
                                ctxt, translateStoreXeGPUCacheHint(
                                          op.getL1Hint(), op.getL3Hint())));
diff --git a/mlir/lib/Conversion/XeVMToLLVM/XeVMToLLVM.cpp b/mlir/lib/Conversion/XeVMToLLVM/XeVMToLLVM.cpp
index c51da4d5d4d3d..231bbea0eb212 100644
--- a/mlir/lib/Conversion/XeVMToLLVM/XeVMToLLVM.cpp
+++ b/mlir/lib/Conversion/XeVMToLLVM/XeVMToLLVM.cpp
@@ -241,8 +241,9 @@ static std::optional<StoreCacheControl> getCacheControl(BlockStoreOp op) {
 }
 
 static std::optional<LoadCacheControl> getCacheControl(LLVM::LoadOp op) {
-  if (op->hasAttr("cache_control")) {
-    auto attr = op->getAttrOfType<xevm::LoadCacheControlAttr>("cache_control");
+  if (op->hasDiscardableAttr("cache_control")) {
+    auto attr = op->getDiscardableAttrOfType<xevm::LoadCacheControlAttr>(
+        "cache_control");
     if (!attr)
       return std::nullopt;
     return std::optional<LoadCacheControl>(attr.getValue());
@@ -251,8 +252,9 @@ static std::optional<LoadCacheControl> getCacheControl(LLVM::LoadOp op) {
 }
 
 static std::optional<StoreCacheControl> getCacheControl(LLVM::StoreOp op) {
-  if (op->hasAttr("cache_control")) {
-    auto attr = op->getAttrOfType<xevm::StoreCacheControlAttr>("cache_control");
+  if (op->hasDiscardableAttr("cache_control")) {
+    auto attr = op->getDiscardableAttrOfType<xevm::StoreCacheControlAttr>(
+        "cache_control");
     if (!attr)
       return std::nullopt;
     return std::optional<StoreCacheControl>(attr.getValue());
@@ -533,7 +535,15 @@ static LLVM::CallOp createDeviceFunctionCall(
     funcOp.setArgAttr(idx, attrName, rewriter.getUnitAttr());
 
   auto callOp = LLVM::CallOp::create(rewriter, loc, funcOp, args);
-  callOp->setAttrs(funcOp->getAttrs());
+  NamedAttrList attrs(funcOp->getDiscardableAttrDictionary());
+  funcOp->getName().populateInherentAttrs(funcOp, attrs);
+  for (NamedAttribute attr :
+       attrs.getDictionary(funcOp.getContext()).getValue()) {
+    if (callOp->getInherentAttr(attr.getName()).has_value())
+      callOp->setInherentAttr(attr.getName(), attr.getValue());
+    else
+      callOp->setDiscardableAttr(attr.getName(), attr.getValue());
+  }
 
   return callOp;
 }
@@ -968,14 +978,15 @@ class LLVMLoadStoreToOCLPattern : public OpConversionPattern<OpType> {
   LogicalResult
   matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
                   ConversionPatternRewriter &rewriter) const override {
-    if (!op->hasAttr("cache_control"))
+    if (!op->hasDiscardableAttr("cache_control"))
       return failure();
 
     auto *moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
     std::optional<ArrayAttr> optCacheControls =
         getCacheControlMetadata(rewriter, op);
     if (!optCacheControls) {
-      rewriter.modifyOpInPlace(op, [&]() { op->removeAttr("cache_control"); });
+      rewriter.modifyOpInPlace(
+          op, [&]() { op->removeDiscardableAttr("cache_control"); });
       return success();
     }
 
@@ -991,7 +1002,7 @@ class LLVMLoadStoreToOCLPattern : public OpConversionPattern<OpType> {
     // Replace the pointer operand with the annotated one.
     rewriter.modifyOpInPlace(op, [&]() {
       op->setOperand(ptrIdx, annotatedPtr);
-      op->removeAttr("cache_control");
+      op->removeDiscardableAttr("cache_control");
     });
     return success();
   }
@@ -1784,7 +1795,7 @@ void ::mlir::populateXeVMToLLVMConversionPatterns(ConversionTarget &target,
       return addrSpace != 3;
     }
     // cache_control attribute should be converted.
-    return !op->hasAttr("cache_control");
+    return !op->hasDiscardableAttr("cache_control");
   });
   target.addIllegalDialect<XeVMDialect>();
   patterns
diff --git a/mlir/lib/Dialect/AMDGPU/Transforms/EmulateAtomics.cpp b/mlir/lib/Dialect/AMDGPU/Transforms/EmulateAtomics.cpp
index 332ac8cdb60c0..b2483426b8ee8 100644
--- a/mlir/lib/Dialect/AMDGPU/Transforms/EmulateAtomics.cpp
+++ b/mlir/lib/Dialect/AMDGPU/Transforms/EmulateAtomics.cpp
@@ -110,7 +110,8 @@ LogicalResult RawBufferAtomicByCasPattern<AtomicOp, ArithOp>::matchAndRewrite(
     ConversionPatternRewriter &rewriter) const {
   Location loc = atomicOp.getLoc();
 
-  ArrayRef<NamedAttribute> origAttrs = atomicOp->getAttrs();
+  NamedAttrList origAttrs(atomicOp->getDiscardableAttrDictionary());
+  atomicOp->getName().populateInherentAttrs(atomicOp, origAttrs);
   ValueRange operands = adaptor.getOperands();
   Value data = operands.take_front()[0];
   ValueRange invariantArgs = operands.drop_front();
diff --git a/mlir/lib/Dialect/AMDGPU/Transforms/MaskedloadToLoad.cpp b/mlir/lib/Dialect/AMDGPU/Transforms/MaskedloadToLoad.cpp
index 89ef51f922cad..8c2d697632e20 100644
--- a/mlir/lib/Dialect/AMDGPU/Transforms/MaskedloadToLoad.cpp
+++ b/mlir/lib/Dialect/AMDGPU/Transforms/MaskedloadToLoad.cpp
@@ -81,7 +81,7 @@ struct MaskedLoadLowering final : OpRewritePattern<vector::MaskedLoadOp> {
 
   LogicalResult matchAndRewrite(vector::MaskedLoadOp maskedOp,
                                 PatternRewriter &rewriter) const override {
-    if (maskedOp->hasAttr(kMaskedloadNeedsMask))
+    if (maskedOp->hasDiscardableAttr(kMaskedloadNeedsMask))
       return rewriter.notifyMatchFailure(maskedOp, "already rewritten");
 
     if (failed(hasBufferAddressSpace(maskedOp.getBase().getType()))) {
@@ -150,7 +150,7 @@ struct MaskedLoadLowering final : OpRewritePattern<vector::MaskedLoadOp> {
 
     auto thenBuilder = [&](OpBuilder &builder, Location loc) {
       Operation *read = builder.clone(*maskedOp.getOperation());
-      read->setAttr(kMaskedloadNeedsMask, builder.getUnitAttr());
+      read->setDiscardableAttr(kMaskedloadNeedsMask, builder.getUnitAttr());
       Value readResult = read->getResult(0);
       scf::YieldOp::create(builder, loc, readResult);
     };
diff --git a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
index 321c8e34d907c..4f65b392da603 100644
--- a/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
+++ b/mlir/lib/Dialect/Affine/Analysis/Utils.cpp
@@ -1945,7 +1945,8 @@ void mlir::affine::getComputationSliceState(
   for (unsigned i = 0; i < numSliceLoopIVs; ++i) {
     Value iv = getSliceLoop(i).getInductionVar();
     if (sequentialLoops.count(iv) == 0 &&
-        getSliceLoop(i)->getAttr(kSliceFusionBarrierAttrName) == nullptr)
+        getSliceLoop(i)->getDiscardableAttr(kSliceFusionBarrierAttrName) ==
+            nullptr)
       continue;
     // Skip reset of bounds of reduction loop inserted in the destination loop
     // that meets the following conditions:
diff --git a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
index b1f7b987703a5..aa24cb0d3d3c8 100644
--- a/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
+++ b/mlir/lib/Dialect/Affine/IR/AffineOps.cpp
@@ -574,7 +574,8 @@ void AffineApplyOp::print(OpAsmPrinter &p) {
   p << " " << getMapAttr();
   printDimAndSymbolList(operand_begin(), operand_end(),
                         getAffineMap().getNumDims(), p);
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"map"});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          /*elidedAttrs=*/{"map"});
 }
 
 LogicalResult AffineApplyOp::verify() {
@@ -2507,7 +2508,7 @@ void AffineForOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
                 printBlockTerminators);
   p.printOptionalAttrDict(
-      (*this)->getAttrs(),
+      (*this)->getDiscardableAttrDictionary().getValue(),
       /*elidedAttrs=*/{getLowerBoundMapAttrName(getOperation()->getName()),
                        getUpperBoundMapAttrName(getOperation()->getName()),
                        getStepAttrName(getOperation()->getName()),
@@ -3199,8 +3200,7 @@ ValueRange AffineIfOp::getSuccessorInputs(RegionSuccessor successor) {
 LogicalResult AffineIfOp::verify() {
   // Verify that we have a condition attribute.
   // FIXME: This should be specified in the arguments list in ODS.
-  auto conditionAttr =
-      (*this)->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName());
+  auto conditionAttr = getConditionAttr();
   if (!conditionAttr)
     return emitOpError("requires an integer set attribute named 'condition'");
 
@@ -3270,8 +3270,7 @@ ParseResult AffineIfOp::parse(OpAsmParser &parser, OperationState &result) {
 }
 
 void AffineIfOp::print(OpAsmPrinter &p) {
-  auto conditionAttr =
-      (*this)->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName());
+  auto conditionAttr = getConditionAttr();
   p << " " << conditionAttr;
   printDimAndSymbolList(operand_begin(), operand_end(),
                         conditionAttr.getValue().getNumDims(), p);
@@ -3290,18 +3289,14 @@ void AffineIfOp::print(OpAsmPrinter &p) {
   }
 
   // Print the attribute list.
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
                           /*elidedAttrs=*/getConditionAttrStrName());
 }
 
-IntegerSet AffineIfOp::getIntegerSet() {
-  return (*this)
-      ->getAttrOfType<IntegerSetAttr>(getConditionAttrStrName())
-      .getValue();
-}
+IntegerSet AffineIfOp::getIntegerSet() { return getConditionAttr().getValue(); }
 
 void AffineIfOp::setIntegerSet(IntegerSet newSet) {
-  (*this)->setAttr(getConditionAttrStrName(), IntegerSetAttr::get(newSet));
+  setConditionAttr(IntegerSetAttr::get(newSet));
 }
 
 void AffineIfOp::setConditional(IntegerSet set, ValueRange operands) {
@@ -3451,12 +3446,13 @@ ParseResult AffineLoadOp::parse(OpAsmParser &parser, OperationState &result) {
 
 void AffineLoadOp::print(OpAsmPrinter &p) {
   p << " " << getMemRef() << '[';
-  if (AffineMapAttr mapAttr =
-          (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
+  if (AffineMapAttr mapAttr = getMapAttr())
     p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
   p << ']';
-  p.printOptionalAttrDict((*this)->getAttrs(),
-                          /*elidedAttrs=*/{getMapAttrStrName()});
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  if (IntegerAttr alignment = getAlignmentAttr())
+    attrs.append(getAlignmentAttrName(), alignment);
+  p.printOptionalAttrDict(attrs, /*elidedAttrs=*/{getMapAttrStrName()});
   p << " : " << getMemRefType();
 }
 
@@ -3488,10 +3484,9 @@ LogicalResult AffineLoadOp::verify() {
   if (getType() != memrefType.getElementType())
     return emitOpError("result type must match element type of memref");
 
-  if (failed(verifyMemoryOpIndexing(
-          *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
-          getMapOperands(), memrefType,
-          /*numIndexOperands=*/getNumOperands() - 1)))
+  if (failed(verifyMemoryOpIndexing(*this, getMapAttr(), getMapOperands(),
+                                    memrefType,
+                                    /*numIndexOperands=*/getNumOperands() - 1)))
     return failure();
 
   return success();
@@ -3587,12 +3582,13 @@ ParseResult AffineStoreOp::parse(OpAsmParser &parser, OperationState &result) {
 void AffineStoreOp::print(OpAsmPrinter &p) {
   p << " " << getValueToStore();
   p << ", " << getMemRef() << '[';
-  if (AffineMapAttr mapAttr =
-          (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
+  if (AffineMapAttr mapAttr = getMapAttr())
     p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
   p << ']';
-  p.printOptionalAttrDict((*this)->getAttrs(),
-                          /*elidedAttrs=*/{getMapAttrStrName()});
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  if (IntegerAttr alignment = getAlignmentAttr())
+    attrs.append(getAlignmentAttrName(), alignment);
+  p.printOptionalAttrDict(attrs, /*elidedAttrs=*/{getMapAttrStrName()});
   p << " : " << getMemRefType();
 }
 
@@ -3603,10 +3599,9 @@ LogicalResult AffineStoreOp::verify() {
     return emitOpError(
         "value to store must have the same type as memref element type");
 
-  if (failed(verifyMemoryOpIndexing(
-          *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
-          getMapOperands(), memrefType,
-          /*numIndexOperands=*/getNumOperands() - 2)))
+  if (failed(verifyMemoryOpIndexing(*this, getMapAttr(), getMapOperands(),
+                                    memrefType,
+                                    /*numIndexOperands=*/getNumOperands() - 2)))
     return failure();
 
   return success();
@@ -3642,14 +3637,14 @@ static LogicalResult verifyAffineMinMaxOp(T op) {
 
 template <typename T>
 static void printAffineMinMaxOp(OpAsmPrinter &p, T op) {
-  p << ' ' << op->getAttr(T::getMapAttrStrName());
+  p << ' ' << op.getMapAttr();
   auto operands = op.getOperands();
   unsigned numDims = op.getMap().getNumDims();
   p << '(' << operands.take_front(numDims) << ')';
 
   if (operands.size() != numDims)
     p << '[' << operands.drop_front(numDims) << ']';
-  p.printOptionalAttrDict(op->getAttrs(),
+  p.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue(),
                           /*elidedAttrs=*/{T::getMapAttrStrName()});
 }
 
@@ -3695,7 +3690,7 @@ static OpFoldResult foldMinMaxOp(T op, ArrayRef<Attribute> operands) {
     // If the map is the same, report that folding did not happen.
     if (foldedMap == op.getMap())
       return {};
-    op->setAttr("map", AffineMapAttr::get(foldedMap));
+    op.setMapAttr(AffineMapAttr::get(foldedMap));
     return op.getResult();
   }
 
@@ -4033,21 +4028,20 @@ ParseResult AffinePrefetchOp::parse(OpAsmParser &parser,
 
 void AffinePrefetchOp::print(OpAsmPrinter &p) {
   p << " " << getMemref() << '[';
-  AffineMapAttr mapAttr =
-      (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName());
+  AffineMapAttr mapAttr = getMapAttr();
   if (mapAttr)
     p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
   p << ']' << ", " << (getIsWrite() ? "write" : "read") << ", " << "locality<"
     << getLocalityHint() << ">, " << (getIsDataCache() ? "data" : "instr");
   p.printOptionalAttrDict(
-      (*this)->getAttrs(),
+      (*this)->getDiscardableAttrDictionary().getValue(),
       /*elidedAttrs=*/{getMapAttrStrName(), getLocalityHintAttrStrName(),
                        getIsDataCacheAttrStrName(), getIsWriteAttrStrName()});
   p << " : " << getMemRefType();
 }
 
 LogicalResult AffinePrefetchOp::verify() {
-  auto mapAttr = (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName());
+  auto mapAttr = getMapAttr();
   if (mapAttr) {
     AffineMap map = mapAttr.getValue();
     if (map.getNumResults() != getMemRefType().getRank())
@@ -4475,7 +4469,7 @@ void AffineParallelOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/getNumResults());
   p.printOptionalAttrDict(
-      (*this)->getAttrs(),
+      (*this)->getDiscardableAttrDictionary().getValue(),
       /*elidedAttrs=*/{AffineParallelOp::getReductionsAttrStrName(),
                        AffineParallelOp::getLowerBoundsMapAttrStrName(),
                        AffineParallelOp::getLowerBoundsGroupsAttrStrName(),
@@ -4818,12 +4812,13 @@ ParseResult AffineVectorLoadOp::parse(OpAsmParser &parser,
 
 void AffineVectorLoadOp::print(OpAsmPrinter &p) {
   p << " " << getMemRef() << '[';
-  if (AffineMapAttr mapAttr =
-          (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
+  if (AffineMapAttr mapAttr = getMapAttr())
     p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
   p << ']';
-  p.printOptionalAttrDict((*this)->getAttrs(),
-                          /*elidedAttrs=*/{getMapAttrStrName()});
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  if (IntegerAttr alignment = getAlignmentAttr())
+    attrs.append(getAlignmentAttrName(), alignment);
+  p.printOptionalAttrDict(attrs, /*elidedAttrs=*/{getMapAttrStrName()});
   p << " : " << getMemRefType() << ", " << getType();
 }
 
@@ -4839,10 +4834,9 @@ static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType,
 
 LogicalResult AffineVectorLoadOp::verify() {
   MemRefType memrefType = getMemRefType();
-  if (failed(verifyMemoryOpIndexing(
-          *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
-          getMapOperands(), memrefType,
-          /*numIndexOperands=*/getNumOperands() - 1)))
+  if (failed(verifyMemoryOpIndexing(*this, getMapAttr(), getMapOperands(),
+                                    memrefType,
+                                    /*numIndexOperands=*/getNumOperands() - 1)))
     return failure();
 
   if (failed(verifyVectorMemoryOp(getOperation(), memrefType, getVectorType())))
@@ -4913,21 +4907,21 @@ ParseResult AffineVectorStoreOp::parse(OpAsmParser &parser,
 void AffineVectorStoreOp::print(OpAsmPrinter &p) {
   p << " " << getValueToStore();
   p << ", " << getMemRef() << '[';
-  if (AffineMapAttr mapAttr =
-          (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()))
+  if (AffineMapAttr mapAttr = getMapAttr())
     p.printAffineMapOfSSAIds(mapAttr, getMapOperands());
   p << ']';
-  p.printOptionalAttrDict((*this)->getAttrs(),
-                          /*elidedAttrs=*/{getMapAttrStrName()});
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  if (IntegerAttr alignment = getAlignmentAttr())
+    attrs.append(getAlignmentAttrName(), alignment);
+  p.printOptionalAttrDict(attrs, /*elidedAttrs=*/{getMapAttrStrName()});
   p << " : " << getMemRefType() << ", " << getValueToStore().getType();
 }
 
 LogicalResult AffineVectorStoreOp::verify() {
   MemRefType memrefType = getMemRefType();
-  if (failed(verifyMemoryOpIndexing(
-          *this, (*this)->getAttrOfType<AffineMapAttr>(getMapAttrStrName()),
-          getMapOperands(), memrefType,
-          /*numIndexOperands=*/getNumOperands() - 2)))
+  if (failed(verifyMemoryOpIndexing(*this, getMapAttr(), getMapOperands(),
+                                    memrefType,
+                                    /*numIndexOperands=*/getNumOperands() - 2)))
     return failure();
 
   if (failed(verifyVectorMemoryOp(*this, memrefType, getVectorType())))
diff --git a/mlir/lib/Dialect/Affine/Transforms/PipelineDataTransfer.cpp b/mlir/lib/Dialect/Affine/Transforms/PipelineDataTransfer.cpp
index 9a1c731b5a97e..97258afd18024 100644
--- a/mlir/lib/Dialect/Affine/Transforms/PipelineDataTransfer.cpp
+++ b/mlir/lib/Dialect/Affine/Transforms/PipelineDataTransfer.cpp
@@ -368,7 +368,7 @@ void PipelineDataTransfer::runOnAffineForOp(AffineForOp forOp) {
     // Tagging operations with shifts for debugging purposes.
     LLVM_DEBUG({
       OpBuilder b(&op);
-      op.setAttr("shift", b.getI64IntegerAttr(shifts[s - 1]));
+      op.setDiscardableAttr("shift", b.getI64IntegerAttr(shifts[s - 1]));
     });
   }
 
diff --git a/mlir/lib/Dialect/Affine/Transforms/SimplifyAffineStructures.cpp b/mlir/lib/Dialect/Affine/Transforms/SimplifyAffineStructures.cpp
index c09afc03c5257..e4509b9ba5463 100644
--- a/mlir/lib/Dialect/Affine/Transforms/SimplifyAffineStructures.cpp
+++ b/mlir/lib/Dialect/Affine/Transforms/SimplifyAffineStructures.cpp
@@ -64,7 +64,10 @@ struct SimplifyAffineStructures
     }
 
     // Simplification was successful, so update the attribute.
-    op->setAttr(name, simplified);
+    if (op->getInherentAttr(name).has_value())
+      op->setInherentAttr(name, simplified);
+    else
+      op->setDiscardableAttr(name, simplified);
   }
 
   IntegerSet simplify(IntegerSet set) { return simplifyIntegerSet(set); }
@@ -99,7 +102,9 @@ void SimplifyAffineStructures::runOnOperation() {
   // fold/apply canonicalization patterns when we have affine dialect ops.
   SmallVector<Operation *> opsToSimplify;
   func.walk([&](Operation *op) {
-    for (auto attr : op->getAttrs()) {
+    NamedAttrList attrs(op->getDiscardableAttrDictionary());
+    op->getName().populateInherentAttrs(op, attrs);
+    for (auto attr : attrs) {
       if (auto mapAttr = dyn_cast<AffineMapAttr>(attr.getValue()))
         simplifyAndUpdateAttribute(op, attr.getName(), mapAttr);
       else if (auto setAttr = dyn_cast<IntegerSetAttr>(attr.getValue()))
diff --git a/mlir/lib/Dialect/Affine/Transforms/SuperVectorize.cpp b/mlir/lib/Dialect/Affine/Transforms/SuperVectorize.cpp
index 6c0cf507de5b8..6c6e8ed979d23 100644
--- a/mlir/lib/Dialect/Affine/Transforms/SuperVectorize.cpp
+++ b/mlir/lib/Dialect/Affine/Transforms/SuperVectorize.cpp
@@ -1504,9 +1504,11 @@ static Operation *widenOp(Operation *op, VectorizationState &state) {
   // name that works both in scalar mode and vector mode.
   // TODO: Is it worth considering an Operation.clone operation which
   // changes the type so we can promote an Operation with less boilerplate?
-  Operation *vecOp =
-      state.builder.create(op->getLoc(), op->getName().getIdentifier(),
-                           vectorOperands, vectorTypes, op->getAttrs());
+  OperationState vecState(op->getLoc(), op->getName(), vectorOperands,
+                          vectorTypes,
+                          op->getDiscardableAttrDictionary().getValue());
+  vecState.propertiesAttr = op->getPropertiesAsAttribute();
+  Operation *vecOp = state.builder.create(vecState);
   state.registerOpVectorReplacement(op, vecOp);
   return vecOp;
 }
diff --git a/mlir/lib/Dialect/Affine/Utils/Utils.cpp b/mlir/lib/Dialect/Affine/Utils/Utils.cpp
index 7043083298615..e2ad3151a934c 100644
--- a/mlir/lib/Dialect/Affine/Utils/Utils.cpp
+++ b/mlir/lib/Dialect/Affine/Utils/Utils.cpp
@@ -312,7 +312,7 @@ static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) {
   operandMap.clear();
   b.setInsertionPointAfter(hoistOverOp);
   // We'll set an attribute to identify this op in a clone of this sub-tree.
-  ifOp->setAttr(idForIfOp, b.getBoolAttr(true));
+  ifOp->setDiscardableAttr(idForIfOp, b.getBoolAttr(true));
   hoistOverOpClone = b.clone(*hoistOverOp, operandMap);
 
   // Promote the 'then' block of the original affine.if in the then version.
@@ -327,7 +327,7 @@ static AffineIfOp hoistAffineIfOp(AffineIfOp ifOp, Operation *hoistOverOp) {
   // Find the clone of the original affine.if op in the else version.
   AffineIfOp ifCloneInElse;
   hoistOverOpClone->walk([&](AffineIfOp ifClone) {
-    if (!ifClone->getAttr(idForIfOp))
+    if (!ifClone->getDiscardableAttr(idForIfOp))
       return WalkResult::advance();
     ifCloneInElse = ifClone;
     return WalkResult::interrupt();
@@ -1298,17 +1298,16 @@ LogicalResult mlir::affine::replaceAllMemRefUsesWith(
 
   // Add attribute for 'newMap', other Attributes do not change.
   auto newMapAttr = AffineMapAttr::get(newMap);
-  for (auto namedAttr : op->getAttrs()) {
-    if (affMapAccInterface &&
-        namedAttr.getName() ==
-            affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef).getName())
-      state.attributes.push_back({namedAttr.getName(), newMapAttr});
-    else
-      state.attributes.push_back(namedAttr);
-  }
+  state.addAttributes(op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
 
   // Create the new operation.
   auto *repOp = builder.create(state);
+  if (affMapAccInterface) {
+    StringAttr mapAttrName =
+        affMapAccInterface.getAffineMapAttrForMemRef(oldMemRef).getName();
+    repOp->setInherentAttr(mapAttrName, newMapAttr);
+  }
   op->replaceAllUsesWith(repOp);
   op->erase();
 
diff --git a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
index 7157e29800db0..bf2ab29caad4b 100644
--- a/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
+++ b/mlir/lib/Dialect/Arith/IR/ArithOps.cpp
@@ -1461,9 +1461,14 @@ struct NarrowExtremum final : OpRewritePattern<TruncOp> {
         return failure();
     }
 
-    rewriter.replaceOpWithNewOp<ExtremumOp>(truncOp, TypeRange{narrowType},
-                                            ValueRange{lhs, rhs},
-                                            extremumOp->getAttrs());
+    SmallVector<NamedAttribute> discardableAttrs(
+        extremumOp->getDiscardableAttrs());
+    OperationState state(truncOp.getLoc(), ExtremumOp::getOperationName(),
+                         ValueRange{lhs, rhs}, TypeRange{narrowType},
+                         discardableAttrs);
+    state.propertiesAttr = extremumOp->getPropertiesAsAttribute();
+    Operation *newExtremum = rewriter.create(state);
+    rewriter.replaceOp(truncOp, newExtremum->getResults());
     return success();
   }
 };
@@ -3061,7 +3066,7 @@ ParseResult SelectOp::parse(OpAsmParser &parser, OperationState &result) {
 
 void arith::SelectOp::print(OpAsmPrinter &p) {
   p << " " << getOperands();
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
   p << " : ";
   if (ShapedType condType = dyn_cast<ShapedType>(getCondition().getType()))
     p << condType << ", ";
diff --git a/mlir/lib/Dialect/Arith/Transforms/EmulateUnsupportedFloats.cpp b/mlir/lib/Dialect/Arith/Transforms/EmulateUnsupportedFloats.cpp
index b6e101952676a..23659d8010f45 100644
--- a/mlir/lib/Dialect/Arith/Transforms/EmulateUnsupportedFloats.cpp
+++ b/mlir/lib/Dialect/Arith/Transforms/EmulateUnsupportedFloats.cpp
@@ -68,9 +68,11 @@ LogicalResult EmulateFloatPattern::matchAndRewrite(
     // If you're seeing it, there's a bug.
     return op->emitOpError("type conversion failed in float emulation");
   }
-  Operation *expandedOp =
-      rewriter.create(loc, op->getName().getIdentifier(), operands, resultTypes,
-                      op->getAttrs(), op->getSuccessors(), /*regions=*/{});
+  OperationState state(loc, op->getName(), operands, resultTypes,
+                       op->getDiscardableAttrDictionary().getValue(),
+                       op->getSuccessors());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  Operation *expandedOp = rewriter.create(state);
   SmallVector<Value> newResults(expandedOp->getResults());
   for (auto [res, oldType, newType] : llvm::zip_equal(
            MutableArrayRef{newResults}, op->getResultTypes(), resultTypes)) {
diff --git a/mlir/lib/Dialect/Arith/Transforms/IntRangeOptimizations.cpp b/mlir/lib/Dialect/Arith/Transforms/IntRangeOptimizations.cpp
index 298c0dc2f3bda..8b66ba6855b68 100644
--- a/mlir/lib/Dialect/Arith/Transforms/IntRangeOptimizations.cpp
+++ b/mlir/lib/Dialect/Arith/Transforms/IntRangeOptimizations.cpp
@@ -542,7 +542,7 @@ struct NarrowLoopBounds final : OpInterfaceRewritePattern<LoopLikeOpInterface> {
   LogicalResult matchAndRewrite(LoopLikeOpInterface loopLike,
                                 PatternRewriter &rewriter) const override {
     // Skip ops where bounds narrowing previously failed.
-    if (loopLike->hasAttr(boundsNarrowingFailedAttr))
+    if (loopLike->hasDiscardableAttr(boundsNarrowingFailedAttr))
       return rewriter.notifyMatchFailure(loopLike,
                                          "bounds narrowing previously failed");
 
@@ -669,7 +669,8 @@ struct NarrowLoopBounds final : OpInterfaceRewritePattern<LoopLikeOpInterface> {
           failed(loopLike.setLoopSteps(newSteps))) {
         // Mark op to prevent future attempts. IR was modified (attribute
         // added), so we must return success() from the pattern.
-        loopLike->setAttr(boundsNarrowingFailedAttr, rewriter.getUnitAttr());
+        loopLike->setDiscardableAttr(boundsNarrowingFailedAttr,
+                                     rewriter.getUnitAttr());
         updateFailed = true;
         return;
       }
diff --git a/mlir/lib/Dialect/Arith/Transforms/UnsignedWhenEquivalent.cpp b/mlir/lib/Dialect/Arith/Transforms/UnsignedWhenEquivalent.cpp
index c9eaa66d6ea49..0b900b42863b5 100644
--- a/mlir/lib/Dialect/Arith/Transforms/UnsignedWhenEquivalent.cpp
+++ b/mlir/lib/Dialect/Arith/Transforms/UnsignedWhenEquivalent.cpp
@@ -92,8 +92,9 @@ struct ConvertOpToUnsigned final : OpRewritePattern<Signed> {
             staticallyNonNegative(this->solver, static_cast<Operation *>(op))))
       return failure();
 
-    rw.replaceOpWithNewOp<Unsigned>(op, op->getResultTypes(), op->getOperands(),
-                                    op->getAttrs());
+    rw.replaceOpWithNewOp<Unsigned>(
+        op, op->getResultTypes(), op->getOperands(),
+        op->getDiscardableAttrDictionary().getValue());
     return success();
   }
 
diff --git a/mlir/lib/Dialect/ArmSME/Transforms/EnableArmStreaming.cpp b/mlir/lib/Dialect/ArmSME/Transforms/EnableArmStreaming.cpp
index c5c90762eaa92..e85e62099bf40 100644
--- a/mlir/lib/Dialect/ArmSME/Transforms/EnableArmStreaming.cpp
+++ b/mlir/lib/Dialect/ArmSME/Transforms/EnableArmStreaming.cpp
@@ -122,7 +122,7 @@ struct EnableArmStreamingPass
         return;
     }
 
-    if (function->getAttr(kEnableArmStreamingIgnoreAttr) ||
+    if (function->getDiscardableAttr(kEnableArmStreamingIgnoreAttr) ||
         streamingMode == ArmStreamingMode::Disabled)
       return;
 
@@ -137,8 +137,8 @@ struct EnableArmStreamingPass
     // streaming-mode (see section B1.1.1, IDGNQM of spec [1]). It may be worth
     // supporting this later.
     if (zaMode != ArmZaMode::Disabled)
-      function->setAttr((Twine("llvm.") + stringifyArmZaMode(zaMode)).str(),
-                        unitAttr);
+      function->setDiscardableAttr(
+          (Twine("llvm.") + stringifyArmZaMode(zaMode)).str(), unitAttr);
   }
 };
 } // namespace
diff --git a/mlir/lib/Dialect/ArmSVE/Transforms/LegalizeVectorStorage.cpp b/mlir/lib/Dialect/ArmSVE/Transforms/LegalizeVectorStorage.cpp
index 3a409ad9ed9d6..c1af8a5c80fcf 100644
--- a/mlir/lib/Dialect/ArmSVE/Transforms/LegalizeVectorStorage.cpp
+++ b/mlir/lib/Dialect/ArmSVE/Transforms/LegalizeVectorStorage.cpp
@@ -83,7 +83,7 @@ void replaceOpWithUnrealizedConversion(PatternRewriter &rewriter, TOp op,
 /// `unrealized_conversion_cast`s added by this pass.
 static FailureOr<Value> getSVELegalizedMemref(Value illegalMemref) {
   Operation *definingOp = illegalMemref.getDefiningOp();
-  if (!definingOp || !definingOp->hasAttr(kSVELegalizerTag))
+  if (!definingOp || !definingOp->hasDiscardableAttr(kSVELegalizerTag))
     return failure();
   auto unrealizedConversion =
       llvm::cast<UnrealizedConversionCastOp>(definingOp);
@@ -464,7 +464,7 @@ struct LegalizeVectorStorage
     ConversionTarget target(getContext());
     target.addDynamicallyLegalOp<UnrealizedConversionCastOp>(
         [](UnrealizedConversionCastOp unrealizedConversion) {
-          return !unrealizedConversion->hasAttr(kSVELegalizerTag);
+          return !unrealizedConversion->hasDiscardableAttr(kSVELegalizerTag);
         });
     // This detects if we failed to completely legalize the IR.
     if (failed(applyPartialConversion(getOperation(), target, {})))
diff --git a/mlir/lib/Dialect/Async/IR/Async.cpp b/mlir/lib/Dialect/Async/IR/Async.cpp
index c11400c061584..4d533e1579480 100644
--- a/mlir/lib/Dialect/Async/IR/Async.cpp
+++ b/mlir/lib/Dialect/Async/IR/Async.cpp
@@ -127,8 +127,9 @@ void ExecuteOp::print(OpAsmPrinter &p) {
 
   // -> (!async.value<!return.type>, ...)
   p.printOptionalArrowTypeList(llvm::drop_begin(getResultTypes()));
-  p.printOptionalAttrDictWithKeyword((*this)->getAttrs(),
-                                     {kOperandSegmentSizesAttr});
+  p.printOptionalAttrDictWithKeyword(
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      {kOperandSegmentSizesAttr});
   p << ' ';
   p.printRegion(getBodyRegion(), /*printEntryBlockArgs=*/false);
 }
@@ -367,7 +368,7 @@ LogicalResult FuncOp::verify() {
 
 LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  auto fnAttr = getCalleeAttr();
   if (!fnAttr)
     return emitOpError("requires a 'callee' symbol reference attribute");
   FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
diff --git a/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp b/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
index f4d37bd0e8f34..401851dd594de 100644
--- a/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
+++ b/mlir/lib/Dialect/Async/Transforms/AsyncToAsyncRuntime.cpp
@@ -244,8 +244,9 @@ static CoroMachinery setupCoroMachinery(func::FuncOp func) {
 
   // The switch-resumed API based coroutine should be marked with
   // presplitcoroutine attribute to mark the function as a coroutine.
-  func->setAttr("llvm.passthrough", builder.getArrayAttr(StringAttr::get(
-                                        ctx, "presplitcoroutine")));
+  func->setDiscardableAttr(
+      "llvm.passthrough",
+      builder.getArrayAttr(StringAttr::get(ctx, "presplitcoroutine")));
 
   CoroMachinery machinery;
   machinery.func = func;
@@ -475,9 +476,11 @@ class AsyncFuncOpLowering : public OpConversionPattern<async::FuncOp> {
     SymbolTable::setSymbolVisibility(newFuncOp,
                                      SymbolTable::getSymbolVisibility(op));
     // Copy over all attributes other than the name.
-    for (const auto &namedAttr : op->getAttrs()) {
+    for (const auto &namedAttr :
+         op->getDiscardableAttrDictionary().getValue()) {
       if (namedAttr.getName() != SymbolTable::getSymbolAttrName())
-        newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+        newFuncOp->setDiscardableAttr(namedAttr.getName(),
+                                      namedAttr.getValue());
     }
 
     rewriter.inlineRegionBefore(op.getBody(), newFuncOp.getBody(),
diff --git a/mlir/lib/Dialect/Bufferization/IR/BufferizationOps.cpp b/mlir/lib/Dialect/Bufferization/IR/BufferizationOps.cpp
index 9b9e631e7cebd..84c44fb89afd8 100644
--- a/mlir/lib/Dialect/Bufferization/IR/BufferizationOps.cpp
+++ b/mlir/lib/Dialect/Bufferization/IR/BufferizationOps.cpp
@@ -328,8 +328,11 @@ void AllocTensorOp::print(OpAsmPrinter &p) {
     p << " copy(" << getCopy() << ")";
   if (getSizeHint())
     p << " size_hint=" << getSizeHint();
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{
-                              AllocTensorOp::getOperandSegmentSizeAttr()});
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  if (Attribute memorySpace = getMemorySpaceAttr())
+    attrs.append(getMemorySpaceAttrName(), memorySpace);
+  p.printOptionalAttrDict(
+      attrs, /*elidedAttrs=*/{AllocTensorOp::getOperandSegmentSizeAttr()});
   p << " : ";
   auto type = getResult().getType();
   if (auto validType = llvm::dyn_cast<::mlir::TensorType>(type))
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.cpp b/mlir/lib/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.cpp
index 8ca968367b026..73d3312021d87 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.cpp
@@ -332,10 +332,12 @@ struct CallOpInterface
     }
 
     // 3. Create the new CallOp.
-    Operation *newCallOp =
+    func::CallOp newCallOp =
         func::CallOp::create(rewriter, callOp.getLoc(), funcOp.getSymName(),
                              resultTypes, newOperands);
-    newCallOp->setAttrs(callOp->getAttrs());
+    newCallOp.setArgAttrsAttr(callOp.getArgAttrsAttr());
+    newCallOp.setResAttrsAttr(callOp.getResAttrsAttr());
+    newCallOp->setDiscardableAttrs(callOp->getDiscardableAttrDictionary());
 
     // 4. Replace the old op with the new op.
     replaceOpWithBufferizedValues(rewriter, callOp, newCallOp->getResults());
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
index 57ef3b88b291c..937a765d49ad8 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotAnalysis.cpp
@@ -89,7 +89,7 @@ constexpr StringLiteral kBbArgAliasSetAttrName = "__bbarg_alias_set_attr__";
 static void setInPlaceOpOperand(OpOperand &opOperand, bool inPlace) {
   Operation *op = opOperand.getOwner();
   SmallVector<StringRef> inPlaceVector;
-  if (auto attr = op->getAttr(kInPlaceOperandsAttrName)) {
+  if (auto attr = op->getDiscardableAttr(kInPlaceOperandsAttrName)) {
     inPlaceVector = SmallVector<StringRef>(llvm::to_vector<4>(
         cast<ArrayAttr>(attr).getAsValueRange<StringAttr>()));
     // The existing attribute may have fewer entries than the current operand
@@ -104,8 +104,8 @@ static void setInPlaceOpOperand(OpOperand &opOperand, bool inPlace) {
         inPlaceVector[opOperand.getOperandNumber()] = "false";
   }
   inPlaceVector[opOperand.getOperandNumber()] = inPlace ? "true" : "false";
-  op->setAttr(kInPlaceOperandsAttrName,
-              OpBuilder(op).getStrArrayAttr(inPlaceVector));
+  op->setDiscardableAttr(kInPlaceOperandsAttrName,
+                         OpBuilder(op).getStrArrayAttr(inPlaceVector));
 }
 
 //===----------------------------------------------------------------------===//
@@ -446,21 +446,23 @@ static void annotateConflict(OpOperand *uRead, OpOperand *uConflictingWrite,
       id +
       "[CONFL-WRITE: " + std::to_string(uConflictingWrite->getOperandNumber()) +
       "]";
-  conflictingWritingOp->setAttr(conflictingWriteAttr, b.getUnitAttr());
+  conflictingWritingOp->setDiscardableAttr(conflictingWriteAttr,
+                                           b.getUnitAttr());
 
   std::string readAttr =
       id + "[READ: " + std::to_string(uRead->getOperandNumber()) + "]";
-  readingOp->setAttr(readAttr, b.getUnitAttr());
+  readingOp->setDiscardableAttr(readAttr, b.getUnitAttr());
 
   if (auto opResult = dyn_cast<OpResult>(definition)) {
     std::string defAttr =
         id + "[DEF: result " + std::to_string(opResult.getResultNumber()) + "]";
-    opResult.getDefiningOp()->setAttr(defAttr, b.getUnitAttr());
+    opResult.getDefiningOp()->setDiscardableAttr(defAttr, b.getUnitAttr());
   } else {
     auto bbArg = cast<BlockArgument>(definition);
     std::string defAttr =
         id + "[DEF: bbArg " + std::to_string(bbArg.getArgNumber()) + "]";
-    bbArg.getOwner()->getParentOp()->setAttr(defAttr, b.getUnitAttr());
+    bbArg.getOwner()->getParentOp()->setDiscardableAttr(defAttr,
+                                                        b.getUnitAttr());
   }
 }
 
@@ -908,12 +910,12 @@ static void annotateNonWritableTensor(Value value) {
   if (auto opResult = dyn_cast<OpResult>(value)) {
     std::string attr = id + "[NOT-WRITABLE: result " +
                        std::to_string(opResult.getResultNumber()) + "]";
-    opResult.getDefiningOp()->setAttr(attr, b.getUnitAttr());
+    opResult.getDefiningOp()->setDiscardableAttr(attr, b.getUnitAttr());
   } else {
     auto bbArg = cast<BlockArgument>(value);
     std::string attr = id + "[NOT-WRITABLE: bbArg " +
                        std::to_string(bbArg.getArgNumber()) + "]";
-    bbArg.getOwner()->getParentOp()->setAttr(attr, b.getUnitAttr());
+    bbArg.getOwner()->getParentOp()->setDiscardableAttr(attr, b.getUnitAttr());
   }
 }
 
@@ -1299,7 +1301,8 @@ static void annotateOpsWithAliasSets(Operation *op,
       }
     }
     if (!opResultAliasSets.empty())
-      op->setAttr(kOpResultAliasSetAttrName, b.getArrayAttr(opResultAliasSets));
+      op->setDiscardableAttr(kOpResultAliasSetAttrName,
+                             b.getArrayAttr(opResultAliasSets));
 
     // Build alias set array for every BlockArgument.
     SmallVector<Attribute> regionAliasSets;
@@ -1319,7 +1322,8 @@ static void annotateOpsWithAliasSets(Operation *op,
       regionAliasSets.push_back(b.getArrayAttr(blockAliasSets));
     }
     if (hasTensorBbArg)
-      op->setAttr(kBbArgAliasSetAttrName, b.getArrayAttr(regionAliasSets));
+      op->setDiscardableAttr(kBbArgAliasSetAttrName,
+                             b.getArrayAttr(regionAliasSets));
   });
 }
 
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
index 4d044bbb74df1..ed8c3f919488c 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
@@ -99,8 +99,8 @@ static void annotateEquivalentReturnBbArg(OpOperand &returnVal,
   Operation *op = returnVal.getOwner();
 
   SmallVector<int64_t> equivBbArgs;
-  if (op->hasAttr(kEquivalentArgsAttr)) {
-    auto attr = cast<ArrayAttr>(op->getAttr(kEquivalentArgsAttr));
+  if (op->hasDiscardableAttr(kEquivalentArgsAttr)) {
+    auto attr = cast<ArrayAttr>(op->getDiscardableAttr(kEquivalentArgsAttr));
     equivBbArgs = llvm::map_to_vector<4>(attr, [](Attribute a) {
       return cast<IntegerAttr>(a).getValue().getSExtValue();
     });
@@ -110,7 +110,7 @@ static void annotateEquivalentReturnBbArg(OpOperand &returnVal,
   equivBbArgs[returnVal.getOperandNumber()] = bbArg.getArgNumber();
 
   OpBuilder b(op->getContext());
-  op->setAttr(kEquivalentArgsAttr, b.getI64ArrayAttr(equivBbArgs));
+  op->setDiscardableAttr(kEquivalentArgsAttr, b.getI64ArrayAttr(equivBbArgs));
 }
 
 /// Store function BlockArguments that are equivalent to/aliasing a returned
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
index 7b8340e363e39..d7aba40313a47 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OwnershipBasedBufferDeallocation.cpp
@@ -674,10 +674,10 @@ Operation *BufferDeallocation::appendOpResults(Operation *op,
   SmallVector<Value> oldResults(op->getResults());
 
   newTypes.append(types.begin(), types.end());
-  auto *newOp = Operation::create(op->getLoc(), op->getName(), newTypes,
-                                  op->getOperands(), op->getAttrDictionary(),
-                                  op->getPropertiesStorage(),
-                                  op->getSuccessors(), op->getNumRegions());
+  auto *newOp = Operation::create(
+      op->getLoc(), op->getName(), newTypes, op->getOperands(),
+      op->getDiscardableAttrDictionary(), op->getPropertiesStorage(),
+      op->getSuccessors(), op->getNumRegions());
   for (auto [oldRegion, newRegion] :
        llvm::zip(op->getRegions(), newOp->getRegions()))
     newRegion.takeBody(oldRegion);
@@ -875,7 +875,7 @@ BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
       // usually forbidden in the input IR (not supported by the buffer
       // deallocation pass). However, if they are under manual deallocation,
       // they can be safely ignored by the buffer deallocation pass.
-      if (!op->hasAttr(BufferizationDialect::kManualDeallocation))
+      if (!op->hasDiscardableAttr(BufferizationDialect::kManualDeallocation))
         return op->emitError(
             "memory free side-effect on MemRef value not supported!");
 
@@ -913,7 +913,7 @@ BufferDeallocation::handleInterface(MemoryEffectOpInterface op) {
         continue;
       }
 
-      if (op->hasAttr(BufferizationDialect::kManualDeallocation)) {
+      if (op->hasDiscardableAttr(BufferizationDialect::kManualDeallocation)) {
         // This allocation will be deallocated manually. Assign an ownership of
         // "false", so that it will never be deallocated by the buffer
         // deallocation pass.
diff --git a/mlir/lib/Dialect/DLTI/DLTI.cpp b/mlir/lib/Dialect/DLTI/DLTI.cpp
index da572f123fdc1..d6adf8f21aded 100644
--- a/mlir/lib/Dialect/DLTI/DLTI.cpp
+++ b/mlir/lib/Dialect/DLTI/DLTI.cpp
@@ -525,7 +525,7 @@ getClosestQueryable(Operation *op) {
 
   // Search op and its ancestors for the first attached DLTIQueryInterface attr.
   do {
-    for (NamedAttribute attr : op->getAttrs())
+    for (NamedAttribute attr : op->getDiscardableAttrDictionary().getValue())
       if ((queryable = dyn_cast<DLTIQueryInterface>(attr.getValue())))
         break;
   } while (!queryable && (op = op->getParentOp()));
diff --git a/mlir/lib/Dialect/DLTI/Traits.cpp b/mlir/lib/Dialect/DLTI/Traits.cpp
index 3f6dd2900a915..400ff40c3d368 100644
--- a/mlir/lib/Dialect/DLTI/Traits.cpp
+++ b/mlir/lib/Dialect/DLTI/Traits.cpp
@@ -24,11 +24,11 @@ LogicalResult mlir::impl::verifyHasDefaultDLTIDataLayoutTrait(Operation *op) {
 }
 
 DataLayoutSpecInterface mlir::impl::getDataLayoutSpec(Operation *op) {
-  return op->getAttrOfType<DataLayoutSpecInterface>(
+  return op->getDiscardableAttrOfType<DataLayoutSpecInterface>(
       DLTIDialect::kDataLayoutAttrName);
 }
 
 TargetSystemSpecInterface mlir::impl::getTargetSystemSpec(Operation *op) {
-  return op->getAttrOfType<TargetSystemSpecAttr>(
+  return op->getDiscardableAttrOfType<TargetSystemSpecAttr>(
       DLTIDialect::kTargetSystemDescAttrName);
 }
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index f40b6e67b30fc..4c1db1ff45306 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -717,7 +717,7 @@ void ForOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/false);
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 LogicalResult ForOp::verifyRegions() {
@@ -740,7 +740,7 @@ LogicalResult ForOp::verifyRegions() {
 
 LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  auto fnAttr = getCalleeAttr();
   if (!fnAttr)
     return emitOpError("requires a 'callee' symbol reference attribute");
   FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
@@ -955,7 +955,7 @@ void IfOp::print(OpAsmPrinter &p) {
                   /*printBlockTerminators=*/printBlockTerminators);
   }
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 /// Given the region at `index`, or the parent operation if `index` is None,
@@ -1747,7 +1747,8 @@ void DoOp::print(OpAsmPrinter &p) {
   p.printRegion(getBodyRegion(), /*printEntryBlockArgs=*/false);
   p << " while ";
   p.printRegion(getConditionRegion());
-  p.printOptionalAttrDictWithKeyword(getOperation()->getAttrs());
+  p.printOptionalAttrDictWithKeyword(
+      getOperation()->getDiscardableAttrDictionary().getValue());
 }
 
 LogicalResult emitc::DoOp::verify() {
diff --git a/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
index 6900e0d989b92..1caf379dc5f37 100644
--- a/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
+++ b/mlir/lib/Dialect/EmitC/Transforms/MLGOAddReflectionMap.cpp
@@ -147,7 +147,8 @@ class MLGOAddReflectionMapClass : public OpRewritePattern<ClassOp> {
     std::vector<std::pair<StringRef, StringRef>> fieldNames;
     classOp.walk([&](FieldOp fieldOp) {
       for (const auto &attr : includedFieldAttrs) {
-        auto arrayAttr = dyn_cast_if_present<ArrayAttr>(fieldOp->getAttr(attr));
+        auto arrayAttr =
+            dyn_cast_if_present<ArrayAttr>(fieldOp->getDiscardableAttr(attr));
 
         if (!arrayAttr)
           continue;
diff --git a/mlir/lib/Dialect/Func/IR/FuncOps.cpp b/mlir/lib/Dialect/Func/IR/FuncOps.cpp
index 54eb8cac737e8..2493243e24b8d 100644
--- a/mlir/lib/Dialect/Func/IR/FuncOps.cpp
+++ b/mlir/lib/Dialect/Func/IR/FuncOps.cpp
@@ -62,7 +62,7 @@ Operation *FuncDialect::materializeConstant(OpBuilder &builder, Attribute value,
 
 LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  auto fnAttr = getCalleeAttr();
   if (!fnAttr)
     return emitOpError("requires a 'callee' symbol reference attribute");
   FuncOp fn = symbolTable.lookupNearestSymbolFrom<FuncOp>(*this, fnAttr);
@@ -196,16 +196,16 @@ void FuncOp::print(OpAsmPrinter &p) {
 void FuncOp::cloneInto(FuncOp dest, IRMapping &mapper) {
   // Add the attributes of this function to dest.
   llvm::MapVector<StringAttr, Attribute> newAttrMap;
-  for (const auto &attr : dest->getAttrs())
+  for (const auto &attr : dest->getDiscardableAttrDictionary().getValue())
     newAttrMap.insert({attr.getName(), attr.getValue()});
-  for (const auto &attr : (*this)->getAttrs())
+  for (const auto &attr : (*this)->getDiscardableAttrDictionary().getValue())
     newAttrMap.insert({attr.getName(), attr.getValue()});
 
   auto newAttrs = llvm::map_to_vector(
       newAttrMap, [](std::pair<StringAttr, Attribute> attrPair) {
         return NamedAttribute(attrPair.first, attrPair.second);
       });
-  dest->setAttrs(DictionaryAttr::get(getContext(), newAttrs));
+  dest->setDiscardableAttrs(DictionaryAttr::get(getContext(), newAttrs));
 
   // Clone the body.
   getBody().cloneInto(&dest.getBody(), mapper);
diff --git a/mlir/lib/Dialect/Func/Transforms/DuplicateFunctionElimination.cpp b/mlir/lib/Dialect/Func/Transforms/DuplicateFunctionElimination.cpp
index c480e469fdcc8..d91474dd8ed91 100644
--- a/mlir/lib/Dialect/Func/Transforms/DuplicateFunctionElimination.cpp
+++ b/mlir/lib/Dialect/Func/Transforms/DuplicateFunctionElimination.cpp
@@ -31,7 +31,8 @@ struct DuplicateFuncOpEquivalenceInfo
     llvm::hash_code hash = {};
     func::FuncOp func = const_cast<func::FuncOp &>(cFunc);
     StringAttr symNameAttrName = func.getSymNameAttrName();
-    for (NamedAttribute namedAttr : cFunc->getAttrs()) {
+    for (NamedAttribute namedAttr :
+         cFunc->getDiscardableAttrDictionary().getValue()) {
       StringAttr attrName = namedAttr.getName();
       if (attrName == symNameAttrName)
         continue;
diff --git a/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp b/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
index 22c5e3c9b86ad..4250f06c6a12d 100644
--- a/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
+++ b/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp
@@ -266,7 +266,7 @@ bool GPUDialect::isKernel(Operation *op) {
   if (auto gpuFunc = dyn_cast<GPUFuncOp>(op))
     return gpuFunc.isKernel();
   return static_cast<bool>(
-      op->getAttrOfType<UnitAttr>(getKernelFuncAttrName()));
+      op->getDiscardableAttrOfType<UnitAttr>(getKernelFuncAttrName()));
 }
 
 namespace {
@@ -695,7 +695,8 @@ void gpu::addAsyncDependency(Operation *op, Value token) {
     return;
   auto attrName =
       OpTrait::AttrSizedOperandSegments<void>::getOperandSegmentSizeAttr();
-  auto sizeAttr = op->template getAttrOfType<DenseI32ArrayAttr>(attrName);
+  auto sizeAttr = dyn_cast_or_null<DenseI32ArrayAttr>(
+      op->getInherentAttr(attrName).value_or(Attribute{}));
 
   // Async dependencies is the only variadic operand.
   if (!sizeAttr)
@@ -703,7 +704,8 @@ void gpu::addAsyncDependency(Operation *op, Value token) {
 
   SmallVector<int32_t, 8> sizes(sizeAttr.asArrayRef());
   ++sizes.front();
-  op->setAttr(attrName, Builder(op->getContext()).getDenseI32ArrayAttr(sizes));
+  op->setInherentAttr(StringAttr::get(op->getContext(), attrName),
+                      Builder(op->getContext()).getDenseI32ArrayAttr(sizes));
 }
 
 //===----------------------------------------------------------------------===//
@@ -961,11 +963,11 @@ void LaunchOp::print(OpAsmPrinter &p) {
   p << ' ';
 
   p.printRegion(getBody(), /*printEntryBlockArgs=*/false);
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{
-                              LaunchOp::getOperandSegmentSizeAttr(),
-                              getWorkgroupAttributionsAttrName(),
-                              getCooperativeAttrName(), moduleAttrName,
-                              functionAttrName});
+  p.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(), /*elidedAttrs=*/{
+          LaunchOp::getOperandSegmentSizeAttr(),
+          getWorkgroupAttributionsAttrName(), getCooperativeAttrName(),
+          moduleAttrName, functionAttrName});
 }
 
 // Parse the size assignment blocks for blocks and threads.  These have the form
@@ -1346,7 +1348,7 @@ LogicalResult LaunchFuncOp::verify() {
   if (!module)
     return emitOpError("expected to belong to a module");
 
-  if (!module->getAttrOfType<UnitAttr>(
+  if (!module->getDiscardableAttrOfType<UnitAttr>(
           GPUDialect::getContainerModuleAttrName()))
     return emitOpError("expected the closest surrounding module to have the '" +
                        GPUDialect::getContainerModuleAttrName() +
@@ -1378,8 +1380,7 @@ LaunchFuncOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
 
   // Ignore launch ops with missing attributes here. The errors will be
   // reported by the verifiers of those ops.
-  if (!launchOp->getAttrOfType<SymbolRefAttr>(
-          LaunchFuncOp::getKernelAttrName(launchOp->getName())))
+  if (!launchOp.getKernelAttr())
     return success();
 
   // Check that `launch_func` refers to a well-formed GPU kernel container.
@@ -1813,7 +1814,9 @@ void GPUFuncOp::print(OpAsmPrinter &p) {
 
 static DictionaryAttr getAttributionAttrs(GPUFuncOp op, unsigned index,
                                           StringAttr attrName) {
-  auto allAttrs = llvm::dyn_cast_or_null<ArrayAttr>(op->getAttr(attrName));
+  ArrayAttr allAttrs = attrName == op.getWorkgroupAttribAttrsAttrName()
+                           ? op.getWorkgroupAttribAttrsAttr()
+                           : op.getPrivateAttribAttrsAttr();
   if (!allAttrs || index >= allAttrs.size())
     return DictionaryAttr();
   return llvm::cast<DictionaryAttr>(allAttrs[index]);
@@ -1830,7 +1833,9 @@ DictionaryAttr GPUFuncOp::getPrivateAttributionAttrs(unsigned index) {
 static void setAttributionAttrs(GPUFuncOp op, unsigned index,
                                 DictionaryAttr value, StringAttr attrName) {
   MLIRContext *ctx = op.getContext();
-  auto allAttrs = llvm::dyn_cast_or_null<ArrayAttr>(op->getAttr(attrName));
+  ArrayAttr allAttrs = attrName == op.getWorkgroupAttribAttrsAttrName()
+                           ? op.getWorkgroupAttribAttrsAttr()
+                           : op.getPrivateAttribAttrsAttr();
   SmallVector<Attribute> elements;
   if (allAttrs)
     elements.append(allAttrs.begin(), allAttrs.end());
@@ -1841,7 +1846,10 @@ static void setAttributionAttrs(GPUFuncOp op, unsigned index,
   else
     elements[index] = value;
   ArrayAttr newValue = ArrayAttr::get(ctx, elements);
-  op->setAttr(attrName, newValue);
+  if (attrName == op.getWorkgroupAttribAttrsAttrName())
+    op.setWorkgroupAttribAttrsAttr(newValue);
+  else
+    op.setPrivateAttribAttrsAttr(newValue);
 }
 
 void GPUFuncOp::setworkgroupAttributionAttrs(unsigned index,
@@ -2027,7 +2035,7 @@ void GPUModuleOp::setTargets(ArrayRef<TargetAttrInterface> targets) {
 }
 
 LogicalResult GPUModuleOp::verify() {
-  auto targets = getOperation()->getAttrOfType<ArrayAttr>("targets");
+  auto targets = getTargetsAttr();
 
   if (!targets)
     return success();
@@ -2453,8 +2461,7 @@ void WarpExecuteOnLane0Op::print(OpAsmPrinter &p) {
   p << "(" << getLaneid() << ")";
 
   SmallVector<StringRef> coreAttr = {getWarpSizeAttrName()};
-  auto warpSizeAttr = getOperation()->getAttr(getWarpSizeAttrName());
-  p << "[" << llvm::cast<IntegerAttr>(warpSizeAttr).getInt() << "]";
+  p << "[" << getWarpSize() << "]";
 
   if (!getArgs().empty())
     p << " args(" << getArgs() << " : " << getArgs().getTypes() << ")";
@@ -2464,7 +2471,8 @@ void WarpExecuteOnLane0Op::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/true,
                 /*printBlockTerminators=*/!getResults().empty());
-  p.printOptionalAttrDict(getOperation()->getAttrs(), coreAttr);
+  p.printOptionalAttrDict(
+      getOperation()->getDiscardableAttrDictionary().getValue(), coreAttr);
 }
 
 ParseResult WarpExecuteOnLane0Op::parse(OpAsmParser &parser,
diff --git a/mlir/lib/Dialect/GPU/IR/InferIntRangeInterfaceImpls.cpp b/mlir/lib/Dialect/GPU/IR/InferIntRangeInterfaceImpls.cpp
index 33695833b1ad8..b15deed706076 100644
--- a/mlir/lib/Dialect/GPU/IR/InferIntRangeInterfaceImpls.cpp
+++ b/mlir/lib/Dialect/GPU/IR/InferIntRangeInterfaceImpls.cpp
@@ -68,7 +68,9 @@ getKnownLaunchAttr(GPUFuncOp func, DimensionKind dims, Dimension dim) {
 static std::optional<uint32_t> getKnownLaunchAttr(FunctionOpInterface func,
                                                   StringRef attrName,
                                                   Dimension dim) {
-  auto bounds = func.getOperation()->getAttrOfType<DenseI32ArrayAttr>(attrName);
+  auto bounds =
+      func.getOperation()->getDiscardableAttrOfType<DenseI32ArrayAttr>(
+          attrName);
   if (!bounds)
     return std::nullopt;
   if (bounds.size() <= static_cast<uint32_t>(dim))
diff --git a/mlir/lib/Dialect/GPU/Transforms/EliminateBarriers.cpp b/mlir/lib/Dialect/GPU/Transforms/EliminateBarriers.cpp
index 21162546fe1b2..05ede7b017031 100644
--- a/mlir/lib/Dialect/GPU/Transforms/EliminateBarriers.cpp
+++ b/mlir/lib/Dialect/GPU/Transforms/EliminateBarriers.cpp
@@ -45,7 +45,7 @@ using namespace mlir::gpu;
 /// Returns `true` if the op is defines the parallel region that is subject to
 /// barrier synchronization.
 static bool isParallelRegionBoundary(Operation *op) {
-  if (op->hasAttr("__parallel_region_boundary_for_test"))
+  if (op->hasDiscardableAttr("__parallel_region_boundary_for_test"))
     return true;
 
   return isa<GPUFuncOp, LaunchOp>(op);
diff --git a/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp b/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp
index 831ac7fb0354e..0ee3d425f66be 100644
--- a/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp
+++ b/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp
@@ -394,8 +394,9 @@ class GpuKernelOutliningPass
     // If any new module was inserted in this module, annotate this module as
     // a container module.
     if (modified)
-      getOperation()->setAttr(gpu::GPUDialect::getContainerModuleAttrName(),
-                              UnitAttr::get(&getContext()));
+      getOperation()->setDiscardableAttr(
+          gpu::GPUDialect::getContainerModuleAttrName(),
+          UnitAttr::get(&getContext()));
   }
 
 private:
@@ -429,7 +430,8 @@ class GpuKernelOutliningPass
     // If a valid data layout spec was provided, attach it to the kernel module.
     // Otherwise, the default data layout will be used.
     if (dataLayoutSpec)
-      kernelModule->setAttr(DLTIDialect::kDataLayoutAttrName, dataLayoutSpec);
+      kernelModule->setDiscardableAttr(DLTIDialect::kDataLayoutAttrName,
+                                       dataLayoutSpec);
 
     SymbolTable symbolTable(kernelModule);
     symbolTable.insert(kernelFunc);
diff --git a/mlir/lib/Dialect/GPU/Transforms/ParallelLoopMapper.cpp b/mlir/lib/Dialect/GPU/Transforms/ParallelLoopMapper.cpp
index 594c7a265667e..07b6dd482f172 100644
--- a/mlir/lib/Dialect/GPU/Transforms/ParallelLoopMapper.cpp
+++ b/mlir/lib/Dialect/GPU/Transforms/ParallelLoopMapper.cpp
@@ -44,8 +44,9 @@ gpu::setMappingAttr(ParallelOp ploopOp,
     specifiedMappings.insert(processor);
   }
   ArrayRef<Attribute> mappingAsAttrs(mapping.data(), mapping.size());
-  ploopOp->setAttr(getMappingAttrName(),
-                   ArrayAttr::get(ploopOp.getContext(), mappingAsAttrs));
+  ploopOp->setDiscardableAttr(
+      getMappingAttrName(),
+      ArrayAttr::get(ploopOp.getContext(), mappingAsAttrs));
   return success();
 }
 
@@ -126,7 +127,7 @@ static void
 mapParallelOp(ParallelOp parallelOp, MappingLevel mappingLevel = MapGrid,
               MappingPolicy mappingPolicy = MappingPolicy::OutermostFirst) {
   // Do not try to add a mapping to already mapped loops or nested loops.
-  if (parallelOp->getAttr(getMappingAttrName()) ||
+  if (parallelOp->getDiscardableAttr(getMappingAttrName()) ||
       ((mappingLevel == MapGrid) && parallelOp->getParentOfType<ParallelOp>()))
     return;
 
diff --git a/mlir/lib/Dialect/IRDL/IRDLLoading.cpp b/mlir/lib/Dialect/IRDL/IRDLLoading.cpp
index 54c7d17a97b50..b9652a3c85cfa 100644
--- a/mlir/lib/Dialect/IRDL/IRDLLoading.cpp
+++ b/mlir/lib/Dialect/IRDL/IRDLLoading.cpp
@@ -54,7 +54,7 @@ LogicalResult getSegmentSizesFromAttr(Operation *op, StringRef elemName,
                                       ArrayRef<Variadicity> variadicities,
                                       SmallVectorImpl<int> &segmentSizes) {
   // Get the segment sizes attribute, and check that it is of the right type.
-  Attribute segmentSizesAttr = op->getAttr(attrName);
+  Attribute segmentSizesAttr = op->getDiscardableAttr(attrName);
   if (!segmentSizesAttr) {
     return op->emitError() << "'" << attrName
                            << "' attribute is expected but not provided";
@@ -209,7 +209,7 @@ static LogicalResult irdlOpVerifier(
 
   /// Сheck that we have all needed attributes passed
   /// and they satisfy the constraints.
-  DictionaryAttr actualAttrs = op->getAttrDictionary();
+  DictionaryAttr actualAttrs = op->getDiscardableAttrDictionary();
 
   for (auto [name, constraint] : attributeConstrs) {
     /// First, check if the attribute actually passed.
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
index b3a7d2e46030d..2bf6bbd74755f 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
@@ -503,8 +503,8 @@ TargetFeaturesAttr TargetFeaturesAttr::featuresAt(Operation *op) {
   auto parentFunction = op->getParentOfType<FunctionOpInterface>();
   if (!parentFunction)
     return {};
-  return parentFunction.getOperation()->getAttrOfType<TargetFeaturesAttr>(
-      getAttributeName());
+  return parentFunction.getOperation()
+      ->getDiscardableAttrOfType<TargetFeaturesAttr>(getAttributeName());
 }
 
 FailureOr<Attribute> TargetFeaturesAttr::query(DataLayoutEntryKey key) {
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 374ad4a9dcb83..1c724d16a19a9 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -50,6 +50,15 @@ using mlir::LLVM::tailcallkind::getMaxEnumValForTailCallKind;
 
 static constexpr const char kElemTypeAttrName[] = "elem_type";
 
+static NamedAttrList getAttrsForPrinting(Operation *op) {
+  NamedAttrList attrs(op->getRawDictionaryAttrs());
+  NamedAttrList inherentAttrs;
+  op->getName().populateInherentAttrs(op, inherentAttrs);
+  for (NamedAttribute attr : inherentAttrs)
+    attrs.set(attr.getName(), attr.getValue());
+  return NamedAttrList(attrs.getDictionary(op->getContext()));
+}
+
 static auto processFMFAttr(ArrayRef<NamedAttribute> attrs) {
   SmallVector<NamedAttribute, 8> filteredAttrs(
       llvm::make_filter_range(attrs, [&](NamedAttribute attr) {
@@ -278,14 +287,15 @@ static std::optional<ParseResult> parseOpBundles(
 void ICmpOp::print(OpAsmPrinter &p) {
   p << " \"" << stringifyICmpPredicate(getPredicate()) << "\" " << getOperand(0)
     << ", " << getOperand(1);
-  p.printOptionalAttrDict((*this)->getAttrs(), {"predicate"});
+  p.printOptionalAttrDict(getAttrsForPrinting(*this).getAttrs(), {"predicate"});
   p << " : " << getLhs().getType();
 }
 
 void FCmpOp::print(OpAsmPrinter &p) {
   p << " \"" << stringifyFCmpPredicate(getPredicate()) << "\" " << getOperand(0)
     << ", " << getOperand(1);
-  p.printOptionalAttrDict(processFMFAttr((*this)->getAttrs()), {"predicate"});
+  p.printOptionalAttrDict(processFMFAttr(getAttrsForPrinting(*this).getAttrs()),
+                          {"predicate"});
   p << " : " << getLhs().getType();
 }
 
@@ -397,13 +407,10 @@ void AllocaOp::print(OpAsmPrinter &p) {
     p << " inalloca";
 
   p << ' ' << getArraySize() << " x " << getElemType();
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary().getValue());
   if (getAlignment() && *getAlignment() != 0)
-    p.printOptionalAttrDict((*this)->getAttrs(),
-                            {kElemTypeAttrName, getInallocaAttrName()});
-  else
-    p.printOptionalAttrDict(
-        (*this)->getAttrs(),
-        {getAlignmentAttrName(), kElemTypeAttrName, getInallocaAttrName()});
+    attrs.append(getAlignmentAttrName(), getAlignmentAttr());
+  p.printOptionalAttrDict(attrs);
   p << " : " << funcTy;
 }
 
@@ -1379,7 +1386,7 @@ void CallOp::print(OpAsmPrinter &p) {
                    getOpBundleOperands().getTypes(), getOpBundleTags());
   }
 
-  p.printOptionalAttrDict(processFMFAttr((*this)->getAttrs()),
+  p.printOptionalAttrDict(processFMFAttr(getAttrsForPrinting(*this).getAttrs()),
                           {getCalleeAttrName(), getTailCallKindAttrName(),
                            getVarCalleeTypeAttrName(), getCConvAttrName(),
                            getOperandSegmentSizesAttrName(),
@@ -1707,7 +1714,7 @@ void InvokeOp::print(OpAsmPrinter &p) {
                    getOpBundleOperands().getTypes(), getOpBundleTags());
   }
 
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict(getAttrsForPrinting(*this).getAttrs(),
                           {getCalleeAttrName(), getOperandSegmentSizeAttr(),
                            getCConvAttrName(), getVarCalleeTypeAttrName(),
                            getOpBundleSizesAttrName(),
@@ -1887,7 +1894,7 @@ void LandingpadOp::print(OpAsmPrinter &p) {
       << value.getType() << ") ";
   }
 
-  p.printOptionalAttrDict((*this)->getAttrs(), {"cleanup"});
+  p.printOptionalAttrDict(getAttrsForPrinting(*this).getAttrs(), {"cleanup"});
 
   p << ": " << getType();
 }
@@ -2480,7 +2487,7 @@ void GlobalOp::print(OpAsmPrinter &p) {
   // Note that the alignment attribute is printed using the
   // default syntax here, even though it is an inherent attribute
   // (as defined in https://mlir.llvm.org/docs/LangRef/#attributes)
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict(getAttrsForPrinting(*this).getAttrs(),
                           {SymbolTable::getSymbolAttrName(),
                            getGlobalTypeAttrName(), getConstantAttrName(),
                            getValueAttrName(), getLinkageAttrName(),
@@ -2841,7 +2848,7 @@ void AliasOp::print(OpAsmPrinter &p) {
   printCommonGlobalAndAlias<AliasOp>(p, *this);
 
   p.printSymbolName(getSymName());
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict(getAttrsForPrinting(*this).getAttrs(),
                           {SymbolTable::getSymbolAttrName(),
                            getAliasTypeAttrName(), getLinkageAttrName(),
                            getUnnamedAddrAttrName(), getTlsModeAttrName(),
@@ -4204,7 +4211,7 @@ void CallIntrinsicOp::print(OpAsmPrinter &p) {
                    getOpBundleOperands().getTypes(), getOpBundleTagsAttr());
   }
 
-  p.printOptionalAttrDict(processFMFAttr((*this)->getAttrs()),
+  p.printOptionalAttrDict(processFMFAttr(getAttrsForPrinting(*this).getAttrs()),
                           {getOperandSegmentSizesAttrName(),
                            getOpBundleSizesAttrName(), getIntrinAttrName(),
                            getOpBundleTagsAttrName(), getArgAttrsAttrName(),
diff --git a/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
index 3cb03e297d03c..2786bdf8c9464 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp
@@ -810,7 +810,9 @@ void MmaOp::print(OpAsmPrinter &p) {
     printMmaOperand(frag);
   }
 
-  p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary().getValue());
+  (*this)->getName().populateInherentAttrs(*this, attrs);
+  p.printOptionalAttrDict(attrs, ignoreAttrNames);
 
   // Print the types of the operands and result.
   p << " : " << "(";
@@ -1275,7 +1277,9 @@ void MmaSpOp::print(OpAsmPrinter &p) {
   for (const auto &frag : frags)
     printMmaSpOperand(frag);
 
-  p.printOptionalAttrDict((*this)->getAttrs(), ignoreAttrNames);
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary().getValue());
+  (*this)->getName().populateInherentAttrs(*this, attrs);
+  p.printOptionalAttrDict(attrs, ignoreAttrNames);
   p << " : ";
   p << "(";
   for (int i = 0; i < 3; ++i) {
@@ -1840,7 +1844,9 @@ void MmaBlockScaleOp::print(OpAsmPrinter &p) {
   printOperandList(p, "scaleB",
                    {getScaleBData(), getByteIdB(), getThreadIdB()});
 
-  p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary().getValue());
+  (*this)->getName().populateInherentAttrs(*this, attrs);
+  p.printOptionalAttrDict(attrs, ignoreAttrNames);
 
   // Print type signature
   p << " : (";
@@ -2071,7 +2077,9 @@ void MmaSpBlockScaleOp::print(OpAsmPrinter &p) {
   printOperandList(p, "scaleB",
                    {getScaleBData(), getByteIdB(), getThreadIdB()});
 
-  p.printOptionalAttrDict(this->getOperation()->getAttrs(), ignoreAttrNames);
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary().getValue());
+  (*this)->getName().populateInherentAttrs(*this, attrs);
+  p.printOptionalAttrDict(attrs, ignoreAttrNames);
 
   // Print type signature
   p << " : (";
@@ -5324,12 +5332,13 @@ LogicalResult Tcgen05StOp::verify() {
 
 /// Infer the result ranges for the NVVM SpecialRangeableRegisterOp that might
 /// have ConstantRangeAttr.
-static void nvvmInferResultRanges(Operation *op, Value result,
+static void nvvmInferResultRanges(std::optional<LLVM::ConstantRangeAttr> range,
+                                  Value result,
                                   ArrayRef<::mlir::ConstantIntRanges> argRanges,
                                   SetIntRangeFn setResultRanges) {
-  if (auto rangeAttr = op->getAttrOfType<LLVM::ConstantRangeAttr>("range")) {
-    setResultRanges(result, {rangeAttr.getLower(), rangeAttr.getUpper(),
-                             rangeAttr.getLower(), rangeAttr.getUpper()});
+  if (range) {
+    setResultRanges(result, {range->getLower(), range->getUpper(),
+                             range->getLower(), range->getUpper()});
   } else {
     setResultRanges(result, IntegerValueRange::getMaxRange(result).getValue());
   }
@@ -6364,8 +6373,8 @@ LogicalResult NVVMDialect::verifyOperationAttribute(Operation *op,
   }
   // blocksareclusters must be used along with reqntid and cluster_dim
   if (attrName == NVVMDialect::getBlocksAreClustersAttrName()) {
-    if (!op->hasAttr(NVVMDialect::getReqntidAttrName()) ||
-        !op->hasAttr(NVVMDialect::getClusterDimAttrName())) {
+    if (!op->hasDiscardableAttr(NVVMDialect::getReqntidAttrName()) ||
+        !op->hasDiscardableAttr(NVVMDialect::getClusterDimAttrName())) {
       return op->emitError()
              << "'" << attrName << "' attribute must be used along with " << "'"
              << NVVMDialect::getReqntidAttrName() << "' and " << "'"
@@ -6384,7 +6393,7 @@ LogicalResult NVVMDialect::verifyRegionArgAttribute(Operation *op,
   if (!funcOp)
     return success();
 
-  bool isKernel = op->hasAttr(NVVMDialect::getKernelFuncAttrName());
+  bool isKernel = op->hasDiscardableAttr(NVVMDialect::getKernelFuncAttrName());
   StringAttr attrName = argAttr.getName();
   if (attrName == NVVM::NVVMDialect::getGridConstantAttrName()) {
     if (!isKernel) {
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
index 657835ae54149..9409141f6e45a 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
@@ -286,7 +286,7 @@ static void createNewAliasScopesFromNoAliasParameter(
         continue;
       ssaCopies.insert(ssaCopy);
 
-      if (!ssaCopy->hasAttr(LLVM::LLVMDialect::getNoAliasAttrName()))
+      if (!ssaCopy->hasDiscardableAttr(LLVM::LLVMDialect::getNoAliasAttrName()))
         continue;
       noAliasParams.insert(ssaCopy);
     }
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/RequestCWrappers.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/RequestCWrappers.cpp
index a5fccbdc368fe..10d203c470f1a 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/RequestCWrappers.cpp
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/RequestCWrappers.cpp
@@ -25,8 +25,9 @@ class RequestCWrappersPass
     : public LLVM::impl::LLVMRequestCWrappersPassBase<RequestCWrappersPass> {
 public:
   void runOnOperation() override {
-    getOperation()->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                            UnitAttr::get(&getContext()));
+    getOperation()->setDiscardableAttr(
+        LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+        UnitAttr::get(&getContext()));
   }
 };
 } // namespace
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgInterfaces.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgInterfaces.cpp
index e3f4988b3ab76..d3a496b6cbb89 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgInterfaces.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgInterfaces.cpp
@@ -898,8 +898,8 @@ mlir::linalg::inferConvolutionDims(LinalgOp linalgOp) {
   return inferConvolutionDimsImpl(
       indexingMaps, linalgOp.getIteratorTypesArray(), inputExprWalker,
       /*allowEmptyConvolvedDims=*/false,
-      linalgOp->getAttrOfType<DenseIntElementsAttr>("strides"),
-      linalgOp->getAttrOfType<DenseIntElementsAttr>("dilations"));
+      linalgOp->getDiscardableAttrOfType<DenseIntElementsAttr>("strides"),
+      linalgOp->getDiscardableAttrOfType<DenseIntElementsAttr>("dilations"));
 }
 
 FailureOr<ConvolutionDimensions>
@@ -1067,8 +1067,8 @@ mlir::linalg::detail::isConvolutionInterfaceImpl(
   if (dimensions) {
     FailureOr<ConvolutionDimensions> res = inferConvolutionDimsImpl(
         indexingMaps, iteratorTypes, inputExprWalker, allowEmptyConvolvedDims,
-        linalgOp->getAttrOfType<DenseIntElementsAttr>("strides"),
-        linalgOp->getAttrOfType<DenseIntElementsAttr>("dilations"));
+        linalgOp->getDiscardableAttrOfType<DenseIntElementsAttr>("strides"),
+        linalgOp->getDiscardableAttrOfType<DenseIntElementsAttr>("dilations"));
     assert(succeeded(res) && "unexpected failure to infer convolution dims");
     *dimensions = *res;
   }
diff --git a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
index 170e1edf8a55d..113552d68fef7 100644
--- a/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
+++ b/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp
@@ -418,7 +418,9 @@ static void printNamedStructuredOpResults(OpAsmPrinter &p,
 static void printNamedStructuredOp(OpAsmPrinter &p, Operation *op,
                                    ValueRange inputs, ValueRange outputs,
                                    ArrayRef<StringRef> elidedAttrs = {}) {
-  p.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
+  NamedAttrList attrs(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, attrs);
+  p.printOptionalAttrDict(attrs, elidedAttrs);
 
   // Printing is shared with generic ops, except for the region and
   // attributes.
@@ -1218,7 +1220,11 @@ void GenericOp::print(OpAsmPrinter &p) {
   llvm::StringSet<> genericAttrNamesSet;
   genericAttrNamesSet.insert_range(genericAttrNames);
   SmallVector<NamedAttribute, 8> genericAttrs;
-  for (auto attr : (*this)->getAttrs()) {
+  for (StringRef attrName : genericAttrNames) {
+    std::optional<Attribute> value = (*this)->getInherentAttr(attrName);
+    if (!value || !*value)
+      continue;
+    NamedAttribute attr{StringAttr::get(getContext(), attrName), *value};
     if (attr.getName() == getIteratorTypesAttrName()) {
       auto iteratorTypes =
           llvm::cast<ArrayAttr>(attr.getValue())
@@ -1251,13 +1257,13 @@ void GenericOp::print(OpAsmPrinter &p) {
   genericAttrNamesSet.insert(genericAttrNames.back());
 
   bool hasExtraAttrs = false;
-  for (NamedAttribute n : (*this)->getAttrs()) {
+  for (NamedAttribute n : (*this)->getDiscardableAttrDictionary().getValue()) {
     if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.getName().strref())))
       break;
   }
   if (hasExtraAttrs) {
     p << " attrs = ";
-    p.printOptionalAttrDict((*this)->getAttrs(),
+    p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
                             /*elidedAttrs=*/genericAttrNames);
   }
 
@@ -1667,8 +1673,10 @@ static bool canUseShortForm(Block *body, bool initFirst = false,
 static void printShortForm(OpAsmPrinter &p, Operation *payloadOp) {
   SmallVector<StringRef> elidedAttrs;
   std::string attrToElide;
+  NamedAttrList attrs(payloadOp->getDiscardableAttrDictionary());
+  payloadOp->getName().populateInherentAttrs(payloadOp, attrs);
   p << " { " << payloadOp->getName().getStringRef();
-  for (const auto &attr : payloadOp->getAttrs()) {
+  for (const auto &attr : attrs) {
     auto fastAttr =
         llvm::dyn_cast<mlir::arith::FastMathFlagsAttr>(attr.getValue());
     if (fastAttr && fastAttr.getValue() == mlir::arith::FastMathFlags::none) {
@@ -1677,7 +1685,7 @@ static void printShortForm(OpAsmPrinter &p, Operation *payloadOp) {
       break;
     }
   }
-  p.printOptionalAttrDict(payloadOp->getAttrs(), elidedAttrs);
+  p.printOptionalAttrDict(attrs, elidedAttrs);
   p << " }";
 }
 
@@ -1690,7 +1698,7 @@ void MapOp::print(OpAsmPrinter &p) {
   }
 
   printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 
   if (!useShortForm) {
     // Print region if the payload op was not detected.
@@ -1900,7 +1908,8 @@ void ReduceOp::print(OpAsmPrinter &p) {
 
   printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
   printDenseI64ArrayAttr(p, getDimensionsAttrName(), getDimensions());
-  p.printOptionalAttrDict((*this)->getAttrs(), {getDimensionsAttrName()});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          {getDimensionsAttrName()});
   if (!useShortForm) {
     // Print region if the payload op was not detected.
     p.increaseIndent();
@@ -2079,7 +2088,8 @@ void TransposeOp::getAsmResultNames(
 void TransposeOp::print(OpAsmPrinter &p) {
   printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
   printDenseI64ArrayAttr(p, getPermutationAttrName(), getPermutation());
-  p.printOptionalAttrDict((*this)->getAttrs(), {getPermutationAttrName()});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          {getPermutationAttrName()});
 }
 
 LogicalResult TransposeOp::verify() {
@@ -2327,7 +2337,8 @@ void BroadcastOp::getAsmResultNames(
 void BroadcastOp::print(OpAsmPrinter &p) {
   printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
   printDenseI64ArrayAttr(p, getDimensionsAttrName(), getDimensions());
-  p.printOptionalAttrDict((*this)->getAttrs(), {getDimensionsAttrName()});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          {getDimensionsAttrName()});
 }
 
 LogicalResult BroadcastOp::verify() {
@@ -2475,7 +2486,7 @@ void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
 void linalg::YieldOp::print(OpAsmPrinter &p) {
   if (getNumOperands() > 0)
     p << ' ' << getOperands();
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
   if (getNumOperands() > 0)
     p << " : " << getOperandTypes();
 }
@@ -2636,7 +2647,9 @@ std::string mlir::linalg::generateLibraryCallName(Operation *op) {
   assert(isa<LinalgOp>(op));
   std::string name(op->getName().getStringRef().str());
   std::string fun = "";
-  for (NamedAttribute kv : op->getAttrs()) {
+  NamedAttrList attrs(op->getDiscardableAttrDictionary());
+  op->getName().populateInherentAttrs(op, attrs);
+  for (NamedAttribute kv : attrs) {
     if (UnaryFnAttr ufa = llvm::dyn_cast<UnaryFnAttr>(kv.getValue())) {
       fun = stringifyEnum(ufa.getValue()).str() + "_";
     } else if (BinaryFnAttr bfa = llvm::dyn_cast<BinaryFnAttr>(kv.getValue())) {
@@ -4211,9 +4224,9 @@ MatmulTransposeAOp::create(OpBuilder &builder, Location location,
 }
 
 bool MatmulTransposeAOp::classof(Operation *op) {
-  return dyn_cast_or_null<linalg::MatmulOp>(op) &&
-         MatmulTransposeAOp::isDefaultIndexingMaps(
-             op->getAttr("indexing_maps"));
+  auto matmulOp = dyn_cast_or_null<linalg::MatmulOp>(op);
+  return matmulOp && MatmulTransposeAOp::isDefaultIndexingMaps(
+                         matmulOp.getIndexingMapsAttr());
 }
 
 SmallVector<AffineMap>
@@ -4305,9 +4318,9 @@ MatmulTransposeBOp::create(OpBuilder &builder, Location location,
 }
 
 bool MatmulTransposeBOp::classof(Operation *op) {
-  return dyn_cast_or_null<linalg::MatmulOp>(op) &&
-         MatmulTransposeBOp::isDefaultIndexingMaps(
-             op->getAttr("indexing_maps"));
+  auto matmulOp = dyn_cast_or_null<linalg::MatmulOp>(op);
+  return matmulOp && MatmulTransposeBOp::isDefaultIndexingMaps(
+                         matmulOp.getIndexingMapsAttr());
 }
 
 SmallVector<AffineMap>
@@ -4398,9 +4411,9 @@ BatchMatmulTransposeAOp::create(OpBuilder &builder, Location location,
 }
 
 bool BatchMatmulTransposeAOp::classof(Operation *op) {
-  return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
-         BatchMatmulTransposeAOp::isDefaultIndexingMaps(
-             op->getAttr("indexing_maps"));
+  auto matmulOp = dyn_cast_or_null<linalg::BatchMatmulOp>(op);
+  return matmulOp && BatchMatmulTransposeAOp::isDefaultIndexingMaps(
+                         matmulOp.getIndexingMapsAttr());
 }
 
 SmallVector<AffineMap>
@@ -4491,9 +4504,9 @@ BatchMatmulTransposeBOp::create(OpBuilder &builder, Location location,
 }
 
 bool BatchMatmulTransposeBOp::classof(Operation *op) {
-  return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
-         BatchMatmulTransposeBOp::isDefaultIndexingMaps(
-             op->getAttr("indexing_maps"));
+  auto matmulOp = dyn_cast_or_null<linalg::BatchMatmulOp>(op);
+  return matmulOp && BatchMatmulTransposeBOp::isDefaultIndexingMaps(
+                         matmulOp.getIndexingMapsAttr());
 }
 
 //===----------------------------------------------------------------------===//
@@ -5547,7 +5560,7 @@ void PackOp::print(OpAsmPrinter &p) {
 
   p << " into " << getDest();
 
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
                           {"static_inner_tiles", "inner_dims_pos",
                            "outer_dims_perm", "operandSegmentSizes"});
 
@@ -6296,7 +6309,7 @@ void UnPackOp::print(OpAsmPrinter &p) {
 
   p << " into " << getDest();
 
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
                           {"static_inner_tiles", "inner_dims_pos",
                            "outer_dims_perm", "operandSegmentSizes"});
 
diff --git a/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp b/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
index 22724e3c31121..697de8446cbd8 100644
--- a/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
+++ b/mlir/lib/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp
@@ -1675,9 +1675,13 @@ transform::MatchOp::apply(transform::TransformRewriter &rewriter,
         if (attr.getName() == getInterfaceAttrName() ||
             attr.getName() == getOpsAttrName())
           continue;
-        if (!op->hasAttr(attr.getName()))
+        std::optional<Attribute> inherent = op->getInherentAttr(attr.getName());
+        Attribute actual = inherent.has_value()
+                               ? *inherent
+                               : op->getDiscardableAttr(attr.getName());
+        if (!actual)
           return;
-        if (op->getAttr(attr.getName()) != attr.getValue())
+        if (actual != attr.getValue())
           return;
       }
     }
@@ -3113,8 +3117,11 @@ void SplitOp::print(OpAsmPrinter &printer) {
   else
     printer << getDynamicChunkSizes();
   printer << " ";
-  printer.printOptionalAttrDict(getOperation()->getAttrs(),
-                                {getStaticChunkSizesAttrName()});
+  NamedAttrList attrs(getOperation()->getDiscardableAttrDictionary());
+  attrs.append(getDimensionAttrName(), getDimensionAttr());
+  if (UnitAttr multiway = getMultiwayAttr())
+    attrs.append(getMultiwayAttrName(), multiway);
+  printer.printOptionalAttrDict(attrs, {getStaticChunkSizesAttrName()});
   printer << " : " << getTarget().getType();
   if (staticChunkSize == ShapedType::kDynamic)
     printer << ", " << getDynamicChunkSizes().getType();
diff --git a/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp b/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
index ca5ee62e5ffeb..3d5b7cd0ee16c 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/BufferizableOpInterfaceImpl.cpp
@@ -75,7 +75,8 @@ static LogicalResult bufferizeDestinationStyleOpInterface(
   // return anything.
   assert(op->getNumRegions() == 1 && "expected that op has 1 region");
   OperationState opState(op->getLoc(), op->getName(), newOperands, TypeRange{},
-                         op->getAttrs());
+                         op->getDiscardableAttrDictionary().getValue());
+  opState.propertiesAttr = op->getPropertiesAsAttribute();
   opState.addRegion();
   Operation *newOp = Operation::create(opState);
   newOp->getRegion(0).getBlocks().splice(newOp->getRegion(0).begin(),
@@ -225,8 +226,11 @@ struct PackOpInterface
       operands.push_back(val);
     llvm::append_range(operands, packOp.getInnerTiles());
 
-    linalg::PackOp::create(rewriter, packOp.getLoc(), TypeRange{}, operands,
-                           op->getAttrs());
+    OperationState opState(packOp.getLoc(), packOp->getName(), operands,
+                           TypeRange{},
+                           op->getDiscardableAttrDictionary().getValue());
+    opState.propertiesAttr = op->getPropertiesAsAttribute();
+    rewriter.create(opState);
     replaceOpWithBufferizedValues(rewriter, op, *destBuffer);
     return success();
   }
@@ -263,8 +267,11 @@ struct UnPackOpInterface
     operands.push_back(*destBuffer);
     llvm::append_range(operands, unPackOp.getInnerTiles());
 
-    linalg::UnPackOp::create(rewriter, unPackOp.getLoc(), TypeRange{}, operands,
-                             op->getAttrs());
+    OperationState opState(unPackOp.getLoc(), unPackOp->getName(), operands,
+                           TypeRange{},
+                           op->getDiscardableAttrDictionary().getValue());
+    opState.propertiesAttr = op->getPropertiesAsAttribute();
+    rewriter.create(opState);
     replaceOpWithBufferizedValues(rewriter, op, *destBuffer);
     return success();
   }
diff --git a/mlir/lib/Dialect/Linalg/Transforms/DropUnitDims.cpp b/mlir/lib/Dialect/Linalg/Transforms/DropUnitDims.cpp
index c3dca148b7f94..9bc6694b38d0d 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/DropUnitDims.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/DropUnitDims.cpp
@@ -986,11 +986,11 @@ struct RankReduceContractionOps : OpRewritePattern<FromOpTy> {
     auto collapsedOp = ToOpTy::create(rewriter, loc, collapsedResultTy,
                                       ValueRange{collapsedLhs, collapsedRhs},
                                       ValueRange{collapsedInit});
-    for (auto attr : contractionOp->getAttrs()) {
+    for (auto attr : contractionOp->getDiscardableAttrDictionary().getValue()) {
       if (attr.getName() == LinalgDialect::kMemoizedIndexingMapsAttrName ||
           attr.getName() == "indexing_maps")
         continue;
-      collapsedOp->setAttr(attr.getName(), attr.getValue());
+      collapsedOp->setDiscardableAttr(attr.getName(), attr.getValue());
     }
 
     auto results = contractionOp.getResults();
diff --git a/mlir/lib/Dialect/Linalg/Transforms/ElementwiseToLinalg.cpp b/mlir/lib/Dialect/Linalg/Transforms/ElementwiseToLinalg.cpp
index aea17d8de4a76..a991f51732218 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/ElementwiseToLinalg.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/ElementwiseToLinalg.cpp
@@ -142,10 +142,11 @@ struct ConvertAnyElementwiseMappableOpOnRankedTensors : public RewritePattern {
               llvm::map_to_vector<6>(op->getResultTypes(), [](Type type) {
                 return cast<TensorType>(type).getElementType();
               });
-          Operation *scalarOp =
-              builder.create(loc, op->getName().getIdentifier(),
-                             regionArgs.take_front(op->getNumOperands()),
-                             resultEltTys, op->getAttrs());
+          OperationState state(
+              loc, op->getName(), regionArgs.take_front(op->getNumOperands()),
+              resultEltTys, op->getDiscardableAttrDictionary().getValue());
+          state.propertiesAttr = op->getPropertiesAsAttribute();
+          Operation *scalarOp = builder.create(state);
           linalg::YieldOp::create(builder, loc, scalarOp->getResults());
         });
     return success();
diff --git a/mlir/lib/Dialect/Linalg/Transforms/EraseUnusedOperandsAndResults.cpp b/mlir/lib/Dialect/Linalg/Transforms/EraseUnusedOperandsAndResults.cpp
index cbd63576619b6..ab73f00e9e1ac 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/EraseUnusedOperandsAndResults.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/EraseUnusedOperandsAndResults.cpp
@@ -269,9 +269,9 @@ mlir::linalg::deduplicateOperandsAndRemoveDeadResults(
       });
   // Copy over unknown attributes. They might be load bearing for some flow.
   ArrayRef<StringRef> odsAttrs = genericOp.getAttributeNames();
-  for (NamedAttribute kv : genericOp->getAttrs())
+  for (NamedAttribute kv : genericOp->getDiscardableAttrDictionary().getValue())
     if (!llvm::is_contained(odsAttrs, kv.getName().getValue()))
-      newOp->setAttr(kv.getName(), kv.getValue());
+      newOp->setDiscardableAttr(kv.getName(), kv.getValue());
 
   // Fix up the payload of the canonicalized operation.
   populateOpPayload(genericOp, newOp, origInsToNewInsPos, origOutsToNewOutsPos,
diff --git a/mlir/lib/Dialect/Linalg/Transforms/FoldIntoElementwise.cpp b/mlir/lib/Dialect/Linalg/Transforms/FoldIntoElementwise.cpp
index 94db259d662d4..bbc6d3968b00c 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/FoldIntoElementwise.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/FoldIntoElementwise.cpp
@@ -93,7 +93,8 @@ struct FoldIntoElementwisePattern : public OpInterfaceRewritePattern<LinalgOp> {
     rewriter.modifyOpInPlace(op, [&] {
       for (auto [index, operand] : llvm::enumerate(op.getDpsInputOperands()))
         op->setOperand(operand->getOperandNumber(), newIns[index]);
-      op->setAttr("indexing_maps", rewriter.getAffineMapArrayAttr(newMaps));
+      op->setInherentAttr(rewriter.getStringAttr("indexing_maps"),
+                          rewriter.getAffineMapArrayAttr(newMaps));
     });
     return success();
   }
diff --git a/mlir/lib/Dialect/Linalg/Transforms/SimplifyDepthwiseConv.cpp b/mlir/lib/Dialect/Linalg/Transforms/SimplifyDepthwiseConv.cpp
index c55a0bf7ef9a3..c6a305d539c10 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/SimplifyDepthwiseConv.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/SimplifyDepthwiseConv.cpp
@@ -93,7 +93,7 @@ matchAndReplaceDepthwiseConv(Operation *operation, Value input, Value kernel,
   if (!newConv)
     return failure();
   for (auto attr : preservedAttrs)
-    newConv->setAttr(attr.getName(), attr.getValue());
+    newConv->setDiscardableAttr(attr.getName(), attr.getValue());
 
   // Expand dimensions back out to
   rewriter.replaceOpWithNewOp<tensor::ExpandShapeOp>(
diff --git a/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp b/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
index b00ab8a7d6ee7..43262a6dcc6ab 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/TilingInterfaceImpl.cpp
@@ -33,6 +33,15 @@
 using namespace mlir;
 using namespace mlir::linalg;
 
+static Operation *createWithProperties(OpBuilder &builder, Operation *op,
+                                       TypeRange resultTypes,
+                                       ValueRange operands) {
+  OperationState state(op->getLoc(), op->getName(), operands, resultTypes,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  return builder.create(state);
+}
+
 //===----------------------------------------------------------------------===//
 // Utility methods for implementation of Tiling Interface for Linalg ops
 //===----------------------------------------------------------------------===//
@@ -1149,8 +1158,8 @@ struct PackOpTiling
     for (auto tile : packOp.getInnerTiles())
       tiledOperands.push_back(tile);
 
-    Operation *tiledPackOp = PackOp::create(
-        b, loc, TypeRange{outSlice.getType()}, tiledOperands, op->getAttrs());
+    Operation *tiledPackOp = createWithProperties(
+        b, op, TypeRange{outSlice.getType()}, tiledOperands);
 
     return TilingResult{
         {tiledPackOp},
@@ -1486,8 +1495,8 @@ struct PackOpTiling
     for (auto tile : packOp.getInnerTiles())
       tiledOperands.push_back(tile);
 
-    Operation *tiledPackOp = PackOp::create(
-        b, loc, TypeRange{outSlice.getType()}, tiledOperands, op->getAttrs());
+    Operation *tiledPackOp = createWithProperties(
+        b, op, TypeRange{outSlice.getType()}, tiledOperands);
 
     return TilingResult{
         {tiledPackOp},
@@ -1727,8 +1736,8 @@ struct UnPackOpTiling
     for (auto tile : unpackOp.getInnerTiles())
       tiledOperands.push_back(tile);
 
-    Operation *tiledUnpackOp = UnPackOp::create(
-        b, loc, TypeRange{sliceDest.getType()}, tiledOperands, op->getAttrs());
+    Operation *tiledUnpackOp = createWithProperties(
+        b, op, TypeRange{sliceDest.getType()}, tiledOperands);
 
     if (isPerfectTilingCase)
       return TilingResult{{tiledUnpackOp},
@@ -1986,9 +1995,8 @@ struct UnPackOpTiling
       tiledOperands.push_back(tile);
 
     // Create tiled unpack op.
-    Operation *tiledUnPackOp =
-        UnPackOp::create(b, loc, TypeRange{extractDestSlice.getType()},
-                         tiledOperands, op->getAttrs());
+    Operation *tiledUnPackOp = createWithProperties(
+        b, op, TypeRange{extractDestSlice.getType()}, tiledOperands);
 
     return TilingResult{{tiledUnPackOp},
                         SmallVector<Value>(tiledUnPackOp->getResults()),
diff --git a/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp b/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
index 21ca3108efcd6..f15f646bbd8b0 100644
--- a/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
+++ b/mlir/lib/Dialect/Linalg/Transforms/Vectorization.cpp
@@ -1442,10 +1442,11 @@ vectorizeOneOp(RewriterBase &rewriter, VectorizationState &state,
             : resultType);
   }
   //   d. Build and return the new op.
-  return VectorizationHookResult{
-      VectorizationHookStatus::NewOp,
-      rewriter.create(op->getLoc(), op->getName().getIdentifier(), vecOperands,
-                      resultTypes, op->getAttrs())};
+  OperationState opState(op->getLoc(), op->getName(), vecOperands, resultTypes,
+                         op->getDiscardableAttrDictionary().getValue());
+  opState.propertiesAttr = op->getPropertiesAsAttribute();
+  return VectorizationHookResult{VectorizationHookStatus::NewOp,
+                                 rewriter.create(opState)};
 }
 
 /// Generic vectorization function that rewrites the body of a `linalgOp` into
@@ -2733,8 +2734,8 @@ struct PadOpVectorizationWithTransferReadPattern
 
     rewriter.modifyOpInPlace(xferOp, [&]() {
       SmallVector<bool> inBounds(xferOp.getVectorType().getRank(), false);
-      xferOp->setAttr(xferOp.getInBoundsAttrName(),
-                      rewriter.getBoolArrayAttr(inBounds));
+      xferOp->setInherentAttr(xferOp.getInBoundsAttrName(),
+                              rewriter.getBoolArrayAttr(inBounds));
       xferOp.getBaseMutable().assign(padOp.getSource());
       xferOp.getPaddingMutable().assign(padValue);
     });
@@ -3794,8 +3795,9 @@ struct Conv1DGenerator
 
       SmallVector<bool> inBounds(maskShape.size(), true);
       auto xferOp = cast<VectorTransferOpInterface>(opToMask);
-      xferOp->setAttr(xferOp.getInBoundsAttrName(),
-                      rewriter.getBoolArrayAttr(inBounds));
+      xferOp->setInherentAttr(
+          rewriter.getStringAttr(xferOp.getInBoundsAttrName()),
+          rewriter.getBoolArrayAttr(inBounds));
 
       SmallVector<OpFoldResult> mixedDims = vector::getMixedSizesXfer(
           cast<LinalgOp>(op).hasPureTensorSemantics(), opToMask, rewriter);
diff --git a/mlir/lib/Dialect/Math/Transforms/PolynomialApproximation.cpp b/mlir/lib/Dialect/Math/Transforms/PolynomialApproximation.cpp
index 9b367e0f84cb8..9b234ea79df02 100644
--- a/mlir/lib/Dialect/Math/Transforms/PolynomialApproximation.cpp
+++ b/mlir/lib/Dialect/Math/Transforms/PolynomialApproximation.cpp
@@ -345,8 +345,8 @@ LogicalResult insertCasts(Operation *op, PatternRewriter &rewriter) {
   SmallVector<Value> operands;
   for (auto operand : op->getOperands())
     operands.push_back(arith::ExtFOp::create(rewriter, loc, newType, operand));
-  auto result =
-      T::create(rewriter, loc, TypeRange{newType}, operands, op->getAttrs());
+  auto result = T::create(rewriter, loc, TypeRange{newType}, operands,
+                          op->getDiscardableAttrDictionary().getValue());
   rewriter.replaceOpWithNewOp<arith::TruncFOp>(op, origType, result);
   return success();
 }
diff --git a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
index 0ef57172e380c..0a93254838082 100644
--- a/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
+++ b/mlir/lib/Dialect/MemRef/IR/MemRefOps.cpp
@@ -162,7 +162,8 @@ bubbleDownCastsPassthroughOpImpl(ConcreteOpTy op, OpBuilder &builder,
   // Create the new op and results.
   auto newOp = ConcreteOpTy::create(
       builder, op.getLoc(), TypeRange(resTy), operands, op.getProperties(),
-      llvm::to_vector_of<NamedAttribute>(op->getDiscardableAttrs()));
+      llvm::to_vector_of<NamedAttribute>(
+          op->getDiscardableAttrDictionary().getValue()));
 
   // Insert a memory-space cast to the original memory space of the op.
   MemorySpaceCastOpInterface result = castOp.cloneMemorySpaceCastOp(
@@ -377,7 +378,7 @@ void AllocaScopeOp::print(OpAsmPrinter &p) {
   p.printRegion(getBodyRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/printBlockTerminators);
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 ParseResult AllocaScopeOp::parse(OpAsmParser &parser, OperationState &result) {
@@ -1262,7 +1263,7 @@ void DmaStartOp::print(OpAsmPrinter &p) {
   if (isStrided())
     p << ", " << getStride() << ", " << getNumElementsPerStride();
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
   p << " : " << getSrcMemRef().getType() << ", " << getDstMemRef().getType()
     << ", " << getTagMemRef().getType();
 }
@@ -1652,7 +1653,7 @@ void GenericAtomicRMWOp::print(OpAsmPrinter &p) {
   p << ' ' << getMemref() << "[" << getIndices()
     << "] : " << getMemref().getType() << ' ';
   p.printRegion(getRegion());
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 TypedValue<MemRefType> GenericAtomicRMWOp::getAccessedMemref() {
@@ -1926,7 +1927,7 @@ void PrefetchOp::print(OpAsmPrinter &p) {
   p << ", locality<" << getLocalityHint();
   p << ">, " << (getIsDataCache() ? "data" : "instr");
   p.printOptionalAttrDict(
-      (*this)->getAttrs(),
+      (*this)->getDiscardableAttrDictionary().getValue(),
       /*elidedAttrs=*/{"localityHint", "isWrite", "isDataCache"});
   p << " : " << getMemRefType();
 }
@@ -3820,7 +3821,8 @@ void TransposeOp::build(OpBuilder &b, OperationState &result, Value in,
 // transpose $in $permutation attr-dict : type($in) `to` type(results)
 void TransposeOp::print(OpAsmPrinter &p) {
   p << " " << getIn() << " " << getPermutation();
-  p.printOptionalAttrDict((*this)->getAttrs(), {getPermutationAttrStrName()});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          {getPermutationAttrStrName()});
   p << " : " << getIn().getType() << " to " << getType();
 }
 
diff --git a/mlir/lib/Dialect/MemRef/Transforms/AllocationOpInterfaceImpl.cpp b/mlir/lib/Dialect/MemRef/Transforms/AllocationOpInterfaceImpl.cpp
index 75cc39e61656a..b53e3bdf1cf69 100644
--- a/mlir/lib/Dialect/MemRef/Transforms/AllocationOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/MemRef/Transforms/AllocationOpInterfaceImpl.cpp
@@ -34,11 +34,12 @@ struct DefaultAllocationInterface
   }
   static ::std::optional<::mlir::Operation *>
   buildPromotedAlloc(OpBuilder &builder, Value alloc) {
-    Operation *definingOp = alloc.getDefiningOp();
-    return memref::AllocaOp::create(
-        builder, definingOp->getLoc(),
-        cast<MemRefType>(definingOp->getResultTypes()[0]),
-        definingOp->getOperands(), definingOp->getAttrs());
+    auto allocOp = cast<memref::AllocOp>(alloc.getDefiningOp());
+    memref::AllocaOp allocaOp = memref::AllocaOp::create(
+        builder, allocOp.getLoc(), allocOp.getType(), allocOp.getDynamicSizes(),
+        allocOp.getSymbolOperands(), allocOp.getAlignmentAttr());
+    allocaOp->setDiscardableAttrs(allocOp->getDiscardableAttrDictionary());
+    return allocaOp.getOperation();
   }
 };
 
diff --git a/mlir/lib/Dialect/MemRef/Transforms/MultiBuffer.cpp b/mlir/lib/Dialect/MemRef/Transforms/MultiBuffer.cpp
index ce45f847ccaed..c9e5f496b91b0 100644
--- a/mlir/lib/Dialect/MemRef/Transforms/MultiBuffer.cpp
+++ b/mlir/lib/Dialect/MemRef/Transforms/MultiBuffer.cpp
@@ -224,8 +224,9 @@ mlir::memref::multiBuffer(RewriterBase &rewriter, memref::AllocOp allocOp,
   Location loc = allocOp->getLoc();
   OpBuilder::InsertionGuard g(rewriter);
   rewriter.setInsertionPoint(allocOp);
-  auto mbAlloc = memref::AllocOp::create(rewriter, loc, mbMemRefType,
-                                         ValueRange{}, allocOp->getAttrs());
+  auto mbAlloc = memref::AllocOp::create(
+      rewriter, loc, mbMemRefType, ValueRange{},
+      allocOp->getDiscardableAttrDictionary().getValue());
   LLVM_DEBUG(DBGS() << "--multi-buffered alloc: " << mbAlloc << "\n");
 
   // 3. Within the loop, build the modular leading index (i.e. each loop
diff --git a/mlir/lib/Dialect/MemRef/Transforms/NormalizeMemRefs.cpp b/mlir/lib/Dialect/MemRef/Transforms/NormalizeMemRefs.cpp
index d5e0dace3c775..5e3ac56815d0b 100644
--- a/mlir/lib/Dialect/MemRef/Transforms/NormalizeMemRefs.cpp
+++ b/mlir/lib/Dialect/MemRef/Transforms/NormalizeMemRefs.cpp
@@ -514,7 +514,8 @@ Operation *NormalizeMemRefs::createOpResultsNormalized(func::FuncOp funcOp,
   // the operation results.
   OperationState result(oldOp->getLoc(), oldOp->getName());
   result.addOperands(oldOp->getOperands());
-  result.addAttributes(oldOp->getAttrs());
+  result.addAttributes(oldOp->getDiscardableAttrDictionary().getValue());
+  result.propertiesAttr = oldOp->getPropertiesAsAttribute();
   // Add normalized MemRefType to the OperationState.
   SmallVector<Type, 4> resultTypes;
   OpBuilder b(funcOp);
diff --git a/mlir/lib/Dialect/NVGPU/IR/NVGPUDialect.cpp b/mlir/lib/Dialect/NVGPU/IR/NVGPUDialect.cpp
index 642e588488641..2649564a7828e 100644
--- a/mlir/lib/Dialect/NVGPU/IR/NVGPUDialect.cpp
+++ b/mlir/lib/Dialect/NVGPU/IR/NVGPUDialect.cpp
@@ -272,7 +272,7 @@ LogicalResult MmaSyncOp::verify() {
 
   return verifyMmaSyncOp(this->getOperation(), getMatrixA(), getMatrixB(),
                          getMatrixC(), getMmaShapeAsArray(),
-                         getOperation()->hasAttr(getTf32EnabledAttrName()));
+                         getTf32Enabled().value_or(false));
 }
 
 //===----------------------------------------------------------------------===//
@@ -296,8 +296,7 @@ LogicalResult MmaSparseSyncOp::verify() {
 
   return verifyMmaSyncOp(this->getOperation(), getMatrixA(), getMatrixB(),
                          getMatrixC(), getMmaShapeAsArray(),
-                         getOperation()->hasAttr(getTf32EnabledAttrName()),
-                         true);
+                         getTf32Enabled().value_or(false), true);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/NVGPU/Transforms/MmaSyncTF32Transform.cpp b/mlir/lib/Dialect/NVGPU/Transforms/MmaSyncTF32Transform.cpp
index 7f626a625aaea..118796e23a69a 100644
--- a/mlir/lib/Dialect/NVGPU/Transforms/MmaSyncTF32Transform.cpp
+++ b/mlir/lib/Dialect/NVGPU/Transforms/MmaSyncTF32Transform.cpp
@@ -35,7 +35,7 @@ struct MmaSyncF32ToTF32Pattern : public OpRewritePattern<nvgpu::MmaSyncOp> {
                                 PatternRewriter &rewriter) const override {
     Location location = op->getLoc();
 
-    if (op->hasAttr(op.getTf32EnabledAttrName()) ||
+    if (op.getTf32Enabled().value_or(false) ||
         !cast<VectorType>(op.getMatrixA().getType()).getElementType().isF32())
       return failure();
 
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index cdf34234f06df..69e60410435b6 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -49,7 +49,7 @@ static void attachVarNameAttr(Operation *op, OpBuilder &builder,
                               StringRef varName) {
   if (!varName.empty()) {
     auto varNameAttr = acc::VarNameAttr::get(builder.getContext(), varName);
-    op->setAttr(acc::getVarNameAttrName(), varNameAttr);
+    op->setDiscardableAttr(acc::getVarNameAttrName(), varNameAttr);
   }
 }
 
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
index fbffcb92267d3..eb678fd195b60 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACCCG.cpp
@@ -82,10 +82,11 @@ static void updateComputeRegionInputOperandSegments(ComputeRegionOp op,
                                                     PatternRewriter &rewriter,
                                                     size_t numInput) {
   const size_t numLaunch = op.getLaunchArgs().size();
-  op->setAttr(ComputeRegionOp::getOperandSegmentSizeAttr(),
-              rewriter.getDenseI32ArrayAttr({static_cast<int32_t>(numLaunch),
-                                             static_cast<int32_t>(numInput),
-                                             op.getStream() ? 1 : 0}));
+  op->setInherentAttr(
+      rewriter.getStringAttr(ComputeRegionOp::getOperandSegmentSizeAttr()),
+      rewriter.getDenseI32ArrayAttr({static_cast<int32_t>(numLaunch),
+                                     static_cast<int32_t>(numInput),
+                                     op.getStream() ? 1 : 0}));
 }
 
 struct ComputeRegionRemoveDuplicateArgs
@@ -734,8 +735,13 @@ void ComputeRegionOp::print(OpAsmPrinter &p) {
   p.printOptionalArrowTypeList(getResultTypes());
   p << " ";
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false);
-  p.printOptionalAttrDict((*this)->getAttrs(),
-                          /*elidedAttrs=*/getOperandSegmentSizeAttr());
+  NamedAttrList attrs((*this)->getDiscardableAttrDictionary());
+  attrs.set(getOriginAttrName(), getOriginAttr());
+  if (FlatSymbolRefAttr attr = getKernelFuncNameAttr())
+    attrs.set(getKernelFuncNameAttrName(), attr);
+  if (FlatSymbolRefAttr attr = getKernelModuleNameAttr())
+    attrs.set(getKernelModuleNameAttrName(), attr);
+  p.printOptionalAttrDict(attrs, /*elidedAttrs=*/getOperandSegmentSizeAttr());
 }
 
 ParseResult ComputeRegionOp::parse(OpAsmParser &parser,
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCBindRoutine.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCBindRoutine.cpp
index 81b3cfb627952..88b02f4074ff2 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCBindRoutine.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCBindRoutine.cpp
@@ -55,12 +55,12 @@ namespace {
 static RoutineOp getFirstAccRoutineOp(FunctionOpInterface funcOp,
                                       const SymbolTable &symTab) {
   if (isSpecializedAccRoutine(funcOp)) {
-    auto attr = funcOp->getAttrOfType<SpecializedRoutineAttr>(
+    auto attr = funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
         getSpecializedRoutineAttrName());
     return symTab.lookup<RoutineOp>(attr.getRoutine().getLeafReference());
   }
-  auto routineInfo =
-      funcOp->getAttrOfType<RoutineInfoAttr>(getRoutineInfoAttrName());
+  auto routineInfo = funcOp->getDiscardableAttrOfType<RoutineInfoAttr>(
+      getRoutineInfoAttrName());
   assert(routineInfo && "expected acc.routine_info for acc routine function");
   auto accRoutines = routineInfo.getAccRoutines();
   assert(!accRoutines.empty() && "expected at least one acc routine");
@@ -111,7 +111,7 @@ class ACCBindRoutine : public acc::impl::ACCBindRoutineBase<ACCBindRoutine> {
       if (!(isAccRoutine(callee) || isSpecializedAccRoutine(callee)))
         return;
 
-      if (auto routineInfo = callee->getAttrOfType<RoutineInfoAttr>(
+      if (auto routineInfo = callee->getDiscardableAttrOfType<RoutineInfoAttr>(
               getRoutineInfoAttrName())) {
         if (routineInfo.getAccRoutines().size() > 1) {
           (void)accSupport.emitNYI(callOp.getLoc(), "multiple `acc routine`s");
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
index b88c4fe33097a..7a762a2f189a2 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCCGToGPU.cpp
@@ -193,12 +193,14 @@ getAccRoutineParDim(RoutineOp routineOp, MLIRContext *ctx,
 static RoutineOp getRoutineOpForAccRoutineFunction(FunctionOpInterface funcOp,
                                                    const SymbolTable &symTab) {
   if (isSpecializedAccRoutine(funcOp)) {
-    SpecializedRoutineAttr attr = funcOp->getAttrOfType<SpecializedRoutineAttr>(
-        getSpecializedRoutineAttrName());
+    SpecializedRoutineAttr attr =
+        funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
+            getSpecializedRoutineAttrName());
     return symTab.lookup<RoutineOp>(attr.getRoutine().getLeafReference());
   }
   RoutineInfoAttr routineInfo =
-      funcOp->getAttrOfType<RoutineInfoAttr>(getRoutineInfoAttrName());
+      funcOp->getDiscardableAttrOfType<RoutineInfoAttr>(
+          getRoutineInfoAttrName());
   if (!routineInfo || routineInfo.getAccRoutines().empty())
     return nullptr;
   return symTab.lookup<RoutineOp>(
@@ -210,7 +212,7 @@ static GPUParallelDimAttr
 getSpecializedRoutineDim(FunctionOpInterface funcOp,
                          const ACCToGPUMappingPolicy &policy) {
   SpecializedRoutineAttr specAttr =
-      funcOp->getAttrOfType<SpecializedRoutineAttr>(
+      funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
           getSpecializedRoutineAttrName());
   assert(specAttr && "expected specialized routine attribute");
   return policy.map(funcOp->getContext(), specAttr.getLevel().getValue());
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
index a275a794c9fd5..a5fbed1d36fd7 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
@@ -94,7 +94,7 @@ static bool isOpInSerialRegion(Operation *op) {
     return computeRegion.isEffectivelySerial();
   if (auto funcOp = op->getParentOfType<FunctionOpInterface>()) {
     if (isSpecializedAccRoutine(funcOp)) {
-      auto attr = funcOp->getAttrOfType<SpecializedRoutineAttr>(
+      auto attr = funcOp->getDiscardableAttrOfType<SpecializedRoutineAttr>(
           getSpecializedRoutineAttrName());
       if (attr && attr.getLevel().getValue() == ParLevel::seq)
         return true;
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
index fc77accc415d7..f8efb98fb6771 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCDeclareGPUModuleInsertion.cpp
@@ -72,14 +72,16 @@ namespace {
 
 static bool hasAccDeclareGlobals(ModuleOp mod) {
   for (Operation &op : mod.getBody()->getOperations())
-    if (op.getAttr(acc::getDeclareAttrName()))
+    if (op.getDiscardableAttr(acc::getDeclareAttrName()))
       return true;
   return false;
 }
 
 static void makeDeviceGlobalDeclaration(Operation &globalOp) {
-  globalOp.removeAttr("initVal");
-  globalOp.removeAttr("linkName");
+  globalOp.setInherentAttr(StringAttr::get(globalOp.getContext(), "initVal"),
+                           {});
+  globalOp.setInherentAttr(StringAttr::get(globalOp.getContext(), "linkName"),
+                           {});
   for (Region &region : globalOp.getRegions()) {
     region.dropAllReferences();
     region.getBlocks().clear();
@@ -98,7 +100,7 @@ class ACCDeclareGPUModuleInsertion
     SymbolTable gpuSymTable(gpuMod);
 
     for (Operation &globalOp : mod.getBody()->getOperations()) {
-      if (!globalOp.getAttr(acc::getDeclareAttrName()))
+      if (!globalOp.getDiscardableAttr(acc::getDeclareAttrName()))
         continue;
 
       auto symOp = dyn_cast<SymbolOpInterface>(&globalOp);
@@ -107,8 +109,8 @@ class ACCDeclareGPUModuleInsertion
 
       StringAttr name = symOp.getNameAttr();
       Operation *deviceGlobal = globalOp.clone();
-      auto declareAttr =
-          globalOp.getAttrOfType<acc::DeclareAttr>(acc::getDeclareAttrName());
+      auto declareAttr = globalOp.getDiscardableAttrOfType<acc::DeclareAttr>(
+          acc::getDeclareAttrName());
       auto globalVar = dyn_cast<acc::GlobalVariableOpInterface>(&globalOp);
       bool makeUnifiedDeclaration =
           cudaUnified &&
@@ -152,10 +154,11 @@ class ACCDeclareGPUModuleInsertion
         }
         // Propagate acc.declare onto the GPU copy if it was cloned before the
         // host global was marked.
-        if (!existing->getAttr(acc::getDeclareAttrName()))
+        if (!existing->getDiscardableAttr(acc::getDeclareAttrName()))
           if (Attribute declareAttr =
-                  globalOp.getAttr(acc::getDeclareAttrName()))
-            existing->setAttr(acc::getDeclareAttrName(), declareAttr);
+                  globalOp.getDiscardableAttr(acc::getDeclareAttrName()))
+            existing->setDiscardableAttr(acc::getDeclareAttrName(),
+                                         declareAttr);
         deviceGlobal->destroy();
         continue;
       }
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksData.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksData.cpp
index eda3994d2ab09..fde2def1794ad 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksData.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksData.cpp
@@ -200,7 +200,7 @@ static void emitDataMappingRemarks(ValueRange mappingOperands,
         [&]() {
           std::string message = "Generating ";
           message += directivePrefix.str();
-          if (op->getAttr(acc::getFromDefaultClauseAttrName()))
+          if (op->getDiscardableAttr(acc::getFromDefaultClauseAttrName()))
             message += "default ";
           else if (acc::getImplicitFlag(op))
             message += "implicit ";
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
index 29e71fe1e9097..cb082a0708052 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
@@ -96,7 +96,7 @@ static void emitLoopMappingRemark(acc::ComputeRegionOp computeRegion,
                                   const acc::ACCToGPUMappingPolicy &policy,
                                   llvm::StringRef gpuDimSeparator) {
   acc::GPUParallelDimsAttr parDimsAttr =
-      loopOp->getAttrOfType<acc::GPUParallelDimsAttr>(
+      loopOp->getDiscardableAttrOfType<acc::GPUParallelDimsAttr>(
           acc::GPUParallelDimsAttr::name);
 
   SmallVector<acc::GPUParallelDimAttr, 1> seqParDims;
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
index 628454905b488..8d7d5d3282327 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitData.cpp
@@ -530,8 +530,8 @@ Operation *ACCImplicitData::generateDataClauseOpForCandidate(
       newDataOp = acc::PresentOp::create(builder, loc, var,
                                          /*structured=*/true, /*implicit=*/true,
                                          accSupport.getVariableName(var));
-      newDataOp->setAttr(acc::getFromDefaultClauseAttrName(),
-                         builder.getUnitAttr());
+      newDataOp->setDiscardableAttr(acc::getFromDefaultClauseAttrName(),
+                                    builder.getUnitAttr());
     } else {
       auto copyinOp =
           acc::CopyinOp::create(builder, loc, var,
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitDeclare.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitDeclare.cpp
index 4d77353c20f56..28b8e064d7768 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitDeclare.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitDeclare.cpp
@@ -331,9 +331,10 @@ static void collectGlobalsFromDeviceRegion(Region &region,
 // Adds the declare attribute to the operation `op`.
 static void addDeclareAttr(MLIRContext *context, Operation *op,
                            acc::DataClause clause) {
-  op->setAttr(acc::getDeclareAttrName(),
-              acc::DeclareAttr::get(context,
-                                    acc::DataClauseAttr::get(context, clause)));
+  op->setDiscardableAttr(
+      acc::getDeclareAttrName(),
+      acc::DeclareAttr::get(context,
+                            acc::DataClauseAttr::get(context, clause)));
 }
 
 // This pass applies implicit declare actions for globals referenced in
@@ -381,7 +382,7 @@ class ACCImplicitDeclare
                                              symTab);
           })
           .Case([&](acc::GlobalVariableOpInterface globalVarOp) {
-            if (globalVarOp->getAttr(acc::getDeclareAttrName()))
+            if (globalVarOp->getDiscardableAttr(acc::getDeclareAttrName()))
               if (Region *initRegion = globalVarOp.getInitRegion())
                 collectGlobalsFromDeviceRegion(*initRegion, globalsToAccDeclare,
                                                accSupport, symTab);
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
index e2ea3f1aed3b5..1ac1d023e1ff4 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
@@ -119,10 +119,10 @@ class ACCImplicitRoutine
         /* gangDimDeviceType=*/nullptr);
 
     // Assert that the callee does not already have routine info attribute
-    assert(!callee->hasAttr(acc::getRoutineInfoAttrName()) &&
+    assert(!callee->hasDiscardableAttr(acc::getRoutineInfoAttrName()) &&
            "function is already associated with a routine");
 
-    callee->setAttr(
+    callee->setDiscardableAttr(
         acc::getRoutineInfoAttrName(),
         mlir::acc::RoutineInfoAttr::get(
             builder.getContext(),
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
index 70aacd251c329..e1775ce3706e0 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCRecipeMaterialization.cpp
@@ -88,12 +88,13 @@ static void saveVarName(StringRef name, Value dst) {
   if (name.empty())
     return;
   if (Operation *dstOp = dst.getDefiningOp()) {
-    if (dstOp->getAttrOfType<acc::VarNameAttr>(acc::getVarNameAttrName()))
+    if (dstOp->getDiscardableAttrOfType<acc::VarNameAttr>(
+            acc::getVarNameAttrName()))
       return;
     if (isa<ACC_DATA_ENTRY_OPS>(dstOp))
       return;
-    dstOp->setAttr(acc::getVarNameAttrName(),
-                   acc::VarNameAttr::get(dstOp->getContext(), name));
+    dstOp->setDiscardableAttr(acc::getVarNameAttrName(),
+                              acc::VarNameAttr::get(dstOp->getContext(), name));
     return;
   }
   auto blockArg = dyn_cast<BlockArgument>(dst);
@@ -126,13 +127,14 @@ static void resolveVarNamePlaceholders(Block *block, Block::iterator ip,
                                        StringRef name) {
   StringRef placeholder = acc::getVarNamePlaceholder();
   for (auto it = block->begin(); it != std::next(ip); ++it) {
-    auto attr = it->getAttrOfType<acc::VarNameAttr>(acc::getVarNameAttrName());
+    auto attr = it->getDiscardableAttrOfType<acc::VarNameAttr>(
+        acc::getVarNameAttrName());
     if (attr && attr.getName() == placeholder) {
       if (name.empty())
-        it->removeAttr(acc::getVarNameAttrName());
+        it->removeDiscardableAttr(acc::getVarNameAttrName());
       else
-        it->setAttr(acc::getVarNameAttrName(),
-                    acc::VarNameAttr::get(it->getContext(), name));
+        it->setDiscardableAttr(acc::getVarNameAttrName(),
+                               acc::VarNameAttr::get(it->getContext(), name));
     }
   }
 }
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineLowering.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineLowering.cpp
index 9d5f0fc434d7e..af970c730e7fe 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineLowering.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineLowering.cpp
@@ -107,13 +107,15 @@ static func::FuncOp createFunctionForDeviceStaging(func::FuncOp hostFunc,
   FunctionType funcType = hostFunc.getFunctionType();
   func::FuncOp deviceFunc =
       func::FuncOp::create(rewriter, loc, hostFunc.getName(), funcType);
-  deviceFunc->setAttrs(hostFunc->getAttrs());
-  deviceFunc->removeAttr(getRoutineInfoAttrName());
-  deviceFunc->setAttr(getSpecializedRoutineAttrName(),
-                      SpecializedRoutineAttr::get(
-                          ctx, SymbolRefAttr::get(ctx, routineOp.getSymName()),
-                          ParLevelAttr::get(ctx, parLevel),
-                          StringAttr::get(ctx, hostFunc.getName())));
+  deviceFunc->setDiscardableAttrs(
+      hostFunc->getDiscardableAttrDictionary().getValue());
+  deviceFunc->removeDiscardableAttr(getRoutineInfoAttrName());
+  deviceFunc->setDiscardableAttr(
+      getSpecializedRoutineAttrName(),
+      SpecializedRoutineAttr::get(
+          ctx, SymbolRefAttr::get(ctx, routineOp.getSymName()),
+          ParLevelAttr::get(ctx, parLevel),
+          StringAttr::get(ctx, hostFunc.getName())));
 
   Block *sourceBlock = &hostFunc.getBody().front();
   Block *newBlock = rewriter.createBlock(&deviceFunc.getRegion());
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineToGPUFunc.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineToGPUFunc.cpp
index 6bbb4b53cb5bb..c98a7544d5af7 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineToGPUFunc.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCRoutineToGPUFunc.cpp
@@ -244,8 +244,9 @@ static LogicalResult cloneFuncsToGPUModule(
 
     gpu::GPUFuncOp deviceFuncOp = createGPUFuncFromFunc(builder, srcFunc);
 
-    if (auto specRoutineAttr = srcFunc->getAttrOfType<SpecializedRoutineAttr>(
-            getSpecializedRoutineAttrName())) {
+    if (auto specRoutineAttr =
+            srcFunc->getDiscardableAttrOfType<SpecializedRoutineAttr>(
+                getSpecializedRoutineAttrName())) {
       StringAttr funcName = specRoutineAttr.getFuncName();
       if (failed(SymbolTable::replaceAllSymbolUses(
               StringAttr::get(ctx, deviceFuncOp.getName()), funcName, mod))) {
@@ -253,11 +254,14 @@ static LogicalResult cloneFuncsToGPUModule(
                            "cannot replace symbol for acc routine");
         return failure();
       }
-      deviceFuncOp->setAttr(SymbolTable::getSymbolAttrName(), funcName);
+      deviceFuncOp->setDiscardableAttr(SymbolTable::getSymbolAttrName(),
+                                       funcName);
     }
-    if (auto specAttr = srcFunc->getAttrOfType<SpecializedRoutineAttr>(
-            getSpecializedRoutineAttrName()))
-      deviceFuncOp->setAttr(getSpecializedRoutineAttrName(), specAttr);
+    if (auto specAttr =
+            srcFunc->getDiscardableAttrOfType<SpecializedRoutineAttr>(
+                getSpecializedRoutineAttrName()))
+      deviceFuncOp->setDiscardableAttr(getSpecializedRoutineAttrName(),
+                                       specAttr);
 
     gpuSymTab.insert(deviceFuncOp);
   }
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 5e04a4154adf3..1faba3cc7bebf 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -120,8 +120,8 @@ std::string mlir::acc::getVariableName(mlir::Value v) {
       return std::to_string(*constVal);
 
     // Check for `acc.var_name` attribute
-    if (auto varNameAttr =
-            definingOp->getAttrOfType<VarNameAttr>(getVarNameAttrName()))
+    if (auto varNameAttr = definingOp->getDiscardableAttrOfType<VarNameAttr>(
+            getVarNameAttrName()))
       return varNameAttr.getName().str();
 
     // If it is a data entry operation, get name via getVarName
@@ -222,8 +222,8 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
           mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
     // If this symbol is actually an acc routine or a specialized acc routine -
     // then it is expected for it to be offloaded - therefore it is valid.
-    if (func->hasAttr(mlir::acc::getRoutineInfoAttrName()) ||
-        func->hasAttr(mlir::acc::getSpecializedRoutineAttrName()))
+    if (func->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName()) ||
+        func->hasDiscardableAttr(mlir::acc::getSpecializedRoutineAttrName()))
       return true;
 
     // If this symbol is a call to an LLVM intrinsic, then it is likely valid.
@@ -240,7 +240,8 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
   }
 
   // A declare attribute is needed for symbol references.
-  bool hasDeclare = definingOp->hasAttr(mlir::acc::getDeclareAttrName());
+  bool hasDeclare =
+      definingOp->hasDiscardableAttr(mlir::acc::getDeclareAttrName());
   return hasDeclare;
 }
 
@@ -261,8 +262,9 @@ bool mlir::acc::isDeviceValue(mlir::Value val) {
 
   // `acc.declare` with deviceptr marks data that is already associated with
   // the device.
-  if (auto declareAttr = defOp->getAttrOfType<mlir::acc::DeclareAttr>(
-          mlir::acc::getDeclareAttrName()))
+  if (auto declareAttr =
+          defOp->getDiscardableAttrOfType<mlir::acc::DeclareAttr>(
+              mlir::acc::getDeclareAttrName()))
     if (declareAttr.getDataClause().getValue() ==
         mlir::acc::DataClause::acc_deviceptr)
       return true;
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
index e97699414fb36..ce11f8b422270 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsCG.cpp
@@ -171,7 +171,8 @@ GPUParallelDimsAttr getParDimsAttr(Operation *op) {
       .Case<ACC_OP_WITH_PAR_DIMS_LIST>(
           [](auto parOp) { return parOp.getParDimsAttr(); })
       .Default([](Operation *op) -> GPUParallelDimsAttr {
-        if (Attribute attr = op->getAttr(GPUParallelDimsAttr::name)) {
+        if (Attribute attr =
+                op->getDiscardableAttr(GPUParallelDimsAttr::name)) {
           GPUParallelDimsAttr parDimsAttr = dyn_cast<GPUParallelDimsAttr>(attr);
           assert(parDimsAttr && "acc.par_dims must be a GPUParallelDimsAttr");
           return parDimsAttr;
@@ -193,8 +194,9 @@ void setParDimsAttr(Operation *op, GPUParallelDimsAttr attr) {
   llvm::TypeSwitch<Operation *>(op)
       .Case<ACC_OP_WITH_PAR_DIMS_LIST>(
           [&](auto parOp) { parOp.setParDimsAttr(attr); })
-      .Default(
-          [&](Operation *op) { op->setAttr(GPUParallelDimsAttr::name, attr); });
+      .Default([&](Operation *op) {
+        op->setDiscardableAttr(GPUParallelDimsAttr::name, attr);
+      });
 }
 
 void updateParDimsAttr(Operation *op, GPUParallelDimsAttr attr) {
@@ -203,19 +205,21 @@ void updateParDimsAttr(Operation *op, GPUParallelDimsAttr attr) {
   llvm::TypeSwitch<Operation *>(op)
       .Case<ACC_OP_WITH_PAR_DIMS_LIST>(
           [&](auto parOp) { parOp.setParDimsAttr(attr); })
-      .Default(
-          [&](Operation *op) { op->setAttr(GPUParallelDimsAttr::name, attr); });
+      .Default([&](Operation *op) {
+        op->setDiscardableAttr(GPUParallelDimsAttr::name, attr);
+      });
 }
 
 #undef ACC_OP_WITH_PAR_DIMS_LIST
 
 bool hasGPUBlockRedundantAttr(Operation *op) {
-  return op->hasAttrOfType<GPUBlockRedundantAttr>(GPUBlockRedundantAttr::name);
+  return op->hasDiscardableAttrOfType<GPUBlockRedundantAttr>(
+      GPUBlockRedundantAttr::name);
 }
 
 void setGPUBlockRedundantAttr(Operation *op) {
-  op->setAttr(GPUBlockRedundantAttr::name,
-              GPUBlockRedundantAttr::get(op->getContext()));
+  op->setDiscardableAttr(GPUBlockRedundantAttr::name,
+                         GPUBlockRedundantAttr::get(op->getContext()));
 }
 
 void copyParDimsAttr(Operation *from, Operation *to) {
@@ -225,7 +229,8 @@ void copyParDimsAttr(Operation *from, Operation *to) {
 }
 
 ActiveParDimsAttr getActiveParDimsAttr(Operation *op) {
-  return op->getAttrOfType<ActiveParDimsAttr>(ActiveParDimsAttr::name);
+  return op->getDiscardableAttrOfType<ActiveParDimsAttr>(
+      ActiveParDimsAttr::name);
 }
 
 bool hasActiveParDimsAttr(Operation *op) {
@@ -233,7 +238,7 @@ bool hasActiveParDimsAttr(Operation *op) {
 }
 
 void setActiveParDimsAttr(Operation *op, ActiveParDimsAttr attr) {
-  op->setAttr(ActiveParDimsAttr::name, attr);
+  op->setDiscardableAttr(ActiveParDimsAttr::name, attr);
 }
 
 void setActiveParDimsAttr(Operation *op, ArrayRef<GPUParallelDimAttr> dims) {
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsGPU.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsGPU.cpp
index d0f454302a2a8..9ec382c67363f 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsGPU.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsGPU.cpp
@@ -33,8 +33,8 @@ std::optional<gpu::GPUModuleOp> getOrCreateGPUModule(ModuleOp mod, bool create,
 
   // Create a new GPU module
   auto *ctx = mod.getContext();
-  mod->setAttr(gpu::GPUDialect::getContainerModuleAttrName(),
-               UnitAttr::get(ctx));
+  mod->setDiscardableAttr(gpu::GPUDialect::getContainerModuleAttrName(),
+                          UnitAttr::get(ctx));
 
   OpBuilder builder(ctx);
   auto gpuMod = gpu::GPUModuleOp::create(builder, mod.getLoc(), moduleName);
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsLoop.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsLoop.cpp
index 52f97e0867ee6..285e02c0e83b1 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsLoop.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtilsLoop.cpp
@@ -340,12 +340,14 @@ convertUnstructuredACCLoopToSCFExecuteRegion(LoopOp loopOp,
 }
 
 void setCollapseCountAttr(Operation *op, uint64_t count) {
-  op->setAttr(getCollapseCountAttrName(),
-              IntegerAttr::get(IntegerType::get(op->getContext(), 64), count));
+  op->setDiscardableAttr(
+      getCollapseCountAttrName(),
+      IntegerAttr::get(IntegerType::get(op->getContext(), 64), count));
 }
 
 uint64_t getCollapseCount(Operation *op) {
-  if (auto attr = op->getAttrOfType<IntegerAttr>(getCollapseCountAttrName()))
+  if (auto attr =
+          op->getDiscardableAttrOfType<IntegerAttr>(getCollapseCountAttrName()))
     return attr.getValue().getZExtValue();
   return 1;
 }
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index 6aabe6e918049..4d6cbef993f94 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4364,7 +4364,7 @@ void CanonicalLoopOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/true);
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 mlir::ParseResult CanonicalLoopOp::parse(::mlir::OpAsmParser &parser,
@@ -4452,7 +4452,7 @@ void UnrollHeuristicOp::build(::mlir::OpBuilder &odsBuilder,
 void UnrollHeuristicOp::print(OpAsmPrinter &p) {
   p << '(' << getApplyee() << ')';
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 mlir::ParseResult UnrollHeuristicOp::parse(::mlir::OpAsmParser &parser,
@@ -4505,7 +4505,7 @@ void UnrollFullOp::build(::mlir::OpBuilder &odsBuilder,
 void UnrollFullOp::print(OpAsmPrinter &p) {
   p << '(' << getApplyee() << ')';
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 mlir::ParseResult UnrollFullOp::parse(::mlir::OpAsmParser &parser,
@@ -4576,7 +4576,10 @@ void UnrollPartialOp::build(::mlir::OpBuilder &odsBuilder,
 void UnrollPartialOp::print(OpAsmPrinter &p) {
   p << '(' << getApplyee() << ')';
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  SmallVector<NamedAttribute> attrs((*this)->getDiscardableAttrs());
+  attrs.emplace_back(getUnrollFactorAttrName(), getUnrollFactorAttr());
+  llvm::sort(attrs);
+  p.printOptionalAttrDict(attrs);
 }
 
 mlir::ParseResult UnrollPartialOp::parse(::mlir::OpAsmParser &parser,
@@ -4944,7 +4947,7 @@ LogicalResult AtomicReadOp::verify() {
 
   int64_t version = 50;
   if (auto moduleOp = getOperation()->getParentOfType<ModuleOp>())
-    if (Attribute verAttr = moduleOp->getAttr("omp.version"))
+    if (Attribute verAttr = moduleOp->getDiscardableAttr("omp.version"))
       version = llvm::cast<VersionAttr>(verAttr).getVersion();
 
   if (auto mo = getMemoryOrder()) {
@@ -4970,7 +4973,7 @@ LogicalResult AtomicWriteOp::verify() {
 
   int64_t version = 50;
   if (auto moduleOp = getOperation()->getParentOfType<ModuleOp>())
-    if (Attribute verAttr = moduleOp->getAttr("omp.version"))
+    if (Attribute verAttr = moduleOp->getDiscardableAttr("omp.version"))
       version = llvm::cast<VersionAttr>(verAttr).getVersion();
 
   if (auto mo = getMemoryOrder()) {
@@ -5010,7 +5013,7 @@ LogicalResult AtomicUpdateOp::verify() {
 
   int64_t version = 50;
   if (auto moduleOp = getOperation()->getParentOfType<ModuleOp>())
-    if (Attribute verAttr = moduleOp->getAttr("omp.version"))
+    if (Attribute verAttr = moduleOp->getDiscardableAttr("omp.version"))
       version = llvm::cast<VersionAttr>(verAttr).getVersion();
 
   if (auto mo = getMemoryOrder()) {
@@ -5064,12 +5067,13 @@ LogicalResult AtomicCaptureOp::verifyRegions() {
   if (verifyRegionsCommon().failed())
     return mlir::failure();
 
-  if (getFirstOp()->getAttr("hint") || getSecondOp()->getAttr("hint"))
+  if (getFirstOp()->getInherentAttr("hint").value_or(Attribute{}) ||
+      getSecondOp()->getInherentAttr("hint").value_or(Attribute{}))
     return emitOpError(
         "operations inside capture region must not have hint clause");
 
-  if (getFirstOp()->getAttr("memory_order") ||
-      getSecondOp()->getAttr("memory_order"))
+  if (getFirstOp()->getInherentAttr("memory_order").value_or(Attribute{}) ||
+      getSecondOp()->getInherentAttr("memory_order").value_or(Attribute{}))
     return emitOpError(
         "operations inside capture region must not have memory_order clause");
   return success();
diff --git a/mlir/lib/Dialect/OpenMP/Utils/Utils.cpp b/mlir/lib/Dialect/OpenMP/Utils/Utils.cpp
index 481efdba89e1e..82ef6611f6dae 100644
--- a/mlir/lib/Dialect/OpenMP/Utils/Utils.cpp
+++ b/mlir/lib/Dialect/OpenMP/Utils/Utils.cpp
@@ -42,20 +42,20 @@ void mlir::omp::setOffloadModuleInterfaceAttributes(
 }
 
 void mlir::omp::setOpenMPVersionAttribute(ModuleOp module, int64_t version) {
-  module->setAttr(
+  module->setDiscardableAttr(
       StringAttr::get(module.getContext(), llvm::Twine{"omp.version"}),
       VersionAttr::get(module.getContext(), version));
 }
 
 int64_t mlir::omp::getOpenMPVersionAttribute(ModuleOp module,
                                              int64_t fallback) {
-  if (Attribute verAttr = module->getAttr("omp.version"))
+  if (Attribute verAttr = module->getDiscardableAttr("omp.version"))
     return llvm::cast<VersionAttr>(verAttr).getVersion();
   return fallback;
 }
 
 bool mlir::omp::isOpenMPModule(ModuleOp module) {
-  return module->hasAttr("omp.version");
+  return module->hasDiscardableAttr("omp.version");
 }
 
 static bool allocaUseRequiresSharedMem(const OpOperand &use) {
@@ -75,7 +75,7 @@ static bool allocaUseRequiresSharedMem(const OpOperand &use) {
       OperandRange privateVars = argIface.getPrivateVars();
       auto it = llvm::find(privateVars, use.get());
       if (it != privateVars.end()) {
-        auto privateSyms = owner->getAttrOfType<ArrayAttr>("private_syms");
+        ArrayAttr privateSyms = argIface.getPrivateSymsAttribute();
         size_t idx = std::distance(privateVars.begin(), it);
         auto privateOp =
             SymbolTable::lookupNearestSymbolFrom<omp::PrivateClauseOp>(
diff --git a/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp b/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp
index 1c713eb6f467c..541f2d64846fc 100644
--- a/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp
+++ b/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp
@@ -183,7 +183,7 @@ void ForEachOp::print(OpAsmPrinter &p) {
   BlockArgument arg = getLoopVariable();
   p << ' ' << arg << " : " << arg.getType() << " in " << getValues() << ' ';
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false);
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
   p << " -> ";
   p.printSuccessor(getSuccessor());
 }
diff --git a/mlir/lib/Dialect/Quant/Transforms/NormalizeQuantTypes.cpp b/mlir/lib/Dialect/Quant/Transforms/NormalizeQuantTypes.cpp
index 966d7589d42c3..fe7a3e38b5e4f 100644
--- a/mlir/lib/Dialect/Quant/Transforms/NormalizeQuantTypes.cpp
+++ b/mlir/lib/Dialect/Quant/Transforms/NormalizeQuantTypes.cpp
@@ -124,7 +124,8 @@ class ConvertGenericOpwithSubChannelType : public ConversionPattern {
       return failure();
 
     auto *newOp = Operation::create(
-        op->getLoc(), op->getName(), resultTypes, operands, op->getAttrs(),
+        op->getLoc(), op->getName(), resultTypes, operands,
+        op->getDiscardableAttrDictionary().getValue(),
         op->getPropertiesStorage(), op->getSuccessors(), op->getNumRegions());
     for (auto regions : llvm::zip(op->getRegions(), newOp->getRegions())) {
       Region &before = std::get<0>(regions);
diff --git a/mlir/lib/Dialect/SCF/IR/SCF.cpp b/mlir/lib/Dialect/SCF/IR/SCF.cpp
index 3388fa490f996..9e125af247799 100644
--- a/mlir/lib/Dialect/SCF/IR/SCF.cpp
+++ b/mlir/lib/Dialect/SCF/IR/SCF.cpp
@@ -168,7 +168,8 @@ void ExecuteRegionOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/true);
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"no_inline"});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          /*elidedAttrs=*/{"no_inline"});
 }
 
 LogicalResult ExecuteRegionOp::verify() {
@@ -530,7 +531,7 @@ void ForOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/!getInitArgs().empty());
-  p.printOptionalAttrDict((*this)->getAttrs(),
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
                           /*elidedAttrs=*/getUnsignedCmpAttrName().strref());
 }
 
@@ -636,7 +637,7 @@ ForOp::replaceWithAdditionalYields(RewriterBase &rewriter,
   scf::ForOp newLoop = scf::ForOp::create(
       rewriter, getLoc(), getLowerBound(), getUpperBound(), getStep(), inits,
       [](OpBuilder &, Location, Value, ValueRange) {}, getUnsignedCmp());
-  newLoop->setAttrs(getPrunedAttributeList(getOperation(), {}));
+  newLoop->setDiscardableAttrs(getPrunedAttributeList(getOperation(), {}));
 
   // Generate the new yield values and append them to the scf.yield operation.
   auto yieldOp = cast<scf::YieldOp>(getBody()->getTerminator());
@@ -909,7 +910,8 @@ mlir::scf::replaceAndCastForOpIterArg(RewriterBase &rewriter, scf::ForOp forOp,
       rewriter, forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(),
       forOp.getStep(), newIterOperands, /*bodyBuilder=*/nullptr,
       forOp.getUnsignedCmp());
-  newForOp->setAttrs(forOp->getAttrs());
+  newForOp->setDiscardableAttrs(
+      forOp->getDiscardableAttrDictionary().getValue());
   Block &newBlock = newForOp.getRegion().front();
   SmallVector<Value, 4> newBlockTransferArgs(newBlock.getArguments().begin(),
                                              newBlock.getArguments().end());
@@ -1139,10 +1141,12 @@ void ForallOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/getNumResults() > 0);
-  p.printOptionalAttrDict(op->getAttrs(), {getOperandSegmentSizesAttrName(),
-                                           getStaticLowerBoundAttrName(),
-                                           getStaticUpperBoundAttrName(),
-                                           getStaticStepAttrName()});
+  NamedAttrList attrs(op->getDiscardableAttrDictionary());
+  if (ArrayAttr mapping = getMappingAttr())
+    attrs.append(getMappingAttrName(), mapping);
+  p.printOptionalAttrDict(
+      attrs, {getOperandSegmentSizesAttrName(), getStaticLowerBoundAttrName(),
+              getStaticUpperBoundAttrName(), getStaticStepAttrName()});
 }
 
 ParseResult ForallOp::parse(OpAsmParser &parser, OperationState &result) {
@@ -1455,12 +1459,13 @@ class ForallOpControlOperandsFolder : public OpRewritePattern<ForallOp> {
       op.getDynamicStepMutable().assign(dynamicStep);
       op.setStaticStep(staticStep);
 
-      op->setAttr(ForallOp::getOperandSegmentSizeAttr(),
-                  rewriter.getDenseI32ArrayAttr(
-                      {static_cast<int32_t>(dynamicLowerBound.size()),
-                       static_cast<int32_t>(dynamicUpperBound.size()),
-                       static_cast<int32_t>(dynamicStep.size()),
-                       static_cast<int32_t>(op.getNumResults())}));
+      op->setInherentAttr(
+          rewriter.getStringAttr(ForallOp::getOperandSegmentSizeAttr()),
+          rewriter.getDenseI32ArrayAttr(
+              {static_cast<int32_t>(dynamicLowerBound.size()),
+               static_cast<int32_t>(dynamicUpperBound.size()),
+               static_cast<int32_t>(dynamicStep.size()),
+               static_cast<int32_t>(op.getNumResults())}));
     });
     return success();
   }
@@ -1664,6 +1669,7 @@ struct ForallOpSingleOrZeroIterationDimsFolder
                              newMixedUpperBounds, newMixedSteps,
                              op.getOutputs(), std::nullopt, nullptr);
     newOp.getBodyRegion().getBlocks().clear();
+    newOp.setMappingAttr(op.getMappingAttr());
     // The new loop needs to keep all attributes from the old one, except for
     // "operandSegmentSizes" and static loop bound attributes which capture
     // the outdated information of the old iteration domain.
@@ -1671,11 +1677,12 @@ struct ForallOpSingleOrZeroIterationDimsFolder
                                         newOp.getStaticLowerBoundAttrName(),
                                         newOp.getStaticUpperBoundAttrName(),
                                         newOp.getStaticStepAttrName()};
-    for (const auto &namedAttr : op->getAttrs()) {
+    for (const auto &namedAttr :
+         op->getDiscardableAttrDictionary().getValue()) {
       if (llvm::is_contained(elidedAttrs, namedAttr.getName()))
         continue;
       rewriter.modifyOpInPlace(newOp, [&]() {
-        newOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+        newOp->setDiscardableAttr(namedAttr.getName(), namedAttr.getValue());
       });
     }
     rewriter.cloneRegionBefore(op.getRegion(), newOp.getRegion(),
@@ -1875,7 +1882,8 @@ void InParallelOp::print(OpAsmPrinter &p) {
   p.printRegion(getRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/false);
-  p.printOptionalAttrDict(getOperation()->getAttrs());
+  p.printOptionalAttrDict(
+      getOperation()->getDiscardableAttrDictionary().getValue());
 }
 
 ParseResult InParallelOp::parse(OpAsmParser &parser, OperationState &result) {
@@ -2104,7 +2112,7 @@ void IfOp::print(OpAsmPrinter &p) {
                   /*printBlockTerminators=*/printBlockTerminators);
   }
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 void IfOp::getSuccessorRegions(RegionBranchPoint point,
@@ -2951,7 +2959,7 @@ void ParallelOp::print(OpAsmPrinter &p) {
   p << ' ';
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false);
   p.printOptionalAttrDict(
-      (*this)->getAttrs(),
+      (*this)->getDiscardableAttrDictionary().getValue(),
       /*elidedAttrs=*/ParallelOp::getOperandSegmentSizeAttr());
 }
 
@@ -3371,7 +3379,8 @@ void scf::WhileOp::print(OpAsmPrinter &p) {
   p.printRegion(getBefore(), /*printEntryBlockArgs=*/false);
   p << " do ";
   p.printRegion(getAfter());
-  p.printOptionalAttrDictWithKeyword((*this)->getAttrs());
+  p.printOptionalAttrDictWithKeyword(
+      (*this)->getDiscardableAttrDictionary().getValue());
 }
 
 /// Verifies that two ranges of types match, i.e. have the same number of
diff --git a/mlir/lib/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.cpp b/mlir/lib/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.cpp
index f8302126a4a4e..dad79b19f949a 100644
--- a/mlir/lib/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.cpp
@@ -763,7 +763,8 @@ struct ForOpInterface
         rewriter, forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(),
         forOp.getStep(), castedInitArgs, /*bodyBuilder=*/nullptr,
         forOp.getUnsignedCmp());
-    newForOp->setAttrs(forOp->getAttrs());
+    newForOp->setDiscardableAttrs(
+        forOp->getDiscardableAttrDictionary().getValue());
     Block *loopBody = newForOp.getBody();
 
     // Set up new iter_args. The loop body uses tensors, so wrap the (memref)
diff --git a/mlir/lib/Dialect/SCF/Transforms/ForToWhile.cpp b/mlir/lib/Dialect/SCF/Transforms/ForToWhile.cpp
index ddcbda86cf1f3..cb416cddc1c64 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ForToWhile.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ForToWhile.cpp
@@ -49,8 +49,9 @@ struct ForLoopLoweringPattern : public OpRewritePattern<ForOp> {
     SmallVector<Value> initArgs;
     initArgs.push_back(forOp.getLowerBound());
     llvm::append_range(initArgs, forOp.getInitArgs());
-    auto whileOp = WhileOp::create(rewriter, forOp.getLoc(), lcvTypes, initArgs,
-                                   forOp->getAttrs());
+    auto whileOp =
+        WhileOp::create(rewriter, forOp.getLoc(), lcvTypes, initArgs,
+                        forOp->getDiscardableAttrDictionary().getValue());
 
     // 'before' region contains the loop condition and forwarding of iteration
     // arguments to the 'after' region.
diff --git a/mlir/lib/Dialect/SCF/Transforms/ForallToParallel.cpp b/mlir/lib/Dialect/SCF/Transforms/ForallToParallel.cpp
index b95604fa44cb9..3ba51374feb31 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ForallToParallel.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ForallToParallel.cpp
@@ -51,7 +51,7 @@ LogicalResult mlir::scf::forallToParallelLoop(RewriterBase &rewriter,
 
   // If the mapping attribute is present, propagate to the new parallelOp.
   if (forallOp.getMapping())
-    parallelOp->setAttr("mapping", *forallOp.getMapping());
+    parallelOp->setDiscardableAttr("mapping", *forallOp.getMapping());
 
   // Erase the scf.forall op.
   rewriter.replaceOp(forallOp, parallelOp);
diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
index a39e5520a144b..c9f20d2026876 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
@@ -272,7 +272,7 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
                                          "unsigned loops are not supported");
 
     // Do not peel already peeled loops.
-    if (forOp->hasAttr(kPeeledLoopLabel))
+    if (forOp->hasDiscardableAttr(kPeeledLoopLabel))
       return failure();
 
     scf::ForOp partialIteration;
@@ -288,7 +288,7 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
         // loop.
         Operation *op = forOp.getOperation();
         while ((op = op->getParentOfType<scf::ForOp>())) {
-          if (op->hasAttr(kPartialIterationLabel))
+          if (op->hasDiscardableAttr(kPartialIterationLabel))
             return failure();
         }
       }
@@ -300,11 +300,13 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
 
     // Apply label, so that the same loop is not rewritten a second time.
     rewriter.modifyOpInPlace(partialIteration, [&]() {
-      partialIteration->setAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
-      partialIteration->setAttr(kPartialIterationLabel, rewriter.getUnitAttr());
+      partialIteration->setDiscardableAttr(kPeeledLoopLabel,
+                                           rewriter.getUnitAttr());
+      partialIteration->setDiscardableAttr(kPartialIterationLabel,
+                                           rewriter.getUnitAttr());
     });
     rewriter.modifyOpInPlace(forOp, [&]() {
-      forOp->setAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
+      forOp->setDiscardableAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
     });
     return success();
   }
@@ -351,8 +353,8 @@ struct ForLoopPeeling : public impl::SCFForLoopPeelingBase<ForLoopPeeling> {
 
     // Drop the markers.
     parentOp->walk([](Operation *op) {
-      op->removeAttr(kPeeledLoopLabel);
-      op->removeAttr(kPartialIterationLabel);
+      op->removeDiscardableAttr(kPeeledLoopLabel);
+      op->removeDiscardableAttr(kPartialIterationLabel);
     });
   }
 };
diff --git a/mlir/lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp b/mlir/lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp
index 9468927021495..103c03c62b832 100644
--- a/mlir/lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/StructuralTypeConversions.cpp
@@ -120,7 +120,7 @@ class ConvertForOpTypes
                                 /*bodyBuilder=*/nullptr, op.getUnsignedCmp());
 
     // Reserve whatever attributes in the original op.
-    newOp->setAttrs(op->getAttrs());
+    newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary().getValue());
 
     // We do not need the empty block created by rewriter.
     rewriter.eraseBlock(newOp.getBody(0));
@@ -145,7 +145,7 @@ class ConvertIfOpTypes
     IfOp newOp =
         IfOp::create(rewriter, op.getLoc(), dstTypes,
                      llvm::getSingleElement(adaptor.getCondition()), true);
-    newOp->setAttrs(op->getAttrs());
+    newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary().getValue());
 
     // We do not need the empty blocks created by rewriter.
     rewriter.eraseBlock(newOp.elseBlock());
diff --git a/mlir/lib/Dialect/SMT/IR/SMTOps.cpp b/mlir/lib/Dialect/SMT/IR/SMTOps.cpp
index 06ad4ab51b88b..1508f73af5a52 100644
--- a/mlir/lib/Dialect/SMT/IR/SMTOps.cpp
+++ b/mlir/lib/Dialect/SMT/IR/SMTOps.cpp
@@ -117,7 +117,8 @@ ParseResult EqOp::parse(OpAsmParser &parser, OperationState &result) {
 
 void EqOp::print(OpAsmPrinter &printer) {
   printer << ' ' << getInputs();
-  printer.printOptionalAttrDict(getOperation()->getAttrs());
+  printer.printOptionalAttrDict(
+      getOperation()->getDiscardableAttrDictionary().getValue());
   printer << " : " << getInputs().front().getType();
 }
 
@@ -139,7 +140,8 @@ ParseResult DistinctOp::parse(OpAsmParser &parser, OperationState &result) {
 
 void DistinctOp::print(OpAsmPrinter &printer) {
   printer << ' ' << getInputs();
-  printer.printOptionalAttrDict(getOperation()->getAttrs());
+  printer.printOptionalAttrDict(
+      getOperation()->getDiscardableAttrDictionary().getValue());
   printer << " : " << getInputs().front().getType();
 }
 
@@ -258,7 +260,8 @@ ParseResult RepeatOp::parse(OpAsmParser &parser, OperationState &result) {
 
 void RepeatOp::print(OpAsmPrinter &printer) {
   printer << " " << getCount() << " times " << getInput();
-  printer.printOptionalAttrDict((*this)->getAttrs());
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue());
   printer << " : " << getInput().getType();
 }
 
@@ -295,7 +298,8 @@ OpFoldResult IntConstantOp::fold(FoldAdaptor adaptor) {
 
 void IntConstantOp::print(OpAsmPrinter &p) {
   p << " " << getValue();
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          /*elidedAttrs=*/{"value"});
 }
 
 ParseResult IntConstantOp::parse(OpAsmParser &parser, OperationState &result) {
diff --git a/mlir/lib/Dialect/SPIRV/IR/AtomicOps.cpp b/mlir/lib/Dialect/SPIRV/IR/AtomicOps.cpp
index 7029268177128..2c19829106c4c 100644
--- a/mlir/lib/Dialect/SPIRV/IR/AtomicOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/AtomicOps.cpp
@@ -42,11 +42,7 @@ static LogicalResult verifyAtomicUpdateOp(Operation *op) {
                              << stringifyTypeName<ExpectedElementType>()
                              << " value, found " << elementType;
 
-  StringAttr semanticsAttrName =
-      AtomicOpTy::getSemanticsAttrName(op->getName());
-  auto memorySemantics =
-      op->getAttrOfType<spirv::MemorySemanticsAttr>(semanticsAttrName)
-          .getValue();
+  auto memorySemantics = cast<AtomicOpTy>(op).getSemantics();
   if (failed(verifyMemorySemantics(op, memorySemantics))) {
     return failure();
   }
diff --git a/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp b/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
index 0ad88dee9fe9e..44cee60decdbd 100644
--- a/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/ControlFlowOps.cpp
@@ -204,11 +204,11 @@ FunctionCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
 }
 
 CallInterfaceCallable FunctionCallOp::getCallableForCallee() {
-  return (*this)->getAttrOfType<SymbolRefAttr>(getCalleeAttrName());
+  return getCalleeAttr();
 }
 
 void FunctionCallOp::setCalleeFromCallable(CallInterfaceCallable callee) {
-  (*this)->setAttr(getCalleeAttrName(), cast<SymbolRefAttr>(callee));
+  setCalleeAttr(cast<FlatSymbolRefAttr>(cast<SymbolRefAttr>(callee)));
 }
 
 Operation::operand_range FunctionCallOp::getArgOperands() {
diff --git a/mlir/lib/Dialect/SPIRV/IR/DotProductOps.cpp b/mlir/lib/Dialect/SPIRV/IR/DotProductOps.cpp
index dada8925b88e1..c76f9475f9938 100644
--- a/mlir/lib/Dialect/SPIRV/IR/DotProductOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/DotProductOps.cpp
@@ -72,11 +72,9 @@ static LogicalResult verifyIntegerDotProduct(Operation *op) {
   // ODS enforces that vector 1 and vector 2, and result and the accumulator
   // have the same types.
   Type factorTy = op->getOperand(0).getType();
-  StringAttr packedVectorFormatAttrName =
-      IntegerDotProductOpTy::getFormatAttrName(op->getName());
+  auto dotOp = cast<IntegerDotProductOpTy>(op);
   if (auto intTy = dyn_cast<IntegerType>(factorTy)) {
-    auto packedVectorFormat = dyn_cast_or_null<spirv::PackedVectorFormatAttr>(
-        op->getAttr(packedVectorFormatAttrName));
+    spirv::PackedVectorFormatAttr packedVectorFormat = dotOp.getFormatAttr();
     if (!packedVectorFormat)
       return op->emitOpError("requires Packed Vector Format attribute for "
                              "integer vector operands");
@@ -90,7 +88,7 @@ static LogicalResult verifyIntegerDotProduct(Operation *op) {
                         "integer vector operands to be 32-bits wide",
                         packedVectorFormat.getValue()));
   } else {
-    if (op->hasAttr(packedVectorFormatAttrName))
+    if (dotOp.getFormatAttr())
       return op->emitOpError(llvm::formatv(
           "with invalid format attribute for vector operands of type '{0}'",
           factorTy));
@@ -132,11 +130,9 @@ getIntegerDotProductCapabilities(Operation *op) {
   SmallVector<ArrayRef<spirv::Capability>, 1> capabilities = {dotProductCap};
 
   Type factorTy = op->getOperand(0).getType();
-  StringAttr packedVectorFormatAttrName =
-      IntegerDotProductOpTy::getFormatAttrName(op->getName());
+  auto dotOp = cast<IntegerDotProductOpTy>(op);
   if (auto intTy = dyn_cast<IntegerType>(factorTy)) {
-    auto formatAttr = cast<spirv::PackedVectorFormatAttr>(
-        op->getAttr(packedVectorFormatAttrName));
+    spirv::PackedVectorFormatAttr formatAttr = dotOp.getFormatAttr();
     if (formatAttr.getValue() ==
         spirv::PackedVectorFormat::PackedVectorFormat4x8Bit)
       capabilities.push_back(dotProductInput4x8BitPackedCap);
diff --git a/mlir/lib/Dialect/SPIRV/IR/GroupOps.cpp b/mlir/lib/Dialect/SPIRV/IR/GroupOps.cpp
index fe6f00e9e5bca..b9dde2b2869a8 100644
--- a/mlir/lib/Dialect/SPIRV/IR/GroupOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/GroupOps.cpp
@@ -22,11 +22,7 @@ namespace mlir::spirv {
 
 template <typename OpTy>
 static LogicalResult verifyGroupNonUniformArithmeticOp(Operation *groupOp) {
-  GroupOperation operation =
-      groupOp
-          ->getAttrOfType<GroupOperationAttr>(
-              OpTy::getGroupOperationAttrName(groupOp->getName()))
-          .getValue();
+  GroupOperation operation = cast<OpTy>(groupOp).getGroupOperation();
   if (operation == GroupOperation::ClusteredReduce &&
       groupOp->getNumOperands() == 1)
     return groupOp->emitOpError("cluster size operand must be provided for "
diff --git a/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp b/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
index 602ba932f8838..9fb3519a2e5e2 100644
--- a/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
@@ -177,12 +177,11 @@ static LogicalResult verifyMemoryAccessAttribute(MemoryOpTy memoryOp) {
   // ODS checks for attributes values. Just need to verify that if the
   // memory-access attribute is Aligned, then the alignment attribute must be
   // present.
-  auto *op = memoryOp.getOperation();
-  auto memAccessAttr = op->getAttr(memoryOp.getMemoryAccessAttrName());
+  auto memAccessAttr = memoryOp.getMemoryAccessAttr();
   if (!memAccessAttr) {
     // Alignment attribute shouldn't be present if memory access attribute is
     // not present.
-    if (op->getAttr(memoryOp.getAlignmentAttrName())) {
+    if (memoryOp.getAlignmentAttr()) {
       return memoryOp.emitOpError(
           "invalid alignment specification without aligned memory access "
           "specification");
@@ -190,7 +189,7 @@ static LogicalResult verifyMemoryAccessAttribute(MemoryOpTy memoryOp) {
     return success();
   }
 
-  auto memAccess = cast<spirv::MemoryAccessAttr>(memAccessAttr);
+  auto memAccess = memAccessAttr;
 
   if (!memAccess) {
     return memoryOp.emitOpError("invalid memory access specifier: ")
@@ -199,11 +198,11 @@ static LogicalResult verifyMemoryAccessAttribute(MemoryOpTy memoryOp) {
 
   if (spirv::bitEnumContainsAll(memAccess.getValue(),
                                 spirv::MemoryAccess::Aligned)) {
-    if (!op->getAttr(memoryOp.getAlignmentAttrName())) {
+    if (!memoryOp.getAlignmentAttr()) {
       return memoryOp.emitOpError("missing alignment value");
     }
   } else {
-    if (op->getAttr(memoryOp.getAlignmentAttrName())) {
+    if (memoryOp.getAlignmentAttr()) {
       return memoryOp.emitOpError(
           "invalid alignment specification with non-aligned memory access "
           "specification");
@@ -221,12 +220,11 @@ static LogicalResult verifySourceMemoryAccessAttribute(MemoryOpTy memoryOp) {
   // ODS checks for attributes values. Just need to verify that if the
   // memory-access attribute is Aligned, then the alignment attribute must be
   // present.
-  auto *op = memoryOp.getOperation();
-  auto memAccessAttr = op->getAttr(memoryOp.getSourceMemoryAccessAttrName());
+  auto memAccessAttr = memoryOp.getSourceMemoryAccessAttr();
   if (!memAccessAttr) {
     // Alignment attribute shouldn't be present if memory access attribute is
     // not present.
-    if (op->getAttr(memoryOp.getSourceAlignmentAttrName())) {
+    if (memoryOp.getSourceAlignmentAttr()) {
       return memoryOp.emitOpError(
           "invalid alignment specification without aligned memory access "
           "specification");
@@ -234,7 +232,7 @@ static LogicalResult verifySourceMemoryAccessAttribute(MemoryOpTy memoryOp) {
     return success();
   }
 
-  auto memAccess = cast<spirv::MemoryAccessAttr>(memAccessAttr);
+  auto memAccess = memAccessAttr;
 
   if (!memAccess) {
     return memoryOp.emitOpError("invalid memory access specifier: ")
@@ -243,11 +241,11 @@ static LogicalResult verifySourceMemoryAccessAttribute(MemoryOpTy memoryOp) {
 
   if (spirv::bitEnumContainsAll(memAccess.getValue(),
                                 spirv::MemoryAccess::Aligned)) {
-    if (!op->getAttr(memoryOp.getSourceAlignmentAttrName())) {
+    if (!memoryOp.getSourceAlignmentAttr()) {
       return memoryOp.emitOpError("missing alignment value");
     }
   } else {
-    if (op->getAttr(memoryOp.getSourceAlignmentAttrName())) {
+    if (memoryOp.getSourceAlignmentAttr()) {
       return memoryOp.emitOpError(
           "invalid alignment specification with non-aligned memory access "
           "specification");
@@ -406,7 +404,8 @@ void LoadOp::print(OpAsmPrinter &printer) {
 
   printMemoryAccessAttribute(*this, printer, elidedAttrs);
 
-  printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(), elidedAttrs);
   printer << " : " << getType();
 }
 
@@ -454,7 +453,8 @@ void StoreOp::print(OpAsmPrinter &printer) {
   printMemoryAccessAttribute(*this, printer, elidedAttrs);
 
   printer << " : " << getValue().getType();
-  printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(), elidedAttrs);
 }
 
 LogicalResult StoreOp::verify() {
@@ -486,7 +486,8 @@ void CopyMemoryOp::print(OpAsmPrinter &printer) {
                                    getSourceMemoryAccess(),
                                    getSourceAlignment());
 
-  printer.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
+  printer.printOptionalAttrDict(
+      (*this)->getDiscardableAttrDictionary().getValue(), elidedAttrs);
 
   Type pointeeType =
       cast<spirv::PointerType>(getTarget().getType()).getPointeeType();
@@ -672,7 +673,7 @@ LogicalResult VariableOp::verify() {
   }
 
   auto getDecorationAttr = [op = getOperation()](spirv::Decoration decoration) {
-    return op->getAttr(spirv::getDecorationString(decoration));
+    return op->getDiscardableAttr(spirv::getDecorationString(decoration));
   };
 
   // TODO: generate these strings using ODS.
diff --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
index acc38dffe4a2b..d73a87fcd72eb 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
@@ -1284,14 +1284,15 @@ struct ConvertSelectionOpToSelect final : OpRewritePattern<spirv::SelectionOp> {
     Value trueValue = getSrcValue(trueBlock);
     Value falseValue = getSrcValue(falseBlock);
     Value ptrValue = getDstPtr(trueBlock);
-    auto storeOpAttributes =
-        cast<spirv::StoreOp>(trueBlock->front())->getAttrs();
+    auto storeOp = cast<spirv::StoreOp>(trueBlock->front());
 
     auto selectOp = spirv::SelectOp::create(
         rewriter, selectionOp.getLoc(), trueValue.getType(),
         brConditionalOp.getCondition(), trueValue, falseValue);
-    spirv::StoreOp::create(rewriter, selectOp.getLoc(), ptrValue,
-                           selectOp.getResult(), storeOpAttributes);
+    auto newStore = spirv::StoreOp::create(
+        rewriter, selectOp.getLoc(), ptrValue, selectOp.getResult(),
+        storeOp.getMemoryAccessAttr(), storeOp.getAlignmentAttr());
+    newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
 
     // `spirv.mlir.selection` is not needed anymore.
     rewriter.eraseOp(op);
diff --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
index fed2764080b01..907e671c43b79 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVOps.cpp
@@ -109,7 +109,7 @@ LogicalResult spirv::verifyPhysicalStorageBufferDecorations(Operation *op,
     return success();
 
   auto getDecorationAttr = [op](spirv::Decoration decoration) {
-    return op->getAttr(spirv::getDecorationString(decoration));
+    return op->getDiscardableAttr(spirv::getDecorationString(decoration));
   };
 
   bool hasAliasedPtr =
@@ -132,13 +132,17 @@ LogicalResult spirv::verifyPhysicalStorageBufferDecorations(Operation *op,
 
 void spirv::printVariableDecorations(Operation *op, OpAsmPrinter &printer,
                                      SmallVectorImpl<StringRef> &elidedAttrs) {
+  NamedAttrList attrs(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, attrs);
+
   // Print optional descriptor binding
   auto descriptorSetName = llvm::convertToSnakeFromCamelCase(
       stringifyDecoration(spirv::Decoration::DescriptorSet));
   auto bindingName = llvm::convertToSnakeFromCamelCase(
       stringifyDecoration(spirv::Decoration::Binding));
-  auto descriptorSet = op->getAttrOfType<IntegerAttr>(descriptorSetName);
-  auto binding = op->getAttrOfType<IntegerAttr>(bindingName);
+  auto descriptorSet =
+      dyn_cast_or_null<IntegerAttr>(attrs.get(descriptorSetName));
+  auto binding = dyn_cast_or_null<IntegerAttr>(attrs.get(bindingName));
   if (descriptorSet && binding) {
     elidedAttrs.push_back(descriptorSetName);
     elidedAttrs.push_back(bindingName);
@@ -149,12 +153,12 @@ void spirv::printVariableDecorations(Operation *op, OpAsmPrinter &printer,
   // Print BuiltIn attribute if present
   auto builtInName = llvm::convertToSnakeFromCamelCase(
       stringifyDecoration(spirv::Decoration::BuiltIn));
-  if (auto builtin = op->getAttrOfType<StringAttr>(builtInName)) {
+  if (auto builtin = dyn_cast_or_null<StringAttr>(attrs.get(builtInName))) {
     printer << " " << builtInName << "(\"" << builtin.getValue() << "\")";
     elidedAttrs.push_back(builtInName);
   }
 
-  printer.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
+  printer.printOptionalAttrDict(attrs, elidedAttrs);
 }
 
 static ParseResult parseOneResultSameOperandTypeOp(OpAsmParser &parser,
@@ -200,7 +204,7 @@ static void printOneResultOp(Operation *op, OpAsmPrinter &p) {
 
   p << ' ';
   p.printOperands(op->getOperands());
-  p.printOptionalAttrDict(op->getAttrs());
+  p.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   // Now we can output only one type for all operands and the result.
   p << " : " << resultType;
 }
@@ -330,7 +334,7 @@ static ParseResult parseArithmeticExtendedBinaryOp(OpAsmParser &parser,
 static void printArithmeticExtendedBinaryOp(Operation *op,
                                             OpAsmPrinter &printer) {
   printer << ' ';
-  printer.printOptionalAttrDict(op->getAttrs());
+  printer.printOptionalAttrDict(op->getDiscardableAttrDictionary().getValue());
   printer.printOperands(op->getOperands());
   printer << " : " << op->getResultTypes().front();
 }
@@ -1498,8 +1502,7 @@ LogicalResult spirv::GlobalVariableOp::verify() {
     }
   }
 
-  if (auto init = (*this)->getAttrOfType<FlatSymbolRefAttr>(
-          this->getInitializerAttrName())) {
+  if (FlatSymbolRefAttr init = getInitializerAttr()) {
     Operation *initOp = SymbolTable::lookupNearestSymbolFrom(
         (*this)->getParentOp(), init.getAttr());
     // TODO: Currently only variable initialization with specialization
@@ -1753,7 +1756,8 @@ void spirv::ModuleOp::print(OpAsmPrinter &printer) {
     elidedAttrs.push_back(spirv::ModuleOp::getVCETripleAttrName());
   }
 
-  printer.printOptionalAttrDictWithKeyword((*this)->getAttrs(), elidedAttrs);
+  printer.printOptionalAttrDictWithKeyword(
+      (*this)->getDiscardableAttrDictionary().getValue(), elidedAttrs);
   printer << ' ';
   printer.printRegion(getRegion());
 }
@@ -1889,13 +1893,15 @@ ParseResult spirv::SpecConstantOp::parse(OpAsmParser &parser,
 void spirv::SpecConstantOp::print(OpAsmPrinter &printer) {
   printer << ' ';
   printer.printSymbolName(getSymName());
-  if (auto specID = (*this)->getAttrOfType<IntegerAttr>(kSpecIdAttrName))
+  if (auto specID =
+          (*this)->getDiscardableAttrOfType<IntegerAttr>(kSpecIdAttrName))
     printer << ' ' << kSpecIdAttrName << '(' << specID.getInt() << ')';
   printer << " = " << getDefaultValue();
 }
 
 LogicalResult spirv::SpecConstantOp::verify() {
-  if (auto specID = (*this)->getAttrOfType<IntegerAttr>(kSpecIdAttrName))
+  if (auto specID =
+          (*this)->getDiscardableAttrOfType<IntegerAttr>(kSpecIdAttrName))
     if (specID.getValue().isNegative())
       return emitOpError("SpecId cannot be negative");
 
diff --git a/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp b/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
index c604fd087ba46..4761a9a3e21aa 100644
--- a/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/TargetAndABI.cpp
@@ -134,7 +134,7 @@ spirv::EntryPointABIAttr spirv::lookupEntryPointABI(Operation *op) {
   if (!op)
     return {};
 
-  if (auto attr = op->getAttrOfType<spirv::EntryPointABIAttr>(
+  if (auto attr = op->getDiscardableAttrOfType<spirv::EntryPointABIAttr>(
           spirv::getEntryPointABIAttrName()))
     return attr;
 
@@ -189,7 +189,7 @@ spirv::TargetEnvAttr spirv::lookupTargetEnv(Operation *op) {
     if (!op)
       break;
 
-    if (auto attr = op->getAttrOfType<spirv::TargetEnvAttr>(
+    if (auto attr = op->getDiscardableAttrOfType<spirv::TargetEnvAttr>(
             spirv::getTargetEnvAttrName()))
       return attr;
 
diff --git a/mlir/lib/Dialect/SPIRV/Linking/ModuleCombiner/ModuleCombiner.cpp b/mlir/lib/Dialect/SPIRV/Linking/ModuleCombiner/ModuleCombiner.cpp
index 2b9c7296830dc..f4467afa1a32c 100644
--- a/mlir/lib/Dialect/SPIRV/Linking/ModuleCombiner/ModuleCombiner.cpp
+++ b/mlir/lib/Dialect/SPIRV/Linking/ModuleCombiner/ModuleCombiner.cpp
@@ -70,10 +70,11 @@ static LogicalResult updateSymbolAndAllUses(SymbolOpInterface op,
 /// variable and a spec constant as duplicates because their descriptor set +
 /// binding and spec_id, respectively, happen to hash to the same value.
 static llvm::hash_code computeHash(SymbolOpInterface symbolOp) {
-  auto range =
-      llvm::make_filter_range(symbolOp->getAttrs(), [](NamedAttribute attr) {
-        return attr.getName() != SymbolTable::getSymbolAttrName();
-      });
+  NamedAttrList attrs(symbolOp->getDiscardableAttrDictionary());
+  symbolOp->getName().populateInherentAttrs(symbolOp, attrs);
+  auto range = llvm::make_filter_range(attrs, [](NamedAttribute attr) {
+    return attr.getName() != SymbolTable::getSymbolAttrName();
+  });
 
   return llvm::hash_combine(symbolOp->getName(),
                             llvm::hash_combine_range(range));
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/DecorateCompositeTypeLayoutPass.cpp b/mlir/lib/Dialect/SPIRV/Transforms/DecorateCompositeTypeLayoutPass.cpp
index a517ca946f3a4..6581f0a822a82 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/DecorateCompositeTypeLayoutPass.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/DecorateCompositeTypeLayoutPass.cpp
@@ -40,7 +40,8 @@ class SPIRVGlobalVariableOpLayoutInfoDecoration
 
   LogicalResult matchAndRewrite(spirv::GlobalVariableOp op,
                                 PatternRewriter &rewriter) const override {
-    SmallVector<NamedAttribute, 4> globalVarAttrs;
+    NamedAttrList globalVarAttrs(op->getDiscardableAttrDictionary());
+    op->getName().populateInherentAttrs(op, globalVarAttrs);
 
     auto ptrType = cast<spirv::PointerType>(op.getType());
     auto pointeeType = cast<spirv::StructType>(ptrType.getPointeeType());
@@ -53,12 +54,8 @@ class SPIRVGlobalVariableOpLayoutInfoDecoration
     auto decoratedType =
         spirv::PointerType::get(structType, ptrType.getStorageClass());
 
-    // Save all named attributes except "type" attribute.
-    for (const auto &attr : op->getAttrs()) {
-      if (attr.getName() == "type")
-        continue;
-      globalVarAttrs.push_back(attr);
-    }
+    // The new type is passed through the operation-specific builder below.
+    globalVarAttrs.erase("type");
 
     rewriter.replaceOpWithNewOp<spirv::GlobalVariableOp>(
         op, TypeAttr::get(decoratedType), globalVarAttrs);
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/LowerABIAttributesPass.cpp b/mlir/lib/Dialect/SPIRV/Transforms/LowerABIAttributesPass.cpp
index b262e6b5d7f75..3b849c5d3ae11 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/LowerABIAttributesPass.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/LowerABIAttributesPass.cpp
@@ -158,7 +158,8 @@ static LogicalResult lowerEntryPointABIAttr(spirv::FuncOp funcOp,
                                             OpBuilder &builder) {
   auto entryPointAttrName = spirv::getEntryPointABIAttrName();
   auto entryPointAttr =
-      funcOp->getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName);
+      funcOp->getDiscardableAttrOfType<spirv::EntryPointABIAttr>(
+          entryPointAttrName);
   if (!entryPointAttr) {
     return failure();
   }
@@ -228,9 +229,9 @@ static LogicalResult lowerEntryPointABIAttr(spirv::FuncOp funcOp,
   }
   if (entryPointAttr.getWorkgroupSize() || entryPointAttr.getSubgroupSize() ||
       entryPointAttr.getTargetWidth())
-    funcOp->setAttr(entryPointAttrName, entryPointAttr);
+    funcOp->setDiscardableAttr(entryPointAttrName, entryPointAttr);
   else
-    funcOp->removeAttr(entryPointAttrName);
+    funcOp->removeDiscardableAttr(entryPointAttrName);
   return success();
 }
 
@@ -277,7 +278,7 @@ class LowerABIAttributesPass final
 LogicalResult ProcessInterfaceVarABI::matchAndRewrite(
     spirv::FuncOp funcOp, OpAdaptor adaptor,
     ConversionPatternRewriter &rewriter) const {
-  if (!funcOp->getAttrOfType<spirv::EntryPointABIAttr>(
+  if (!funcOp->getDiscardableAttrOfType<spirv::EntryPointABIAttr>(
           spirv::getEntryPointABIAttrName())) {
     // TODO: Non-entry point functions are not handled.
     return failure();
@@ -458,7 +459,8 @@ void LowerABIAttributesPass::runOnOperation() {
   SmallVector<spirv::FuncOp, 1> entryPointFns;
   auto entryPointAttrName = spirv::getEntryPointABIAttrName();
   module.walk([&](spirv::FuncOp funcOp) {
-    if (funcOp->getAttrOfType<spirv::EntryPointABIAttr>(entryPointAttrName)) {
+    if (funcOp->getDiscardableAttrOfType<spirv::EntryPointABIAttr>(
+            entryPointAttrName)) {
       entryPointFns.push_back(funcOp);
     }
   });
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
index 1c8a22fb35639..a1d714e90ce4e 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
@@ -868,9 +868,7 @@ static spirv::GlobalVariableOp getBuiltinVariable(Block &body,
   // Look through all global variables in the given `body` block and check if
   // there is a spirv.GlobalVariable that has the same `builtin` attribute.
   for (auto varOp : body.getOps<spirv::GlobalVariableOp>()) {
-    if (auto builtinAttr = varOp->getAttrOfType<StringAttr>(
-            spirv::SPIRVDialect::getAttributeName(
-                spirv::Decoration::BuiltIn))) {
+    if (StringAttr builtinAttr = varOp.getBuiltInAttr()) {
       auto varBuiltIn = spirv::symbolizeBuiltIn(builtinAttr.getValue());
       if (varBuiltIn == builtin) {
         return varOp;
@@ -1023,11 +1021,19 @@ struct FuncOpConversion final : OpConversionPattern<func::FuncOp> {
                                  resultType ? TypeRange(resultType)
                                             : TypeRange()));
 
+    newFuncOp.setArgAttrsAttr(funcOp.getArgAttrsAttr());
+    newFuncOp.setResAttrsAttr(funcOp.getResAttrsAttr());
+    cast<SymbolOpInterface>(newFuncOp.getOperation())
+        .setVisibility(
+            cast<SymbolOpInterface>(funcOp.getOperation()).getVisibility());
+
     // Copy over all attributes other than the function name and type.
-    for (NamedAttribute namedAttr : funcOp->getAttrs()) {
+    for (NamedAttribute namedAttr :
+         funcOp->getDiscardableAttrDictionary().getValue()) {
       if (namedAttr.getName() != funcOp.getFunctionTypeAttrName() &&
           namedAttr.getName() != SymbolTable::getSymbolAttrName())
-        newFuncOp->setAttr(namedAttr.getName(), namedAttr.getValue());
+        newFuncOp->setDiscardableAttr(namedAttr.getName(),
+                                      namedAttr.getValue());
     }
 
     rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(),
@@ -1289,11 +1295,13 @@ static void addNoWrapDecorations(Operation *op,
                                  spirv::LinearizedIndexNoWrapFlags flags,
                                  OpBuilder &builder) {
   if (flags.noSignedWrap)
-    op->setAttr(spirv::getDecorationString(spirv::Decoration::NoSignedWrap),
-                builder.getUnitAttr());
+    op->setDiscardableAttr(
+        spirv::getDecorationString(spirv::Decoration::NoSignedWrap),
+        builder.getUnitAttr());
   if (flags.noUnsignedWrap)
-    op->setAttr(spirv::getDecorationString(spirv::Decoration::NoUnsignedWrap),
-                builder.getUnitAttr());
+    op->setDiscardableAttr(
+        spirv::getDecorationString(spirv::Decoration::NoUnsignedWrap),
+        builder.getUnitAttr());
 }
 
 static std::optional<uint64_t> getMaxLinearizedIndex(ArrayRef<int64_t> shape,
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/UnifyAliasedResourcePass.cpp b/mlir/lib/Dialect/SPIRV/Transforms/UnifyAliasedResourcePass.cpp
index ecfce68db036b..99eaa3f0b1665 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/UnifyAliasedResourcePass.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/UnifyAliasedResourcePass.cpp
@@ -47,7 +47,7 @@ using AliasedResourceMap =
 static AliasedResourceMap collectAliasedResources(spirv::ModuleOp moduleOp) {
   AliasedResourceMap aliasedResources;
   moduleOp->walk([&aliasedResources](spirv::GlobalVariableOp varOp) {
-    if (varOp->getAttrOfType<UnitAttr>("aliased")) {
+    if (varOp->getDiscardableAttrOfType<UnitAttr>("aliased")) {
       std::optional<uint32_t> set = varOp.getDescriptorSet();
       std::optional<uint32_t> binding = varOp.getBinding();
       if (set && binding)
@@ -559,8 +559,10 @@ struct ConvertStore : public ConvertAliasResource<spirv::StoreOp> {
     Value value = adaptor.getValue();
     if (srcElemType != dstElemType)
       value = spirv::BitcastOp::create(rewriter, loc, dstElemType, value);
-    rewriter.replaceOpWithNewOp<spirv::StoreOp>(storeOp, adaptor.getPtr(),
-                                                value, storeOp->getAttrs());
+    auto newStore = rewriter.replaceOpWithNewOp<spirv::StoreOp>(
+        storeOp, adaptor.getPtr(), value, storeOp.getMemoryAccessAttr(),
+        storeOp.getAlignmentAttr());
+    newStore->setDiscardableAttrs(storeOp->getDiscardableAttrDictionary());
     return success();
   }
 };
@@ -626,7 +628,7 @@ void UnifyAliasedResourcePass::runOnOperation() {
   for (const auto &dr : resourceMap) {
     const auto &resources = dr.second;
     if (resources.size() == 1)
-      resources.front()->removeAttr("aliased");
+      resources.front()->removeDiscardableAttr("aliased");
   }
 }
 } // namespace
diff --git a/mlir/lib/Dialect/SPIRV/Transforms/UpdateVCEPass.cpp b/mlir/lib/Dialect/SPIRV/Transforms/UpdateVCEPass.cpp
index 68e41838a0952..be8fa3a0b81c3 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/UpdateVCEPass.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/UpdateVCEPass.cpp
@@ -259,5 +259,5 @@ void UpdateVCEPass::runOnOperation() {
   auto triple = spirv::VerCapExtAttr::get(
       deducedVersion, deducedCapabilities.getArrayRef(),
       deducedExtensions.getArrayRef(), &getContext());
-  module->setAttr(spirv::ModuleOp::getVCETripleAttrName(), triple);
+  module.setVceTripleAttr(triple);
 }
diff --git a/mlir/lib/Dialect/Shape/IR/Shape.cpp b/mlir/lib/Dialect/Shape/IR/Shape.cpp
index 0bb130eac1bff..6b84acb992590 100644
--- a/mlir/lib/Dialect/Shape/IR/Shape.cpp
+++ b/mlir/lib/Dialect/Shape/IR/Shape.cpp
@@ -269,7 +269,7 @@ void AssumingOp::print(OpAsmPrinter &p) {
   p.printRegion(getDoRegion(),
                 /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/yieldsResults);
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 namespace {
@@ -593,8 +593,9 @@ struct RemoveDuplicateOperandsPattern : public OpRewritePattern<OpTy> {
 
     // Reduce op to equivalent with unique operands.
     if (unique.size() < op.getNumOperands()) {
-      rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(),
-                                        unique.takeVector(), op->getAttrs());
+      rewriter.replaceOpWithNewOp<OpTy>(
+          op, op->getResultTypes(), unique.takeVector(),
+          op->getDiscardableAttrDictionary().getValue());
       return success();
     }
 
@@ -716,8 +717,9 @@ struct RemoveEmptyShapeOperandsPattern : public OpRewritePattern<OpTy> {
 
     // Reduce op to equivalent without empty shape operands.
     if (newOperands.size() < op.getNumOperands()) {
-      rewriter.replaceOpWithNewOp<OpTy>(op, op->getResultTypes(), newOperands,
-                                        op->getAttrs());
+      rewriter.replaceOpWithNewOp<OpTy>(
+          op, op->getResultTypes(), newOperands,
+          op->getDiscardableAttrDictionary().getValue());
       return success();
     }
 
@@ -891,7 +893,8 @@ OpFoldResult ConcatOp::fold(FoldAdaptor adaptor) {
 
 void ConstShapeOp::print(OpAsmPrinter &p) {
   p << " ";
-  p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"shape"});
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue(),
+                          /*elidedAttrs=*/{"shape"});
   p << "[";
   interleaveComma(getShape().getValues<int64_t>(), p);
   p << "] : ";
@@ -1262,7 +1265,8 @@ void FunctionLibraryOp::print(OpAsmPrinter &p) {
   p << ' ';
   p.printSymbolName(getName());
   p.printOptionalAttrDictWithKeyword(
-      (*this)->getAttrs(), {mlir::SymbolTable::getSymbolAttrName(), "mapping"});
+      (*this)->getDiscardableAttrDictionary().getValue(),
+      {mlir::SymbolTable::getSymbolAttrName(), "mapping"});
   p << ' ';
   p.printRegion(getRegion(), /*printEntryBlockArgs=*/false,
                 /*printBlockTerminators=*/false);
@@ -2055,7 +2059,7 @@ void ReduceOp::print(OpAsmPrinter &p) {
   p.printOptionalArrowTypeList(getResultTypes());
   p << ' ';
   p.printRegion(getRegion());
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 #define GET_OP_CLASSES
diff --git a/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp b/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
index 6c26a31bf88a0..781189210ecd3 100644
--- a/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
+++ b/mlir/lib/Dialect/SparseTensor/IR/SparseTensorDialect.cpp
@@ -1799,7 +1799,7 @@ static LogicalResult verifyNumBlockArgs(T *op, Region &region,
 }
 
 LogicalResult BinaryOp::verify() {
-  NamedAttrList attrs = (*this)->getAttrs();
+  NamedAttrList attrs = (*this)->getDiscardableAttrDictionary().getValue();
   Type leftType = getX().getType();
   Type rightType = getY().getType();
   Type outputType = getOutput().getType();
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseAssembler.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseAssembler.cpp
index 4428339b38c5d..a5c74f6941554 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseAssembler.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseAssembler.cpp
@@ -233,12 +233,13 @@ struct SparseFuncAssembler : public OpRewritePattern<func::FuncOp> {
     func::ReturnOp::create(rewriter, loc, outputs);
 
     // Finally, migrate a potential c-interface property.
-    if (funcOp->getAttrOfType<UnitAttr>(
+    if (funcOp->getDiscardableAttrOfType<UnitAttr>(
             LLVM::LLVMDialect::getEmitCWrapperAttrName())) {
-      func->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                    UnitAttr::get(context));
+      func->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                               UnitAttr::get(context));
       rewriter.modifyOpInPlace(funcOp, [&]() {
-        funcOp->removeAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName());
+        funcOp->removeDiscardableAttr(
+            LLVM::LLVMDialect::getEmitCWrapperAttrName());
       });
     }
     return success();
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseGPUCodegen.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseGPUCodegen.cpp
index 14418b09f8354..8243bf9317c22 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseGPUCodegen.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseGPUCodegen.cpp
@@ -50,8 +50,8 @@ enum class CuSparseFormat {
 
 /// Marks the given top module as a GPU container module.
 static void markAsGPUContainer(ModuleOp topModule) {
-  topModule->setAttr(gpu::GPUDialect::getContainerModuleAttrName(),
-                     UnitAttr::get(topModule->getContext()));
+  topModule->setDiscardableAttr(gpu::GPUDialect::getContainerModuleAttrName(),
+                                UnitAttr::get(topModule->getContext()));
 }
 
 /// Constructs a new GPU module (for GPU kernels) inside the given top module,
@@ -1189,7 +1189,8 @@ struct ForallRewriter : public OpRewritePattern<scf::ParallelOp> {
     // of the form
     //   forall (i = 0; i < N; i++)
     // so that cyclic scheduling over the threads is easy.
-    if (!forallOp->hasAttr(LoopEmitter::getLoopEmitterLoopAttrName()) ||
+    if (!forallOp->hasDiscardableAttr(
+            LoopEmitter::getLoopEmitterLoopAttrName()) ||
         forallOp.getNumReductions() != 0 || forallOp.getNumLoops() != 1 ||
         !matchPattern(forallOp.getLowerBound()[0], m_Zero()) ||
         !matchPattern(forallOp.getStep()[0], m_One()))
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseReinterpretMap.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseReinterpretMap.cpp
index 85f64648c212e..1e2648957a34a 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseReinterpretMap.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseReinterpretMap.cpp
@@ -420,7 +420,7 @@ struct GenericOpScheduler : public OpRewritePattern<linalg::GenericOp> {
     }
 
     const StringRef sorted = "sorted";
-    if (linalgOp->hasAttr(sorted))
+    if (linalgOp->hasDiscardableAttr(sorted))
       return failure();
 
     // Pass strategy to IterationGraphSorter.
@@ -462,7 +462,7 @@ struct GenericOpScheduler : public OpRewritePattern<linalg::GenericOp> {
 
     // Marks the GenericOp to avoid recursive matching.
     rewriter.modifyOpInPlace(linalgOp, [&]() {
-      linalgOp->setAttr(sorted, rewriter.getBoolAttr(true));
+      linalgOp->setDiscardableAttr(sorted, rewriter.getBoolAttr(true));
     });
 
     // Already sorted.
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
index c60ca523d81f3..548eef578c4ff 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/SparseVectorization.cpp
@@ -585,9 +585,9 @@ static bool vectorizeStmt(PatternRewriter &rewriter, scf::ForOp forOp, VL vl,
           scf::ForOp::create(rewriter, loc, forOp.getLowerBound(),
                              forOp.getUpperBound(), step, vinit,
                              /*bodyBuilder=*/nullptr, forOp.getUnsignedCmp());
-      forOpNew->setAttr(
+      forOpNew->setDiscardableAttr(
           LoopEmitter::getLoopEmitterLoopAttrName(),
-          forOp->getAttr(LoopEmitter::getLoopEmitterLoopAttrName()));
+          forOp->getDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName()));
       rewriter.setInsertionPointToStart(forOpNew.getBody());
     } else {
       rewriter.modifyOpInPlace(forOp, [&]() { forOp.setStep(step); });
@@ -666,7 +666,7 @@ struct ForOpRewriter : public OpRewritePattern<scf::ForOp> {
     // sparsifier, which means no data dependence analysis is required,
     // and its loop-body is very restricted in form.
     if (!op.getRegion().hasOneBlock() || !isOneInteger(op.getStep()) ||
-        !op->hasAttr(LoopEmitter::getLoopEmitterLoopAttrName()))
+        !op->hasDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName()))
       return failure();
     // Analyze (!codegen) and rewrite (codegen) loop-body.
     if (vectorizeStmt(rewriter, op, vl, /*codegen=*/false) &&
@@ -683,7 +683,8 @@ static LogicalResult cleanReducChain(PatternRewriter &rewriter, Operation *op,
                                      Value inp) {
   if (auto redOp = inp.getDefiningOp<vector::ReductionOp>()) {
     if (auto forOp = redOp.getVector().getDefiningOp<scf::ForOp>()) {
-      if (forOp->hasAttr(LoopEmitter::getLoopEmitterLoopAttrName())) {
+      if (forOp->hasDiscardableAttr(
+              LoopEmitter::getLoopEmitterLoopAttrName())) {
         rewriter.replaceOp(op, redOp.getVector());
         return success();
       }
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
index 6004ab26f4663..860ea68029c3a 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Sparsification.cpp
@@ -876,7 +876,7 @@ static void finalizeWhileOp(CodegenEnv &env, OpBuilder &builder,
     while (auto ifOp = dyn_cast_or_null<scf::IfOp>(
                builder.getInsertionBlock()->getParentOp())) {
       // Break on IfOp for slicing filtering.
-      if (ifOp->getAttr(LoopEmitter::getLoopEmitterLoopAttrName()) ==
+      if (ifOp->getDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName()) ==
           StringAttr::get(ifOp->getContext(), "slice"))
         break;
 
@@ -1411,7 +1411,7 @@ struct GenericOpSparsifier : public OpRewritePattern<linalg::GenericOp> {
       return failure();
 
     // Only accept scheduled loops.
-    if (!op->hasAttr("sorted")) {
+    if (!op->hasDiscardableAttr("sorted")) {
       return rewriter.notifyMatchFailure(
           op, "Loops not yet scheduled, try run --sparse-reinterpret-map "
               "before sparsification.");
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/CodegenUtils.cpp b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/CodegenUtils.cpp
index 3c6a905ebb696..1d9bf51f8d25a 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/CodegenUtils.cpp
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/CodegenUtils.cpp
@@ -332,8 +332,8 @@ FlatSymbolRefAttr mlir::sparse_tensor::getFunc(ModuleOp module, StringRef name,
         FunctionType::get(context, operands.getTypes(), resultType));
     func.setPrivate();
     if (static_cast<bool>(emitCInterface))
-      func->setAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
-                    UnitAttr::get(context));
+      func->setDiscardableAttr(LLVM::LLVMDialect::getEmitCWrapperAttrName(),
+                               UnitAttr::get(context));
   }
   return result;
 }
diff --git a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
index 355804bcc33fb..a47062987b4b6 100644
--- a/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
+++ b/mlir/lib/Dialect/SparseTensor/Transforms/Utils/LoopEmitter.h
@@ -257,7 +257,8 @@ class LoopEmitter {
         : tidLvls(tidLvls), loop(loop), userCodeBlock(userBlock), iv(iv) {
       // Attached a special tag to loop emitter generated loop.
       if (loopTag)
-        loop->setAttr(LoopEmitter::getLoopEmitterLoopAttrName(), loopTag);
+        loop->setDiscardableAttr(LoopEmitter::getLoopEmitterLoopAttrName(),
+                                 loopTag);
     }
     // The set of <tensor, lvl>, with *only* trivial index expressions, that are
     // used as the condition for the generated loop. Extra information is
diff --git a/mlir/lib/Dialect/Tensor/Transforms/ScalarizeFunctionResult.cpp b/mlir/lib/Dialect/Tensor/Transforms/ScalarizeFunctionResult.cpp
index 60385d0c8b50d..5ed42f278645e 100644
--- a/mlir/lib/Dialect/Tensor/Transforms/ScalarizeFunctionResult.cpp
+++ b/mlir/lib/Dialect/Tensor/Transforms/ScalarizeFunctionResult.cpp
@@ -228,7 +228,8 @@ static void rewriteScalarizableFunction(func::FuncOp func,
     rewriter.setInsertionPoint(directCall);
     func::CallOp newDirectCall = func::CallOp::create(
         rewriter, directCall.getLoc(), func, directCall.getOperands());
-    newDirectCall->setAttrs(directCall->getAttrs());
+    newDirectCall->setDiscardableAttrs(
+        directCall->getDiscardableAttrDictionary().getValue());
 
     if (!directCall.getResult(0).use_empty()) {
       Value wrappedResult = tensor::FromElementsOp::create(
diff --git a/mlir/lib/Dialect/Tosa/IR/TargetEnv.cpp b/mlir/lib/Dialect/Tosa/IR/TargetEnv.cpp
index 56e4901811dcb..246cb058a91ae 100644
--- a/mlir/lib/Dialect/Tosa/IR/TargetEnv.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TargetEnv.cpp
@@ -182,7 +182,8 @@ TargetEnvAttr lookupTargetEnv(Operation *op) {
     if (!op)
       break;
 
-    if (auto attr = op->getAttrOfType<TargetEnvAttr>(TargetEnvAttr::name))
+    if (auto attr =
+            op->getDiscardableAttrOfType<TargetEnvAttr>(TargetEnvAttr::name))
       return attr;
 
     op = op->getParentOp();
diff --git a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
index 46f84940d4718..75b2a7472d9b9 100644
--- a/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
+++ b/mlir/lib/Dialect/Tosa/IR/TosaOps.cpp
@@ -400,10 +400,11 @@ void printWithNanPropagationHandling(OpAsmPrinter &parser, Operation *op) {
   parser << " ";
   parser.printOperands(op->getOperands());
 
-  NamedAttrList toPrint(op->getAttrs());
+  NamedAttrList toPrint(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, toPrint);
   // remove default NanPropagate attribute
   const auto kDefaultNanValue = NanPropagationMode::PROPAGATE;
-  for (auto attr : op->getAttrs()) {
+  for (auto attr : toPrint) {
     if (auto nanAttr = dyn_cast<NanPropagationModeAttr>(attr.getValue())) {
       if (nanAttr.getValue() == kDefaultNanValue) {
         // elide from toPrint
@@ -430,12 +431,13 @@ void printWithEnumHandling(OpAsmPrinter &parser, Operation *op) {
   parser << " ";
   parser.printOperands(op->getOperands());
 
-  if (!op->getAttrs().empty()) {
+  NamedAttrList toPrint(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, toPrint);
+  if (!toPrint.empty()) {
     parser << " {";
-    llvm::interleaveComma(op->getAttrs(), parser,
-                          [&](const NamedAttribute namedAttr) {
-                            printNamedAttr(parser, namedAttr);
-                          });
+    llvm::interleaveComma(toPrint, parser, [&](NamedAttribute attr) {
+      printNamedAttr(parser, attr);
+    });
     parser << "}";
   }
 
@@ -5762,7 +5764,7 @@ void IfOp::print(OpAsmPrinter &p) {
     p.printRegion(elseRegion);
   }
 
-  p.printOptionalAttrDict((*this)->getAttrs());
+  p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
 }
 
 LogicalResult IfOp::verify() {
@@ -5971,7 +5973,8 @@ void WhileOp::print(OpAsmPrinter &parser) {
   parser.printRegion(getCondGraph(), /*printEntryBlockArgs=*/false);
   parser << " do ";
   parser.printRegion(getBodyGraph());
-  parser.printOptionalAttrDictWithKeyword((*this)->getAttrs());
+  parser.printOptionalAttrDictWithKeyword(
+      (*this)->getDiscardableAttrDictionary().getValue());
 }
 
 // Create a rank-1 const tensor for zero point of the source tensor.
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaAttachTarget.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaAttachTarget.cpp
index 410d55d63e5fd..332409361a1ac 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaAttachTarget.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaAttachTarget.cpp
@@ -67,7 +67,7 @@ class TosaAttachTarget
     if (failed(TargetEnv::verifyTargetInformation(targetEnvAttr, mod.getLoc())))
       return signalPassFailure();
 
-    mod->setAttr(TargetEnvAttr::name, targetEnvAttr);
+    mod->setDiscardableAttr(TargetEnvAttr::name, targetEnvAttr);
   }
 
 private:
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypeToSignless.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypeToSignless.cpp
index 5a293087dd5f0..7657993d90c29 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypeToSignless.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaConvertIntegerTypeToSignless.cpp
@@ -84,7 +84,8 @@ class ConvertGenericOpWithIntegerTensorType : public ConversionPattern {
 
     // Create new op with replaced operands and results
     auto *newOp = Operation::create(
-        op->getLoc(), op->getName(), resultTypes, operands, op->getAttrs(),
+        op->getLoc(), op->getName(), resultTypes, operands,
+        op->getDiscardableAttrDictionary().getValue(),
         op->getPropertiesStorage(), op->getSuccessors(), op->getNumRegions());
 
     // Handle regions in e.g. tosa.cond_if and tosa.while_loop
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
index fa58bf3c8c589..78c74132d9e40 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaNarrowTypes.cpp
@@ -371,7 +371,9 @@ LogicalResult convertGenericOp(Operation *op, ValueRange operands,
                        newResults, {}, op->getSuccessors());
 
   // Keep attribute payloads consistent with the converted element types.
-  for (const NamedAttribute &namedAttribute : op->getAttrs()) {
+  NamedAttrList sourceAttrs(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, sourceAttrs);
+  for (const NamedAttribute &namedAttribute : sourceAttrs) {
     const Attribute attribute = namedAttribute.getValue();
 
     if (isa<IntegerAttr>(attribute) || isa<FloatAttr>(attribute)) {
@@ -501,7 +503,7 @@ class ConvertCastOpWithBoundsChecking
 
     rewriter.replaceOpWithNewOp<tosa::CastOp>(
         op, typeConverter->convertType(resultType), adaptor.getInput(),
-        op->getAttrs());
+        op->getDiscardableAttrDictionary().getValue());
     return success();
   }
 };
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaReduceTransposes.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaReduceTransposes.cpp
index ffd48e8ab16ae..57069ed74eb99 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaReduceTransposes.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaReduceTransposes.cpp
@@ -385,13 +385,13 @@ std::optional<Value> TosaReduceTransposes::buildMappedToValue(
   // turn "live" until the transpose being hoisted through this chain
   // is replaced with the proper value from the new chain.
 
-  return rewriter
-      .create(op->getLoc(), op->getName().getIdentifier(), operands,
-              RankedTensorType::get(
-                  applyTOSAPermutation(resultType.getShape(), hoistedPerms),
-                  resultType.getElementType()),
-              op->getAttrs())
-      ->getResult(0);
+  Type newResultType = RankedTensorType::get(
+      applyTOSAPermutation(resultType.getShape(), hoistedPerms),
+      resultType.getElementType());
+  OperationState state(op->getLoc(), op->getName(), operands, newResultType,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  return rewriter.create(state)->getResult(0);
 }
 
 std::optional<Value> TosaReduceTransposes::buildMappedToValue(
diff --git a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
index e4985b8e0c7f0..9a37e57af0d2a 100644
--- a/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
+++ b/mlir/lib/Dialect/Tosa/Transforms/TosaValidation.cpp
@@ -29,6 +29,7 @@
 #include "mlir/Transforms/DialectConversion.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/FormatVariadic.h"
 
 namespace mlir {
@@ -1022,7 +1023,10 @@ LogicalResult TosaValidation::CheckVariable(Operation *op) {
 LogicalResult TosaValidation::CheckVariableReadOrWrite(Operation *op) {
   if (isa<mlir::tosa::VariableReadOp>(op) ||
       isa<mlir::tosa::VariableWriteOp>(op)) {
-    mlir::StringAttr nameAttr = cast<mlir::StringAttr>(op->getAttr("name"));
+    mlir::StringAttr nameAttr =
+        TypeSwitch<Operation *, mlir::StringAttr>(op)
+            .Case<mlir::tosa::VariableReadOp, mlir::tosa::VariableWriteOp>(
+                [](auto variableOp) { return variableOp.getNameAttr(); });
     if (!variablesMap.count(nameAttr))
       return op->emitOpError() << "name has not been declared";
 
diff --git a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
index e0d4b3470c981..0d082a97414a4 100644
--- a/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
+++ b/mlir/lib/Dialect/Tosa/Utils/ConversionUtils.cpp
@@ -191,8 +191,7 @@ bool mlir::tosa::getConstShapeValues(Operation *op,
     return false;
   }
   if (auto constOp = mlir::dyn_cast<tosa::ConstShapeOp>(op)) {
-    Attribute constOpAttr = constOp->getAttr("values");
-    DenseElementsAttr elementsAttr = cast<DenseElementsAttr>(constOpAttr);
+    DenseElementsAttr elementsAttr = constOp.getValuesAttr();
     for (int i = 0; i < elementsAttr.size(); i++) {
       int64_t val = elementsAttr.getValues<int64_t>()[i];
       resultShape.push_back(val);
diff --git a/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp b/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
index f12eab759a583..05820427fac8b 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformDialect.cpp
@@ -172,8 +172,8 @@ void transform::TransformDialect::initializeLibraryModule() {
   auto loc =
       FileLineColLoc::get(context, "<transform-dialect-library-module>", 0, 0);
   libraryModule = ModuleOp::create(loc, "__transform_library");
-  libraryModule.get()->setAttr(TransformDialect::kWithNamedSequenceAttrName,
-                               UnitAttr::get(context));
+  libraryModule.get()->setDiscardableAttr(
+      TransformDialect::kWithNamedSequenceAttrName, UnitAttr::get(context));
 }
 
 void transform::TransformDialect::reportDuplicateAttributeRegistration(
diff --git a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
index 2f1648a0b0c30..422836883185b 100644
--- a/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
+++ b/mlir/lib/Dialect/Transform/IR/TransformOps.cpp
@@ -268,13 +268,13 @@ transform::AnnotateOp::apply(transform::TransformRewriter &rewriter,
                << params.size() << " vs " << targets.size() << ")";
       }
       for (auto &&[target, attr] : llvm::zip_equal(targets, params))
-        target->setAttr(getName(), attr);
+        target->setDiscardableAttr(getName(), attr);
       return DiagnosedSilenceableFailure::success();
     }
     attr = params[0];
   }
   for (auto *target : targets)
-    target->setAttr(getName(), attr);
+    target->setDiscardableAttr(getName(), attr);
   return DiagnosedSilenceableFailure::success();
 }
 
@@ -2068,8 +2068,7 @@ void transform::IncludeOp::getEffects(
 
   // Bail if the callee is unknown. This may run as part of the verification
   // process before we verified the validity of the callee or of this op.
-  auto target =
-      getOperation()->getAttrOfType<SymbolRefAttr>(getTargetAttrName());
+  auto target = getTargetAttr();
   if (!target)
     return defaultEffects();
   auto callee = SymbolTable::lookupNearestSymbolFrom<NamedSequenceOp>(
@@ -2089,7 +2088,7 @@ LogicalResult
 transform::IncludeOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   // Access through indirection and do additional checking because this may be
   // running before the main op verifier.
-  auto targetAttr = getOperation()->getAttrOfType<SymbolRefAttr>("target");
+  auto targetAttr = getTargetAttr();
   if (!targetAttr)
     return emitOpError() << "expects a 'target' symbol reference attribute";
 
@@ -2439,7 +2438,7 @@ verifyYieldingSingleBlockOp(FunctionOpInterface op, bool allowExternal) {
 static DiagnosedSilenceableFailure
 verifyNamedSequenceOp(transform::NamedSequenceOp op, bool emitWarnings) {
   if (Operation *parent = op->getParentWithTrait<OpTrait::SymbolTable>()) {
-    if (!parent->getAttr(
+    if (!parent->getDiscardableAttr(
             transform::TransformDialect::kWithNamedSequenceAttrName)) {
       DiagnosedSilenceableFailure diag =
           emitSilenceableFailure(op)
diff --git a/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp b/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
index 0084327817075..27ab65b67e8b1 100644
--- a/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
+++ b/mlir/lib/Dialect/Transform/Interfaces/TransformInterfaces.cpp
@@ -939,8 +939,8 @@ transform::TransformState::applyTransform(TransformOpInterface transform) {
   DiagnosedSilenceableFailure trackingFailure =
       trackingListener.checkAndResetError();
   if (!transform->hasTrait<ReportTrackingListenerFailuresOpTrait>() ||
-      transform->hasAttr(FindPayloadReplacementOpInterface::
-                             kSilenceTrackingFailuresAttrName)) {
+      transform->hasDiscardableAttr(FindPayloadReplacementOpInterface::
+                                        kSilenceTrackingFailuresAttrName)) {
     // Only report failures for ReportTrackingListenerFailuresOpTrait ops. Also
     // do not report failures if the above mentioned attribute is set.
     if (trackingFailure.isSilenceableFailure())
diff --git a/mlir/lib/Dialect/Transform/Transforms/InterpreterPass.cpp b/mlir/lib/Dialect/Transform/Transforms/InterpreterPass.cpp
index 19906f15ae85f..01f6de7c744c8 100644
--- a/mlir/lib/Dialect/Transform/Transforms/InterpreterPass.cpp
+++ b/mlir/lib/Dialect/Transform/Transforms/InterpreterPass.cpp
@@ -34,7 +34,7 @@ static Operation *findPayloadRoot(Operation *passRoot, StringRef tag) {
   auto tagAttrName = StringAttr::get(
       passRoot->getContext(), transform::TransformDialect::kTargetTagAttrName);
   WalkResult walkResult = passRoot->walk([&](Operation *op) {
-    auto attr = op->getAttrOfType<StringAttr>(tagAttrName);
+    auto attr = op->getDiscardableAttrOfType<StringAttr>(tagAttrName);
     if (!attr || attr.getValue() != tag)
       return WalkResult::advance();
 
diff --git a/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterUtils.cpp b/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterUtils.cpp
index 9ab484ff68078..428bfa75c9f30 100644
--- a/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterUtils.cpp
+++ b/mlir/lib/Dialect/Transform/Transforms/TransformInterpreterUtils.cpp
@@ -237,8 +237,8 @@ LogicalResult transform::detail::assembleTransformLibraryFromPaths(
   OwningOpRef<ModuleOp> mergedParsedLibraries =
       ModuleOp::create(loc, "__transform");
   {
-    mergedParsedLibraries.get()->setAttr("transform.with_named_sequence",
-                                         UnitAttr::get(context));
+    mergedParsedLibraries.get()->setDiscardableAttr(
+        "transform.with_named_sequence", UnitAttr::get(context));
     // TODO: extend `mergeSymbolsInto` to support multiple `other` modules.
     for (OwningOpRef<ModuleOp> &parsedLibrary : parsedLibraries) {
       if (failed(transform::detail::mergeSymbolsInto(
diff --git a/mlir/lib/Dialect/Utils/StructuredOpsUtils.cpp b/mlir/lib/Dialect/Utils/StructuredOpsUtils.cpp
index bcff5d32a3d2c..4139b71033780 100644
--- a/mlir/lib/Dialect/Utils/StructuredOpsUtils.cpp
+++ b/mlir/lib/Dialect/Utils/StructuredOpsUtils.cpp
@@ -198,7 +198,8 @@ Operation *mlir::clone(OpBuilder &b, Operation *op, TypeRange newResultTypes,
                        ValueRange newOperands) {
   IRMapping bvm;
   OperationState state(op->getLoc(), op->getName(), newOperands, newResultTypes,
-                       op->getAttrs());
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
   for (Region &r : op->getRegions()) {
     Region *newRegion = state.addRegion();
     b.cloneRegionBefore(r, *newRegion, newRegion->begin(), bvm);
@@ -210,7 +211,8 @@ Operation *mlir::cloneWithoutRegions(OpBuilder &b, Operation *op,
                                      TypeRange newResultTypes,
                                      ValueRange newOperands) {
   OperationState state(op->getLoc(), op->getName(), newOperands, newResultTypes,
-                       op->getAttrs());
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
   for (size_t cnt = 0, e = op->getNumRegions(); cnt < e; ++cnt)
     state.addRegion();
   return b.create(state);
@@ -221,7 +223,7 @@ mlir::getPrunedAttributeList(Operation *op, ArrayRef<StringRef> elidedAttrs) {
   llvm::StringSet<> elidedAttrsSet;
   elidedAttrsSet.insert_range(elidedAttrs);
   SmallVector<NamedAttribute> attrs;
-  for (auto attr : op->getAttrs()) {
+  for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
     if (elidedAttrsSet.count(attr.getName()))
       continue;
     attrs.push_back(attr);
diff --git a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
index 7dbe1847f077d..5ecee3c681790 100644
--- a/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
+++ b/mlir/lib/Dialect/Vector/IR/VectorOps.cpp
@@ -971,8 +971,10 @@ void ContractionOp::print(OpAsmPrinter &p) {
   auto attrNames = getTraitAttrNames();
   llvm::StringSet<> traitAttrsSet;
   traitAttrsSet.insert_range(attrNames);
+  NamedAttrList allAttrs(getOperation()->getRawDictionaryAttrs());
+  getOperation()->getName().populateInherentAttrs(getOperation(), allAttrs);
   SmallVector<NamedAttribute, 8> attrs;
-  for (auto attr : (*this)->getAttrs()) {
+  for (auto attr : allAttrs) {
     if (attr.getName() == getIteratorTypesAttrName()) {
       auto iteratorTypes =
           llvm::cast<ArrayAttr>(attr.getValue())
@@ -1002,7 +1004,7 @@ void ContractionOp::print(OpAsmPrinter &p) {
   p << " " << dictAttr << " " << getLhs() << ", ";
   p << getRhs() << ", " << getAcc();
 
-  p.printOptionalAttrDict((*this)->getAttrs(), attrNames);
+  p.printOptionalAttrDict(allAttrs.getAttrs(), attrNames);
   p << " : " << getLhs().getType() << ", " << getRhs().getType() << " into "
     << getResultType();
 }
@@ -4350,7 +4352,10 @@ void OuterProductOp::print(OpAsmPrinter &p) {
   p << " " << getLhs() << ", " << getRhs();
   if (getAcc()) {
     p << ", " << getAcc();
-    p.printOptionalAttrDict((*this)->getAttrs());
+    SmallVector<NamedAttribute> attrs((*this)->getDiscardableAttrs());
+    attrs.emplace_back(getKindAttrName(), getKindAttr());
+    llvm::sort(attrs);
+    p.printOptionalAttrDict(attrs);
   }
   p << " : " << getLhs().getType() << ", " << getRhs().getType();
 }
@@ -5123,7 +5128,7 @@ verifyTransferOp(VectorTransferOpInterface op, ShapedType shapedType,
                  VectorType vectorType, VectorType maskType,
                  VectorType inferredMaskType, AffineMap permutationMap,
                  ArrayAttr inBounds) {
-  if (op->hasAttr("masked")) {
+  if (op->hasDiscardableAttr("masked")) {
     return op->emitOpError("masked attribute has been removed. "
                            "Use in_bounds instead.");
   }
@@ -5199,14 +5204,14 @@ verifyTransferOp(VectorTransferOpInterface op, ShapedType shapedType,
 }
 
 static void printTransferAttrs(OpAsmPrinter &p, VectorTransferOpInterface op) {
-  SmallVector<StringRef, 3> elidedAttrs;
-  elidedAttrs.push_back(TransferReadOp::getOperandSegmentSizeAttr());
-  if (op.getPermutationMap().isMinorIdentity())
-    elidedAttrs.push_back(op.getPermutationMapAttrName());
+  NamedAttrList attrs(op->getDiscardableAttrDictionary().getValue());
   // Elide in_bounds attribute if all dims are out-of-bounds.
-  if (llvm::none_of(op.getInBoundsValues(), [](bool b) { return b; }))
-    elidedAttrs.push_back(op.getInBoundsAttrName());
-  p.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
+  if (llvm::any_of(op.getInBoundsValues(), [](bool b) { return b; }))
+    attrs.append(op.getInBoundsAttrName(), op.getInBounds());
+  if (!op.getPermutationMap().isMinorIdentity())
+    attrs.append(op.getPermutationMapAttrName(),
+                 AffineMapAttr::get(op.getPermutationMap()));
+  p.printOptionalAttrDict(attrs);
 }
 
 void TransferReadOp::print(OpAsmPrinter &p) {
@@ -7935,7 +7940,8 @@ void mlir::vector::MaskOp::print(OpAsmPrinter &p) {
     p.printCustomOrGenericOp(&singleBlock->front());
   p << " }";
 
-  p.printOptionalAttrDict(getOperation()->getAttrs());
+  p.printOptionalAttrDict(
+      getOperation()->getDiscardableAttrDictionary().getValue());
 
   p << " : " << getMask().getType();
   if (getNumResults() > 0)
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
index c500942af7942..522c555da378a 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorDistribute.cpp
@@ -194,7 +194,8 @@ static Operation *cloneOpWithOperandsAndTypes(RewriterBase &rewriter,
                                               ArrayRef<Value> operands,
                                               ArrayRef<Type> resultTypes) {
   OperationState res(loc, op->getName().getStringRef(), operands, resultTypes,
-                     op->getAttrs());
+                     op->getDiscardableAttrDictionary().getValue());
+  res.propertiesAttr = op->getPropertiesAsAttribute();
   return rewriter.create(res);
 }
 
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorDropLeadUnitDim.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorDropLeadUnitDim.cpp
index d86e9f224bf9d..48808ea23f1a3 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorDropLeadUnitDim.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorDropLeadUnitDim.cpp
@@ -49,6 +49,15 @@ static VectorType trimLeadingOneDims(VectorType oldType) {
 static SmallVector<int64_t> splatZero(int64_t rank) {
   return SmallVector<int64_t>(rank, 0);
 }
+
+static Operation *createWithProperties(OpBuilder &builder, Operation *op,
+                                       ValueRange operands,
+                                       TypeRange resultTypes) {
+  OperationState state(op->getLoc(), op->getName(), operands, resultTypes,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  return builder.create(state);
+}
 namespace {
 
 // Casts away leading one dimensions in vector.extract_strided_slice's vector
@@ -530,8 +539,7 @@ class CastAwayElementwiseLeadingOneDim : public RewritePattern {
       }
     }
     Operation *newOp =
-        rewriter.create(op->getLoc(), op->getName().getIdentifier(),
-                        newOperands, newVecType, op->getAttrs());
+        createWithProperties(rewriter, op, newOperands, TypeRange{newVecType});
     rewriter.replaceOpWithNewOp<vector::BroadcastOp>(op, vecType,
                                                      newOp->getResult(0));
     return success();
@@ -589,9 +597,8 @@ struct CastAwayLoadLikeLeadingOneDim : public OpRewritePattern<OpTy> {
       }
     }
 
-    Operation *newOp =
-        rewriter.create(loc, op->getName().getIdentifier(), newOperands,
-                        TypeRange{newResultType}, op->getAttrs());
+    Operation *newOp = createWithProperties(rewriter, op, newOperands,
+                                            TypeRange{newResultType});
     rewriter.replaceOpWithNewOp<vector::BroadcastOp>(op, oldResultType,
                                                      newOp->getResult(0));
     return success();
@@ -626,8 +633,7 @@ struct CastAwayStoreLikeLeadingOneDim : public OpRewritePattern<OpTy> {
     }
 
     Operation *newOp =
-        rewriter.create(loc, op->getName().getIdentifier(), newOperands,
-                        op->getResultTypes(), op->getAttrs());
+        createWithProperties(rewriter, op, newOperands, op->getResultTypes());
     rewriter.replaceOp(op, newOp->getResults());
     return success();
   }
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorLinearize.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorLinearize.cpp
index e6c28036ea1c5..025bfc0e3d9e2 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorLinearize.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorLinearize.cpp
@@ -67,7 +67,8 @@ struct LinearizeConstantLike final
     assert(resType && "expected 1-D vector type");
 
     StringAttr attrName = rewriter.getStringAttr("value");
-    Attribute value = op->getAttr(attrName);
+    Attribute value = op->getInherentAttr(attrName).value_or(
+        op->getDiscardableAttr(attrName));
     if (!value)
       return rewriter.notifyMatchFailure(loc, "no 'value' attr");
 
@@ -82,7 +83,10 @@ struct LinearizeConstantLike final
       return failure();
 
     Operation *newOp = *convertResult;
-    newOp->setAttr(attrName, *newValue);
+    if (newOp->getInherentAttr(attrName))
+      newOp->setInherentAttr(attrName, *newValue);
+    else
+      newOp->setDiscardableAttr(attrName, *newValue);
     rewriter.replaceOp(op, newOp);
     return success();
   }
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorTransferSplitRewritePatterns.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorTransferSplitRewritePatterns.cpp
index bd14e43747f81..d436266654de9 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorTransferSplitRewritePatterns.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorTransferSplitRewritePatterns.cpp
@@ -518,7 +518,8 @@ LogicalResult mlir::vector::splitFullAndPartialTransfer(
   auto inBoundsAttr = b.getBoolArrayAttr(bools);
   if (options.vectorTransferSplit == VectorTransferSplit::ForceInBounds) {
     b.modifyOpInPlace(xferOp, [&]() {
-      xferOp->setAttr(xferOp.getInBoundsAttrName(), inBoundsAttr);
+      xferOp->setInherentAttr(b.getStringAttr(xferOp.getInBoundsAttrName()),
+                              inBoundsAttr);
     });
     return success();
   }
@@ -591,7 +592,8 @@ LogicalResult mlir::vector::splitFullAndPartialTransfer(
       xferReadOp.setOperand(i, fullPartialIfOp.getResult(i));
 
     b.modifyOpInPlace(xferOp, [&]() {
-      xferOp->setAttr(xferOp.getInBoundsAttrName(), inBoundsAttr);
+      xferOp->setInherentAttr(b.getStringAttr(xferOp.getInBoundsAttrName()),
+                              inBoundsAttr);
     });
 
     return success();
@@ -610,7 +612,7 @@ LogicalResult mlir::vector::splitFullAndPartialTransfer(
   mapping.map(xferWriteOp.getBase(), memrefAndIndices.front());
   mapping.map(xferWriteOp.getIndices(), memrefAndIndices.drop_front());
   auto *clone = b.clone(*xferWriteOp, mapping);
-  clone->setAttr(xferWriteOp.getInBoundsAttrName(), inBoundsAttr);
+  clone->setInherentAttr(xferWriteOp.getInBoundsAttrName(), inBoundsAttr);
 
   // Create a potential copy from the allocated buffer to the final output in
   // the slow path case.
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorTransforms.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorTransforms.cpp
index d7beb08d3333c..fca71a867b3d9 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorTransforms.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorTransforms.cpp
@@ -41,6 +41,14 @@
 using namespace mlir;
 using namespace mlir::vector;
 
+static Operation *createWithProperties(OpBuilder &builder, Operation *op,
+                                       ValueRange operands, TypeRange types) {
+  OperationState state(op->getLoc(), op->getName(), operands, types,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  return builder.create(state);
+}
+
 template <typename IntType>
 static SmallVector<IntType> extractVector(ArrayAttr arrayAttr) {
   return llvm::to_vector<4>(llvm::map_range(
@@ -476,8 +484,7 @@ struct ReorderCastOpsOnBroadcast
     if (auto vecTy = dyn_cast<VectorType>(bcastOp.getSourceType()))
       castResTy = vecTy.clone(castResTy);
     auto *castOp =
-        rewriter.create(op->getLoc(), op->getName().getIdentifier(),
-                        bcastOp.getSource(), castResTy, op->getAttrs());
+        createWithProperties(rewriter, op, bcastOp.getSource(), castResTy);
     rewriter.replaceOpWithNewOp<vector::BroadcastOp>(
         op, op->getResult(0).getType(), castOp->getResult(0));
     return success();
@@ -556,8 +563,7 @@ struct ReorderElementwiseOpsOnTranspose final
     auto vectorType = srcType.clone(
         cast<VectorType>(op->getResultTypes()[0]).getElementType());
     Operation *elementwiseOp =
-        rewriter.create(op->getLoc(), op->getName().getIdentifier(), srcValues,
-                        vectorType, op->getAttrs());
+        createWithProperties(rewriter, op, srcValues, vectorType);
     rewriter.replaceOpWithNewOp<vector::TransposeOp>(
         op, op->getResultTypes()[0], elementwiseOp->getResult(0),
         transposeMaps.front());
@@ -1114,8 +1120,7 @@ struct ReorderElementwiseOpsOnBroadcast final
 
     // Create the "elementwise" Op
     Operation *elementwiseOp =
-        rewriter.create(op->getLoc(), op->getName().getIdentifier(), srcValues,
-                        unbroadcastResultType, op->getAttrs());
+        createWithProperties(rewriter, op, srcValues, unbroadcastResultType);
 
     // Replace the original Op with the elementwise Op
     rewriter.replaceOpWithNewOp<vector::BroadcastOp>(
@@ -2036,8 +2041,7 @@ struct DropUnitDimFromElementwiseOps final
         dropNonScalableUnitDimFromType(resultVectorType);
     // Create an updated elementwise Op without unit dim.
     Operation *elementwiseOp =
-        rewriter.create(loc, op->getName().getIdentifier(), newOperands,
-                        newResultVectorType, op->getAttrs());
+        createWithProperties(rewriter, op, newOperands, newResultVectorType);
 
     // Restore the unit dim by applying vector.shape_cast to the result.
     rewriter.replaceOpWithNewOp<ShapeCastOp>(op, resultVectorType,
diff --git a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
index 62869111496d1..aa409e71e7050 100644
--- a/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
+++ b/mlir/lib/Dialect/Vector/Transforms/VectorUnroll.cpp
@@ -79,8 +79,10 @@ static Operation *cloneOpWithOperandsAndTypes(OpBuilder &builder, Location loc,
                                               Operation *op,
                                               ArrayRef<Value> operands,
                                               ArrayRef<Type> resultTypes) {
-  return builder.create(loc, op->getName().getIdentifier(), operands,
-                        resultTypes, op->getAttrs());
+  OperationState state(loc, op->getName(), operands, resultTypes,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
+  return builder.create(state);
 }
 
 /// Return the target shape for unrolling for the given `op`. Return
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUBlocking.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUBlocking.cpp
index 9d1686403a431..f70ff26b3d8f7 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUBlocking.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUBlocking.cpp
@@ -433,14 +433,15 @@ void XeGPUBlockingPass::runOnOperation() {
       if (!isa<scf::ForOp, scf::WhileOp, scf::ConditionOp, scf::IfOp>(loopOp))
         return;
       SmallVector<StringRef> toRemove;
-      for (const NamedAttribute &attr : loopOp->getAttrs()) {
+      for (const NamedAttribute &attr :
+           loopOp->getDiscardableAttrDictionary().getValue()) {
         StringRef name = attr.getName().strref();
         if (name.starts_with("layout_operand_") ||
             name.starts_with("layout_result_"))
           toRemove.push_back(name);
       }
       for (StringRef name : toRemove)
-        loopOp->removeAttr(name);
+        loopOp->removeDiscardableAttr(name);
     });
 
     // Source (N:1) and target (1:1) materializations using
@@ -524,15 +525,16 @@ void XeGPUBlockingPass::runOnOperation() {
     // Remove the layout attributes cached per operands.
     for (OpOperand &opr : op->getOpOperands()) {
       std::string name = xegpu::getTemporaryLayoutName(opr);
-      if (op->hasAttrOfType<xegpu::DistributeLayoutAttr>(name))
-        op->removeAttr(name);
+      if (op->hasDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(name))
+        op->removeDiscardableAttr(name);
     }
 
     // Update the layout attributes per result.
     for (OpResult result : op->getOpResults()) {
       std::string name = xegpu::getTemporaryLayoutName(result);
-      if (auto layout = op->getAttrOfType<xegpu::DistributeLayoutAttr>(name)) {
-        op->removeAttr(name);
+      if (auto layout =
+              op->getDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(name)) {
+        op->removeDiscardableAttr(name);
         if (!isa<LoopLikeOpInterface>(op))
           xegpu::setDistributeLayoutAttr(result, layout.dropInstData());
       }
@@ -540,9 +542,9 @@ void XeGPUBlockingPass::runOnOperation() {
 
     // Drop left-over inst_data if the unroll pattern does not being applied,
     // say, inst_data just matches their shape.
-    SmallVector<NamedAttribute> newAttrs =
-        xegpu::dropInstDataOnAttrs(op->getAttrs());
-    op->setAttrs(newAttrs);
+    SmallVector<NamedAttribute> newAttrs = xegpu::dropInstDataOnAttrs(
+        op->getDiscardableAttrDictionary().getValue());
+    op->setDiscardableAttrs(newAttrs);
   });
 
   // Resolve UnrealizedConversionCastOps generated by SCF structural type
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
index eb3c3faf75572..7fa3060c6e1ba 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPULayoutImpl.cpp
@@ -377,8 +377,8 @@ template <typename T, typename>
 void xegpu::removeLayoutAttr(const T &operandOrResult) {
   Operation *owner = operandOrResult.getOwner();
   std::string name = xegpu::getTemporaryLayoutName(operandOrResult);
-  if (owner->hasAttrOfType<DistributeLayoutAttr>(name))
-    owner->removeAttr(name);
+  if (owner->hasDiscardableAttrOfType<DistributeLayoutAttr>(name))
+    owner->removeDiscardableAttr(name);
 }
 
 // Explicit instantiation for OpResult
@@ -393,19 +393,19 @@ void xegpu::removeLayoutAttrs(Operation *op) {
   op->walk([&](Operation *nestOp) {
     // Remove all attributes of DistributeLayoutAttr type
     SmallVector<StringAttr> attrsToRemove;
-    for (auto namedAttr : nestOp->getAttrs()) {
+    for (auto namedAttr : nestOp->getDiscardableAttrDictionary().getValue()) {
       if (isa<DistributeLayoutAttr>(namedAttr.getValue()))
         attrsToRemove.push_back(namedAttr.getName());
     }
     for (auto attrName : attrsToRemove)
-      nestOp->removeAttr(attrName);
+      nestOp->removeDiscardableAttr(attrName);
   });
 }
 
 void xegpu::removeTemporaryLayoutAttrs(Operation *op) {
   op->walk([&](Operation *nestOp) {
     SmallVector<StringAttr> attrsToRemove;
-    for (auto namedAttr : nestOp->getDiscardableAttrs()) {
+    for (auto namedAttr : nestOp->getDiscardableAttrDictionary().getValue()) {
       if (isa<xegpu::DistributeLayoutAttr>(namedAttr.getValue()))
         attrsToRemove.push_back(namedAttr.getName());
     }
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
index c4a103fad6c56..1e909319e19e8 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUPropagateLayout.cpp
@@ -1736,10 +1736,12 @@ ResolveLayoutConflicts::resolveTensorDescConsumer(OpOperand &operand) {
         conflictingCreateNdOp.getContext(), currTDescType.getShape(),
         currTDescType.getElementType(), currTDescType.getEncoding(),
         expectedLayout);
-    xegpu::CreateNdDescOp newOp = xegpu::CreateNdDescOp::create(
-        builder, consumerOp->getLoc(), newTensorDescType,
-        conflictingCreateNdOp->getOperands(),
-        conflictingCreateNdOp->getAttrs());
+    OperationState state(
+        consumerOp->getLoc(), conflictingCreateNdOp->getName(),
+        conflictingCreateNdOp->getOperands(), TypeRange{newTensorDescType},
+        conflictingCreateNdOp->getDiscardableAttrDictionary().getValue());
+    state.propertiesAttr = conflictingCreateNdOp->getPropertiesAsAttribute();
+    auto newOp = cast<xegpu::CreateNdDescOp>(builder.create(state));
     // Replace the tensor descriptor operand in the consumer op with the new
     // tensor descriptor.
     consumerOp->replaceUsesOfWith(tdescValue, newOp.getResult());
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
index bc85140f9f121..b84fe9aa97b5c 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUSgToLaneDistribute.cpp
@@ -127,10 +127,12 @@ struct SgToLaneCreateNdDesc
     if (!resultType.getLayout())
       return failure();
 
-    auto newOp = xegpu::CreateNdDescOp::create(
-        rewriter, op.getLoc(), resultType.dropLayouts(), op.getOperands(),
-        op->getAttrs());
-    rewriter.replaceOp(op, newOp.getResult());
+    OperationState state(op.getLoc(), op->getName(), op.getOperands(),
+                         TypeRange{resultType.dropLayouts()},
+                         op->getDiscardableAttrDictionary().getValue());
+    state.propertiesAttr = op->getPropertiesAsAttribute();
+    Operation *newOp = rewriter.create(state);
+    rewriter.replaceOp(op, newOp->getResult(0));
     return success();
   }
 };
@@ -345,10 +347,11 @@ struct SgToLaneElementWise : public ConversionPattern {
     state.addOperands(operands);
     state.addTypes(newResultType);
     // Copy all attributes except for DistributeLayoutAttr.
-    for (auto attr : op->getAttrs()) {
+    for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
       if (!isa<xegpu::DistributeLayoutAttr>(attr.getValue()))
         state.addAttribute(attr.getName(), attr.getValue());
     }
+    state.propertiesAttr = op->getPropertiesAsAttribute();
     Operation *newOp = rewriter.create(state);
 
     rewriter.replaceOp(op, newOp->getResult(0));
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
index 14061ab24ffe0..c3873c916b647 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUUnroll.cpp
@@ -564,9 +564,12 @@ struct UnrollDpasOp : public UnrollPattern<xegpu::DpasOp> {
             if (tmpC)
               operands.push_back(tmpC);
 
-            tmpC = xegpu::DpasOp::create(
-                rewriter, loc, vecTy, operands,
-                xegpu::dropInstDataOnAttrs(op->getAttrs()));
+            OperationState state(
+                loc, op->getName(), operands, TypeRange{vecTy},
+                xegpu::dropInstDataOnAttrs(
+                    op->getDiscardableAttrDictionary().getValue()));
+            state.propertiesAttr = op->getPropertiesAsAttribute();
+            tmpC = rewriter.create(state)->getResult(0);
           }
           newOps.push_back(tmpC);
         }
@@ -666,9 +669,12 @@ struct UnrollDpasMxOp : public UnrollPattern<xegpu::DpasMxOp> {
               operands.push_back(
                   bScaleVals[batch * (kIters * nIters) + k * nIters + j]);
 
-            newDpasMxOp = xegpu::DpasMxOp::create(
-                rewriter, loc, vecTy, operands,
-                xegpu::dropInstDataOnAttrs(op->getAttrs()));
+            OperationState state(
+                loc, op->getName(), operands, TypeRange{vecTy},
+                xegpu::dropInstDataOnAttrs(
+                    op->getDiscardableAttrDictionary().getValue()));
+            state.propertiesAttr = op->getPropertiesAsAttribute();
+            newDpasMxOp = cast<xegpu::DpasMxOp>(rewriter.create(state));
             tmpC = newDpasMxOp.getResult();
           }
           newOps.push_back(newDpasMxOp);
@@ -699,11 +705,7 @@ struct UnrollLoadGatherOp : public UnrollPattern<xegpu::LoadGatherOp> {
       return failure();
 
     SmallVector<int64_t> targetMaskShape(*targetShape);
-    int64_t chunkSize = 1;
-    if (auto chunkSizeAttr = op->getAttr("chunk_size")) {
-      if (auto intAttr = llvm::dyn_cast<IntegerAttr>(chunkSizeAttr))
-        chunkSize = intAttr.getInt();
-    }
+    int64_t chunkSize = op.getChunkSize().value_or(1);
 
     // Unroll mask and offsets with correct shape
     VectorType maskTy = llvm::dyn_cast<VectorType>(mask.getType());
@@ -793,11 +795,7 @@ struct UnrollStoreScatterOp : public UnrollPattern<xegpu::StoreScatterOp> {
     if (!targetShape)
       return failure();
 
-    int64_t chunkSize = 1;
-    if (auto chunkSizeAttr = op->getAttr("chunk_size")) {
-      if (auto intAttr = llvm::dyn_cast<IntegerAttr>(chunkSizeAttr))
-        chunkSize = intAttr.getInt();
-    }
+    int64_t chunkSize = op.getChunkSize().value_or(1);
 
     SmallVector<int64_t> targetMaskShape(*targetShape);
     VectorType maskTy = llvm::dyn_cast<VectorType>(mask.getType());
diff --git a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUWgToSgDistribute.cpp b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUWgToSgDistribute.cpp
index a24e9b2fd7e0f..4886eeff13bac 100644
--- a/mlir/lib/Dialect/XeGPU/Transforms/XeGPUWgToSgDistribute.cpp
+++ b/mlir/lib/Dialect/XeGPU/Transforms/XeGPUWgToSgDistribute.cpp
@@ -41,7 +41,7 @@ static xegpu::RangeAttr getRangeSpecAttr(Operation *op) {
   Operation *parent = op->getParentOfType<scf::IfOp>();
   while (parent) {
     if (auto attr = llvm::dyn_cast_if_present<xegpu::RangeAttr>(
-            parent->getAttr("sg_id_range")))
+            parent->getDiscardableAttr("sg_id_range")))
       return attr;
     parent = parent->getParentOfType<scf::IfOp>();
   }
@@ -485,7 +485,8 @@ struct WgToSgElementwiseOp : public ConversionPattern {
       OperationState state(op->getLoc(), op->getName());
       state.addOperands(opOperands);
       state.addTypes(newResultType);
-      state.addAttributes(op->getAttrs());
+      state.addAttributes(op->getDiscardableAttrDictionary().getValue());
+      state.propertiesAttr = op->getPropertiesAsAttribute();
       Operation *newOp = rewriter.create(state);
       xegpu::removeLayoutAttrs(newOp);
       newResults.push_back(newOp->getResult(0));
diff --git a/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp b/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
index 07dcb438ca553..01d39f9ec638d 100644
--- a/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
+++ b/mlir/lib/Dialect/XeGPU/Utils/XeGPUUtils.cpp
@@ -160,9 +160,10 @@ xegpu::DistributeLayoutAttr xegpu::getDistributeLayoutAttr(const Value value) {
     }
 
     std::string layoutName = getTemporaryLayoutName(result);
-    if (defOp->hasAttr(layoutName)) {
+    if (defOp->hasDiscardableAttr(layoutName)) {
       auto layout =
-          defOp->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
+          defOp->getDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(
+              layoutName);
       return layout;
     }
   }
@@ -254,8 +255,9 @@ xegpu::getDistributeLayoutAttr(const OpOperand &opr) {
   }
 
   std::string layoutName = xegpu::getTemporaryLayoutName(opr);
-  if (op->hasAttr(layoutName)) {
-    auto layout = op->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
+  if (op->hasDiscardableAttr(layoutName)) {
+    auto layout =
+        op->getDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
     return layout;
   }
 
@@ -312,11 +314,11 @@ void xegpu::setDistributeLayoutAttr(
   }
 
   std::string name = xegpu::getTemporaryLayoutName(result);
-  if (owner->hasAttrOfType<DistributeLayoutAttr>(name)) {
+  if (owner->hasDiscardableAttrOfType<DistributeLayoutAttr>(name)) {
     return;
   }
   if (layout) {
-    owner->setAttr(name, layout);
+    owner->setDiscardableAttr(name, layout);
   }
 }
 
@@ -360,11 +362,11 @@ void xegpu::setDistributeLayoutAttr(const OpOperand &operand,
   }
 
   std::string name = xegpu::getTemporaryLayoutName(operand);
-  if (owner->hasAttrOfType<DistributeLayoutAttr>(name)) {
+  if (owner->hasDiscardableAttrOfType<DistributeLayoutAttr>(name)) {
     return;
   }
   if (layout) {
-    owner->setAttr(name, layout);
+    owner->setDiscardableAttr(name, layout);
   }
 }
 
@@ -374,8 +376,9 @@ xegpu::getTemporaryLayout(const T &operandOrResult) {
   Operation *op = operandOrResult.getOwner();
 
   std::string layoutName = xegpu::getTemporaryLayoutName(operandOrResult);
-  if (op->hasAttr(layoutName)) {
-    auto layout = op->getAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
+  if (op->hasDiscardableAttr(layoutName)) {
+    auto layout =
+        op->getDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(layoutName);
     return layout;
   }
 
@@ -392,11 +395,11 @@ void xegpu::setTemporaryLayout(const T &operandOrResult,
                                const xegpu::DistributeLayoutAttr layout) {
   Operation *owner = operandOrResult.getOwner();
   std::string name = xegpu::getTemporaryLayoutName(operandOrResult);
-  if (owner->hasAttrOfType<xegpu::DistributeLayoutAttr>(name)) {
+  if (owner->hasDiscardableAttrOfType<xegpu::DistributeLayoutAttr>(name)) {
     return;
   }
   if (layout) {
-    owner->setAttr(name, layout);
+    owner->setDiscardableAttr(name, layout);
   }
 }
 
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index c94cf8f77bf6b..ece1fdf3eab33 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -762,7 +762,11 @@ class DummyAliasOperationPrinter : private OpAsmPrinter {
       printType(type);
 
     // Consider the attributes of the operation for aliases.
-    for (const NamedAttribute &attr : op->getAttrs())
+    for (const NamedAttribute &attr : op->getDiscardableAttrs())
+      printAttribute(attr.getValue());
+    NamedAttrList inherentAttrs;
+    op->getName().populateInherentAttrs(op, inherentAttrs);
+    for (const NamedAttribute &attr : inherentAttrs)
       printAttribute(attr.getValue());
   }
 
@@ -3834,9 +3838,10 @@ void OperationPrinter::printGenericOp(Operation *op, bool printOpName) {
     os << ')';
   }
 
-  printOptionalAttrDict(op->getPropertiesStorage()
-                            ? llvm::to_vector(op->getDiscardableAttrs())
-                            : op->getAttrs());
+  printOptionalAttrDict(
+      op->getPropertiesStorage()
+          ? llvm::to_vector(op->getDiscardableAttrDictionary().getValue())
+          : op->getRawDictionaryAttrs().getValue());
 
   // Print the type signature of the operation.
   os << " : ";
diff --git a/mlir/lib/IR/AttrTypeSubElements.cpp b/mlir/lib/IR/AttrTypeSubElements.cpp
index 863e1aa95f2fb..bbc86c4507f9c 100644
--- a/mlir/lib/IR/AttrTypeSubElements.cpp
+++ b/mlir/lib/IR/AttrTypeSubElements.cpp
@@ -94,8 +94,17 @@ void detail::AttrTypeReplacerBase<Concrete>::replaceElementsIn(
 
   // Update the attribute dictionary.
   if (replaceAttrs) {
-    if (auto newAttrs = replaceIfDifferent(op->getAttrDictionary()))
-      op->setAttrs(cast<DictionaryAttr>(newAttrs));
+    if (auto newAttrs = replaceIfDifferent(op->getDiscardableAttrDictionary()))
+      op->setDiscardableAttrs(cast<DictionaryAttr>(newAttrs));
+
+    if (op->getPropertiesStorageSize()) {
+      NamedAttrList inherentAttrs;
+      op->getName().populateInherentAttrs(op, inherentAttrs);
+      for (NamedAttribute attr : inherentAttrs) {
+        if (Attribute replacement = replaceIfDifferent(attr.getValue()))
+          op->setInherentAttr(attr.getName(), replacement);
+      }
+    }
   }
 
   // If we aren't updating locations or types, we're done.
diff --git a/mlir/lib/IR/BuiltinAttributes.cpp b/mlir/lib/IR/BuiltinAttributes.cpp
index 0f84f7a682fcf..3a24a77267112 100644
--- a/mlir/lib/IR/BuiltinAttributes.cpp
+++ b/mlir/lib/IR/BuiltinAttributes.cpp
@@ -348,10 +348,9 @@ FlatSymbolRefAttr SymbolRefAttr::get(StringAttr value) {
 }
 
 FlatSymbolRefAttr SymbolRefAttr::get(Operation *symbol) {
-  auto symName =
-      symbol->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
-  assert(symName && "value does not have a valid symbol name");
-  return SymbolRefAttr::get(symName);
+  auto symbolOp = dyn_cast<SymbolOpInterface>(symbol);
+  assert(symbolOp && "value does not have a valid symbol name");
+  return SymbolRefAttr::get(symbolOp.getNameAttr());
 }
 
 StringAttr SymbolRefAttr::getLeafReference() const {
diff --git a/mlir/lib/IR/BuiltinDialect.cpp b/mlir/lib/IR/BuiltinDialect.cpp
index c88b328282275..f2a84134616e5 100644
--- a/mlir/lib/IR/BuiltinDialect.cpp
+++ b/mlir/lib/IR/BuiltinDialect.cpp
@@ -139,7 +139,8 @@ DataLayoutSpecInterface ModuleOp::getDataLayoutSpec() {
   // Take the first and only (if present) attribute that implements the
   // interface. This needs a linear search, but is called only once per data
   // layout object construction that is used for repeated queries.
-  for (NamedAttribute attr : getOperation()->getAttrs())
+  for (NamedAttribute attr :
+       getOperation()->getDiscardableAttrDictionary().getValue())
     if (auto spec = llvm::dyn_cast<DataLayoutSpecInterface>(attr.getValue()))
       return spec;
   return {};
@@ -149,7 +150,8 @@ TargetSystemSpecInterface ModuleOp::getTargetSystemSpec() {
   // Take the first and only (if present) attribute that implements the
   // interface. This needs a linear search, but is called only once per data
   // layout object construction that is used for repeated queries.
-  for (NamedAttribute attr : getOperation()->getAttrs())
+  for (NamedAttribute attr :
+       getOperation()->getDiscardableAttrDictionary().getValue())
     if (auto spec = llvm::dyn_cast<TargetSystemSpecInterface>(attr.getValue()))
       return spec;
   return {};
@@ -158,7 +160,7 @@ TargetSystemSpecInterface ModuleOp::getTargetSystemSpec() {
 LogicalResult ModuleOp::verify() {
   // Check that none of the attributes are non-dialect attributes, except for
   // the symbol related attributes.
-  for (auto attr : (*this)->getAttrs()) {
+  for (auto attr : (*this)->getDiscardableAttrDictionary().getValue()) {
     if (!attr.getName().strref().contains('.') &&
         !llvm::is_contained(
             ArrayRef<StringRef>{mlir::SymbolTable::getSymbolAttrName(),
@@ -172,7 +174,8 @@ LogicalResult ModuleOp::verify() {
   // Check that there is at most one data layout spec attribute.
   StringRef layoutSpecAttrName;
   DataLayoutSpecInterface layoutSpec;
-  for (const NamedAttribute &na : (*this)->getAttrs()) {
+  for (const NamedAttribute &na :
+       (*this)->getDiscardableAttrDictionary().getValue()) {
     if (auto spec = llvm::dyn_cast<DataLayoutSpecInterface>(na.getValue())) {
       if (layoutSpec) {
         InFlightDiagnostic diag =
diff --git a/mlir/lib/IR/Operation.cpp b/mlir/lib/IR/Operation.cpp
index b7227d0802ea8..52ccce03e3c87 100644
--- a/mlir/lib/IR/Operation.cpp
+++ b/mlir/lib/IR/Operation.cpp
@@ -143,7 +143,9 @@ Operation *Operation::create(Location location, OperationName name,
     new (&blockOperands[i]) BlockOperand(op, successors[i]);
 
   // This must be done after properties are initialized.
+  LLVM_SUPPRESS_DEPRECATED_DECLARATIONS_PUSH
   op->setAttrs(attributes);
+  LLVM_SUPPRESS_DEPRECATED_DECLARATIONS_POP
 
   return op;
 }
@@ -1269,7 +1271,10 @@ LogicalResult OpTrait::impl::verifyValueSizeAttr(Operation *op,
                                                  StringRef attrName,
                                                  StringRef valueGroupName,
                                                  size_t expectedCount) {
-  auto sizeAttr = op->getAttrOfType<DenseI32ArrayAttr>(attrName);
+  Attribute rawSizeAttr = op->getDiscardableAttr(attrName);
+  if (op->getPropertiesStorageSize())
+    rawSizeAttr = op->getInherentAttr(attrName).value_or(rawSizeAttr);
+  auto sizeAttr = dyn_cast_or_null<DenseI32ArrayAttr>(rawSizeAttr);
   if (!sizeAttr)
     return op->emitOpError("requires dense i32 array attribute '")
            << attrName << "'";
diff --git a/mlir/lib/IR/OperationSupport.cpp b/mlir/lib/IR/OperationSupport.cpp
index 32c6426429ae8..1b964d9a38f95 100644
--- a/mlir/lib/IR/OperationSupport.cpp
+++ b/mlir/lib/IR/OperationSupport.cpp
@@ -523,7 +523,13 @@ void MutableOperandRange::updateLength(unsigned newLength) {
     segments[segment.first] += diff;
     segment.second.setValue(
         DenseI32ArrayAttr::get(attr.getContext(), segments));
-    owner->setAttr(segment.second.getName(), segment.second.getValue());
+    if (owner->getPropertiesStorageSize() &&
+        owner->getInherentAttr(segment.second.getName()))
+      owner->setInherentAttr(segment.second.getName(),
+                             segment.second.getValue());
+    else
+      owner->setDiscardableAttr(segment.second.getName(),
+                                segment.second.getValue());
   }
 }
 
diff --git a/mlir/lib/IR/PatternMatch.cpp b/mlir/lib/IR/PatternMatch.cpp
index cd067f2cc25b3..0b080d2920230 100644
--- a/mlir/lib/IR/PatternMatch.cpp
+++ b/mlir/lib/IR/PatternMatch.cpp
@@ -260,7 +260,9 @@ Operation *RewriterBase::eraseOpResults(Operation *op,
   InsertionGuard g(*this);
   setInsertionPoint(op);
   OperationState state(op->getLoc(), op->getName().getStringRef(),
-                       op->getOperands(), newResultTypes, op->getAttrs());
+                       op->getOperands(), newResultTypes,
+                       op->getDiscardableAttrDictionary().getValue());
+  state.propertiesAttr = op->getPropertiesAsAttribute();
   for ([[maybe_unused]] auto i : llvm::seq<unsigned>(0, op->getNumRegions()))
     state.addRegion();
   Operation *newOp = create(state);
diff --git a/mlir/lib/IR/SymbolTable.cpp b/mlir/lib/IR/SymbolTable.cpp
index 100eba7146d81..0c9b21f6dcf8a 100644
--- a/mlir/lib/IR/SymbolTable.cpp
+++ b/mlir/lib/IR/SymbolTable.cpp
@@ -22,13 +22,21 @@ static bool isPotentiallyUnknownSymbolTable(Operation *op) {
   return op->getNumRegions() == 1 && !op->getDialect();
 }
 
+static Attribute getSymbolAttribute(Operation *op, StringRef name) {
+  if (op->getPropertiesStorageSize())
+    if (std::optional<Attribute> inherent = op->getInherentAttr(name))
+      return *inherent;
+  return op->getDiscardableAttr(name);
+}
+
 /// Returns the string name of the given symbol, or null if this is not a
 /// symbol.
 static StringAttr getNameIfSymbol(Operation *op) {
-  return op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
+  return dyn_cast_or_null<StringAttr>(
+      getSymbolAttribute(op, SymbolTable::getSymbolAttrName()));
 }
 static StringAttr getNameIfSymbol(Operation *op, StringAttr symbolAttrNameId) {
-  return op->getAttrOfType<StringAttr>(symbolAttrNameId);
+  return dyn_cast_or_null<StringAttr>(getSymbolAttribute(op, symbolAttrNameId));
 }
 
 /// Computes the nested symbol reference attribute for the symbol 'symbolName'
@@ -299,13 +307,19 @@ StringAttr SymbolTable::getSymbolName(Operation *symbol) {
 
 /// Sets the name of the given symbol operation.
 void SymbolTable::setSymbolName(Operation *symbol, StringAttr name) {
-  symbol->setAttr(getSymbolAttrName(), name);
+  StringAttr attrName =
+      StringAttr::get(symbol->getContext(), getSymbolAttrName());
+  if (symbol->getPropertiesStorageSize() && symbol->getInherentAttr(attrName))
+    symbol->setInherentAttr(attrName, name);
+  else
+    symbol->setDiscardableAttr(attrName, name);
 }
 
 /// Returns the visibility of the given symbol operation.
 SymbolTable::Visibility SymbolTable::getSymbolVisibility(Operation *symbol) {
   // If the attribute doesn't exist, assume public.
-  StringAttr vis = symbol->getAttrOfType<StringAttr>(getVisibilityAttrName());
+  StringAttr vis = dyn_cast_or_null<StringAttr>(
+      getSymbolAttribute(symbol, getVisibilityAttrName()));
   if (!vis)
     return Visibility::Public;
 
@@ -322,7 +336,11 @@ void SymbolTable::setSymbolVisibility(Operation *symbol, Visibility vis) {
   // If the visibility is public, just drop the attribute as this is the
   // default.
   if (vis == Visibility::Public) {
-    symbol->removeAttr(StringAttr::get(ctx, getVisibilityAttrName()));
+    StringAttr attrName = StringAttr::get(ctx, getVisibilityAttrName());
+    if (symbol->getPropertiesStorageSize() && symbol->getInherentAttr(attrName))
+      symbol->setInherentAttr(attrName, {});
+    else
+      symbol->removeDiscardableAttr(attrName);
     return;
   }
 
@@ -331,7 +349,11 @@ void SymbolTable::setSymbolVisibility(Operation *symbol, Visibility vis) {
          "unknown symbol visibility kind");
 
   StringRef visName = vis == Visibility::Private ? "private" : "nested";
-  symbol->setAttr(getVisibilityAttrName(), StringAttr::get(ctx, visName));
+  StringAttr attrName = StringAttr::get(ctx, getVisibilityAttrName());
+  if (symbol->getPropertiesStorageSize() && symbol->getInherentAttr(attrName))
+    symbol->setInherentAttr(attrName, StringAttr::get(ctx, visName));
+  else
+    symbol->setDiscardableAttr(attrName, StringAttr::get(ctx, visName));
 }
 
 /// Returns the nearest symbol table from a given operation `from`. Returns
@@ -512,13 +534,19 @@ static LogicalResult verifyOpTypeSymbolUses(Operation *op,
           return failure();
 
   // Verify types nested within the operation's attributes.
-  WalkResult attrResult =
-      op->getAttrDictionary().walk<WalkOrder::PreOrder>([&](Type type) {
-        if (verify(type).wasInterrupted())
-          return WalkResult::interrupt();
-        return WalkResult::advance();
-      });
-  return failure(attrResult.wasInterrupted());
+  auto verifyTypes = [&](Attribute attr) {
+    return attr.walk<WalkOrder::PreOrder>([&](Type type) {
+      if (verify(type).wasInterrupted())
+        return WalkResult::interrupt();
+      return WalkResult::advance();
+    });
+  };
+  if (verifyTypes(op->getRawDictionaryAttrs()).wasInterrupted())
+    return failure();
+  if (Attribute properties = op->getPropertiesAsAttribute())
+    if (verifyTypes(properties).wasInterrupted())
+      return failure();
+  return success();
 }
 
 LogicalResult detail::verifySymbolTable(Operation *op) {
@@ -534,8 +562,7 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
   for (auto &block : op->getRegion(0)) {
     for (auto &op : block) {
       // Check for a symbol name attribute.
-      auto nameAttr =
-          op.getAttrOfType<StringAttr>(mlir::SymbolTable::getSymbolAttrName());
+      StringAttr nameAttr = getNameIfSymbol(&op);
       if (!nameAttr)
         continue;
 
@@ -562,7 +589,7 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
     if (SymbolUserOpInterface user = dyn_cast<SymbolUserOpInterface>(op))
       if (failed(user.verifySymbolUses(symbolTable)))
         return WalkResult::interrupt();
-    for (auto &attr : op->getDiscardableAttrs()) {
+    for (auto &attr : op->getDiscardableAttrDictionary().getValue()) {
       if (auto user = dyn_cast<SymbolUserAttrInterface>(attr.getValue())) {
         if (!verifiedAttrs.insert(attr.getValue()))
           continue;
@@ -582,12 +609,13 @@ LogicalResult detail::verifySymbolTable(Operation *op) {
 
 LogicalResult detail::verifySymbol(Operation *op) {
   // Verify the name attribute.
-  if (!op->getAttrOfType<StringAttr>(mlir::SymbolTable::getSymbolAttrName()))
+  if (!getNameIfSymbol(op))
     return op->emitOpError() << "requires string attribute '"
                              << mlir::SymbolTable::getSymbolAttrName() << "'";
 
   // Verify the visibility attribute.
-  if (Attribute vis = op->getAttr(mlir::SymbolTable::getVisibilityAttrName())) {
+  if (Attribute vis =
+          getSymbolAttribute(op, mlir::SymbolTable::getVisibilityAttrName())) {
     StringAttr visStrAttr = llvm::dyn_cast<StringAttr>(vis);
     if (!visStrAttr)
       return op->emitOpError() << "requires visibility attribute '"
@@ -614,14 +642,20 @@ LogicalResult detail::verifySymbol(Operation *op) {
 static WalkResult
 walkSymbolRefs(Operation *op,
                function_ref<WalkResult(SymbolTable::SymbolUse)> callback) {
-  return op->getAttrDictionary().walk<WalkOrder::PreOrder>(
-      [&](SymbolRefAttr symbolRef) {
-        if (callback({op, symbolRef}).wasInterrupted())
-          return WalkResult::interrupt();
+  auto walk = [&](Attribute attr) {
+    return attr.walk<WalkOrder::PreOrder>([&](SymbolRefAttr symbolRef) {
+      if (callback({op, symbolRef}).wasInterrupted())
+        return WalkResult::interrupt();
 
-        // Don't walk nested references.
-        return WalkResult::skip();
-      });
+      // Don't walk nested references.
+      return WalkResult::skip();
+    });
+  };
+  if (walk(op->getRawDictionaryAttrs()).wasInterrupted())
+    return WalkResult::interrupt();
+  if (Attribute properties = op->getPropertiesAsAttribute())
+    return walk(properties);
+  return WalkResult::advance();
 }
 
 /// Walk all of the uses, for any symbol, that are nested within the given
diff --git a/mlir/lib/Interfaces/FunctionImplementation.cpp b/mlir/lib/Interfaces/FunctionImplementation.cpp
index 90f32896e8181..cc4eeedf69352 100644
--- a/mlir/lib/Interfaces/FunctionImplementation.cpp
+++ b/mlir/lib/Interfaces/FunctionImplementation.cpp
@@ -169,21 +169,27 @@ void function_interface_impl::printFunctionAttributes(
   SmallVector<StringRef, 8> ignoredAttrs = {SymbolTable::getSymbolAttrName()};
   ignoredAttrs.append(elided.begin(), elided.end());
 
-  p.printOptionalAttrDictWithKeyword(op->getAttrs(), ignoredAttrs);
+  NamedAttrList attrs(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, attrs);
+  p.printOptionalAttrDictWithKeyword(attrs, ignoredAttrs);
 }
 
 void function_interface_impl::printFunctionOp(
     OpAsmPrinter &p, FunctionOpInterface op, bool isVariadic,
     StringRef typeAttrName, StringAttr argAttrsName, StringAttr resAttrsName) {
   // Print the operation and the function name.
-  auto funcName =
-      op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName())
-          .getValue();
+  auto symbol = cast<SymbolOpInterface>(op.getOperation());
+  StringRef funcName = symbol.getName();
   p << ' ';
 
   StringRef visibilityAttrName = SymbolTable::getVisibilityAttrName();
-  if (auto visibility = op->getAttrOfType<StringAttr>(visibilityAttrName))
-    p << visibility.getValue() << ' ';
+  std::optional<Attribute> inherentVisibility =
+      op->getInherentAttr(visibilityAttrName);
+  Attribute visibility = inherentVisibility.has_value()
+                             ? *inherentVisibility
+                             : op->getDiscardableAttr(visibilityAttrName);
+  if (auto value = dyn_cast_or_null<StringAttr>(visibility))
+    p << value.getValue() << ' ';
   p.printSymbolName(funcName);
 
   ArrayRef<Type> argTypes = op.getArgumentTypes();
diff --git a/mlir/lib/Interfaces/Utils/MemorySlotUtils.cpp b/mlir/lib/Interfaces/Utils/MemorySlotUtils.cpp
index c497970b8b188..9beadc1a13c4a 100644
--- a/mlir/lib/Interfaces/Utils/MemorySlotUtils.cpp
+++ b/mlir/lib/Interfaces/Utils/MemorySlotUtils.cpp
@@ -31,7 +31,8 @@ Operation *mlir::memoryslot::replaceWithNewResults(RewriterBase &rewriter,
   RewriterBase::InsertionGuard guard(rewriter);
   rewriter.setInsertionPoint(op);
   OperationState state(op->getLoc(), op->getName(), op->getOperands(),
-                       resultTypes, op->getAttrs());
+                       resultTypes,
+                       op->getDiscardableAttrDictionary().getValue());
   state.propertiesAttr = op->getPropertiesAsAttribute();
   unsigned numRegions = op->getNumRegions();
   for (unsigned i = 0; i < numRegions; ++i)
diff --git a/mlir/lib/Pass/IRPrinting.cpp b/mlir/lib/Pass/IRPrinting.cpp
index 006ce3cbe794a..61a6adb0f1a24 100644
--- a/mlir/lib/Pass/IRPrinting.cpp
+++ b/mlir/lib/Pass/IRPrinting.cpp
@@ -70,9 +70,8 @@ static void printIRHeader(raw_ostream &out, StringRef title, Pass *pass,
   pass->printAsTextualPipeline(out);
   if (printModuleScope) {
     out << " ('" << op->getName() << "' operation";
-    if (auto symbolName =
-            op->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName()))
-      out << ": @" << symbolName.getValue();
+    if (auto symbol = dyn_cast<SymbolOpInterface>(op))
+      out << ": @" << symbol.getName();
     out << ")";
   }
   out << " //----- //\n";
@@ -230,10 +229,8 @@ getOpAndSymbolNames(Operation *op, StringRef passName,
   ++counters.try_emplace(op, -1).first->second;
   while (iter) {
     countPrefix.push_back(counters[iter]);
-    StringAttr symbolNameAttr =
-        iter->getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName());
-    std::string symbolName =
-        symbolNameAttr ? symbolNameAttr.str() : "no-symbol-name";
+    auto symbol = dyn_cast<SymbolOpInterface>(iter);
+    std::string symbolName = symbol ? symbol.getName().str() : "no-symbol-name";
     llvm::replace(symbolName, '/', '_');
     llvm::replace(symbolName, '\\', '_');
 
diff --git a/mlir/lib/Rewrite/ByteCode.cpp b/mlir/lib/Rewrite/ByteCode.cpp
index 2daf2635d96d5..27cacbbb07749 100644
--- a/mlir/lib/Rewrite/ByteCode.cpp
+++ b/mlir/lib/Rewrite/ByteCode.cpp
@@ -1788,7 +1788,9 @@ void ByteCodeExecutor::executeGetAttribute() {
   unsigned memIndex = read();
   Operation *op = read<Operation *>();
   StringAttr attrName = read<StringAttr>();
-  Attribute attr = op->getAttr(attrName);
+  Attribute attr = op->getDiscardableAttr(attrName);
+  if (op->getPropertiesStorageSize())
+    attr = op->getInherentAttr(attrName).value_or(attr);
 
   LDBG() << "  * Operation: " << *op << "\n  * Attribute: " << attrName
          << "\n  * Result: " << attr;
@@ -1857,7 +1859,11 @@ executeGetOperandsResults(RangeT values, Operation *op, unsigned index,
   } else if (op->hasTrait<AttrSizedSegmentsT>()) {
     LDBG() << "  * Extracting values from `" << attrSizedSegments << "`";
 
-    auto segmentAttr = op->getAttrOfType<DenseI32ArrayAttr>(attrSizedSegments);
+    Attribute rawSegmentAttr = op->getDiscardableAttr(attrSizedSegments);
+    if (op->getPropertiesStorageSize())
+      rawSegmentAttr =
+          op->getInherentAttr(attrSizedSegments).value_or(rawSegmentAttr);
+    auto segmentAttr = dyn_cast_or_null<DenseI32ArrayAttr>(rawSegmentAttr);
     if (!segmentAttr || segmentAttr.asArrayRef().size() <= index)
       return nullptr;
 
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index d5815fafcd6a7..8ec07bdae0ec2 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -1836,9 +1836,11 @@ CppEmitter::emitOperandsAndAttributes(Operation &op,
                                       ArrayRef<StringRef> exclude) {
   if (failed(emitOperands(op)))
     return failure();
+  NamedAttrList attrs(op.getRawDictionaryAttrs());
+  op.getName().populateInherentAttrs(&op, attrs);
   // Insert comma in between operands and non-filtered attributes if needed.
   if (op.getNumOperands() > 0) {
-    for (NamedAttribute attr : op.getAttrs()) {
+    for (NamedAttribute attr : attrs) {
       if (!llvm::is_contained(exclude, attr.getName().strref())) {
         os << ", ";
         break;
@@ -1854,7 +1856,7 @@ CppEmitter::emitOperandsAndAttributes(Operation &op,
       return failure();
     return success();
   };
-  return interleaveCommaWithError(op.getAttrs(), os, emitNamedAttribute);
+  return interleaveCommaWithError(attrs, os, emitNamedAttribute);
 }
 
 LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
diff --git a/mlir/lib/Target/LLVM/XeVM/Target.cpp b/mlir/lib/Target/LLVM/XeVM/Target.cpp
index 18f6b7b3e67c8..731ac4bd677f9 100644
--- a/mlir/lib/Target/LLVM/XeVM/Target.cpp
+++ b/mlir/lib/Target/LLVM/XeVM/Target.cpp
@@ -619,7 +619,8 @@ XeVMTargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
   auto xeTarget = cast<XeVMTargetAttr>(attribute);
   if (xeTarget.getTriple().starts_with("spirv")) {
     gpuMod.walk([&](LLVM::LLVMFuncOp funcOp) {
-      if (funcOp->hasAttr(gpu::GPUDialect::getKernelFuncAttrName())) {
+      if (funcOp->hasDiscardableAttr(
+              gpu::GPUDialect::getKernelFuncAttrName())) {
         funcOp.setIntelReqdSubGroupSize(16);
         return WalkResult::interrupt();
       }
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
index b6dee4e89ee4c..6733d6ee3ceb0 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMIRToLLVMTranslation.cpp
@@ -285,7 +285,7 @@ static LogicalResult setMmraAttr(llvm::MDNode *node, Operation *op,
     }
     mlirMmra = ArrayAttr::get(ctx, tags);
   }
-  op->setAttr(LLVMDialect::getMmraAttrName(), mlirMmra);
+  op->setDiscardableAttr(LLVMDialect::getMmraAttrName(), mlirMmra);
   return success();
 }
 
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index 3e0a228c773d5..aa62d0d0db4b1 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
@@ -639,7 +639,7 @@ convertOperationImpl(Operation &opInst, llvm::IRBuilderBase &builder,
                               invOp.getOpBundleTags(), moduleTranslation);
     ArrayRef<llvm::Value *> operandsRef(operands);
     llvm::InvokeInst *result;
-    if (auto attr = opInst.getAttrOfType<FlatSymbolRefAttr>("callee")) {
+    if (auto attr = invOp.getCalleeAttr()) {
       if (llvm::Function *function =
               moduleTranslation.lookupFunction(attr.getValue())) {
         result = builder.CreateInvoke(
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 1a861767b5512..bb20290b86822 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5715,8 +5715,7 @@ static void extractAtomicControlFlags(omp::AtomicUpdateOp atomicUpdateOp,
   isIgnoreDenormalMode = false;
   isFineGrainedMemory = false;
   isRemoteMemory = false;
-  if (atomicUpdateOp &&
-      atomicUpdateOp->hasAttr(atomicUpdateOp.getAtomicControlAttrName())) {
+  if (atomicUpdateOp && atomicUpdateOp.getAtomicControlAttr()) {
     mlir::omp::AtomicControlAttr atomicControlAttr =
         atomicUpdateOp.getAtomicControlAttr();
     isIgnoreDenormalMode = atomicControlAttr.getIgnoreDenormalMode();
@@ -9827,7 +9826,7 @@ convertDeclareTargetAttr(Operation *op, mlir::omp::DeclareTargetAttr attribute,
 
       std::vector<llvm::Triple> targetTriple;
       auto targetTripleAttr = dyn_cast_or_null<mlir::StringAttr>(
-          op->getParentOfType<mlir::ModuleOp>()->getAttr(
+          op->getParentOfType<mlir::ModuleOp>()->getDiscardableAttr(
               LLVM::LLVMDialect::getTargetTripleAttrName()));
       if (targetTripleAttr)
         targetTriple.emplace_back(targetTripleAttr.data());
diff --git a/mlir/lib/Target/LLVMIR/Dialect/ROCDL/ROCDLToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/ROCDL/ROCDLToLLVMIRTranslation.cpp
index 42393218f8705..18eb07a9e17df 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/ROCDL/ROCDLToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/ROCDL/ROCDLToLLVMIRTranslation.cpp
@@ -195,8 +195,8 @@ class ROCDLDialectLLVMIRTranslationInterface
 
       StringRef flatAttrName =
           dialect->getFlatWorkGroupSizeAttrHelper().getName();
-      if (auto flatAttr =
-              dyn_cast_if_present<StringAttr>(op->getAttr(flatAttrName))) {
+      if (auto flatAttr = dyn_cast_if_present<StringAttr>(
+              op->getDiscardableAttr(flatAttrName))) {
         if (flatAttr.getValue() != expectedFlatWorkGroupSize)
           return op->emitOpError(Twine(flatAttrName) +
                                  " must match rocdl.reqd_work_group_size");
@@ -204,8 +204,8 @@ class ROCDLDialectLLVMIRTranslationInterface
 
       StringRef maxFlatAttrName =
           dialect->getMaxFlatWorkGroupSizeAttrHelper().getName();
-      if (auto maxFlatAttr =
-              dyn_cast_if_present<IntegerAttr>(op->getAttr(maxFlatAttrName))) {
+      if (auto maxFlatAttr = dyn_cast_if_present<IntegerAttr>(
+              op->getDiscardableAttr(maxFlatAttrName))) {
         llvm::SmallString<16> expectedMaxFlatWorkGroupSize;
         llvm::raw_svector_ostream maxAttrValueStream(
             expectedMaxFlatWorkGroupSize);
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index 2ab4529ddef53..a22b3524a3c89 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -1025,8 +1025,9 @@ LogicalResult ModuleImport::convertDependentLibrariesMetadata() {
           libraries.push_back(mdString->getString());
     }
     if (!libraries.empty())
-      mlirModule->setAttr(LLVM::LLVMDialect::getDependentLibrariesAttrName(),
-                          builder.getStrArrayAttr(libraries));
+      mlirModule->setDiscardableAttr(
+          LLVM::LLVMDialect::getDependentLibrariesAttrName(),
+          builder.getStrArrayAttr(libraries));
   }
   return success();
 }
@@ -1042,8 +1043,9 @@ LogicalResult ModuleImport::convertIdentMetadata() {
       if (auto *md = dyn_cast<llvm::MDNode>(named.getOperand(0)))
         if (md->getNumOperands() == 1)
           if (auto *mdStr = dyn_cast<llvm::MDString>(md->getOperand(0)))
-            mlirModule->setAttr(LLVMDialect::getIdentAttrName(),
-                                builder.getStringAttr(mdStr->getString()));
+            mlirModule->setDiscardableAttr(
+                LLVMDialect::getIdentAttrName(),
+                builder.getStringAttr(mdStr->getString()));
   }
   return success();
 }
@@ -1059,8 +1061,9 @@ LogicalResult ModuleImport::convertCommandlineMetadata() {
       if (auto *md = dyn_cast<llvm::MDNode>(nmd.getOperand(0)))
         if (md->getNumOperands() == 1)
           if (auto *mdStr = dyn_cast<llvm::MDString>(md->getOperand(0)))
-            mlirModule->setAttr(LLVMDialect::getCommandlineAttrName(),
-                                builder.getStringAttr(mdStr->getString()));
+            mlirModule->setDiscardableAttr(
+                LLVMDialect::getCommandlineAttrName(),
+                builder.getStringAttr(mdStr->getString()));
   }
   return success();
 }
@@ -1179,13 +1182,13 @@ LogicalResult ModuleImport::convertDataLayout() {
   for (StringRef token : dataLayoutImporter.getUnhandledTokens())
     emitWarning(loc, "unhandled data layout token: ") << token;
 
-  mlirModule->setAttr(DLTIDialect::kDataLayoutAttrName,
-                      dataLayoutImporter.getDataLayoutSpec());
+  mlirModule->setDiscardableAttr(DLTIDialect::kDataLayoutAttrName,
+                                 dataLayoutImporter.getDataLayoutSpec());
   return success();
 }
 
 void ModuleImport::convertTargetTriple() {
-  mlirModule->setAttr(
+  mlirModule->setDiscardableAttr(
       LLVM::LLVMDialect::getTargetTripleAttrName(),
       builder.getStringAttr(llvmModule->getTargetTriple().str()));
 }
@@ -1201,8 +1204,8 @@ void ModuleImport::convertModuleLevelAsm() {
         asmArrayAttr.push_back(builder.getStringAttr(line));
   }
 
-  mlirModule->setAttr(LLVM::LLVMDialect::getModuleLevelAsmAttrName(),
-                      builder.getArrayAttr(asmArrayAttr));
+  mlirModule->setDiscardableAttr(LLVM::LLVMDialect::getModuleLevelAsmAttrName(),
+                                 builder.getArrayAttr(asmArrayAttr));
 }
 
 LogicalResult ModuleImport::convertFunctions() {
@@ -1284,7 +1287,7 @@ void ModuleImport::setFastmathFlagsAttr(llvm::Instruction *inst,
   value = bitEnumSet(value, FastmathFlags::afn, flags.approxFunc());
   value = bitEnumSet(value, FastmathFlags::reassoc, flags.allowReassoc());
   FastmathFlagsAttr attr = FastmathFlagsAttr::get(builder.getContext(), value);
-  iface->setAttr(iface.getFastmathAttrName(), attr);
+  iface.setFastmathAttr(attr);
 }
 
 /// Returns `type` if it is a builtin integer or floating-point vector type that
diff --git a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
index b87a581a5185e..eed0ee3f8f1d2 100644
--- a/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp
@@ -949,9 +949,11 @@ llvm::CallInst *mlir::LLVM::detail::createIntrinsicCall(
   SmallVector<llvm::OperandBundleDef> opBundles;
   size_t numOpBundleOperands = 0;
   auto opBundleSizesAttr = cast_if_present<DenseI32ArrayAttr>(
-      intrOp->getAttr(LLVMDialect::getOpBundleSizesAttrName()));
+      intrOp->getInherentAttr(LLVMDialect::getOpBundleSizesAttrName())
+          .value_or(Attribute{}));
   auto opBundleTagsAttr = cast_if_present<ArrayAttr>(
-      intrOp->getAttr(LLVMDialect::getOpBundleTagsAttrName()));
+      intrOp->getInherentAttr(LLVMDialect::getOpBundleTagsAttrName())
+          .value_or(Attribute{}));
 
   if (opBundleSizesAttr && opBundleTagsAttr) {
     ArrayRef<int> opBundleSizes = opBundleSizesAttr.asArrayRef();
@@ -982,7 +984,7 @@ llvm::CallInst *mlir::LLVM::detail::createIntrinsicCall(
   SmallVector<llvm::Value *> args(immArgPositions.size() + operands.size());
   for (auto [immArgPos, immArgName] :
        llvm::zip(immArgPositions, immArgAttrNames)) {
-    Attribute attr = intrOp->getAttr(immArgName);
+    Attribute attr = intrOp->getInherentAttr(immArgName).value_or(Attribute{});
     if (auto intrinsicIntegerAttr =
             dyn_cast<LLVM::IntrinsicIntegerAttrInterface>(attr))
       attr = intrinsicIntegerAttr.getIntegerAttr();
@@ -2426,7 +2428,7 @@ LogicalResult ModuleTranslation::createTBAAMetadata() {
 }
 
 LogicalResult ModuleTranslation::createIdentMetadata() {
-  if (auto attr = mlirModule->getAttrOfType<StringAttr>(
+  if (auto attr = mlirModule->getDiscardableAttrOfType<StringAttr>(
           LLVMDialect::getIdentAttrName())) {
     StringRef ident = attr;
     llvm::LLVMContext &ctx = llvmModule->getContext();
@@ -2440,7 +2442,7 @@ LogicalResult ModuleTranslation::createIdentMetadata() {
 }
 
 LogicalResult ModuleTranslation::createCommandlineMetadata() {
-  if (auto attr = mlirModule->getAttrOfType<StringAttr>(
+  if (auto attr = mlirModule->getDiscardableAttrOfType<StringAttr>(
           LLVMDialect::getCommandlineAttrName())) {
     StringRef cmdLine = attr;
     llvm::LLVMContext &ctx = llvmModule->getContext();
diff --git a/mlir/lib/Target/LLVMIR/Transforms/TargetToDataLayout.cpp b/mlir/lib/Target/LLVMIR/Transforms/TargetToDataLayout.cpp
index c0f9ceb6313d0..1246c9cacf98f 100644
--- a/mlir/lib/Target/LLVMIR/Transforms/TargetToDataLayout.cpp
+++ b/mlir/lib/Target/LLVMIR/Transforms/TargetToDataLayout.cpp
@@ -33,7 +33,7 @@ struct TargetToDataLayoutPass
     if (initializeLLVMTargets)
       LLVM::detail::initializeBackendsOnce();
 
-    auto targetAttr = op->getAttrOfType<LLVM::TargetAttrInterface>(
+    auto targetAttr = op->getDiscardableAttrOfType<LLVM::TargetAttrInterface>(
         LLVM::LLVMDialect::getTargetAttrName());
     if (!targetAttr) {
       op->emitError()
@@ -52,11 +52,12 @@ struct TargetToDataLayoutPass
     DataLayoutSpecInterface dataLayoutSpec =
         mlir::translateDataLayout(dataLayout.value(), &getContext());
 
-    if (auto existingDlSpec = op->getAttrOfType<DataLayoutSpecInterface>(
-            DLTIDialect::kDataLayoutAttrName)) {
+    if (auto existingDlSpec =
+            op->getDiscardableAttrOfType<DataLayoutSpecInterface>(
+                DLTIDialect::kDataLayoutAttrName)) {
       dataLayoutSpec = existingDlSpec.combineWith({dataLayoutSpec});
     }
 
-    op->setAttr(DLTIDialect::kDataLayoutAttrName, dataLayoutSpec);
+    op->setDiscardableAttr(DLTIDialect::kDataLayoutAttrName, dataLayoutSpec);
   }
 };
diff --git a/mlir/lib/Target/LLVMIR/Transforms/TargetToTargetFeatures.cpp b/mlir/lib/Target/LLVMIR/Transforms/TargetToTargetFeatures.cpp
index 24f37cce513af..0cc5f5aabb630 100644
--- a/mlir/lib/Target/LLVMIR/Transforms/TargetToTargetFeatures.cpp
+++ b/mlir/lib/Target/LLVMIR/Transforms/TargetToTargetFeatures.cpp
@@ -37,7 +37,7 @@ struct TargetToTargetFeaturesPass
     if (initializeLLVMTargets)
       LLVM::detail::initializeBackendsOnce();
 
-    auto targetAttr = op->getAttrOfType<LLVM::TargetAttr>(
+    auto targetAttr = op->getDiscardableAttrOfType<LLVM::TargetAttr>(
         LLVM::LLVMDialect::getTargetAttrName());
     if (!targetAttr) {
       op->emitError() << "no LLVM::TargetAttr attribute at key \""
@@ -74,6 +74,7 @@ struct TargetToTargetFeaturesPass
         LLVM::TargetAttr::get(&getContext(), targetAttr.getTriple(),
                               targetAttr.getChip(), fullTargetFeaturesAttr);
 
-    op->setAttr(LLVM::LLVMDialect::getTargetAttrName(), updatedTargetAttr);
+    op->setDiscardableAttr(LLVM::LLVMDialect::getTargetAttrName(),
+                           updatedTargetAttr);
   }
 };
diff --git a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
index 9e44f43ad889d..8e02d070c0cfa 100644
--- a/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
+++ b/mlir/lib/Target/SPIRV/Deserialization/Deserializer.cpp
@@ -44,6 +44,15 @@ static inline bool isFnEntryBlock(Block *block) {
          isa_and_nonnull<spirv::FuncOp>(block->getParentOp());
 }
 
+static void setInherentOrDiscardableAttr(Operation *op, StringAttr name,
+                                         Attribute value) {
+  if (op->getName().getInherentAttr(op, name).has_value()) {
+    op->getName().setInherentAttr(op, name, value);
+    return;
+  }
+  op->setDiscardableAttr(name, value);
+}
+
 //===----------------------------------------------------------------------===//
 // Deserializer Method Definitions
 //===----------------------------------------------------------------------===//
@@ -208,10 +217,8 @@ spirv::Deserializer::processExtInstImport(ArrayRef<uint32_t> words) {
 }
 
 void spirv::Deserializer::attachVCETriple() {
-  (*module)->setAttr(
-      spirv::ModuleOp::getVCETripleAttrName(),
-      spirv::VerCapExtAttr::get(version, capabilities.getArrayRef(),
-                                extensions.getArrayRef(), context));
+  module->setVceTripleAttr(spirv::VerCapExtAttr::get(
+      version, capabilities.getArrayRef(), extensions.getArrayRef(), context));
 }
 
 LogicalResult
@@ -219,14 +226,10 @@ spirv::Deserializer::processMemoryModel(ArrayRef<uint32_t> operands) {
   if (operands.size() != 2)
     return emitError(unknownLoc, "OpMemoryModel must have two operands");
 
-  (*module)->setAttr(
-      module->getAddressingModelAttrName(),
-      opBuilder.getAttr<spirv::AddressingModelAttr>(
-          static_cast<spirv::AddressingModel>(operands.front())));
+  module->setAddressingModel(
+      static_cast<spirv::AddressingModel>(operands.front()));
 
-  (*module)->setAttr(module->getMemoryModelAttrName(),
-                     opBuilder.getAttr<spirv::MemoryModelAttr>(
-                         static_cast<spirv::MemoryModel>(operands.back())));
+  module->setMemoryModel(static_cast<spirv::MemoryModel>(operands.back()));
 
   return success();
 }
@@ -438,7 +441,7 @@ LogicalResult spirv::Deserializer::resolveDeferredIdDecorations() {
              << decorationName << " references unknown target <id> "
              << entry.targetID;
 
-    targetOp->setAttr(symbol, symRef);
+    setInherentOrDiscardableAttr(targetOp, symbol, symRef);
   }
   return success();
 }
@@ -580,7 +583,7 @@ spirv::Deserializer::processFunction(ArrayRef<uint32_t> operands) {
   // Processing other function attributes.
   if (decorations.count(fnID)) {
     for (auto attr : decorations[fnID].getAttrs()) {
-      funcOp->setAttr(attr.getName(), attr.getValue());
+      setInherentOrDiscardableAttr(funcOp, attr.getName(), attr.getValue());
     }
   }
   curFunction = funcMap[fnID] = funcOp;
@@ -986,7 +989,7 @@ spirv::Deserializer::createSpecConstant(Location loc, uint32_t resultID,
                                           defaultValue);
   if (decorations.count(resultID)) {
     for (auto attr : decorations[resultID].getAttrs())
-      op->setAttr(attr.getName(), attr.getValue());
+      setInherentOrDiscardableAttr(op, attr.getName(), attr.getValue());
   }
   specConstMap[resultID] = op;
   return op;
@@ -1073,7 +1076,7 @@ spirv::Deserializer::processGlobalVariable(ArrayRef<uint32_t> operands) {
   // Decorations.
   if (decorations.count(variableID)) {
     for (auto attr : decorations[variableID].getAttrs())
-      varOp->setAttr(attr.getName(), attr.getValue());
+      setInherentOrDiscardableAttr(varOp, attr.getName(), attr.getValue());
   }
   globalVariableMap[variableID] = varOp;
   return success();
diff --git a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
index 32fa603e6c23d..9ed2401ac4099 100644
--- a/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/SerializeOps.cpp
@@ -109,7 +109,7 @@ LogicalResult Serializer::processSpecConstantOp(spirv::SpecConstantOp op) {
   if (auto resultID = prepareConstantScalar(op.getLoc(), op.getDefaultValue(),
                                             /*isSpec=*/true)) {
     // Emit the OpDecorate instruction for SpecId.
-    if (auto specID = op->getAttrOfType<IntegerAttr>("spec_id")) {
+    if (auto specID = op->getDiscardableAttrOfType<IntegerAttr>("spec_id")) {
       auto val = static_cast<uint32_t>(specID.getInt());
       if (failed(emitDecoration(resultID, spirv::Decoration::SpecId, {val})))
         return failure();
@@ -393,7 +393,9 @@ LogicalResult Serializer::processFuncOp(spirv::FuncOp op) {
   // Only attributes we should be considering for decoration are the
   // ::mlir::spirv::Decoration attributes.
 
-  for (auto attr : op->getAttrs()) {
+  NamedAttrList attrs(op->getDiscardableAttrDictionary().getValue());
+  op->getName().populateInherentAttrs(op, attrs);
+  for (auto attr : attrs) {
     // Only generate OpDecorate op for spirv::Decoration attributes.
     auto isValidDecoration = mlir::spirv::symbolizeEnum<spirv::Decoration>(
         llvm::convertToCamelFromSnakeCase(attr.getName().strref(),
@@ -669,11 +671,7 @@ LogicalResult Serializer::processVariableOp(spirv::VariableOp op) {
   resultID = getNextID();
   valueIDMap[op.getResult()] = resultID;
   operands.push_back(resultID);
-  auto attr = op->getAttr(spirv::attributeName<spirv::StorageClass>());
-  if (attr) {
-    operands.push_back(
-        static_cast<uint32_t>(cast<spirv::StorageClassAttr>(attr).getValue()));
-  }
+  operands.push_back(static_cast<uint32_t>(op.getStorageClass()));
   elidedAttrs.push_back(spirv::attributeName<spirv::StorageClass>());
   for (auto arg : op.getODSOperands(0)) {
     auto argID = getValueID(arg);
@@ -685,7 +683,7 @@ LogicalResult Serializer::processVariableOp(spirv::VariableOp op) {
   if (failed(emitDebugLine(functionHeader, op.getLoc())))
     return failure();
   encodeInstructionInto(functionHeader, spirv::Opcode::OpVariable, operands);
-  for (auto attr : op->getAttrs()) {
+  for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
     if (llvm::any_of(elidedAttrs, [&](StringRef elided) {
           return attr.getName() == elided;
         })) {
@@ -728,7 +726,7 @@ Serializer::processGlobalVariableOp(spirv::GlobalVariableOp varOp) {
   StringRef initAttrName = varOp.getInitializerAttrName().getValue();
   if (std::optional<StringRef> initSymbolName = varOp.getInitializer()) {
     uint32_t initializerID = 0;
-    auto initRef = varOp->getAttrOfType<FlatSymbolRefAttr>(initAttrName);
+    FlatSymbolRefAttr initRef = varOp.getInitializerAttr();
     Operation *initOp = SymbolTable::lookupNearestSymbolFrom(
         varOp->getParentOp(), initRef.getAttr());
 
@@ -752,7 +750,9 @@ Serializer::processGlobalVariableOp(spirv::GlobalVariableOp varOp) {
   elidedAttrs.push_back(initAttrName);
 
   // Encode decorations.
-  for (auto attr : varOp->getAttrs()) {
+  NamedAttrList attrs(varOp->getDiscardableAttrDictionary().getValue());
+  varOp->getName().populateInherentAttrs(varOp, attrs);
+  for (auto attr : attrs) {
     if (llvm::any_of(elidedAttrs, [&](StringRef elided) {
           return attr.getName() == elided;
         })) {
@@ -1111,34 +1111,26 @@ Serializer::processOp<spirv::CopyMemoryOp>(spirv::CopyMemoryOp op) {
   }
 
   StringAttr memoryAccess = op.getMemoryAccessAttrName();
-  if (auto attr = op->getAttr(memoryAccess)) {
-    operands.push_back(
-        static_cast<uint32_t>(cast<spirv::MemoryAccessAttr>(attr).getValue()));
-  }
+  if (std::optional<spirv::MemoryAccess> value = op.getMemoryAccess())
+    operands.push_back(static_cast<uint32_t>(*value));
 
   elidedAttrs.push_back(memoryAccess.strref());
 
   StringAttr alignment = op.getAlignmentAttrName();
-  if (auto attr = op->getAttr(alignment)) {
-    operands.push_back(static_cast<uint32_t>(
-        cast<IntegerAttr>(attr).getValue().getZExtValue()));
-  }
+  if (std::optional<uint32_t> value = op.getAlignment())
+    operands.push_back(*value);
 
   elidedAttrs.push_back(alignment.strref());
 
   StringAttr sourceMemoryAccess = op.getSourceMemoryAccessAttrName();
-  if (auto attr = op->getAttr(sourceMemoryAccess)) {
-    operands.push_back(
-        static_cast<uint32_t>(cast<spirv::MemoryAccessAttr>(attr).getValue()));
-  }
+  if (std::optional<spirv::MemoryAccess> value = op.getSourceMemoryAccess())
+    operands.push_back(static_cast<uint32_t>(*value));
 
   elidedAttrs.push_back(sourceMemoryAccess.strref());
 
   StringAttr sourceAlignment = op.getSourceAlignmentAttrName();
-  if (auto attr = op->getAttr(sourceAlignment)) {
-    operands.push_back(static_cast<uint32_t>(
-        cast<IntegerAttr>(attr).getValue().getZExtValue()));
-  }
+  if (std::optional<uint32_t> value = op.getSourceAlignment())
+    operands.push_back(*value);
 
   elidedAttrs.push_back(sourceAlignment.strref());
   if (failed(emitDebugLine(functionBody, op.getLoc())))
diff --git a/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp b/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
index c3c065b693623..3919c3ed5ec36 100644
--- a/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
+++ b/mlir/lib/Target/SPIRV/Serialization/Serializer.cpp
@@ -290,15 +290,8 @@ LogicalResult Serializer::processExtension() {
 }
 
 void Serializer::processMemoryModel() {
-  StringAttr memoryModelName = module.getMemoryModelAttrName();
-  auto mm = static_cast<uint32_t>(
-      module->getAttrOfType<spirv::MemoryModelAttr>(memoryModelName)
-          .getValue());
-
-  StringAttr addressingModelName = module.getAddressingModelAttrName();
-  auto am = static_cast<uint32_t>(
-      module->getAttrOfType<spirv::AddressingModelAttr>(addressingModelName)
-          .getValue());
+  auto mm = static_cast<uint32_t>(module.getMemoryModel());
+  auto am = static_cast<uint32_t>(module.getAddressingModel());
 
   encodeInstructionInto(memoryModel, spirv::Opcode::OpMemoryModel, {am, mm});
 }
@@ -1738,7 +1731,7 @@ Serializer::processCompositeConstructOp(spirv::CompositeConstructOp op) {
   encodeInstructionWithContinuationInto(
       functionBody, spirv::Opcode::OpCompositeConstruct, operands);
 
-  for (auto attr : op->getAttrs()) {
+  for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
     if (failed(processDecoration(loc, resultID, attr)))
       return failure();
   }
@@ -1781,7 +1774,7 @@ LogicalResult Serializer::processOpWithoutGrammarAttr(Operation *op,
   }
 
   if (op->getNumResults() != 0) {
-    for (auto attr : op->getAttrs()) {
+    for (auto attr : op->getDiscardableAttrDictionary().getValue()) {
       if (failed(processDecoration(loc, resultID, attr)))
         return failure();
     }
diff --git a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
index 96448ed987629..f64a429001399 100644
--- a/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
+++ b/mlir/lib/Target/Wasm/TranslateFromWasm.cpp
@@ -21,6 +21,7 @@
 #include "mlir/Support/LLVM.h"
 #include "mlir/Target/Wasm/WasmBinaryEncoding.h"
 #include "mlir/Target/Wasm/WasmImporter.h"
+#include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/DebugLog.h"
 #include "llvm/Support/Endian.h"
@@ -2008,7 +2009,10 @@ WasmBinaryParser::parseSectionItem<WasmSectionType::EXPORT>(ParserHead &ph,
     return failure();
 
   Operation *op = SymbolTable::lookupSymbolIn(mOp, *currentSymbol);
-  op->setAttr("exported", UnitAttr::get(op->getContext()));
+  TypeSwitch<Operation *>(op)
+      .Case<wasmssa::FuncOp, wasmssa::GlobalOp, wasmssa::MemOp,
+            wasmssa::TableOp>([](auto symbol) { symbol.setExported(true); })
+      .Default([](Operation *) { llvm_unreachable("unexpected export kind"); });
   StringAttr symName = SymbolTable::getSymbolName(op);
   return SymbolTable{mOp}.rename(symName, *exportName);
 }
diff --git a/mlir/lib/Transforms/Utils/DialectConversion.cpp b/mlir/lib/Transforms/Utils/DialectConversion.cpp
index 75d5620f0f298..507f0650169ca 100644
--- a/mlir/lib/Transforms/Utils/DialectConversion.cpp
+++ b/mlir/lib/Transforms/Utils/DialectConversion.cpp
@@ -217,7 +217,7 @@ static Operation *getCommonDefiningOp(const ValueVector &values) {
 static bool isPureTypeConversion(const ValueVector &values) {
   assert(!values.empty() && "expected non-empty value vector");
   Operation *op = getCommonDefiningOp(values);
-  return op && op->hasAttr(kPureTypeConversionMarker);
+  return op && op->hasDiscardableAttr(kPureTypeConversionMarker);
 }
 
 ValueVector ConversionValueMapping::lookup(const ValueVector &from) const {
@@ -683,7 +683,8 @@ class ModifyOperationRewrite : public OperationRewrite {
   ModifyOperationRewrite(ConversionPatternRewriterImpl &rewriterImpl,
                          Operation *op)
       : OperationRewrite(Kind::ModifyOperation, rewriterImpl, op),
-        name(op->getName()), loc(op->getLoc()), attrs(op->getAttrDictionary()),
+        name(op->getName()), loc(op->getLoc()),
+        attrs(op->getDiscardableAttrDictionary()),
         operands(op->operand_begin(), op->operand_end()),
         successors(op->successor_begin(), op->successor_end()) {
     if (PropertyRef prop = op->getPropertiesStorage()) {
@@ -721,7 +722,7 @@ class ModifyOperationRewrite : public OperationRewrite {
 
   void rollback() override {
     op->setLoc(loc);
-    op->setAttrs(attrs);
+    op->setDiscardableAttrs(attrs);
     op->setOperands(operands);
     for (const auto &it : llvm::enumerate(successors))
       op->setSuccessor(it.value(), it.index());
@@ -1723,10 +1724,11 @@ ValueRange ConversionPatternRewriterImpl::buildUnresolvedMaterialization(
   if (config.attachDebugMaterializationKind) {
     StringRef kindStr =
         kind == MaterializationKind::Source ? "source" : "target";
-    convertOp->setAttr("__kind__", builder.getStringAttr(kindStr));
+    convertOp->setDiscardableAttr("__kind__", builder.getStringAttr(kindStr));
   }
   if (isPureTypeConversion)
-    convertOp->setAttr(kPureTypeConversionMarker, builder.getUnitAttr());
+    convertOp->setDiscardableAttr(kPureTypeConversionMarker,
+                                  builder.getUnitAttr());
 
   // Register the materialization.
   unresolvedMaterializations[convertOp] =
@@ -3503,7 +3505,7 @@ LogicalResult OperationConverter::applyConversion(ArrayRef<Operation *> ops) {
 
   // Drop markers.
   for (UnrealizedConversionCastOp castOp : remainingCastOps)
-    castOp->removeAttr(kPureTypeConversionMarker);
+    castOp->removeDiscardableAttr(kPureTypeConversionMarker);
 
   // Try to legalize all unresolved materializations.
   if (rewriter.getConfig().buildMaterializations) {
@@ -3947,7 +3949,8 @@ mlir::convertOpResultTypes(Operation *op, ValueRange operands,
     return rewriter.notifyMatchFailure(loc, "couldn't convert return types");
 
   newOp.addTypes(newResultTypes);
-  newOp.addAttributes(op->getAttrs());
+  newOp.addAttributes(op->getDiscardableAttrDictionary().getValue());
+  newOp.propertiesAttr = op->getPropertiesAsAttribute();
   return rewriter.create(newOp);
 }
 
diff --git a/mlir/lib/Transforms/ViewOpGraph.cpp b/mlir/lib/Transforms/ViewOpGraph.cpp
index 2d7e40d18efca..9b4a24861048a 100644
--- a/mlir/lib/Transforms/ViewOpGraph.cpp
+++ b/mlir/lib/Transforms/ViewOpGraph.cpp
@@ -316,7 +316,9 @@ class PrintOpPass : public impl::ViewOpGraphPassBase<PrintOpPass> {
       // Print attributes.
       if (printAttrs) {
         os << "\\l";
-        for (const NamedAttribute &attr : op->getAttrs()) {
+        NamedAttrList attrs(op->getDiscardableAttrDictionary());
+        op->getName().populateInherentAttrs(op, attrs);
+        for (const NamedAttribute &attr : attrs) {
           os << escapeLabelString(attr.getName().getValue().str()) << ": ";
           emitMlirAttr(os, attr.getValue());
           os << "\\l";
@@ -344,10 +346,12 @@ class PrintOpPass : public impl::ViewOpGraphPassBase<PrintOpPass> {
       os << op->getName() << "\\l";
 
       // Print attributes.
-      if (printAttrs && !op->getAttrs().empty()) {
+      NamedAttrList attrs(op->getDiscardableAttrDictionary());
+      op->getName().populateInherentAttrs(op, attrs);
+      if (printAttrs && !attrs.empty()) {
         // Extra line break to separate attributes from the operation name.
         os << "\\l";
-        for (const NamedAttribute &attr : op->getAttrs()) {
+        for (const NamedAttribute &attr : attrs) {
           os << attr.getName().getValue() << ": ";
           emitMlirAttr(os, attr.getValue());
           os << "\\l";
diff --git a/mlir/python/mlir/dialects/gpu/__init__.py b/mlir/python/mlir/dialects/gpu/__init__.py
index b75bd525c9487..2ce4e3a24bf6b 100644
--- a/mlir/python/mlir/dialects/gpu/__init__.py
+++ b/mlir/python/mlir/dialects/gpu/__init__.py
@@ -86,7 +86,10 @@ def __init__(
             if not isinstance(function_type, TypeAttr)
             else function_type
         )
+        if not isinstance(sym_name, (str, StringAttr)):
+            raise ValueError("sym_name must be a string or a StringAttr")
         super().__init__(
+            sym_name,
             function_type,
             arg_attrs=arg_attrs,
             res_attrs=res_attrs,
@@ -96,13 +99,6 @@ def __init__(
             ip=ip,
         )
 
-        if isinstance(sym_name, str):
-            self.attributes[self.SYM_NAME_ATTR_NAME] = StringAttr.get(sym_name)
-        elif isinstance(sym_name, StringAttr):
-            self.attributes[self.SYM_NAME_ATTR_NAME] = sym_name
-        else:
-            raise ValueError("sym_name must be a string or a StringAttr")
-
         if kernel:
             self.attributes[self.KERNEL_ATTR_NAME] = UnitAttr.get()
 
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
index 327e807873714..4ab2a3170ef61 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDeadCodeAnalysis.cpp
@@ -19,7 +19,7 @@ using namespace mlir::dataflow;
 static void printAnalysisResults(DataFlowSolver &solver, Operation *op,
                                  raw_ostream &os) {
   op->walk([&](Operation *op) {
-    auto tag = op->getAttrOfType<StringAttr>("tag");
+    auto tag = op->getDiscardableAttrOfType<StringAttr>("tag");
     if (!tag)
       return;
     os << tag.getValue() << ":\n";
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
index e1ea28c06659f..f969869b92482 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDenseBackwardDataFlowAnalysis.cpp
@@ -320,7 +320,7 @@ struct TestNextAccessPass
       innerAttrs.reserve(nextAcc->get().size());
       for (Operation *nextAccOp : nextAcc->get()) {
         if (auto nextAccTag =
-                nextAccOp->getAttrOfType<StringAttr>(kTagAttrName)) {
+                nextAccOp->getDiscardableAttrOfType<StringAttr>(kTagAttrName)) {
           innerAttrs.push_back(nextAccTag);
           continue;
         }
@@ -356,7 +356,7 @@ struct TestNextAccessPass
     LDBG() << "  Dataflow solver completed successfully";
     LDBG() << "  Walking operations to set next access attributes";
     op->walk([&](Operation *op) {
-      auto tag = op->getAttrOfType<StringAttr>(kTagAttrName);
+      auto tag = op->getDiscardableAttrOfType<StringAttr>(kTagAttrName);
       if (!tag)
         return;
 
@@ -364,8 +364,8 @@ struct TestNextAccessPass
              << OpWithFlags(op, OpPrintingFlags().skipRegions());
       const NextAccess *nextAccess =
           solver.lookupState<NextAccess>(solver.getProgramPointAfter(op));
-      op->setAttr(kNextAccessAttrName,
-                  makeNextAccessAttribute(op, solver, nextAccess));
+      op->setDiscardableAttr(kNextAccessAttrName,
+                             makeNextAccessAttribute(op, solver, nextAccess));
 
       auto iface = dyn_cast<RegionBranchOpInterface>(op);
       if (!iface)
@@ -383,8 +383,9 @@ struct TestNextAccessPass
         entryPointNextAccess.push_back(makeNextAccessAttribute(
             op, solver, solver.lookupState<NextAccess>(successorPoint)));
       }
-      op->setAttr(kAtEntryPointAttrName,
-                  ArrayAttr::get(op->getContext(), entryPointNextAccess));
+      op->setDiscardableAttr(
+          kAtEntryPointAttrName,
+          ArrayAttr::get(op->getContext(), entryPointNextAccess));
     });
   }
 };
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDenseDataFlowAnalysis.h b/mlir/test/lib/Analysis/DataFlow/TestDenseDataFlowAnalysis.h
index 6012c90f84539..9b0508660e9cd 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDenseDataFlowAnalysis.h
+++ b/mlir/test/lib/Analysis/DataFlow/TestDenseDataFlowAnalysis.h
@@ -196,7 +196,7 @@ class UnderlyingValueAnalysis
                  ArrayRef<const UnderlyingValueLattice *> operands,
                  ArrayRef<UnderlyingValueLattice *> results) override {
     // Hook to test error propagation from visitOperation.
-    if (op->hasAttr("always_fail"))
+    if (op->hasDiscardableAttr("always_fail"))
       return op->emitError("this op is always fails");
 
     setAllToEntryStates(results);
diff --git a/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
index f2384f32948a2..c8b67b4ba61c0 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestDenseForwardDataFlowAnalysis.cpp
@@ -252,7 +252,7 @@ struct TestLastModifiedPass
     // Note that if the underlying value could not be computed or is unknown, we
     // conservatively treat the result also unknown.
     op->walk([&](Operation *op) {
-      auto tag = op->getAttrOfType<StringAttr>("tag");
+      auto tag = op->getDiscardableAttrOfType<StringAttr>("tag");
       if (!tag)
         return;
       os << "test_tag: " << tag.getValue() << ":\n";
@@ -285,7 +285,8 @@ struct TestLastModifiedPass
           } else {
             for (Operation *lastModifier : lastMod->get()) {
               if (auto tagName =
-                      lastModifier->getAttrOfType<StringAttr>("tag_name")) {
+                      lastModifier->getDiscardableAttrOfType<StringAttr>(
+                          "tag_name")) {
                 os << "  - " << tagName.getValue() << "\n";
               } else {
                 os << "  - " << lastModifier->getName() << "\n";
diff --git a/mlir/test/lib/Analysis/DataFlow/TestIntegerDivisibilityAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestIntegerDivisibilityAnalysis.cpp
index 626cbc0fac7aa..24d01c61e3783 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestIntegerDivisibilityAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestIntegerDivisibilityAnalysis.cpp
@@ -71,7 +71,8 @@ struct TestIntegerDivisibilityAnalysisPass
       const auto *lattice =
           solver.lookupState<IntegerDivisibilityLattice>(value);
       if (!lattice || lattice->getValue().isUninitialized()) {
-        op->setAttr("divisibility", StringAttr::get(context, "uninitialized"));
+        op->setDiscardableAttr("divisibility",
+                               StringAttr::get(context, "uninitialized"));
         continue;
       }
 
@@ -80,7 +81,7 @@ struct TestIntegerDivisibilityAnalysisPass
       std::string result;
       llvm::raw_string_ostream os(result);
       os << "udiv = " << div.udiv() << ", sdiv = " << div.sdiv();
-      op->setAttr("divisibility", StringAttr::get(context, result));
+      op->setDiscardableAttr("divisibility", StringAttr::get(context, result));
     }
   }
 };
diff --git a/mlir/test/lib/Analysis/DataFlow/TestLivenessAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestLivenessAnalysis.cpp
index 2c9f46411f602..22c2a6bffd52a 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestLivenessAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestLivenessAnalysis.cpp
@@ -38,7 +38,7 @@ struct TestLivenessAnalysisPass
     raw_ostream &os = llvm::outs();
 
     op->walk([&](Operation *op) {
-      auto tag = op->getAttrOfType<StringAttr>("tag");
+      auto tag = op->getDiscardableAttrOfType<StringAttr>("tag");
       if (!tag)
         return;
       os << "test_tag: " << tag.getValue() << ":\n";
diff --git a/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp b/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
index b1978880e2bd6..18c74093e518f 100644
--- a/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
+++ b/mlir/test/lib/Analysis/DataFlow/TestSparseBackwardDataFlowAnalysis.cpp
@@ -104,7 +104,7 @@ WrittenToAnalysis::visitOperation(Operation *op, ArrayRef<WrittenTo *> operands,
                                   ArrayRef<const WrittenTo *> results) {
   if (auto store = dyn_cast<memref::StoreOp>(op)) {
     SetVector<StringAttr> newWrites;
-    newWrites.insert(op->getAttrOfType<StringAttr>("tag_name"));
+    newWrites.insert(op->getDiscardableAttrOfType<StringAttr>("tag_name"));
     propagateIfChanged(operands[0],
                        operands[0]->getValue().addWrites(newWrites));
     return success();
@@ -148,7 +148,7 @@ void WrittenToAnalysis::visitExternalCall(CallOpInterface call,
 
   for (WrittenTo *lattice : operands) {
     SetVector<StringAttr> newWrites;
-    StringAttr name = call->getAttrOfType<StringAttr>("tag_name");
+    StringAttr name = call->getDiscardableAttrOfType<StringAttr>("tag_name");
     if (!name) {
       name = StringAttr::get(call->getContext(),
                              call.getOperation()->getName().getStringRef());
@@ -194,7 +194,7 @@ struct TestWrittenToPass
 
     raw_ostream &os = llvm::outs();
     op->walk([&](Operation *op) {
-      auto tag = op->getAttrOfType<StringAttr>("tag");
+      auto tag = op->getDiscardableAttrOfType<StringAttr>("tag");
       if (!tag)
         return;
       os << "test_tag: " << tag.getValue() << ":\n";
diff --git a/mlir/test/lib/Analysis/TestAliasAnalysis.cpp b/mlir/test/lib/Analysis/TestAliasAnalysis.cpp
index 0125e403272a8..f5eaed98f0268 100644
--- a/mlir/test/lib/Analysis/TestAliasAnalysis.cpp
+++ b/mlir/test/lib/Analysis/TestAliasAnalysis.cpp
@@ -21,14 +21,15 @@ using namespace mlir;
 
 /// Print a value that is used as an operand of an alias query.
 static void printAliasOperand(Operation *op) {
-  llvm::errs() << op->getAttrOfType<StringAttr>("test.ptr").getValue();
+  llvm::errs()
+      << op->getDiscardableAttrOfType<StringAttr>("test.ptr").getValue();
 }
 static void printAliasOperand(Value value) {
   if (BlockArgument arg = dyn_cast<BlockArgument>(value)) {
     Region *region = arg.getParentRegion();
     unsigned parentBlockNumber = arg.getOwner()->computeBlockNumber();
     llvm::errs() << region->getParentOp()
-                        ->getAttrOfType<StringAttr>("test.ptr")
+                        ->getDiscardableAttrOfType<StringAttr>("test.ptr")
                         .getValue()
                  << ".region" << region->getRegionNumber();
     if (parentBlockNumber != 0)
diff --git a/mlir/test/lib/Analysis/TestDataFlowFramework.cpp b/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
index f29136853a9cd..98fbcd8c9b12c 100644
--- a/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
+++ b/mlir/test/lib/Analysis/TestDataFlowFramework.cpp
@@ -238,7 +238,7 @@ void FooAnalysis::visitOperation(Operation *op) {
   result |= state->set(*prevState);
 
   // Modify the state with the attribute, if specified.
-  if (auto attr = op->getAttrOfType<IntegerAttr>(kFooAttrName)) {
+  if (auto attr = op->getDiscardableAttrOfType<IntegerAttr>(kFooAttrName)) {
     uint64_t value = attr.getType().isUnsignedInteger()
                          ? attr.getUInt()
                          : static_cast<uint64_t>(attr.getInt());
@@ -301,7 +301,7 @@ void BarAnalysis::visitOperation(Operation *op) {
       getOrCreateFor<BarState>(point, getProgramPointBefore(op));
   result |= state->join(*prevState);
 
-  if (op->hasAttr(kTagAttrName)) {
+  if (op->hasDiscardableAttr(kTagAttrName)) {
     const FooState *fooState = getOrCreateFor<FooState>(point, point);
     if (fooState->isUninitialized())
       return;
@@ -321,7 +321,7 @@ void TestFooAnalysisPass::runOnOperation() {
   os << "function: @" << func.getSymName() << "\n";
 
   func.walk([&](Operation *op) {
-    auto tag = op->getAttrOfType<StringAttr>(kTagAttrName);
+    auto tag = op->getDiscardableAttrOfType<StringAttr>(kTagAttrName);
     if (!tag)
       return;
     const FooState *state =
@@ -344,7 +344,7 @@ void TestStagedAnalysesPass::runOnOperation() {
     return signalPassFailure();
 
   func.walk([&](Operation *op) {
-    if (!op->hasAttr(kTagAttrName))
+    if (!op->hasDiscardableAttr(kTagAttrName))
       return;
 
     ProgramPoint *point = solver.getProgramPointAfter(op);
@@ -353,9 +353,10 @@ void TestStagedAnalysesPass::runOnOperation() {
     assert(fooState && !fooState->isUninitialized());
     assert(barState && !barState->isUninitialized());
 
-    op->setAttr(kFooStateAttrName,
-                builder.getI64IntegerAttr(fooState->getValue()));
-    op->setAttr(kBarStateAttrName, builder.getBoolAttr(barState->getValue()));
+    op->setDiscardableAttr(kFooStateAttrName,
+                           builder.getI64IntegerAttr(fooState->getValue()));
+    op->setDiscardableAttr(kBarStateAttrName,
+                           builder.getBoolAttr(barState->getValue()));
   });
 }
 
diff --git a/mlir/test/lib/Analysis/TestSlice.cpp b/mlir/test/lib/Analysis/TestSlice.cpp
index 7e8320dbf3ec3..5055a7b36265c 100644
--- a/mlir/test/lib/Analysis/TestSlice.cpp
+++ b/mlir/test/lib/Analysis/TestSlice.cpp
@@ -31,14 +31,14 @@ struct TestTopologicalSortPass
   void runOnOperation() override {
     SetVector<Operation *> toSort;
     getOperation().walk([&](Operation *op) {
-      if (op->hasAttrOfType<UnitAttr>(kToSortMark))
+      if (op->hasDiscardableAttrOfType<UnitAttr>(kToSortMark))
         toSort.insert(op);
     });
 
     auto i32Type = IntegerType::get(&getContext(), 32);
     SetVector<Operation *> sortedOps = topologicalSort(toSort);
     for (auto [index, op] : llvm::enumerate(sortedOps))
-      op->setAttr(kOrderIndex, IntegerAttr::get(i32Type, index));
+      op->setDiscardableAttr(kOrderIndex, IntegerAttr::get(i32Type, index));
   }
 };
 
diff --git a/mlir/test/lib/Analysis/TestTopologicalSort.cpp b/mlir/test/lib/Analysis/TestTopologicalSort.cpp
index bd7e60f1ecc33..f219c8cf0a341 100644
--- a/mlir/test/lib/Analysis/TestTopologicalSort.cpp
+++ b/mlir/test/lib/Analysis/TestTopologicalSort.cpp
@@ -31,14 +31,14 @@ struct TestTopologicalSortAnalysisPass
     OpBuilder builder(op->getContext());
 
     WalkResult result = op->walk([&](Operation *root) {
-      if (!root->hasAttr("root"))
+      if (!root->hasDiscardableAttr("root"))
         return WalkResult::advance();
 
       SmallVector<Operation *> selectedOps;
       root->walk([&](Operation *selected) {
-        if (!selected->hasAttr("selected"))
+        if (!selected->hasDiscardableAttr("selected"))
           return WalkResult::advance();
-        if (root->hasAttr("ordered")) {
+        if (root->hasDiscardableAttr("ordered")) {
           // If the root has an "ordered" attribute, we fill the selectedOps
           // vector in a certain order.
           int64_t pos =
@@ -65,7 +65,7 @@ struct TestTopologicalSortAnalysisPass
       }
 
       for (const auto &it : llvm::enumerate(selectedOps))
-        it.value()->setAttr("pos", builder.getIndexAttr(it.index()));
+        it.value()->setDiscardableAttr("pos", builder.getIndexAttr(it.index()));
 
       return WalkResult::advance();
     });
diff --git a/mlir/test/lib/Dialect/Bufferization/TestTensorLikeAndBufferLike.cpp b/mlir/test/lib/Dialect/Bufferization/TestTensorLikeAndBufferLike.cpp
index 60e60849f3e6c..3e71175a34474 100644
--- a/mlir/test/lib/Dialect/Bufferization/TestTensorLikeAndBufferLike.cpp
+++ b/mlir/test/lib/Dialect/Bufferization/TestTensorLikeAndBufferLike.cpp
@@ -85,7 +85,7 @@ struct TestTensorLikeAndBufferLikePass
     op.walk([](func::FuncOp funcOp) {
       const auto dict = findAllImplementeesOfTensorOrBufferLike(funcOp);
       if (!dict.empty()) {
-        funcOp->setAttr("found", dict);
+        funcOp->setDiscardableAttr("found", dict);
       }
     });
   }
diff --git a/mlir/test/lib/Dialect/DLTI/TestDataLayoutQuery.cpp b/mlir/test/lib/Dialect/DLTI/TestDataLayoutQuery.cpp
index 58c77cb94b47f..f6b403799b1da 100644
--- a/mlir/test/lib/Dialect/DLTI/TestDataLayoutQuery.cpp
+++ b/mlir/test/lib/Dialect/DLTI/TestDataLayoutQuery.cpp
@@ -63,7 +63,7 @@ struct TestDataLayoutQuery
         });
       };
 
-      op->setAttrs(
+      op->setDiscardableAttrs(
           {builder.getNamedAttr("size", convertTypeSizeToAttr(size)),
            builder.getNamedAttr("bitsize", convertTypeSizeToAttr(bitsize)),
            builder.getNamedAttr("alignment", builder.getIndexAttr(alignment)),
diff --git a/mlir/test/lib/Dialect/LLVM/TestPatterns.cpp b/mlir/test/lib/Dialect/LLVM/TestPatterns.cpp
index 69a3d98bc09e4..2252db9bc9802 100644
--- a/mlir/test/lib/Dialect/LLVM/TestPatterns.cpp
+++ b/mlir/test/lib/Dialect/LLVM/TestPatterns.cpp
@@ -79,8 +79,9 @@ struct TestLLVMLegalizePatternsPass
     ConversionTarget target(*ctx);
     target.addLegalOp(OperationName("test.legal_op", ctx));
     target.addLegalDialect<LLVM::LLVMDialect>();
-    target.addDynamicallyLegalOp<func::FuncOp>(
-        [&](func::FuncOp funcOp) { return funcOp->hasAttr("is_legal"); });
+    target.addDynamicallyLegalOp<func::FuncOp>([&](func::FuncOp funcOp) {
+      return funcOp->hasDiscardableAttr("is_legal");
+    });
 
     // Handle a partial conversion.
     DenseSet<Operation *> unlegalizedOps;
diff --git a/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp b/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp
index 7c8b08489c62e..c0a952658001c 100644
--- a/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp
+++ b/mlir/test/lib/Dialect/OpenACC/TestOpenACCSupport.cpp
@@ -49,7 +49,7 @@ void TestOpenACCSupportPass::runOnOperation() {
   func.walk([&](Operation *op) {
     // Check for test.var_name attribute. This is the marker used to identify
     // the operations that need to be tested for getVariableName.
-    if (op->hasAttr("test.var_name")) {
+    if (op->hasDiscardableAttr("test.var_name")) {
       // For each result of this operation, try to get the variable name
       for (auto result : op->getResults()) {
         std::string foundName = support.getVariableName(result);
@@ -61,7 +61,7 @@ void TestOpenACCSupportPass::runOnOperation() {
     // Check for test.recipe_name attribute. This is the marker used to identify
     // the operations that need to be tested for getRecipeName.
     if (auto recipeAttr =
-            op->getAttrOfType<RecipeKindAttr>("test.recipe_name")) {
+            op->getDiscardableAttrOfType<RecipeKindAttr>("test.recipe_name")) {
       RecipeKind kind = recipeAttr.getValue();
       // Get the type from the first result if available
       if (op->getNumResults() > 0) {
@@ -76,7 +76,8 @@ void TestOpenACCSupportPass::runOnOperation() {
 
     // Check for test.emit_nyi attribute. This is the marker used to
     // test whether the not yet implemented case is reported correctly.
-    if (auto messageAttr = op->getAttrOfType<StringAttr>("test.emit_nyi")) {
+    if (auto messageAttr =
+            op->getDiscardableAttrOfType<StringAttr>("test.emit_nyi")) {
       support.emitNYI(op->getLoc(), messageAttr.getValue());
     }
   });
diff --git a/mlir/test/lib/Dialect/OpenACC/TestPointerLikeTypeInterface.cpp b/mlir/test/lib/Dialect/OpenACC/TestPointerLikeTypeInterface.cpp
index e45fd104e7331..e43f0724dcc0f 100644
--- a/mlir/test/lib/Dialect/OpenACC/TestPointerLikeTypeInterface.cpp
+++ b/mlir/test/lib/Dialect/OpenACC/TestPointerLikeTypeInterface.cpp
@@ -101,9 +101,10 @@ void TestPointerLikeTypeInterfacePass::runOnOperation() {
 
   if (testMode == "cast") {
     func.walk([&](Operation *op) {
-      if (!op->hasAttr("test.cast"))
+      if (!op->hasDiscardableAttr("test.cast"))
         return;
-      auto destAttr = dyn_cast_or_null<TypeAttr>(op->getAttr("cast_dest"));
+      auto destAttr =
+          dyn_cast_or_null<TypeAttr>(op->getDiscardableAttr("cast_dest"));
       if (!destAttr || op->getNumResults() == 0)
         return;
       testGenCast(op, op->getResult(0), destAttr.getValue(), builder);
@@ -118,7 +119,7 @@ void TestPointerLikeTypeInterfacePass::runOnOperation() {
     // For store mode, also look for a test value to use
     Value testValue;
     func.walk([&](Operation *op) {
-      if (op->hasAttr("test.ptr")) {
+      if (op->hasDiscardableAttr("test.ptr")) {
         for (auto result : op->getResults()) {
           if (isa<PointerLikeType>(result.getType())) {
             candidates.push_back(
@@ -128,7 +129,7 @@ void TestPointerLikeTypeInterfacePass::runOnOperation() {
         }
       }
       // Collect value marked with test.value for store tests
-      if (testMode == "store" && op->hasAttr("test.value")) {
+      if (testMode == "store" && op->hasDiscardableAttr("test.value")) {
         if (op->getNumResults() > 0)
           testValue = op->getResult(0);
       }
@@ -154,7 +155,7 @@ void TestPointerLikeTypeInterfacePass::runOnOperation() {
     SmallVector<PointerCandidate> sources, destinations;
 
     func.walk([&](Operation *op) {
-      if (op->hasAttr("test.src_ptr")) {
+      if (op->hasDiscardableAttr("test.src_ptr")) {
         for (auto result : op->getResults()) {
           if (isa<PointerLikeType>(result.getType())) {
             sources.push_back(
@@ -163,7 +164,7 @@ void TestPointerLikeTypeInterfacePass::runOnOperation() {
           }
         }
       }
-      if (op->hasAttr("test.dest_ptr")) {
+      if (op->hasDiscardableAttr("test.dest_ptr")) {
         for (auto result : op->getResults()) {
           if (isa<PointerLikeType>(result.getType())) {
             destinations.push_back(
@@ -188,8 +189,9 @@ void TestPointerLikeTypeInterfacePass::walkAndPrint() {
   func.walk([&](Operation *op) {
     // Look for operations marked with "test.ptr", "test.src_ptr", or
     // "test.dest_ptr"
-    if (op->hasAttr("test.ptr") || op->hasAttr("test.src_ptr") ||
-        op->hasAttr("test.dest_ptr")) {
+    if (op->hasDiscardableAttr("test.ptr") ||
+        op->hasDiscardableAttr("test.src_ptr") ||
+        op->hasDiscardableAttr("test.dest_ptr")) {
       llvm::errs() << "Operation: ";
       op->print(llvm::errs());
       llvm::errs() << "\n";
diff --git a/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp b/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
index 7b71f2c5f3b20..6daffb04cd554 100644
--- a/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
+++ b/mlir/test/lib/Dialect/OpenACC/TestRecipePopulate.cpp
@@ -62,7 +62,7 @@ void TestRecipePopulatePass::runOnOperation() {
   SmallVector<std::tuple<Operation *, Value, std::string>> testVars;
 
   module.walk([&](Operation *op) {
-    if (auto varName = op->getAttrOfType<StringAttr>("test.var")) {
+    if (auto varName = op->getDiscardableAttrOfType<StringAttr>("test.var")) {
       for (auto result : op->getResults()) {
         testVars.push_back({op, result, varName.str()});
       }
diff --git a/mlir/test/lib/Dialect/SCF/TestLoopUnrolling.cpp b/mlir/test/lib/Dialect/SCF/TestLoopUnrolling.cpp
index bbeae9d39db8d..f92aa606f018f 100644
--- a/mlir/test/lib/Dialect/SCF/TestLoopUnrolling.cpp
+++ b/mlir/test/lib/Dialect/SCF/TestLoopUnrolling.cpp
@@ -68,7 +68,7 @@ struct TestLoopUnrollingPass
     });
     auto annotateFn = [this](unsigned i, Operation *op, OpBuilder b) {
       if (annotateLoop) {
-        op->setAttr("unrolled_iteration", b.getUI32IntegerAttr(i));
+        op->setDiscardableAttr("unrolled_iteration", b.getUI32IntegerAttr(i));
       }
     };
     for (auto loop : loops) {
diff --git a/mlir/test/lib/Dialect/SCF/TestParallelLoopUnrolling.cpp b/mlir/test/lib/Dialect/SCF/TestParallelLoopUnrolling.cpp
index 77a22a1812537..12a6a6af59fa6 100644
--- a/mlir/test/lib/Dialect/SCF/TestParallelLoopUnrolling.cpp
+++ b/mlir/test/lib/Dialect/SCF/TestParallelLoopUnrolling.cpp
@@ -53,7 +53,7 @@ struct TestParallelLoopUnrollingPass
     });
     auto annotateFn = [this](unsigned i, Operation *op, OpBuilder b) {
       if (annotateLoop) {
-        op->setAttr("unrolled_iteration", b.getUI32IntegerAttr(i));
+        op->setDiscardableAttr("unrolled_iteration", b.getUI32IntegerAttr(i));
       }
     };
     PatternRewriter rewriter(getOperation()->getContext());
diff --git a/mlir/test/lib/Dialect/SCF/TestSCFUtils.cpp b/mlir/test/lib/Dialect/SCF/TestSCFUtils.cpp
index fafa03b6c089f..6a0f042b9cbe4 100644
--- a/mlir/test/lib/Dialect/SCF/TestSCFUtils.cpp
+++ b/mlir/test/lib/Dialect/SCF/TestSCFUtils.cpp
@@ -152,15 +152,15 @@ struct TestSCFPipeliningPass
   static void
   getSchedule(scf::ForOp forOp,
               std::vector<std::pair<Operation *, unsigned>> &schedule) {
-    if (!forOp->hasAttr(kTestPipeliningLoopMarker))
+    if (!forOp->hasDiscardableAttr(kTestPipeliningLoopMarker))
       return;
 
     schedule.resize(forOp.getBody()->getOperations().size() - 1);
     WalkResult result = forOp.walk([&schedule](Operation *op) {
       auto attrStage =
-          op->getAttrOfType<IntegerAttr>(kTestPipeliningStageMarker);
-      auto attrCycle =
-          op->getAttrOfType<IntegerAttr>(kTestPipeliningOpOrderMarker);
+          op->getDiscardableAttrOfType<IntegerAttr>(kTestPipeliningStageMarker);
+      auto attrCycle = op->getDiscardableAttrOfType<IntegerAttr>(
+          kTestPipeliningOpOrderMarker);
       if (attrCycle && attrStage) {
         const APInt &stage = attrStage.getValue();
         if (stage.isNegative() ||
@@ -230,17 +230,20 @@ struct TestSCFPipeliningPass
     OpBuilder b(op);
     switch (part) {
     case mlir::scf::PipeliningOption::PipelinerPart::Prologue:
-      op->setAttr(kTestPipeliningAnnotationPart, b.getStringAttr("prologue"));
+      op->setDiscardableAttr(kTestPipeliningAnnotationPart,
+                             b.getStringAttr("prologue"));
       break;
     case mlir::scf::PipeliningOption::PipelinerPart::Kernel:
-      op->setAttr(kTestPipeliningAnnotationPart, b.getStringAttr("kernel"));
+      op->setDiscardableAttr(kTestPipeliningAnnotationPart,
+                             b.getStringAttr("kernel"));
       break;
     case mlir::scf::PipeliningOption::PipelinerPart::Epilogue:
-      op->setAttr(kTestPipeliningAnnotationPart, b.getStringAttr("epilogue"));
+      op->setDiscardableAttr(kTestPipeliningAnnotationPart,
+                             b.getStringAttr("epilogue"));
       break;
     }
-    op->setAttr(kTestPipeliningAnnotationIteration,
-                b.getI32IntegerAttr(iteration));
+    op->setDiscardableAttr(kTestPipeliningAnnotationIteration,
+                           b.getI32IntegerAttr(iteration));
   }
 
   void getDependentDialects(DialectRegistry &registry) const override {
@@ -262,8 +265,8 @@ struct TestSCFPipeliningPass
     (void)applyPatternsGreedily(getOperation(), std::move(patterns));
     getOperation().walk([](Operation *op) {
       // Clean up the markers.
-      op->removeAttr(kTestPipeliningStageMarker);
-      op->removeAttr(kTestPipeliningOpOrderMarker);
+      op->removeDiscardableAttr(kTestPipeliningStageMarker);
+      op->removeDiscardableAttr(kTestPipeliningOpOrderMarker);
     });
   }
 };
diff --git a/mlir/test/lib/Dialect/SPIRV/TestAvailability.cpp b/mlir/test/lib/Dialect/SPIRV/TestAvailability.cpp
index ae995a970bade..8cdb9041d196a 100644
--- a/mlir/test/lib/Dialect/SPIRV/TestAvailability.cpp
+++ b/mlir/test/lib/Dialect/SPIRV/TestAvailability.cpp
@@ -208,8 +208,9 @@ struct ConvertToIntegerDotProd : RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    rewriter.replaceOpWithNewOp<SPIRVOp>(op, op->getResultTypes(),
-                                         op->getOperands(), op->getAttrs());
+    rewriter.replaceOpWithNewOp<SPIRVOp>(
+        op, op->getResultTypes(), op->getOperands(),
+        op->getDiscardableAttrDictionary().getValue());
     return success();
   }
 };
diff --git a/mlir/test/lib/Dialect/SPIRV/TestEntryPointAbi.cpp b/mlir/test/lib/Dialect/SPIRV/TestEntryPointAbi.cpp
index f492d7cd87d83..4126071c6b6f6 100644
--- a/mlir/test/lib/Dialect/SPIRV/TestEntryPointAbi.cpp
+++ b/mlir/test/lib/Dialect/SPIRV/TestEntryPointAbi.cpp
@@ -69,13 +69,13 @@ void TestSpirvEntryPointABIPass::runOnOperation() {
     SmallVector<int32_t, 3> workgroupSizeVec(workgroupSize.begin(),
                                              workgroupSize.end());
     workgroupSizeVec.resize(3, 1);
-    gpuFunc->setAttr(attrName,
-                     spirv::getEntryPointABIAttr(
-                         context, workgroupSizeVec,
-                         (subgroupSize == 0) ? std::nullopt
-                                             : std::optional<int>(subgroupSize),
-                         (targetWidth == 0) ? std::nullopt
-                                            : std::optional<int>(targetWidth)));
+    gpuFunc->setDiscardableAttr(
+        attrName, spirv::getEntryPointABIAttr(
+                      context, workgroupSizeVec,
+                      (subgroupSize == 0) ? std::nullopt
+                                          : std::optional<int>(subgroupSize),
+                      (targetWidth == 0) ? std::nullopt
+                                         : std::optional<int>(targetWidth)));
   }
 }
 
diff --git a/mlir/test/lib/Dialect/Shape/TestShapeFunctions.cpp b/mlir/test/lib/Dialect/Shape/TestShapeFunctions.cpp
index 5239dc5388951..dc487a63c8400 100644
--- a/mlir/test/lib/Dialect/Shape/TestShapeFunctions.cpp
+++ b/mlir/test/lib/Dialect/Shape/TestShapeFunctions.cpp
@@ -46,7 +46,7 @@ void ReportShapeFnPass::runOnOperation() {
       op->emitRemark() << "associated shape function: " << fn.getName();
       return true;
     }
-    if (auto symbol = op->getAttrOfType<SymbolRefAttr>(shapeFnId)) {
+    if (auto symbol = op->getDiscardableAttrOfType<SymbolRefAttr>(shapeFnId)) {
       auto fn =
           cast<shape::FuncOp>(SymbolTable::lookupSymbolIn(module, symbol));
       op->emitRemark() << "associated shape function: " << fn.getName();
diff --git a/mlir/test/lib/Dialect/Tensor/TestTensorTransforms.cpp b/mlir/test/lib/Dialect/Tensor/TestTensorTransforms.cpp
index 687473ebe6d60..95659da666ba4 100644
--- a/mlir/test/lib/Dialect/Tensor/TestTensorTransforms.cpp
+++ b/mlir/test/lib/Dialect/Tensor/TestTensorTransforms.cpp
@@ -305,7 +305,7 @@ static LogicalResult testTrackingListenerReplacements(Operation *rootOp) {
   // Find replaced op.
   Operation *replaced = nullptr;
   WalkResult status = rootOp->walk([&](Operation *op) {
-    if (op->hasAttr("replaced")) {
+    if (op->hasDiscardableAttr("replaced")) {
       if (replaced) {
         op->emitError("only one 'replaced' op is allowed per test case");
         replaced->emitRemark("other 'replaced' op");
@@ -326,8 +326,8 @@ static LogicalResult testTrackingListenerReplacements(Operation *rootOp) {
   SmallVector<Value> replacements(replaced->getNumResults(), Value());
   status = rootOp->walk([&](Operation *op) {
     for (int64_t i = 0; i < replaced->getNumResults(); ++i) {
-      if (auto attr = op->getAttrOfType<IntegerAttr>("replacement_" +
-                                                     std::to_string(i))) {
+      if (auto attr = op->getDiscardableAttrOfType<IntegerAttr>(
+              "replacement_" + std::to_string(i))) {
         if (replacements[i]) {
           op->emitError("only one 'replacement_" + std::to_string(i) +
                         "' is allowed per test case");
diff --git a/mlir/test/lib/Dialect/Test/TestDialect.cpp b/mlir/test/lib/Dialect/Test/TestDialect.cpp
index 7c1db3884be10..d4d9aa5c7df79 100644
--- a/mlir/test/lib/Dialect/Test/TestDialect.cpp
+++ b/mlir/test/lib/Dialect/Test/TestDialect.cpp
@@ -315,7 +315,8 @@ void test::testSideEffectOpGetEffect(
     Operation *op,
     SmallVectorImpl<SideEffects::EffectInstance<TestEffects::Effect>>
         &effects) {
-  auto effectsAttr = op->getAttrOfType<AffineMapAttr>("effect_parameter");
+  auto effectsAttr =
+      op->getDiscardableAttrOfType<AffineMapAttr>("effect_parameter");
   if (!effectsAttr)
     return;
 
@@ -480,7 +481,8 @@ MutableOperandRange CallWithSegmentsOp::getArgOperandsMutable() {
   // Obtain the canonical segment size attribute name for this op.
   auto segName =
       CallWithSegmentsOp::getOperandSegmentSizesAttrName(op->getName());
-  auto sizesAttr = op->getAttrOfType<DenseI32ArrayAttr>(segName);
+  auto sizesAttr = dyn_cast_or_null<DenseI32ArrayAttr>(
+      op->getInherentAttr(segName).value_or(Attribute{}));
   assert(sizesAttr && "missing operandSegmentSizes attribute on op");
 
   // Compute the start and length of the args segment from the prefix size and
diff --git a/mlir/test/lib/Dialect/Test/TestDialectInterfaces.cpp b/mlir/test/lib/Dialect/Test/TestDialectInterfaces.cpp
index 04d956cce2eea..2b52978fe3636 100644
--- a/mlir/test/lib/Dialect/Test/TestDialectInterfaces.cpp
+++ b/mlir/test/lib/Dialect/Test/TestDialectInterfaces.cpp
@@ -327,7 +327,7 @@ struct TestInlinerInterface : public DialectInlinerInterface {
   bool isLegalToInline(Operation *call, Operation *callable,
                        bool wouldBeCloned) const final {
     // Don't allow inlining calls that are marked `noinline`.
-    return !call->hasAttr("noinline");
+    return !call->hasDiscardableAttr("noinline");
   }
   bool isLegalToInline(Region *, Region *, bool, IRMapping &) const final {
     // Inlining into test dialect regions is legal.
@@ -420,7 +420,8 @@ struct TestInlinerInterface : public DialectInlinerInterface {
     // Set attributed on all ops in the inlined blocks.
     for (Block &block : inlinedBlocks) {
       block.walk([&](Operation *op) {
-        op->setAttr("inlined_conversion", UnitAttr::get(call->getContext()));
+        op->setDiscardableAttr("inlined_conversion",
+                               UnitAttr::get(call->getContext()));
       });
     }
   }
diff --git a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
index 2e8bce9199fd2..734fceeede346 100644
--- a/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
+++ b/mlir/test/lib/Dialect/Test/TestOpDefs.cpp
@@ -82,7 +82,7 @@ SuccessorOperands TestInternalBranchOp::getSuccessorOperands(unsigned index) {
 
 LogicalResult TestCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   // Check that the callee attribute was specified.
-  auto fnAttr = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
+  auto fnAttr = getCalleeAttr();
   if (!fnAttr)
     return emitOpError("requires a 'callee' symbol reference attribute");
   if (!symbolTable.lookupNearestSymbolFrom<FunctionOpInterface>(*this, fnAttr))
@@ -459,7 +459,8 @@ struct TestResource : public SideEffects::Resource::Base<TestResource> {
 void SideEffectOp::getEffects(
     SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
   // Check for an effects attribute on the op instance.
-  ArrayAttr effectsAttr = (*this)->getAttrOfType<ArrayAttr>("effects");
+  ArrayAttr effectsAttr =
+      (*this)->getDiscardableAttrOfType<ArrayAttr>("effects");
   if (!effectsAttr)
     return;
 
@@ -520,7 +521,8 @@ void ConditionalSideEffectOp::getEffects(
 void SideEffectWithRegionOp::getEffects(
     SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
   // Check for an effects attribute on the op instance.
-  ArrayAttr effectsAttr = (*this)->getAttrOfType<ArrayAttr>("effects");
+  ArrayAttr effectsAttr =
+      (*this)->getDiscardableAttrOfType<ArrayAttr>("effects");
   if (!effectsAttr)
     return;
 
@@ -629,10 +631,15 @@ void StringAttrPrettyNameOp::print(OpAsmPrinter &p) {
     }
   }
 
-  if (namesDisagree)
-    p.printOptionalAttrDictWithKeyword((*this)->getAttrs());
-  else
-    p.printOptionalAttrDictWithKeyword((*this)->getAttrs(), {"names"});
+  if (namesDisagree) {
+    SmallVector<NamedAttribute> attrs((*this)->getDiscardableAttrs());
+    attrs.emplace_back(getNamesAttrName(), getNamesAttr());
+    llvm::sort(attrs);
+    p.printOptionalAttrDictWithKeyword(attrs);
+  } else {
+    p.printOptionalAttrDictWithKeyword(
+        (*this)->getDiscardableAttrDictionary().getValue(), {"names"});
+  }
 }
 
 // We set the SSA name in the asm syntax to the contents of the name
@@ -953,7 +960,13 @@ ParseResult TestWithBoundsRegionOp::parse(OpAsmParser &parser,
 }
 
 void TestWithBoundsRegionOp::print(OpAsmPrinter &p) {
-  p.printOptionalAttrDict((*this)->getAttrs());
+  SmallVector<NamedAttribute> attrs((*this)->getDiscardableAttrs());
+  attrs.emplace_back(getUminAttrName(), getUminAttr());
+  attrs.emplace_back(getUmaxAttrName(), getUmaxAttr());
+  attrs.emplace_back(getSminAttrName(), getSminAttr());
+  attrs.emplace_back(getSmaxAttrName(), getSmaxAttr());
+  llvm::sort(attrs);
+  p.printOptionalAttrDict(attrs);
   p << ' ';
   p.printRegionArgument(getRegion().getArgument(0), /*argAttrs=*/{},
                         /*omitType=*/false);
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 3cb7f6ce2a054..3acebf1bc1b7f 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -714,11 +714,11 @@ def ConversionCallOp : TEST_Op<"conversion_call_op",
   }];
   let extraClassDefinition = [{
     ::mlir::CallInterfaceCallable $cppClass::getCallableForCallee() {
-      return (*this)->getAttrOfType<::mlir::SymbolRefAttr>("callee");
+      return getCalleeAttr();
     }
 
     void $cppClass::setCalleeFromCallable(::mlir::CallInterfaceCallable callee) {
-      (*this)->setAttr("callee", cast<SymbolRefAttr>(callee));
+      setCalleeAttr(cast<SymbolRefAttr>(callee));
     }
   }];
 }
@@ -3116,13 +3116,15 @@ def TestLinalgConvOp :
     }
 
     llvm::SmallVector<mlir::utils::IteratorType> getIteratorTypesArray() {
-      auto attrs = getOperation()->getAttrOfType<mlir::ArrayAttr>("iterator_types");
+      auto attrs = getOperation()->getDiscardableAttrOfType<mlir::ArrayAttr>(
+          "iterator_types");
       auto range = attrs.getAsValueRange<IteratorTypeAttr, mlir::utils::IteratorType>();
       return {range.begin(), range.end()};
     }
 
     mlir::ArrayAttr getIndexingMaps() {
-      return getOperation()->getAttrOfType<mlir::ArrayAttr>("indexing_maps");
+      return getOperation()->getDiscardableAttrOfType<mlir::ArrayAttr>(
+          "indexing_maps");
     }
 
     std::string getLibraryCallName() {
@@ -3179,13 +3181,15 @@ def TestLinalgFillOp :
     }
 
     llvm::SmallVector<mlir::utils::IteratorType> getIteratorTypesArray() {
-      auto attrs = getOperation()->getAttrOfType<mlir::ArrayAttr>("iterator_types");
+      auto attrs = getOperation()->getDiscardableAttrOfType<mlir::ArrayAttr>(
+          "iterator_types");
       auto range = attrs.getAsValueRange<IteratorTypeAttr, mlir::utils::IteratorType>();
       return {range.begin(), range.end()};
     }
 
     mlir::ArrayAttr getIndexingMaps() {
-      return getOperation()->getAttrOfType<mlir::ArrayAttr>("indexing_maps");
+      return getOperation()->getDiscardableAttrOfType<mlir::ArrayAttr>(
+          "indexing_maps");
     }
 
     std::string getLibraryCallName() {
@@ -4423,15 +4427,16 @@ def CallWithSegmentsOp : TEST_Op<"call_with_segments",
 
   let extraClassDefinition = [{
     ::mlir::CallInterfaceCallable $cppClass::getCallableForCallee() {
-      if (auto sym = (*this)->getAttrOfType<::mlir::SymbolRefAttr>("callee"))
+      if (auto sym = (*this)->getDiscardableAttrOfType<::mlir::SymbolRefAttr>(
+              "callee"))
         return ::mlir::CallInterfaceCallable(sym);
       return ::mlir::CallInterfaceCallable();
     }
     void $cppClass::setCalleeFromCallable(::mlir::CallInterfaceCallable callee) {
       if (auto sym = callee.dyn_cast<::mlir::SymbolRefAttr>())
-        (*this)->setAttr("callee", sym);
+        (*this)->setDiscardableAttr("callee", sym);
       else
-        (*this)->removeAttr("callee");
+        (*this)->removeDiscardableAttr("callee");
     }
   }];
 }
diff --git a/mlir/test/lib/Dialect/Test/TestOpsSyntax.cpp b/mlir/test/lib/Dialect/Test/TestOpsSyntax.cpp
index 5880c2a2302b0..3a971e462e2b8 100644
--- a/mlir/test/lib/Dialect/Test/TestOpsSyntax.cpp
+++ b/mlir/test/lib/Dialect/Test/TestOpsSyntax.cpp
@@ -456,7 +456,8 @@ void PolyForOp::print(OpAsmPrinter &p) {
 
 void PolyForOp::getAsmBlockArgumentNames(Region &region,
                                          OpAsmSetValueNameFn setNameFn) {
-  auto arrayAttr = getOperation()->getAttrOfType<ArrayAttr>("arg_names");
+  auto arrayAttr =
+      getOperation()->getDiscardableAttrOfType<ArrayAttr>("arg_names");
   if (!arrayAttr)
     return;
   auto args = getRegion().front().getArguments();
diff --git a/mlir/test/lib/Dialect/Test/TestPatterns.cpp b/mlir/test/lib/Dialect/Test/TestPatterns.cpp
index 552a1a473c9fd..58271aab2364a 100644
--- a/mlir/test/lib/Dialect/Test/TestPatterns.cpp
+++ b/mlir/test/lib/Dialect/Test/TestPatterns.cpp
@@ -131,7 +131,7 @@ struct FolderInsertBeforePreviouslyFoldedConstantPattern
 
   LogicalResult matchAndRewrite(TestCastOp op,
                                 PatternRewriter &rewriter) const override {
-    if (!op->hasAttr("test_fold_before_previously_folded_op"))
+    if (!op->hasDiscardableAttr("test_fold_before_previously_folded_op"))
       return failure();
     rewriter.setInsertionPointToStart(op->getBlock());
 
@@ -192,10 +192,11 @@ struct MakeOpEligible : public RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    if (op->hasAttr("eligible"))
+    if (op->hasDiscardableAttr("eligible"))
       return failure();
-    rewriter.modifyOpInPlace(
-        op, [&]() { op->setAttr("eligible", rewriter.getUnitAttr()); });
+    rewriter.modifyOpInPlace(op, [&]() {
+      op->setDiscardableAttr("eligible", rewriter.getUnitAttr());
+    });
     return success();
   }
 };
@@ -210,7 +211,7 @@ struct HoistEligibleOps : public OpRewritePattern<test::OneRegionOp> {
     Operation *toBeHoisted = terminator->getOperands()[0].getDefiningOp();
     if (toBeHoisted->getParentOp() != op)
       return failure();
-    if (!toBeHoisted->hasAttr("eligible"))
+    if (!toBeHoisted->hasDiscardableAttr("eligible"))
       return failure();
     rewriter.moveOpBefore(toBeHoisted, op);
     return success();
@@ -305,7 +306,7 @@ struct MoveAfterParentOp : public RewritePattern {
       return failure();
 
     int64_t moveForwardBy = 0;
-    if (auto advanceBy = op->getAttrOfType<IntegerAttr>("advance"))
+    if (auto advanceBy = op->getDiscardableAttrOfType<IntegerAttr>("advance"))
       moveForwardBy = advanceBy.getInt();
 
     Operation *moveAfter = op->getParentOp();
@@ -363,10 +364,10 @@ struct CloneOp : public RewritePattern {
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
     // Do not clone already cloned ops to avoid going into an infinite loop.
-    if (op->hasAttr("was_cloned"))
+    if (op->hasDiscardableAttr("was_cloned"))
       return failure();
     Operation *cloned = rewriter.clone(*op);
-    cloned->setAttr("was_cloned", rewriter.getUnitAttr());
+    cloned->setDiscardableAttr("was_cloned", rewriter.getUnitAttr());
     return success();
   }
 };
@@ -380,11 +381,11 @@ struct CloneRegionBeforeOp : public RewritePattern {
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
     // Do not clone already cloned ops to avoid going into an infinite loop.
-    if (op->hasAttr("was_cloned"))
+    if (op->hasDiscardableAttr("was_cloned"))
       return failure();
     for (Region &r : op->getRegions())
       rewriter.cloneRegionBefore(r, op->getBlock());
-    op->setAttr("was_cloned", rewriter.getUnitAttr());
+    op->setDiscardableAttr("was_cloned", rewriter.getUnitAttr());
     return success();
   }
 };
@@ -398,7 +399,7 @@ class ReplaceWithNewOp : public RewritePattern {
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
     Operation *newOp;
-    if (op->hasAttr("create_erase_op")) {
+    if (op->hasDiscardableAttr("create_erase_op")) {
       newOp = rewriter.create(
           op->getLoc(),
           OperationName("test.erase_op", op->getContext()).getIdentifier(),
@@ -595,9 +596,10 @@ struct TestStrictPatternDriver
     (void)applyOpPatternsGreedily(ArrayRef(ops), std::move(patterns), config,
                                   &changed, &allErased);
     Builder b(ctx);
-    getOperation()->setAttr("pattern_driver_changed", b.getBoolAttr(changed));
-    getOperation()->setAttr("pattern_driver_all_erased",
-                            b.getBoolAttr(allErased));
+    getOperation()->setDiscardableAttr("pattern_driver_changed",
+                                       b.getBoolAttr(changed));
+    getOperation()->setDiscardableAttr("pattern_driver_all_erased",
+                                       b.getBoolAttr(allErased));
   }
 
   Option<std::string> strictMode{
@@ -614,15 +616,16 @@ struct TestStrictPatternDriver
 
     LogicalResult matchAndRewrite(Operation *op,
                                   PatternRewriter &rewriter) const override {
-      if (op->hasAttr("skip"))
+      if (op->hasDiscardableAttr("skip"))
         return failure();
 
       Operation *newOp =
           rewriter.create(op->getLoc(), op->getName().getIdentifier(),
                           op->getOperands(), op->getResultTypes());
-      rewriter.modifyOpInPlace(
-          op, [&]() { op->setAttr("skip", rewriter.getBoolAttr(true)); });
-      newOp->setAttr("skip", rewriter.getBoolAttr(true));
+      rewriter.modifyOpInPlace(op, [&]() {
+        op->setDiscardableAttr("skip", rewriter.getBoolAttr(true));
+      });
+      newOp->setDiscardableAttr("skip", rewriter.getBoolAttr(true));
 
       return success();
     }
@@ -754,7 +757,8 @@ static void invokeCreateWithInferredReturnType(Operation *op) {
               properties, op->getRegions(), inferredReturnTypes))) {
         OperationState state(location, OpTy::getOperationName());
         // TODO: Expand to regions.
-        OpTy::build(b, state, values, op->getAttrs());
+        OpTy::build(b, state, values,
+                    op->getDiscardableAttrDictionary().getValue());
         (void)b.create(state);
       }
     }
@@ -981,9 +985,9 @@ struct TestValueReplace : public ConversionPattern {
     // Replace the first operand with 2x the second operand.
     Value from = op->getOperand(0);
     Value repl = op->getOperand(1);
-    if (op->hasAttr("conditional")) {
+    if (op->hasDiscardableAttr("conditional")) {
       rewriter.replaceUsesWithIf(from, {repl, repl}, [=](OpOperand &use) {
-        return use.getOwner()->hasAttr("replace_uses");
+        return use.getOwner()->hasDiscardableAttr("replace_uses");
       });
     } else {
       rewriter.replaceAllUsesWith(from, {repl, repl});
@@ -991,8 +995,8 @@ struct TestValueReplace : public ConversionPattern {
     rewriter.modifyOpInPlace(op, [&] {
       // If the "trigger_rollback" attribute is set, keep the op illegal, so
       // that a rollback is triggered.
-      if (!op->hasAttr("trigger_rollback"))
-        op->setAttr("is_legal", rewriter.getUnitAttr());
+      if (!op->hasDiscardableAttr("trigger_rollback"))
+        op->setDiscardableAttr("is_legal", rewriter.getUnitAttr());
     });
     return success();
   }
@@ -1038,7 +1042,7 @@ struct TestUndoPropertiesModification : public ConversionPattern {
   LogicalResult
   matchAndRewrite(Operation *op, ArrayRef<Value> operands,
                   ConversionPatternRewriter &rewriter) const final {
-    if (!op->hasAttr("modify_inplace"))
+    if (!op->hasDiscardableAttr("modify_inplace"))
       return failure();
     rewriter.modifyOpInPlace(
         op, [&]() { cast<TestOpWithProperties>(op).getProperties().setA(42); });
@@ -1255,9 +1259,7 @@ struct TestBoundedRecursiveRewrite
   LogicalResult matchAndRewrite(TestRecursiveRewriteOp op,
                                 PatternRewriter &rewriter) const final {
     // Decrement the depth of the op in-place.
-    rewriter.modifyOpInPlace(op, [&] {
-      op->setAttr("depth", rewriter.getI64IntegerAttr(op.getDepth() - 1));
-    });
+    rewriter.modifyOpInPlace(op, [&] { op.setDepth(op.getDepth() - 1); });
     return success();
   }
 };
@@ -1324,7 +1326,7 @@ class TestReplaceWithValidProducer : public ConversionPattern {
   LogicalResult
   matchAndRewrite(Operation *op, ArrayRef<Value> operands,
                   ConversionPatternRewriter &rewriter) const final {
-    auto attr = op->getAttrOfType<TypeAttr>("type");
+    auto attr = op->getDiscardableAttrOfType<TypeAttr>("type");
     if (!attr)
       return failure();
     rewriter.replaceOpWithNewOp<TestValidProducerOp>(op, attr.getValue());
@@ -1349,7 +1351,8 @@ class TestReplaceWithValidConsumer : public ConversionPattern {
     // converter.
     // with_converter absent: pattern must have been initialized without a type
     // converter.
-    if (op->hasAttr("with_converter") != static_cast<bool>(getTypeConverter()))
+    if (op->hasDiscardableAttr("with_converter") !=
+        static_cast<bool>(getTypeConverter()))
       return failure();
     rewriter.replaceOpWithNewOp<TestValidConsumerOp>(op, operands[0]);
     return success();
@@ -1426,7 +1429,8 @@ class TestMultiple1ToNReplacement : public ConversionPattern {
         types.push_back(t);
       }
       OperationState state(op->getLoc(), name,
-                           /*operands=*/{}, types, op->getAttrs());
+                           /*operands=*/{}, types,
+                           op->getDiscardableAttrDictionary().getValue());
       auto *newOp = rewriter.create(state);
       SmallVector<ValueRange> repls;
       for (size_t i = 0, e = op->getNumResults(); i < e; ++i)
@@ -1470,8 +1474,9 @@ class TestPostOrderLegalization : public ConversionPattern {
     for (Region &r : op->getRegions())
       if (failed(rewriter.legalize(&r)))
         return failure();
-    rewriter.modifyOpInPlace(
-        op, [&]() { op->setAttr("is_legal", rewriter.getUnitAttr()); });
+    rewriter.modifyOpInPlace(op, [&]() {
+      op->setDiscardableAttr("is_legal", rewriter.getUnitAttr());
+    });
     return success();
   }
 };
@@ -1618,10 +1623,10 @@ struct TestLegalizePatternDriver
         [&](func::CallOp op) { return converter.isLegal(op); });
     target.addDynamicallyLegalOp(
         OperationName("test.value_replace", &getContext()),
-        [](Operation *op) { return op->hasAttr("is_legal"); });
+        [](Operation *op) { return op->hasDiscardableAttr("is_legal"); });
     target.addDynamicallyLegalOp(
         OperationName("test.post_order_legalization", &getContext()),
-        [](Operation *op) { return op->hasAttr("is_legal"); });
+        [](Operation *op) { return op->hasDiscardableAttr("is_legal"); });
 
     // TestCreateUnregisteredOp creates `arith.constant` operation,
     // which was not added to target intentionally to test
@@ -1638,7 +1643,7 @@ struct TestLegalizePatternDriver
     // Check support for marking certain operations as recursively legal.
     target.markOpRecursivelyLegal<func::FuncOp, ModuleOp>([](Operation *op) {
       return static_cast<bool>(
-          op->getAttrOfType<UnitAttr>("test.recursively_legal"));
+          op->getDiscardableAttrOfType<UnitAttr>("test.recursively_legal"));
     });
 
     // Mark the bound recursion operation as dynamically legal.
@@ -1679,7 +1684,8 @@ struct TestLegalizePatternDriver
     if (mode == ConversionMode::Full) {
       // Check support for marking unknown operations as dynamically legal.
       target.markUnknownOpDynamicallyLegal([](Operation *op) {
-        return (bool)op->getAttrOfType<UnitAttr>("test.dynamically_legal");
+        return (bool)op->getDiscardableAttrOfType<UnitAttr>(
+            "test.dynamically_legal");
       });
 
       if (failed(applyFullConversion(getOperation(), target,
@@ -1924,7 +1930,7 @@ struct RewriteDynamicOp : public RewritePattern {
 
     OperationState state(op->getLoc(), "test.dynamic_generic",
                          op->getOperands(), op->getResultTypes(),
-                         op->getAttrs());
+                         op->getDiscardableAttrDictionary().getValue());
     auto *newOp = rewriter.create(state);
     rewriter.replaceOp(op, newOp->getResults());
     return success();
@@ -2170,7 +2176,8 @@ struct TestTypeConversionDriver
         op = op->getParentOfType<FunctionOpInterface>();
       if (!op)
         return Type();
-      auto incrementAttr = op->getAttrOfType<IntegerAttr>("increment");
+      auto incrementAttr =
+          op->getDiscardableAttrOfType<IntegerAttr>("increment");
       if (!incrementAttr)
         return Type();
       return IntegerType::get(v.getContext(),
diff --git a/mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.cpp b/mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.cpp
index faf87316f1466..1b6c3daf89624 100644
--- a/mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.cpp
+++ b/mlir/test/lib/Dialect/Transform/TestTransformDialectExtension.cpp
@@ -415,7 +415,7 @@ mlir::test::TestMixedSuccessAndSilenceableOp::applyToOne(
     transform::TransformRewriter &rewriter, Operation *target,
     transform::ApplyToEachResultList &results,
     transform::TransformState &state) {
-  if (target->hasAttr("target_me"))
+  if (target->hasDiscardableAttr("target_me"))
     return DiagnosedSilenceableFailure::success();
   return emitDefaultSilenceableFailure(target);
 }
@@ -663,11 +663,11 @@ mlir::test::TestTrackedRewriteOp::apply(transform::TransformRewriter &rewriter,
     // Erase all payload ops. The outer loop should have only one iteration.
     for (Operation *op : state.getPayloadOps(getIn())) {
       rewriter.setInsertionPoint(op);
-      if (op->hasAttr("erase_me")) {
+      if (op->hasDiscardableAttr("erase_me")) {
         rewriter.eraseOp(op);
         continue;
       }
-      if (!op->hasAttr("replace_me")) {
+      if (!op->hasDiscardableAttr("replace_me")) {
         continue;
       }
 
@@ -695,7 +695,8 @@ class ReplaceWithNewOp : public RewritePattern {
 
   LogicalResult matchAndRewrite(Operation *op,
                                 PatternRewriter &rewriter) const override {
-    auto newName = op->getAttrOfType<StringAttr>("replace_with_new_op");
+    auto newName =
+        op->getDiscardableAttrOfType<StringAttr>("replace_with_new_op");
     if (!newName)
       return failure();
     Operation *newOp = rewriter.create(
@@ -844,7 +845,7 @@ class ReplaceWithNewOpConversion : public ConversionPattern {
   LogicalResult
   matchAndRewrite(Operation *op, ArrayRef<Value> operands,
                   ConversionPatternRewriter &rewriter) const override {
-    if (!op->hasAttr("replace_with_new_op"))
+    if (!op->hasDiscardableAttr("replace_with_new_op"))
       return failure();
     SmallVector<Type> newResultTypes;
     if (failed(getTypeConverter()->convertTypes(op->getResultTypes(),
@@ -939,7 +940,7 @@ mlir::transform::TestCountingNormalFormAttr::checkOperation(
   StringAttr counterName =
       builder.getStringAttr("test.counting_normal_form_count");
   unsigned count = 0;
-  if (auto prev = op->getAttrOfType<IntegerAttr>(counterName))
+  if (auto prev = op->getDiscardableAttrOfType<IntegerAttr>(counterName))
     count = prev.getValue().getZExtValue();
   op->setDiscardableAttr(counterName, builder.getI64IntegerAttr(count + 1));
   return DiagnosedSilenceableFailure::success();
diff --git a/mlir/test/lib/Dialect/XeGPU/TestXeGPUTransforms.cpp b/mlir/test/lib/Dialect/XeGPU/TestXeGPUTransforms.cpp
index f6b0c50da91dd..a4813528e98dd 100644
--- a/mlir/test/lib/Dialect/XeGPU/TestXeGPUTransforms.cpp
+++ b/mlir/test/lib/Dialect/XeGPU/TestXeGPUTransforms.cpp
@@ -166,7 +166,7 @@ class TestStepOpPattern : public OpConversionPattern<vector::StepOp> {
                   ConversionPatternRewriter &rewriter) const override {
 
     auto layoutName = xegpu::getTemporaryLayoutName(op->getResult(0));
-    auto sliceAttr = op->getAttrOfType<xegpu::SliceAttr>(layoutName);
+    auto sliceAttr = op->getDiscardableAttrOfType<xegpu::SliceAttr>(layoutName);
     if (!sliceAttr || sliceAttr.getRank() != 1)
       return failure();
 
@@ -437,7 +437,8 @@ struct TestXeGPULayoutInterface
     target.addDynamicallyLegalOp<vector::StepOp>(
         [&](vector::StepOp op) -> bool {
           auto layoutName = xegpu::getTemporaryLayoutName(op->getResult(0));
-          auto sliceAttr = op->getAttrOfType<xegpu::SliceAttr>(layoutName);
+          auto sliceAttr =
+              op->getDiscardableAttrOfType<xegpu::SliceAttr>(layoutName);
           return isLegal(sliceAttr);
         });
 
diff --git a/mlir/test/lib/IR/TestAffineWalk.cpp b/mlir/test/lib/IR/TestAffineWalk.cpp
index e8b836888b459..68806567ad9bb 100644
--- a/mlir/test/lib/IR/TestAffineWalk.cpp
+++ b/mlir/test/lib/IR/TestAffineWalk.cpp
@@ -43,7 +43,7 @@ void TestAffineWalk::runOnOperation() {
   auto m = getOperation();
   // Test whether the walk is being correctly interrupted.
   m.walk([](Operation *op) {
-    for (NamedAttribute attr : op->getAttrs()) {
+    for (NamedAttribute attr : op->getDiscardableAttrDictionary().getValue()) {
       auto mapAttr = dyn_cast<AffineMapAttr>(attr.getValue());
       if (!mapAttr)
         return;
diff --git a/mlir/test/lib/IR/TestBuiltinAttributeInterfaces.cpp b/mlir/test/lib/IR/TestBuiltinAttributeInterfaces.cpp
index f44b78673ec5f..6bac095f71734 100644
--- a/mlir/test/lib/IR/TestBuiltinAttributeInterfaces.cpp
+++ b/mlir/test/lib/IR/TestBuiltinAttributeInterfaces.cpp
@@ -35,7 +35,10 @@ struct TestElementsAttrInterface
   }
   void runOnOperation() override {
     getOperation().walk([&](Operation *op) {
-      for (NamedAttribute attr : op->getAttrs()) {
+      NamedAttrList attrs(op->getDiscardableAttrDictionary());
+      if (op->getPropertiesStorageSize())
+        op->getName().populateInherentAttrs(op, attrs);
+      for (NamedAttribute attr : attrs) {
         auto elementsAttr = dyn_cast<ElementsAttr>(attr.getValue());
         if (!elementsAttr)
           continue;
diff --git a/mlir/test/lib/IR/TestBuiltinDistinctAttributes.cpp b/mlir/test/lib/IR/TestBuiltinDistinctAttributes.cpp
index 4717ce345fc1f..924a8a91e3903 100644
--- a/mlir/test/lib/IR/TestBuiltinDistinctAttributes.cpp
+++ b/mlir/test/lib/IR/TestBuiltinDistinctAttributes.cpp
@@ -30,11 +30,13 @@ struct DistinctAttributesPass
     /// Walk all operations and create a distinct output attribute given a
     /// distinct input attribute.
     funcOp->walk([](Operation *op) {
-      auto distinctAttr = op->getAttrOfType<DistinctAttr>("distinct.input");
+      auto distinctAttr =
+          op->getDiscardableAttrOfType<DistinctAttr>("distinct.input");
       if (!distinctAttr)
         return;
-      op->setAttr("distinct.output",
-                  DistinctAttr::create(distinctAttr.getReferencedAttr()));
+      op->setDiscardableAttr(
+          "distinct.output",
+          DistinctAttr::create(distinctAttr.getReferencedAttr()));
     });
   }
 };
diff --git a/mlir/test/lib/IR/TestDiagnostics.cpp b/mlir/test/lib/IR/TestDiagnostics.cpp
index 578486c0a3b14..38126b48ea90f 100644
--- a/mlir/test/lib/IR/TestDiagnostics.cpp
+++ b/mlir/test/lib/IR/TestDiagnostics.cpp
@@ -51,7 +51,8 @@ struct TestDiagnosticFilterPass
 
     // Emit a diagnostic for every operation with a valid loc.
     getOperation()->walk([&](Operation *op) {
-      if (LocationAttr locAttr = op->getAttrOfType<LocationAttr>("test.loc"))
+      if (LocationAttr locAttr =
+              op->getDiscardableAttrOfType<LocationAttr>("test.loc"))
         emitError(locAttr, "test diagnostic");
     });
   }
diff --git a/mlir/test/lib/IR/TestDiagnosticsMetadata.cpp b/mlir/test/lib/IR/TestDiagnosticsMetadata.cpp
index 5cb0193baa171..07dc9cb430128 100644
--- a/mlir/test/lib/IR/TestDiagnosticsMetadata.cpp
+++ b/mlir/test/lib/IR/TestDiagnosticsMetadata.cpp
@@ -43,7 +43,8 @@ struct TestDiagnosticMetadataPass
 
     // Emit a diagnostic for every operation with a valid loc.
     getOperation()->walk([&](Operation *op) {
-      if (StringAttr strAttr = op->getAttrOfType<StringAttr>("attr")) {
+      if (StringAttr strAttr =
+              op->getDiscardableAttrOfType<StringAttr>("attr")) {
         if (strAttr.getValue() == "emit_error")
           emitError(op->getLoc(), "test diagnostic metadata")
               .getUnderlyingDiagnostic()
diff --git a/mlir/test/lib/IR/TestDominance.cpp b/mlir/test/lib/IR/TestDominance.cpp
index b34149b3e2cbd..60d37476b5bf9 100644
--- a/mlir/test/lib/IR/TestDominance.cpp
+++ b/mlir/test/lib/IR/TestDominance.cpp
@@ -51,12 +51,13 @@ class DominanceTest {
 
     // Helper function that annotates the IR with block IDs.
     auto annotateBlockId = [&](Operation *op, int64_t blockId) {
-      auto idAttr = op->getAttrOfType<DenseI64ArrayAttr>(kBlockIdsAttrName);
+      auto idAttr =
+          op->getDiscardableAttrOfType<DenseI64ArrayAttr>(kBlockIdsAttrName);
       SmallVector<int64_t> ids;
       if (idAttr)
         ids = llvm::to_vector(idAttr.asArrayRef());
       ids.push_back(blockId);
-      op->setAttr(kBlockIdsAttrName, b.getDenseI64ArrayAttr(ids));
+      op->setDiscardableAttr(kBlockIdsAttrName, b.getDenseI64ArrayAttr(ids));
     };
 
     // Create unique IDs for each block.
diff --git a/mlir/test/lib/IR/TestFunc.cpp b/mlir/test/lib/IR/TestFunc.cpp
index 2d4050d3484d0..7597f06db42f5 100644
--- a/mlir/test/lib/IR/TestFunc.cpp
+++ b/mlir/test/lib/IR/TestFunc.cpp
@@ -26,7 +26,8 @@ struct TestFuncInsertArg
 
     UnknownLoc unknownLoc = UnknownLoc::get(module.getContext());
     for (auto func : module.getOps<FunctionOpInterface>()) {
-      auto inserts = func->getAttrOfType<ArrayAttr>("test.insert_args");
+      auto inserts =
+          func->getDiscardableAttrOfType<ArrayAttr>("test.insert_args");
       if (!inserts || inserts.empty())
         continue;
       SmallVector<unsigned, 4> indicesToInsert;
@@ -44,7 +45,7 @@ struct TestFuncInsertArg
                                    ? Location(cast<LocationAttr>(insert[3]))
                                    : unknownLoc);
       }
-      func->removeAttr("test.insert_args");
+      func->removeDiscardableAttr("test.insert_args");
       if (succeeded(func.insertArguments(indicesToInsert, typesToInsert,
                                          attrsToInsert, locsToInsert)))
         continue;
@@ -68,7 +69,8 @@ struct TestFuncInsertResult
     auto module = getOperation();
 
     for (auto func : module.getOps<FunctionOpInterface>()) {
-      auto inserts = func->getAttrOfType<ArrayAttr>("test.insert_results");
+      auto inserts =
+          func->getDiscardableAttrOfType<ArrayAttr>("test.insert_results");
       if (!inserts || inserts.empty())
         continue;
       SmallVector<unsigned, 4> indicesToInsert;
@@ -82,7 +84,7 @@ struct TestFuncInsertResult
                                     ? cast<DictionaryAttr>(insert[2])
                                     : DictionaryAttr::get(&getContext()));
       }
-      func->removeAttr("test.insert_results");
+      func->removeDiscardableAttr("test.insert_results");
       if (succeeded(func.insertResults(indicesToInsert, typesToInsert,
                                        attrsToInsert)))
         continue;
@@ -166,7 +168,8 @@ struct TestFuncSetType
     SymbolTable symbolTable(module);
 
     for (auto func : module.getOps<FunctionOpInterface>()) {
-      auto sym = func->getAttrOfType<FlatSymbolRefAttr>("test.set_type_from");
+      auto sym = func->getDiscardableAttrOfType<FlatSymbolRefAttr>(
+          "test.set_type_from");
       if (!sym)
         continue;
       func.setType(symbolTable.lookup<FunctionOpInterface>(sym.getValue())
diff --git a/mlir/test/lib/IR/TestOperationEquals.cpp b/mlir/test/lib/IR/TestOperationEquals.cpp
index 7cef2e5bd4bfe..6e765e58b1f26 100644
--- a/mlir/test/lib/IR/TestOperationEquals.cpp
+++ b/mlir/test/lib/IR/TestOperationEquals.cpp
@@ -23,7 +23,7 @@ struct TestOperationEqualPass
     ModuleOp module = getOperation();
     // Expects two operations at the top-level:
     int opCount = module.getBody()->getOperations().size();
-    if (module->hasAttr("test.includes_setup")) {
+    if (module->hasDiscardableAttr("test.includes_setup")) {
       if (opCount < 2) {
         module.emitError()
             << "expected at least 2 top-level ops in the module, got "
@@ -41,9 +41,9 @@ struct TestOperationEqualPass
     llvm::outs() << first->getName().getStringRef() << " with attr "
                  << first->getDiscardableAttrDictionary();
     OperationEquivalence::Flags flags{};
-    if (!first->hasAttr("strict_loc_check"))
+    if (!first->hasDiscardableAttr("strict_loc_check"))
       flags |= OperationEquivalence::IgnoreLocations;
-    if (first->hasAttr("ignore_commutativity"))
+    if (first->hasDiscardableAttr("ignore_commutativity"))
       flags |= OperationEquivalence::IgnoreCommutativity;
     if (OperationEquivalence::isEquivalentTo(first, &module.getBody()->back(),
                                              flags))
diff --git a/mlir/test/lib/IR/TestPrintNesting.cpp b/mlir/test/lib/IR/TestPrintNesting.cpp
index c66149b33371d..b1e878964c49c 100644
--- a/mlir/test/lib/IR/TestPrintNesting.cpp
+++ b/mlir/test/lib/IR/TestPrintNesting.cpp
@@ -35,9 +35,10 @@ struct TestPrintNestingPass
                   << op->getNumOperands() << " operands and "
                   << op->getNumResults() << " results\n";
     // Print the operation attributes
-    if (!op->getAttrs().empty()) {
-      printIndent() << op->getAttrs().size() << " attributes:\n";
-      for (NamedAttribute attr : op->getAttrs())
+    if (!op->getDiscardableAttrDictionary().getValue().empty()) {
+      printIndent() << op->getDiscardableAttrDictionary().getValue().size()
+                    << " attributes:\n";
+      for (NamedAttribute attr : op->getDiscardableAttrDictionary().getValue())
         printIndent() << " - '" << attr.getName().getValue() << "' : '"
                       << attr.getValue() << "'\n";
     }
diff --git a/mlir/test/lib/IR/TestSymbolUses.cpp b/mlir/test/lib/IR/TestSymbolUses.cpp
index 6aac9bfc8baa4..e8170b8531d1d 100644
--- a/mlir/test/lib/IR/TestSymbolUses.cpp
+++ b/mlir/test/lib/IR/TestSymbolUses.cpp
@@ -118,7 +118,8 @@ struct SymbolReplacementPass
     SymbolTableCollection symbolTable;
     SymbolUserMap symbolUsers(symbolTable, module);
     module.getBodyRegion().walk([&](Operation *nestedOp) {
-      StringAttr newName = nestedOp->getAttrOfType<StringAttr>("sym.new_name");
+      StringAttr newName =
+          nestedOp->getDiscardableAttrOfType<StringAttr>("sym.new_name");
       if (!newName)
         return;
       symbolUsers.replaceAllUsesWith(nestedOp, newName);
diff --git a/mlir/test/lib/IR/TestVisitors.cpp b/mlir/test/lib/IR/TestVisitors.cpp
index 148a57f2a739b..603ee12a1aa46 100644
--- a/mlir/test/lib/IR/TestVisitors.cpp
+++ b/mlir/test/lib/IR/TestVisitors.cpp
@@ -251,7 +251,7 @@ static void testBlockAndRegionWalkers(Operation *op) {
 
   llvm::outs() << "Invoke block pre-order visits on blocks\n";
   op->walk([&](Operation *op) {
-    if (!op->hasAttr("walk_blocks"))
+    if (!op->hasDiscardableAttr("walk_blocks"))
       return;
     for (Region &region : op->getRegions()) {
       for (Block &block : region.getBlocks()) {
@@ -262,7 +262,7 @@ static void testBlockAndRegionWalkers(Operation *op) {
 
   llvm::outs() << "Invoke block post-order visits on blocks\n";
   op->walk([&](Operation *op) {
-    if (!op->hasAttr("walk_blocks"))
+    if (!op->hasDiscardableAttr("walk_blocks"))
       return;
     for (Region &region : op->getRegions()) {
       for (Block &block : region.getBlocks()) {
@@ -273,7 +273,7 @@ static void testBlockAndRegionWalkers(Operation *op) {
 
   llvm::outs() << "Invoke region pre-order visits on region\n";
   op->walk([&](Operation *op) {
-    if (!op->hasAttr("walk_regions"))
+    if (!op->hasDiscardableAttr("walk_regions"))
       return;
     for (Region &region : op->getRegions()) {
       region.walk<WalkOrder::PreOrder>(regionPure);
@@ -282,7 +282,7 @@ static void testBlockAndRegionWalkers(Operation *op) {
 
   llvm::outs() << "Invoke region post-order visits on region\n";
   op->walk([&](Operation *op) {
-    if (!op->hasAttr("walk_regions"))
+    if (!op->hasDiscardableAttr("walk_regions"))
       return;
     for (Region &region : op->getRegions()) {
       region.walk<WalkOrder::PostOrder>(regionPure);
diff --git a/mlir/test/lib/IR/TestVisitorsGeneric.cpp b/mlir/test/lib/IR/TestVisitorsGeneric.cpp
index 4556671df0ba0..20193bd71a6f5 100644
--- a/mlir/test/lib/IR/TestVisitorsGeneric.cpp
+++ b/mlir/test/lib/IR/TestVisitorsGeneric.cpp
@@ -64,31 +64,33 @@ struct TestGenericIRVisitorInterruptPass
 
     auto walker = [&](Operation *op, const WalkStage &stage) {
       if (auto interruptBeforeAall =
-              op->getAttrOfType<BoolAttr>("interrupt_before_all"))
+              op->getDiscardableAttrOfType<BoolAttr>("interrupt_before_all"))
         if (interruptBeforeAall.getValue() && stage.isBeforeAllRegions())
           return WalkResult::interrupt();
 
       if (auto interruptAfterAll =
-              op->getAttrOfType<BoolAttr>("interrupt_after_all"))
+              op->getDiscardableAttrOfType<BoolAttr>("interrupt_after_all"))
         if (interruptAfterAll.getValue() && stage.isAfterAllRegions())
           return WalkResult::interrupt();
 
-      if (auto interruptAfterRegion =
-              op->getAttrOfType<IntegerAttr>("interrupt_after_region"))
+      if (auto interruptAfterRegion = op->getDiscardableAttrOfType<IntegerAttr>(
+              "interrupt_after_region"))
         if (stage.isAfterRegion(
                 static_cast<int>(interruptAfterRegion.getInt())))
           return WalkResult::interrupt();
 
-      if (auto skipBeforeAall = op->getAttrOfType<BoolAttr>("skip_before_all"))
+      if (auto skipBeforeAall =
+              op->getDiscardableAttrOfType<BoolAttr>("skip_before_all"))
         if (skipBeforeAall.getValue() && stage.isBeforeAllRegions())
           return WalkResult::skip();
 
-      if (auto skipAfterAll = op->getAttrOfType<BoolAttr>("skip_after_all"))
+      if (auto skipAfterAll =
+              op->getDiscardableAttrOfType<BoolAttr>("skip_after_all"))
         if (skipAfterAll.getValue() && stage.isAfterAllRegions())
           return WalkResult::skip();
 
       if (auto skipAfterRegion =
-              op->getAttrOfType<IntegerAttr>("skip_after_region"))
+              op->getDiscardableAttrOfType<IntegerAttr>("skip_after_region"))
         if (stage.isAfterRegion(static_cast<int>(skipAfterRegion.getInt())))
           return WalkResult::skip();
 
@@ -131,7 +133,7 @@ struct TestGenericIRBlockVisitorInterruptPass
 
     auto walker = [&](Block *block) {
       for (Operation &op : *block)
-        if (op.getAttrOfType<BoolAttr>("interrupt"))
+        if (op.getDiscardableAttrOfType<BoolAttr>("interrupt"))
           return WalkResult::interrupt();
 
       llvm::outs() << "step " << stepNo++ << "\n";
@@ -162,7 +164,7 @@ struct TestGenericIRRegionVisitorInterruptPass
 
     auto walker = [&](Region *region) {
       for (Operation &op : region->getOps())
-        if (op.getAttrOfType<BoolAttr>("interrupt"))
+        if (op.getDiscardableAttrOfType<BoolAttr>("interrupt"))
           return WalkResult::interrupt();
 
       llvm::outs() << "step " << stepNo++ << "\n";
diff --git a/mlir/test/lib/Transforms/TestControlFlowSink.cpp b/mlir/test/lib/Transforms/TestControlFlowSink.cpp
index ad34b6c2ffdf8..7e4decabb0832 100644
--- a/mlir/test/lib/Transforms/TestControlFlowSink.cpp
+++ b/mlir/test/lib/Transforms/TestControlFlowSink.cpp
@@ -43,8 +43,8 @@ struct TestControlFlowSinkPass
     auto moveIntoRegion = [](Operation *op, Region *region) {
       Block &entry = region->front();
       op->moveBefore(&entry, entry.begin());
-      op->setAttr("was_sunk",
-                  Builder(op).getI32IntegerAttr(region->getRegionNumber()));
+      op->setDiscardableAttr(
+          "was_sunk", Builder(op).getI32IntegerAttr(region->getRegionNumber()));
     };
 
     getOperation()->walk([&](Operation *op) {
diff --git a/mlir/test/mlir-linalg-ods-gen/test-linalg-ods-yaml-gen.yaml b/mlir/test/mlir-linalg-ods-gen/test-linalg-ods-yaml-gen.yaml
index 00c70705cbb35..b88c7855b990d 100644
--- a/mlir/test/mlir-linalg-ods-gen/test-linalg-ods-yaml-gen.yaml
+++ b/mlir/test/mlir-linalg-ods-gen/test-linalg-ods-yaml-gen.yaml
@@ -183,7 +183,7 @@ structured_op: !LinalgStructuredOpConfig
 
 #       IMPL:  Test2Op::hasDynamicIndexingMaps() { return true; }
 #       IMPL:  Test2Op::verifyIndexingMapRequiredAttributes()
-#       IMPL:  auto attr = op->getAttrOfType<DenseElementsAttr>("strides")
+#       IMPL:  op->getInherentAttr("strides")
 #       IMPL:  "incorrect element type for index attribute 'strides'"
 #       IMPL:  "incorrect shape for index attribute 'strides'"
 #       IMPL:  void Test2Op::regionBuilder(ImplicitLocOpBuilder &b,
diff --git a/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp b/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
index 67dbab8ca79f9..e4f5143b47bdf 100644
--- a/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
+++ b/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
@@ -623,7 +623,8 @@ SmallVector<utils::IteratorType> {0}::getIteratorTypesArray() {{
 static const char structuredOpIndexingMapsFormat[] = R"FMT(
 ArrayAttr {0}::getIndexingMaps() {{
   static const char memoizeAttr[] = "linalg.memoized_indexing_maps";
-  ArrayAttr cached = getOperation()->getAttrOfType<ArrayAttr>(memoizeAttr);
+  ArrayAttr cached =
+      getOperation()->getDiscardableAttrOfType<ArrayAttr>(memoizeAttr);
   if (cached)
     return cached;
 
@@ -632,7 +633,7 @@ ArrayAttr {0}::getIndexingMaps() {{
   SmallVector<AffineMap> maps;
   {1}
   cached = Builder(context).getAffineMapArrayAttr(maps);
-  getOperation()->setAttr(memoizeAttr, cached);
+  getOperation()->setDiscardableAttr(memoizeAttr, cached);
   return cached;
 }
 )FMT";
@@ -976,7 +977,8 @@ std::string {0}::getLibraryCallName() {{
       // {0}: Attribute name
       // {1}: Attribute size
       static const char attrFmt[] = R"FMT(
-if (auto attr = op->getAttrOfType<DenseElementsAttr>("{0}")) {{
+if (auto attr = llvm::dyn_cast_or_null<DenseElementsAttr>(
+        op->getInherentAttr("{0}").value_or(Attribute{{}))) {{
   if (!attr.getType().getElementType().isInteger(64))
     return op->emitError("incorrect element type for index attribute '{0}'");
   if (attr.getType().getShape() != ArrayRef<int64_t>{{ {1} })
diff --git a/mlir/tools/mlir-tblgen/DialectGen.cpp b/mlir/tools/mlir-tblgen/DialectGen.cpp
index 8eecad39f49f3..d2f9faa7a9db5 100644
--- a/mlir/tools/mlir-tblgen/DialectGen.cpp
+++ b/mlir/tools/mlir-tblgen/DialectGen.cpp
@@ -215,17 +215,17 @@ static const char *const discardableAttrHelperDecl = R"(
         : name(::mlir::StringAttr::get(ctx, getNameStr())) {{}
 
      {2} getAttr(::mlir::Operation *op) const {{
-       return op->getAttrOfType<{2}>(name);
+       return op->getDiscardableAttrOfType<{2}>(name);
      }
      void setAttr(::mlir::Operation *op, {2} val) const {{
-       op->setAttr(name, val);
+       op->setDiscardableAttr(name, val);
      }
      bool isAttrPresent(::mlir::Operation *op) const {{
-       return op->hasAttrOfType<{2}>(name);
+       return op->hasDiscardableAttrOfType<{2}>(name);
      }
      void removeAttr(::mlir::Operation *op) const {{
-       assert(op->hasAttrOfType<{2}>(name));
-       op->removeAttr(name);
+       assert(op->hasDiscardableAttrOfType<{2}>(name));
+       op->removeDiscardableAttr(name);
      }
    };
    {0}AttrHelper get{0}AttrHelper() {
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index d7c119a31795a..6bced8e2c31b0 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -373,7 +373,7 @@ class OpOrAdaptorHelper {
 
   // Get the code snippet for getting the named attribute range.
   StringRef getAttrRange() const {
-    return emitForOp ? "(*this)->getAttrs()" : "odsAttrs";
+    return emitForOp ? "(*this)->getRawDictionaryAttrs()" : "odsAttrs";
   }
 
   // Get the prefix code for emitting an error.
@@ -2349,11 +2349,10 @@ void OpEmitter::genNamedOperandSetters() {
     // MutableOperandRangeRange that provides a range over all of the
     // sub-ranges.
     if (operand.isVariadicOfVariadic()) {
-      body << "  return "
-              "mutableRange.split(*(*this)->getAttrDictionary().getNamed("
-           << op.getGetterName(
-                  operand.constraint.getVariadicOfVariadicSegmentSizeAttr())
-           << "AttrName()));\n";
+      StringRef segmentAttr =
+          operand.constraint.getVariadicOfVariadicSegmentSizeAttr();
+      body << "  return mutableRange.split({" << op.getGetterName(segmentAttr)
+           << "AttrName(), " << op.getGetterName(segmentAttr) << "Attr()});\n";
     } else {
       // Otherwise, we use the full range directly.
       body << "  return mutableRange;\n";
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 470d27cfc6060..12a50f05bdff3 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -2063,7 +2063,9 @@ static const char *regionSingleBlockImplicitTerminatorPrinterCode = R"(
   {
     bool printTerminator = true;
     if (auto *term = {0}.empty() ? nullptr : {0}.begin()->getTerminator()) {{
-      printTerminator = !term->getAttrDictionary().empty() ||
+      ::mlir::NamedAttrList termAttrs(term->getRawDictionaryAttrs());
+      term->getName().populateInherentAttrs(term, termAttrs);
+      printTerminator = !termAttrs.empty() ||
                         term->getNumOperands() != 0 ||
                         term->getNumResults() != 0;
     }
@@ -2220,11 +2222,17 @@ static void genAttrDictPrinter(OperationFormat &fmt, Operator &op,
   if (fmt.hasPropDict)
     body << "  _odsPrinter.printOptionalAttrDict"
          << (withKeyword ? "WithKeyword" : "")
-         << "(llvm::to_vector((*this)->getDiscardableAttrs()), elidedAttrs);\n";
-  else
-    body << "  _odsPrinter.printOptionalAttrDict"
+         << "(llvm::to_vector((*this)->getDiscardableAttrDictionary().getValue("
+            ")), elidedAttrs);\n";
+  else {
+    body << "  ::mlir::NamedAttrList _odsAttrs("
+            "(*this)->getRawDictionaryAttrs());\n"
+            "  (*this)->getName().populateInherentAttrs(*this, _odsAttrs);\n"
+            "  _odsPrinter.printOptionalAttrDict"
          << (withKeyword ? "WithKeyword" : "")
-         << "((*this)->getAttrs(), elidedAttrs);\n";
+         << "(_odsAttrs.getDictionary(getContext()).getValue(), "
+            "elidedAttrs);\n";
+  }
 }
 
 /// Generate the printer for a literal value. `shouldEmitSpace` is true if a
@@ -2266,7 +2274,11 @@ static void genCustomDirectiveParameterPrinter(FormatElement *element,
     body << op.getGetterName(attr->getVar()->name) << "Attr()";
 
   } else if (isa<AttrDictDirective>(element)) {
-    body << "getOperation()->getAttrDictionary()";
+    body << "[&]() { ::mlir::NamedAttrList attrs("
+            "getOperation()->getRawDictionaryAttrs()); "
+            "getOperation()->getName().populateInherentAttrs("
+            "getOperation(), attrs); return attrs.getDictionary("
+            "getOperation()->getContext()); }()";
 
   } else if (isa<PropDictDirective>(element)) {
     body << "getProperties()";
diff --git a/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp b/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
index 4de8c60dfb55a..f9f25ebb1e663 100644
--- a/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
+++ b/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
@@ -514,9 +514,9 @@ constexpr llvm::StringLiteral constantIdEnumAttrs[] = {
 static void emitAttributeSerialization(const Attribute &attr,
                                        ArrayRef<SMLoc> loc, StringRef tabs,
                                        StringRef opVar, StringRef operandList,
-                                       StringRef attrName, raw_ostream &os) {
+                                       StringRef getterName, raw_ostream &os) {
   os << tabs
-     << formatv("if (auto attr = {0}->getAttr(\"{1}\")) {{\n", opVar, attrName);
+     << formatv("if (auto attr = {0}.{1}Attr()) {{\n", opVar, getterName);
   if (llvm::is_contained(constantIdEnumAttrs, attr.getAttrDefName())) {
     EnumInfo baseEnum(attr.getDef().getValueAsDef("enum"));
     os << tabs
@@ -628,7 +628,7 @@ static void emitArgumentSerialization(const Operator &op, ArrayRef<SMLoc> loc,
     for (const NamedAttribute &attr : op.getAttributes()) {
       emitAttributeSerialization(
           (attr.attr.isOptional() ? attr.attr.getBaseAttr() : attr.attr), loc,
-          tabs, opVar, operands, attr.name, os);
+          tabs, opVar, operands, op.getGetterName(attr.name), os);
       os << tabs
          << formatv("{0}.push_back(\"{1}\");\n", elidedAttrs, attr.name);
     }
@@ -659,7 +659,7 @@ static void emitArgumentSerialization(const Operator &op, ArrayRef<SMLoc> loc,
       auto newtabs = tabs.str() + "  ";
       emitAttributeSerialization(
           (attr->attr.isOptional() ? attr->attr.getBaseAttr() : attr->attr),
-          loc, newtabs, opVar, operands, attr->name, os);
+          loc, newtabs, opVar, operands, op.getGetterName(attr->name), os);
       os << newtabs
          << formatv("{0}.push_back(\"{1}\");\n", elidedAttrs, attr->name);
     }
@@ -702,7 +702,10 @@ static void emitDecorationSerialization(const Operator &op, StringRef tabs,
                                         StringRef resultID, raw_ostream &os) {
   if (op.getNumResults() == 1) {
     // All non-argument attributes translated into OpDecorate instruction
-    os << tabs << formatv("for (auto attr : {0}->getAttrs()) {{\n", opVar);
+    os << tabs
+       << formatv("for (auto attr : "
+                  "{0}->getDiscardableAttrDictionary().getValue()) {{\n",
+                  opVar);
     os << tabs
        << formatv("  if (llvm::is_contained({0}, attr.getName())) {{",
                   elidedAttrs);
diff --git a/mlir/unittests/Bytecode/BytecodeTest.cpp b/mlir/unittests/Bytecode/BytecodeTest.cpp
index d8f6510924698..7e11aa14fda44 100644
--- a/mlir/unittests/Bytecode/BytecodeTest.cpp
+++ b/mlir/unittests/Bytecode/BytecodeTest.cpp
@@ -222,7 +222,7 @@ TEST(Bytecode, OpWithoutProperties) {
   ASSERT_TRUE(succeeded(readBytecodeFile(
       llvm::MemoryBufferRef(bytecode, "string-buffer"), block.get(), config)));
   Operation *roundtripped = &block->front();
-  EXPECT_EQ(roundtripped->getAttrs().size(), 2u);
+  EXPECT_EQ(roundtripped->getRawDictionaryAttrs().size(), 2u);
   EXPECT_EQ(roundtripped->getInherentAttr("inherent_attr"), std::nullopt);
   EXPECT_NE(roundtripped->getDiscardableAttr("inherent_attr"), Attribute());
   EXPECT_NE(roundtripped->getDiscardableAttr("other_attr"), Attribute());
@@ -389,7 +389,7 @@ TEST(Bytecode, LocationElisionPreservesAttributes) {
   EXPECT_TRUE(isa<UnknownLoc>(innerOp->getLoc()));
 
   // 2. Verify that the semantic location attribute WAS PRESERVED.
-  Attribute semanticLocAttr = innerOp->getAttr("some_loc_attr");
+  Attribute semanticLocAttr = innerOp->getDiscardableAttr("some_loc_attr");
   ASSERT_TRUE(semanticLocAttr);
   auto locAttr = dyn_cast<LocationAttr>(semanticLocAttr);
   ASSERT_TRUE(locAttr);
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsCGTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsCGTest.cpp
index 1887405871ff3..33877636b050a 100644
--- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsCGTest.cpp
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsCGTest.cpp
@@ -161,7 +161,7 @@ TEST_F(OpenACCUtilsCGTest, getDataLayoutWithSpec) {
   auto indexEntry = DataLayoutEntryAttr::get(IndexType::get(&context),
                                              b.getI32IntegerAttr(32));
   auto spec = DataLayoutSpecAttr::get(&context, {indexEntry});
-  (*module)->setAttr(DLTIDialect::kDataLayoutAttrName, spec);
+  (*module)->setDiscardableAttr(DLTIDialect::kDataLayoutAttrName, spec);
 
   // With explicit spec, should return DataLayout regardless of allowDefault
   auto dl1 = getDataLayout(module->getOperation(), /*allowDefault=*/false);
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsGPUTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsGPUTest.cpp
index e7ee0655d41f0..e4243ed04f8f1 100644
--- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsGPUTest.cpp
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsGPUTest.cpp
@@ -51,8 +51,8 @@ TEST_F(OpenACCUtilsGPUTest, getOrCreateGPUModuleCreatesWhenMissing) {
   EXPECT_EQ(gpuMod->getName(), kDefaultGPUModuleName);
 
   // Module should now have the container module attribute
-  EXPECT_TRUE(
-      (*module)->hasAttr(gpu::GPUDialect::getContainerModuleAttrName()));
+  EXPECT_TRUE((*module)->hasDiscardableAttr(
+      gpu::GPUDialect::getContainerModuleAttrName()));
 }
 
 TEST_F(OpenACCUtilsGPUTest, getOrCreateGPUModuleReturnsExisting) {
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsLoopTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsLoopTest.cpp
index b71affa8ded11..c7337f4967d4c 100644
--- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsLoopTest.cpp
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsLoopTest.cpp
@@ -274,7 +274,7 @@ TEST_F(OpenACCUtilsLoopTest, ConvertLoopToSCFForWithCollapse) {
   // Ensure the collapsed loop has an attribute indicating the number
   // of collapsed loops
   auto collapseAttr =
-      forOp->getAttrOfType<IntegerAttr>(getCollapseCountAttrName());
+      forOp->getDiscardableAttrOfType<IntegerAttr>(getCollapseCountAttrName());
   ASSERT_TRUE(collapseAttr);
   EXPECT_EQ(collapseAttr.getInt(), 2);
 
@@ -311,7 +311,7 @@ TEST_F(OpenACCUtilsLoopTest, ConvertLoopToSCFForNoCollapse) {
   EXPECT_TRUE(hasNestedFor);
 
   // No collapse happened, so no collapse_count attribute is expected
-  EXPECT_FALSE(forOp->hasAttr(getCollapseCountAttrName()));
+  EXPECT_FALSE(forOp->hasDiscardableAttr(getCollapseCountAttrName()));
 }
 
 TEST_F(OpenACCUtilsLoopTest, ConvertLoopToSCFForWithCollapseAndDynamicBounds) {
diff --git a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
index 30c6f4312efaf..caa22fa6d9e10 100644
--- a/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
+++ b/mlir/unittests/Dialect/OpenACC/OpenACCUtilsTest.cpp
@@ -425,7 +425,7 @@ TEST_F(OpenACCUtilsTest, getVariableNameDirect) {
 
   // Set the acc.var_name attribute
   auto varNameAttr = VarNameAttr::get(&context, "my_variable");
-  allocOp.get()->setAttr(getVarNameAttrName(), varNameAttr);
+  allocOp.get()->setDiscardableAttr(getVarNameAttrName(), varNameAttr);
 
   Value varPtr = allocOp->getResult();
 
@@ -442,7 +442,7 @@ TEST_F(OpenACCUtilsTest, getVariableNameThroughCast) {
 
   // Set the acc.var_name attribute on the alloca
   auto varNameAttr = VarNameAttr::get(&context, "casted_variable");
-  allocOp.get()->setAttr(getVarNameAttrName(), varNameAttr);
+  allocOp.get()->setDiscardableAttr(getVarNameAttrName(), varNameAttr);
 
   Value allocResult = allocOp->getResult();
 
@@ -774,8 +774,8 @@ TEST_F(OpenACCUtilsTest, isValidSymbolUseFunctionWithRoutineInfo) {
   // Add routine_info attribute with a reference to a routine
   SmallVector<SymbolRefAttr> routineRefs = {
       SymbolRefAttr::get(&context, "acc_routine")};
-  funcOp.get()->setAttr(getRoutineInfoAttrName(),
-                        RoutineInfoAttr::get(&context, routineRefs));
+  funcOp.get()->setDiscardableAttr(getRoutineInfoAttrName(),
+                                   RoutineInfoAttr::get(&context, routineRefs));
 
   // Create a call operation that uses the function symbol
   SymbolRefAttr funcSymbol = SymbolRefAttr::get(&context, funcName);
@@ -862,7 +862,7 @@ TEST_F(OpenACCUtilsTest, isValidSymbolUseWithDeclareAttr) {
       func::FuncOp::create(b, loc, funcName, funcType);
 
   // Add declare attribute
-  funcOp.get()->setAttr(
+  funcOp.get()->setDiscardableAttr(
       getDeclareAttrName(),
       DeclareAttr::get(&context,
                        DataClauseAttr::get(&context, DataClause::acc_copy)));
@@ -1418,8 +1418,9 @@ static Value memrefViewFromBlockArgWithDeclare(OpBuilder &builder, Location loc,
   Value c0 = arith::ConstantIndexOp::create(builder, loc, 0);
   memref::ViewOp viewOp =
       memref::ViewOp::create(builder, loc, viewTy, buf, c0, ValueRange{});
-  viewOp->setAttr(getDeclareAttrName(),
-                  DeclareAttr::get(ctx, DataClauseAttr::get(ctx, clause)));
+  viewOp->setDiscardableAttr(
+      getDeclareAttrName(),
+      DeclareAttr::get(ctx, DataClauseAttr::get(ctx, clause)));
   func::ReturnOp::create(builder, loc);
   return viewOp.getResult();
 }
diff --git a/mlir/unittests/Dialect/SPIRV/SerializationTest.cpp b/mlir/unittests/Dialect/SPIRV/SerializationTest.cpp
index b0413a8e994ba..626a0f4002641 100644
--- a/mlir/unittests/Dialect/SPIRV/SerializationTest.cpp
+++ b/mlir/unittests/Dialect/SPIRV/SerializationTest.cpp
@@ -531,7 +531,7 @@ TEST_F(SerializationTest, LongCompositeDoesNotDuplicateDeclaredCapability) {
   // Pre-declare LongCompositesINTEL / SPV_INTEL_long_composites in the VCE
   // triple. The serializer must not emit a second OpCapability/OpExtension
   // when a long composite triggers `addLongCompositesCapability()`.
-  module->getOperation()->setAttr(
+  module->getOperation()->setDiscardableAttr(
       spirv::ModuleOp::getVCETripleAttrName(),
       spirv::VerCapExtAttr::get(
           spirv::Version::V_1_0, {spirv::Capability::LongCompositesINTEL},
diff --git a/mlir/unittests/IR/BlobManagerTest.cpp b/mlir/unittests/IR/BlobManagerTest.cpp
index d82482ddb7936..b32dc5a59bac4 100644
--- a/mlir/unittests/IR/BlobManagerTest.cpp
+++ b/mlir/unittests/IR/BlobManagerTest.cpp
@@ -49,7 +49,8 @@ TEST(DialectResourceBlobManagerTest, GetBlobMap) {
 
   Block *block = m->getBody();
   auto &op = block->getOperations().front();
-  auto resourceAttr = op.getAttrOfType<DenseResourceElementsAttr>("attr");
+  auto resourceAttr =
+      op.getDiscardableAttrOfType<DenseResourceElementsAttr>("attr");
   ASSERT_NE(resourceAttr, nullptr);
 
   const auto &dialectManager =
diff --git a/mlir/unittests/IR/OpPropertiesTest.cpp b/mlir/unittests/IR/OpPropertiesTest.cpp
index bea69e9e8f107..c9e18c2067366 100644
--- a/mlir/unittests/IR/OpPropertiesTest.cpp
+++ b/mlir/unittests/IR/OpPropertiesTest.cpp
@@ -394,11 +394,15 @@ TEST(OpPropertiesTest, withoutPropertiesDiscardableAttrs) {
   ParserConfig config(&context);
   OwningOpRef<Operation *> op =
       parseSourceString(withoutPropertiesAttrsSrc, config);
-  ASSERT_EQ(llvm::range_size(op->getDiscardableAttrs()), 1u);
-  EXPECT_EQ(op->getDiscardableAttrs().begin()->getName().getValue(),
+  ASSERT_EQ(llvm::range_size(op->getDiscardableAttrDictionary().getValue()),
+            1u);
+  EXPECT_EQ(op->getDiscardableAttrDictionary()
+                .getValue()
+                .begin()
+                ->getName()
+                .getValue(),
             "other_attr");
 
-  EXPECT_EQ(op->getAttrs().size(), 2u);
   EXPECT_EQ(op->getInherentAttr("inherent_attr"), std::nullopt);
   EXPECT_NE(op->getDiscardableAttr("inherent_attr"), Attribute());
   EXPECT_NE(op->getDiscardableAttr("other_attr"), Attribute());
diff --git a/mlir/unittests/IR/OperationSupportTest.cpp b/mlir/unittests/IR/OperationSupportTest.cpp
index 90e59808c984a..fa5fdfe16fde0 100644
--- a/mlir/unittests/IR/OperationSupportTest.cpp
+++ b/mlir/unittests/IR/OperationSupportTest.cpp
@@ -334,7 +334,7 @@ TEST(OperationEquivalenceTest, HashWorksWithFlags) {
   // Check ignore discardable dictionary attributes.
   SmallVector<NamedAttribute> newAttrs = {
       b.getNamedAttr("foo", b.getStringAttr("f"))};
-  op1->setAttrs(newAttrs);
+  op1->setDiscardableAttrs(newAttrs);
   EXPECT_EQ(getHash(op1, OperationEquivalence::IgnoreDiscardableAttrs),
             getHash(op2, OperationEquivalence::IgnoreDiscardableAttrs));
   EXPECT_NE(getHash(op1, OperationEquivalence::None),
diff --git a/mlir/unittests/Interfaces/DataLayoutInterfacesTest.cpp b/mlir/unittests/Interfaces/DataLayoutInterfacesTest.cpp
index 3067cf103590c..f72c00070bd01 100644
--- a/mlir/unittests/Interfaces/DataLayoutInterfacesTest.cpp
+++ b/mlir/unittests/Interfaces/DataLayoutInterfacesTest.cpp
@@ -280,11 +280,12 @@ struct OpWithLayout : public Op<OpWithLayout, DataLayoutOpInterface::Trait> {
   static StringRef getOperationName() { return "dltest.op_with_layout"; }
 
   DataLayoutSpecInterface getDataLayoutSpec() {
-    return getOperation()->getAttrOfType<DataLayoutSpecInterface>(kAttrName);
+    return getOperation()->getDiscardableAttrOfType<DataLayoutSpecInterface>(
+        kAttrName);
   }
 
   TargetSystemSpecInterface getTargetSystemSpec() {
-    return getOperation()->getAttrOfType<TargetSystemSpecInterface>(
+    return getOperation()->getDiscardableAttrOfType<TargetSystemSpecInterface>(
         kTargetSystemDescAttrName);
   }
 
@@ -332,11 +333,12 @@ struct OpWith7BitByte
   static StringRef getOperationName() { return "dltest.op_with_7bit_byte"; }
 
   DataLayoutSpecInterface getDataLayoutSpec() {
-    return getOperation()->getAttrOfType<DataLayoutSpecInterface>(kAttrName);
+    return getOperation()->getDiscardableAttrOfType<DataLayoutSpecInterface>(
+        kAttrName);
   }
 
   TargetSystemSpecInterface getTargetSystemSpec() {
-    return getOperation()->getAttrOfType<TargetSystemSpecInterface>(
+    return getOperation()->getDiscardableAttrOfType<TargetSystemSpecInterface>(
         kTargetSystemDescAttrName);
   }
 
@@ -732,7 +734,7 @@ TEST(DataLayout, CacheInvalidation) {
   EXPECT_EQ(layout.getTypeSize(Float16Type::get(&ctx)), 6u);
 
   // Replace the data layout spec with a new, empty spec.
-  op->setAttr(kAttrName, CustomDataLayoutSpec::get(&ctx, {}));
+  op->setDiscardableAttr(kAttrName, CustomDataLayoutSpec::get(&ctx, {}));
 
   // Data layout is no longer valid and should trigger assertion when queried.
 #ifndef NDEBUG
diff --git a/mlir/unittests/Pass/PassManagerTest.cpp b/mlir/unittests/Pass/PassManagerTest.cpp
index 3f5db8ebcbb6d..c0b7dcbb7eaed 100644
--- a/mlir/unittests/Pass/PassManagerTest.cpp
+++ b/mlir/unittests/Pass/PassManagerTest.cpp
@@ -51,8 +51,8 @@ struct AnnotateFunctionPass
     auto &ga = getAnalysis<GenericAnalysis>();
     auto &sa = getAnalysis<OpSpecificAnalysis>();
 
-    op->setAttr("isFunc", builder.getBoolAttr(ga.isFunc));
-    op->setAttr("isSecret", builder.getBoolAttr(sa.isSecret));
+    op->setDiscardableAttr("isFunc", builder.getBoolAttr(ga.isFunc));
+    op->setDiscardableAttr("isSecret", builder.getBoolAttr(sa.isSecret));
   }
 };
 
@@ -96,11 +96,11 @@ struct AddAttrFunctionPass
   void runOnOperation() override {
     func::FuncOp op = getOperation();
     Builder builder(op->getParentOfType<ModuleOp>());
-    if (op->hasAttr("didProcess"))
-      op->setAttr("didProcessAgain", builder.getUnitAttr());
+    if (op->hasDiscardableAttr("didProcess"))
+      op->setDiscardableAttr("didProcessAgain", builder.getUnitAttr());
 
     // We always want to set this one.
-    op->setAttr("didProcess", builder.getUnitAttr());
+    op->setDiscardableAttr("didProcess", builder.getUnitAttr());
   }
 };
 
@@ -114,7 +114,7 @@ struct AddSecondAttrFunctionPass
   void runOnOperation() override {
     func::FuncOp op = getOperation();
     Builder builder(op->getParentOfType<ModuleOp>());
-    op->setAttr("didProcess2", builder.getUnitAttr());
+    op->setDiscardableAttr("didProcess2", builder.getUnitAttr());
   }
 };
 
diff --git a/mlir/unittests/TableGen/OpBuildGen.cpp b/mlir/unittests/TableGen/OpBuildGen.cpp
index 09430336de994..5560871f47128 100644
--- a/mlir/unittests/TableGen/OpBuildGen.cpp
+++ b/mlir/unittests/TableGen/OpBuildGen.cpp
@@ -33,6 +33,13 @@ static MLIRContext &getContext() {
 /// Test fixture for providing basic utilities for testing.
 class OpBuildGenTest : public ::testing::Test {
 protected:
+  static NamedAttrList collectAttrs(Operation *op) {
+    NamedAttrList attrs(op->getDiscardableAttrDictionary());
+    if (op->getPropertiesStorageSize())
+      op->getName().populateInherentAttrs(op, attrs);
+    return NamedAttrList(attrs.getDictionary(op->getContext()));
+  }
+
   OpBuildGenTest()
       : ctx(getContext()), builder(&ctx), loc(builder.getUnknownLoc()),
         i32Ty(builder.getI32Type()), f32Ty(builder.getF32Type()),
@@ -60,10 +67,10 @@ class OpBuildGenTest : public ::testing::Test {
     for (unsigned idx : llvm::seq(0U, op->getNumOperands()))
       EXPECT_EQ(op->getOperand(idx), operands[idx]);
 
-    EXPECT_EQ(op->getAttrs().size(), attrs.size());
+    NamedAttrList actualAttrs = collectAttrs(op);
+    EXPECT_EQ(actualAttrs.getAttrs().size(), attrs.size());
     for (unsigned idx : llvm::seq<unsigned>(0U, attrs.size()))
-      EXPECT_EQ(op->getAttr(attrs[idx].getName().strref()),
-                attrs[idx].getValue());
+      EXPECT_EQ(actualAttrs.get(attrs[idx].getName()), attrs[idx].getValue());
 
     EXPECT_TRUE(mlir::succeeded(concreteOp.verify()));
     concreteOp.erase();
@@ -85,11 +92,12 @@ class OpBuildGenTest : public ::testing::Test {
     for (unsigned idx : llvm::seq(0U, op->getNumOperands()))
       EXPECT_EQ(op->getOperand(idx), operands[idx]);
 
-    EXPECT_EQ(op->getAttrs().size(), attrs.size());
-    if (op->getAttrs().size() != attrs.size()) {
+    NamedAttrList actualAttrs = collectAttrs(op);
+    EXPECT_EQ(actualAttrs.getAttrs().size(), attrs.size());
+    if (actualAttrs.getAttrs().size() != attrs.size()) {
       // Simple export where there is mismatch count.
       llvm::errs() << "Op attrs:\n";
-      for (auto it : op->getAttrs())
+      for (auto it : actualAttrs)
         llvm::errs() << "\t" << it.getName() << " = " << it.getValue() << "\n";
 
       llvm::errs() << "Expected attrs:\n";
@@ -97,8 +105,7 @@ class OpBuildGenTest : public ::testing::Test {
         llvm::errs() << "\t" << it.getName() << " = " << it.getValue() << "\n";
     } else {
       for (unsigned idx : llvm::seq<unsigned>(0U, attrs.size()))
-        EXPECT_EQ(op->getAttr(attrs[idx].getName().strref()),
-                  attrs[idx].getValue());
+        EXPECT_EQ(actualAttrs.get(attrs[idx].getName()), attrs[idx].getValue());
     }
 
     EXPECT_TRUE(mlir::succeeded(concreteOp.verify()));
diff --git a/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp b/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
index 5271923d923e2..c2ca29932014a 100644
--- a/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
+++ b/mlir/unittests/Target/LLVM/SerializeToLLVMBitcode.cpp
@@ -105,7 +105,8 @@ std::optional<mlir::gpu::SerializedObject>
 TargetAttrImpl::serializeToObject(Attribute attribute, Operation *module,
                                   const gpu::TargetOptions &options) const {
   // Set a dummy attr to be retrieved by `createObject`.
-  module->setAttr("serialize_attr", UnitAttr::get(module->getContext()));
+  module->setDiscardableAttr("serialize_attr",
+                             UnitAttr::get(module->getContext()));
   std::string targetTriple = llvm::sys::getProcessTriple();
   LLVM::ModuleToObject serializer(
       *module, targetTriple, "", "", 3, options.getInitialLlvmIRCallback(),
@@ -124,7 +125,7 @@ TargetAttrImpl::createObject(Attribute attribute, Operation *module,
       StringAttr::get(
           module->getContext(),
           StringRef(object.getObject().data(), object.getObject().size())),
-      module->getAttrDictionary(), /*kernels=*/nullptr);
+      module->getDiscardableAttrDictionary(), /*kernels=*/nullptr);
 }
 
 // This test checks the correct functioning of `TargetAttrInterface` as an API.



More information about the Mlir-commits mailing list