43 index Dialect
43.1 Beginner Summary
The index dialect contains operations for arithmetic on MLIR’s builtin index type.
The builtin index type is used for loop bounds, induction variables, tensor dimensions, memref subscripts, offsets, sizes, and strides. It is similar to a machine pointer-sized integer, like intptr_t, but MLIR often manipulates it before the final target pointer width is known.
The index dialect exists for that exact problem. It lets the compiler express index arithmetic while preserving target-independent folding and analysis.
Think of it as the dialect for scalar shape, loop, and subscript math when the values have MLIR’s index type and the pipeline should not prematurely commit to i32 or i64.
43.2 Why This Dialect Exists
The index type is target-dependent. On one target it may lower to 32 bits; on another, it may lower to 64 bits.
That creates a subtle compiler problem:
index.constant 3000000000
This value has a different fixed-width interpretation depending on whether the eventual index width is 32 or 64 bits. A compiler still wants to fold, canonicalize, and reason about index expressions before that choice is final.
The index dialect provides operations whose folding rules are aware of this. Constants are stored internally at the maximum supported index bitwidth, 64 bits, but folders only fold when the result is valid for both 32-bit and 64-bit index interpretations.
That makes the dialect safer than blindly lowering index math to ordinary fixed-width integer math too early.
43.3 When It Matters
The index dialect matters whenever a pipeline is doing arithmetic on:
- Loop bounds and induction variables.
- Tensor or memref dimensions.
- Linearized offsets.
- Sizes, strides, and layout calculations.
- Shape-related scalar math that has already become
index. - Target-independent analysis of pointer-width-sized values.
It commonly appears near structured control flow, tensor, memref, affine, and shape-related IR:
tensor/memref/scf/affine/shape-like calculations
-> index arithmetic and canonicalization
-> convert-index-to-llvm or convert-index-to-spirv
-> target-width integer operations
It is especially important in libraries or compiler pipelines that must support both 32-bit and 64-bit targets.
43.4 When To Use It
Use the index dialect when you are computing scalar values of builtin index type and want the operation semantics to remain target-independent.
Use it for:
- Adding, subtracting, and multiplying index values.
- Signed or unsigned division and remainder on index values.
- Ceil and floor division for bounds and tiling math.
- Signed or unsigned min/max on index values.
- Bitwise operations and shifts on index values.
- Comparisons between index values.
- Casts between
indexand fixed-width integer types. - Materializing
indexconstants and querying the eventual index bitwidth.
Do not use it for vector or tensor elementwise arithmetic. The dialect is scalar-only. If you need elementwise tensor or vector arithmetic, use a dialect that models those container types and lower to scalar index operations only when appropriate.
43.5 Core Concepts
43.5.1 Builtin index, Dialect index
There are two related ideas:
indexis a builtin MLIR type.index.*operations are operations in the Index dialect.
The type appears throughout MLIR, even in dialects that do not use Index dialect operations. The dialect provides a dedicated arithmetic vocabulary for that type.
43.5.2 Target-Independent Folding
Index constants are stored with 64-bit internal width. Folding also computes with 64-bit arithmetic, but it is careful about 32-bit targets.
Some operations can always be folded because truncating the folded 64-bit result matches folding after truncating the operands to 32 bits. Addition is the standard example:
trunc(add64(a, b)) == add32(trunc(a), trunc(b))
Other operations are more dangerous. Division, remainder, right shifts, signed and unsigned min/max, and comparisons can be affected by high bits. Those folds are accepted only when the 32-bit and 64-bit interpretations agree for the specific operands.
This is the most important reason the dialect exists.
43.5.3 Signless Index Values
The builtin index type is treated as signless.
For operations where signedness matters, the dialect provides separate signed and unsigned forms:
index.divsandindex.divuindex.remsandindex.remuindex.maxsandindex.maxuindex.minsandindex.minuindex.shrsandindex.shru- signed and unsigned predicates on
index.cmp
The s and u suffixes are part of the operation name because the type itself does not carry signedness.
43.5.4 Width Is Chosen At Conversion Time
The dialect does not decide whether index is 32 or 64 bits. That decision is made when lowering.
For LLVM lowering, convert-index-to-llvm has an index-bitwidth option. A value of 0 means derive the width from the data layout or machine word size.
For SPIR-V lowering, convert-index-to-spirv has a use-64bit-index option. If it is false, index values lower as 32-bit integers.
43.5.5 Scalar Only
Every operation in the dialect works on scalar index values, except casts which convert between scalar index and scalar fixed-width integer types.
There are no vector<...xindex> Index dialect ops and no tensor Index dialect ops.
43.6 Types And Attributes
The dialect does not define a new type. It operates on MLIR’s builtin index type.
It defines a comparison predicate attribute for index.cmp. In assembly, the predicate is written as a keyword:
%is_less = index.cmp slt(%a, %b)
The supported predicates are:
| Predicate | Meaning |
|---|---|
eq |
Equal. |
ne |
Not equal. |
slt |
Signed less than. |
sle |
Signed less than or equal. |
sgt |
Signed greater than. |
sge |
Signed greater than or equal. |
ult |
Unsigned less than. |
ule |
Unsigned less than or equal. |
ugt |
Unsigned greater than. |
uge |
Unsigned greater than or equal. |
43.7 Operations
The dialect has 26 operations.
43.7.1 Constants And Size
| Operation | Purpose |
|---|---|
index.constant |
Creates an index constant. |
index.bool.constant |
Creates an i1 constant, mainly for folded index.cmp results. |
index.sizeof |
Produces the eventual index bitwidth as an index value. |
43.7.2 Arithmetic
| Operation | Purpose |
|---|---|
index.add |
Adds two index values. |
index.sub |
Subtracts the second index value from the first. |
index.mul |
Multiplies two index values. |
index.divs |
Signed division, rounding toward zero. |
index.divu |
Unsigned division, rounding toward zero. |
index.ceildivs |
Signed ceil division, rounding toward positive infinity. |
index.ceildivu |
Unsigned ceil division, rounding toward positive infinity. |
index.floordivs |
Signed floor division, rounding toward negative infinity. |
index.rems |
Signed remainder. |
index.remu |
Unsigned remainder. |
43.7.3 Min, Max, Shifts, And Bitwise Operations
| Operation | Purpose |
|---|---|
index.maxs |
Signed maximum. |
index.maxu |
Unsigned maximum. |
index.mins |
Signed minimum. |
index.minu |
Unsigned minimum. |
index.shl |
Shift left. |
index.shrs |
Signed shift right. |
index.shru |
Unsigned shift right. |
index.and |
Bitwise and. |
index.or |
Bitwise or. |
index.xor |
Bitwise xor. |
43.7.4 Comparisons And Casts
| Operation | Purpose |
|---|---|
index.cmp |
Compares two index values and returns i1. |
index.casts |
Casts between index and fixed-width integers using signed extension when widening. |
index.castu |
Casts between index and fixed-width integers using zero extension when widening. |
43.7.5 Arithmetic Operations
Basic index arithmetic looks like ordinary integer arithmetic, but the operand and result types are index.
func.func @linearize(%i: index, %j: index, %cols: index) -> index {
%row = index.mul %i, %cols
%linear = index.add %row, %j
return %linear : index
}
Signedness matters for division, remainder, min/max, and right shifts. Choose the operation that matches the interpretation you need.
43.7.6 Division Operations
The dialect has several division forms because compiler index math often needs different rounding behavior.
| Operation | Rounding behavior |
|---|---|
index.divs |
Signed divide, rounds toward zero. |
index.divu |
Unsigned divide, rounds toward zero. |
index.ceildivs |
Signed divide, rounds toward positive infinity. |
index.ceildivu |
Unsigned divide, rounds toward positive infinity. |
index.floordivs |
Signed divide, rounds toward negative infinity. |
index.ceildivu is common in tiling and chunking:
func.func @num_tiles(%n: index, %tile: index) -> index {
%tiles = index.ceildivu %n, %tile
return %tiles : index
}
Division by zero is undefined behavior. Signed division overflow is also undefined for the signed division operations.
43.7.7 Shift Operations
index.shl, index.shrs, and index.shru use an index shift amount.
The right-hand side is treated as unsigned. If the shift amount is equal to or greater than the eventual index bitwidth, the result is poison. Because the target width may be 32 or 64 bits, folders avoid folding shifts whose shift amount would already be too large for a 32-bit target.
43.7.8 Comparisons
index.cmp returns i1.
func.func @in_bounds(%i: index, %n: index) -> i1 {
%ok = index.cmp ult(%i, %n)
return %ok : i1
}
Use signed predicates when the mathematical meaning is signed, and unsigned predicates for sizes, offsets, and counts that cannot be negative after interpretation.
43.7.9 Cast Operations
index.casts and index.castu convert between index and concrete integer types.
func.func @casts(%idx: index, %x: i32) -> (i64, index) {
%as_i64 = index.casts %idx : index to i64
%as_index = index.castu %x : i32 to index
return %as_i64, %as_index : i64, index
}
When widening:
index.castssign-extends.index.castuzero-extends.
When narrowing, both forms truncate.
43.8 Transformations
The Index dialect has no large dialect-specific optimization pass. Its transformations are mostly operation-local.
43.8.1 Folding
Most operations have folders.
Important folds include:
- Constant folding for arithmetic, bitwise operations, comparisons, and casts.
- Identity folds such as
index.add %x, 0,index.sub %x, 0, andindex.mul %x, 1. - Zero multiplication such as
index.mul %x, 0. - Comparison folding, including
index.cmpon identical operands. - Materializing folded comparison results with
index.bool.constant.
The distinctive part is the width check. If a fold would produce different results on 32-bit and 64-bit index targets, the folder leaves the operation in the IR.
43.8.2 Canonicalization
Several operations have canonicalization patterns.
Examples include:
- Combining constants through associative and commutative operations such as
index.add,index.mul,index.and,index.or, andindex.xor. - Similar constant-combining for
index.maxs,index.maxu,index.mins, andindex.minu. - Rewriting comparisons against subtraction, such as comparing
x - ywith zero, into a direct comparison betweenxandy.
These patterns make index expressions easier for later passes to analyze.
43.8.3 Integer Range Inference
All Index dialect operations implement integer range inference.
This lets analyses reason about possible values of index expressions without fully lowering them. The implementation considers both 64-bit storage and the 32-bit minimum index width where needed, so the inferred range remains useful before the final target width is known.
43.9 Conversions And Lowering Paths
The two main conversion passes are:
| Pass | Target | Important option |
|---|---|---|
convert-index-to-llvm |
LLVM dialect | index-bitwidth, where 0 derives from data layout or machine word size. |
convert-index-to-spirv |
SPIR-V dialect | use-64bit-index, false by default. |
43.9.1 Lowering To LLVM
convert-index-to-llvm lowers index operations to LLVM dialect integer operations.
Most operations lower one-to-one:
| Index op | LLVM-style target |
|---|---|
index.add |
llvm.add |
index.sub |
llvm.sub |
index.mul |
llvm.mul |
index.divs |
llvm.sdiv |
index.divu |
llvm.udiv |
index.rems |
llvm.srem |
index.remu |
llvm.urem |
index.shl |
llvm.shl |
index.shrs |
llvm.ashr |
index.shru |
llvm.lshr |
The exotic divide operations index.ceildivs, index.ceildivu, and index.floordivs expand to multiple LLVM operations because LLVM does not have single instructions with those exact semantics.
index.casts, index.castu, index.constant, and index.sizeof lower using the chosen index bitwidth.
43.9.2 Lowering To SPIR-V
convert-index-to-spirv lowers index operations to SPIR-V integer operations.
Most operations lower one-to-one to SPIR-V arithmetic, bitwise, comparison, or conversion operations. As with LLVM, ceil and floor division expand to operation sequences.
The use-64bit-index option controls whether index becomes 32-bit or 64-bit SPIR-V integer operations.
43.10 Example IR
43.10.1 Shape And Offset Math
func.func @shape_math(%rows: index, %cols: index,
%i: index, %j: index) -> (index, i1) {
%c4 = index.constant 4
%row_offset = index.mul %i, %cols
%linear = index.add %row_offset, %j
%rounded = index.ceildivu %linear, %c4
%total = index.mul %rows, %cols
%in_bounds = index.cmp ult(%linear, %total)
return %rounded, %in_bounds : index, i1
}
43.10.2 Casts And Target Width
func.func @cast_example(%idx: index, %signed: i32,
%unsigned: i32) -> (i64, index, index) {
%wide = index.casts %idx : index to i64
%from_signed = index.casts %signed : i32 to index
%from_unsigned = index.castu %unsigned : i32 to index
return %wide, %from_signed, %from_unsigned : i64, index, index
}
43.10.3 Querying Index Size
func.func @index_bits() -> index {
%bits = index.sizeof
return %bits : index
}
43.11 Mental Model
Treat the Index dialect as arithmetic on “future pointer-width integers.”
While the IR is still target-independent:
- The values have builtin type
index. - Operations stay in the
indexdialect. - Folding and range inference avoid choices that would be wrong on a different index width.
When the target becomes concrete:
indexlowers toi32,i64, or the configured pointer-width integer type.- Index operations lower to LLVM or SPIR-V integer operations.
- Constants are truncated or extended according to the chosen width.
43.12 Gotchas
43.12.1 index Is Not Always 64-Bit
Do not assume index means i64. The dialect stores constants with 64-bit internal precision so it can reason about the maximum supported width, but the lowered program may use 32-bit index values.
43.12.2 Signedness Is In The Operation
The type is signless. If signedness matters, choose the signed or unsigned operation explicitly.
For example, index.maxs and index.maxu can produce different results for the same bit pattern.
43.12.3 Some Folds Are Intentionally Missing
If a constant expression does not fold, that may be deliberate. The folder may be preserving correctness across both 32-bit and 64-bit index targets.
43.12.4 Shifts Can Produce Poison
A shift amount greater than or equal to the eventual index bitwidth is poison. Because that bitwidth may not be known yet, avoid generating questionable shift amounts in target-independent IR.
43.12.5 Casts Need A Signedness Choice
index.casts and index.castu differ only when widening. Pick the one that matches the intended interpretation of the source value.
43.12.6 The Dialect Is Scalar
The Index dialect is not a replacement for tensor, vector, or arithmetic dialects. It is for scalar builtin index values.
43.13 Source Map
Use these source files when you want to inspect or update the dialect:
| Area | File |
|---|---|
| Dialect definition | mlir/include/mlir/Dialect/Index/IR/IndexDialect.td |
| Operations | mlir/include/mlir/Dialect/Index/IR/IndexOps.td |
| Comparison predicates | mlir/include/mlir/Dialect/Index/IR/IndexEnums.td |
| Operation implementation and folding | mlir/lib/Dialect/Index/IR/IndexOps.cpp |
| Integer range inference | mlir/lib/Dialect/Index/IR/InferIntRangeInterfaceImpls.cpp |
| LLVM conversion | mlir/lib/Conversion/IndexToLLVM/IndexToLLVM.cpp |
| SPIR-V conversion | mlir/lib/Conversion/IndexToSPIRV/IndexToSPIRV.cpp |
| Dialect tests | mlir/test/Dialect/Index/ |
| LLVM conversion tests | mlir/test/Conversion/IndexToLLVM/ |
| SPIR-V conversion tests | mlir/test/Conversion/IndexToSPIRV/ |