9 Attribute
Attributes are immutable, compiler-time data attached to operations, types, or other attributes. They carry facts that shape IR meaning without becoming SSA dataflow. An attribute may encode a constant payload, a symbol name, a comparison predicate, a layout, an affine map, a target property, or a dialect specific configuration object.
%c = arith.constant dense<[1.0, 2.0]> : tensor<2xf32>
%p = arith.cmpi sgt, %x, %y : i32
The dense array is an attribute belonging to arith.constant; sgt is an attribute selecting the meaning of arith.cmpi. Neither is an operand defined by an earlier operation. That is the first distinction to make when reading MLIR: operands are runtime or symbolic SSA inputs; attributes are fixed IR metadata known when the operation is constructed.
9.1 Why Attributes Are Not Values
An SSA value has a type, definition, uses, scope, and usually a runtime interpretation. An attribute is self-contained immutable data. It has no uses, cannot be replaced by a branch argument, and does not participate in dominance. If a threshold is selected by a user at runtime, it must be a value. If a comparison operation is permanently configured to use signed-greater-than, a predicate attribute is the correct representation.
// The value %limit may differ on every execution.
%predicate = arith.cmpi sgt, %x, %limit : i32
// The comparison interpretation is part of the operation definition.
%other = arith.cmpi sgt, %x, %y : i32
The same printed token can be misleadingly familiar: a constant operation produces a value, but its payload is stored as an attribute. This makes constant data available to folding and serialization without requiring a separate computation to construct it.
9.2 Common Attribute Families
Builtin attributes include integer, floating-point, boolean, string, type, array, dictionary, symbol-reference, affine-map, and dense-elements forms. Dialect attributes add concepts that do not fit those generic containers. A few important patterns are:
IntegerAttrpairs an arbitrary-precision integer with its type.StringAttrstores an identifier or textual payload.TypeAttrlets an operation or attribute carry a type as metadata.ArrayAttrandDictionaryAttraggregate attributes structurally.DenseElementsAttrcompactly represents dense constant tensors/vectors.FlatSymbolRefAttrandSymbolRefAttrname a symbol rather than directly pointing to an in-memory operation.
Attributes are structurally uniqued in an MLIR context, like types. They are cheap handles to immutable storage and can safely be shared. Their immutability is what lets analyses retain and compare them without defensive copying.
9.3 Attribute Dictionaries And Inherent Data
Most operations have a dictionary of named attributes. In generic syntax it is visible after the operands; custom syntax may present selected entries as keywords or omit derived information.
"func.func"() <{function_type = (i32) -> i32, sym_name = "increment"}> ({
^bb0(%arg0: i32):
func.return %arg0 : i32
}) : () -> ()
An operation definition can distinguish between inherent attributes that are part of its essential representation and discardable attributes that tools may attach without changing the operation’s primary semantics. Newer MLIR also has properties for structured inherent data. In beginner terms, both attributes and properties are static configuration, but they differ in storage and generated C++ API. Follow the operation definition when deciding where new data belongs; placing semantically essential data in an arbitrary discardable dictionary attribute makes verification and transformations less reliable.
9.4 Dense Constants And The Difference Between Data And IR
DenseElementsAttr is especially important for tensor code. It stores a compile-time collection of element values with a shaped type, often in a compact binary representation. It is a payload, not a sequence of thousands of scalar operations.
%weights = arith.constant dense<[[1.0, 0.0], [0.0, 1.0]]> : tensor<2x2xf32>
This lets a compiler inspect, fold, serialize, or place model weights without exploding the IR. But large constants still have cost: they increase serialized IR size and may need special handling by code generation or external-resource mechanisms. A transformation that duplicates a constant should consider whether it duplicates an enormous payload or merely another handle to a uniqued attribute.
9.5 Attributes In Dialect And Type Design
Attributes are not limited to operations. Dialect types often carry attributes for layouts, encodings, address spaces, or domain-specific constraints. An attribute can itself contain types and nested attributes, allowing a precise declarative description of static structure. For example, affine maps use attributes because the map is compile-time indexing logic, while the dimensions fed into an affine apply are values because they vary at runtime.
When designing a dialect, use an attribute for information that is immutable for one operation instance, affects verification or lowering, and does not need SSA dataflow. Do not put a mutable runtime state into an attribute just because it is convenient to print. Doing so prevents normal dependence analysis and usually makes the IR unsound.
9.6 Verification And Rewrites
An operation verifier should check that required attributes exist, have the right class, and agree with operands, results, regions, and one another. For example, a dimension attribute must be in range; an axis list may need to be sorted and unique; a symbol reference must use an allowed form. Parsing only proves that text can be decoded, not that all cross-field semantic conditions hold.
Rewrites should preserve attributes deliberately. Rebuilding an operation from only operands and result types can silently discard location-sensitive flags, fast-math information, layouts, or dialect configuration. Generated builders and OperationState APIs make this explicit; use an attribute filter only when you can name the attributes that are intentionally removed or recomputed.