Dynamic tiling
Runtime bounds need affine.min or masked loads at the edge; a static interior tile requires peeling or masking, not assumption.
The edge is a minimum
Tiling a dynamic M by 64 means the last iteration owns fewer than 64 rows. The bound is an SSA value:
%tile = affine.min affine_map<(d0)[s0] -> (64, s0 - d0)>(%iv)[%M]
%sA = tensor.extract_slice %A[%iv, 0] [%tile, %K] [1, 1]
: tensor<?x?xf32> to tensor<?x?xf32>
affine.min takes the smaller of the static tile and the remaining extent. It is an affine expression of the induction variable and a symbol, which is why it folds and analyzes; arith.minui on the same values is correct but opaque to affine passes. The slice type is dynamic because %tile is.
The static number still has a job. The tile size is the loop step and the constant in the map; only the final iteration needs the minimum. That distinction matters: if the tile length is dynamic in every iteration, the vectorizer has to mask every access and cannot unroll against a constant. Peel the full tiles into a loop with a static 64, then run an epilogue for the remainder. The interior keeps static slices, static vectors and static unrolling; the edge pays for the generality.
Masked loads are the other spelling. Keep the static tile, compute a mask from the remaining extent, and let the load suppress the out-of-range lanes. The tile shape stays static; the mask carries the dynamic information.
Things that break
affine.min on a value that is not an affine expression fails verification, and arith.minui hides the bound from every affine pass. A loop that computes %tile once outside the loop freezes the wrong bound. And an epilogue that runs when the remainder is zero writes a zero-length slice at best, or reads past the buffer if the address arithmetic assumed a full tile. Confirm the exact affine.min syntax and the mask op names on your pinned release.
Sources
- MLIR affine dialectdocs · LLVM 19
- MLIR scf dialectdocs · LLVM 19
- MLIR tensor dialectdocs · LLVM 19
A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.
Check yourself
With tile size 64 and `%M = 100`, what does `affine.min (64, %M - %iv)` return at `%iv = 64`?
Why peel the full tiles into their own loop?
At `%M = 128` with tile size 64, what does the epilogue loop do?