Unranked handling
Unranked tensors have unknown rank, not just unknown sizes; the path must cast to ranked at entry or reject them.
Rank is the missing fact
tensor<*xf32> is unranked. It does not say the tensor is 2-D with unknown extents; it says the rank itself is unknown. tensor<?x?xf32> is ranked and dynamic, and tensor.dim works on it; tensor<*xf32> has no rank to index, so most linalg and tensor ops reject it. That is the real distinction. An unranked value has to become ranked before the pipeline can do anything with it.
The cast is the entry gate:
func.func @entry(%u: tensor<*xf32>) -> tensor<?x?xf32> {
%r = tensor.cast %u : tensor<*xf32> to tensor<?x?xf32>
return %r : tensor<?x?xf32>
}
If an unranked value reaches buffer formation, the buffer type is likewise unranked, and memref.cast plays the same role. Most pipelines avoid that by casting at entry; confirm on your pinned release whether bufferization accepts unranked operands at all. Either way the cast is a promise about rank, not a check. If a caller passes a 3-D tensor, the cast succeeds and the first tensor.dim reads a descriptor field that means something else.
Reject, or check once
The alternative is to reject unranked input at the boundary. That is a defensible design: the entry function asserts rank, or the driver refuses the shape before building IR. What you cannot do is leave the unranked value in the middle of the pipeline and hope a pass handles it. Some passes have ranked-only implementations, and the failure is a verifier error far from the entry point, or worse, a generic path that keeps the value alive.
If rank is genuinely dynamic, the shape dialect carries rank and extent as values, and the assumption can be checked before the cast. That is more machinery than most matmul paths need; the usual answer is one cast plus a rank check at the boundary, and ranked IR after it.
Things that break
A tensor.cast from unranked to ranked is not a runtime assertion, so a rank mismatch becomes a wrong answer. Calling tensor.dim on an unranked value fails verification, which is the good error. And accepting unranked at an internal boundary means every downstream pass must be audited; the cost shows up as a pass that silently refuses to fire rather than as a diagnostic.
Sources
- MLIR builtin dialectdocs · LLVM 19
- MLIR tensor dialectdocs · LLVM 19
- MLIR memref 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
What separates `tensor<*xf32>` from `tensor<?x?xf32>`?
What does `tensor.cast` from `tensor<*xf32>` to `tensor<?x?xf32>` do at runtime?
Where should an unranked entry value be handled?