TLDR;
This RFC proposes the address
dialect as an intermediate layer between high-level memory representations like memref and low-level dialects like LLVM.
An early implementation of this proposal can be found in this repository. It contains the core operations of the dialect, some canonicalizations, a pass for applying the bare pointer convention, and a conversion pass to LLVM.
Why?
Currently, there are no mechanisms to represent addresses that don’t involve using the LLVM or SPIR-V dialects; thus, any high-level are likely forced to use memref
s even in inconvenient places, like GPU kernel parameters.
See also these discussions:
- Should IndexType be parameterized?
- Representing buffer descriptors in the AMDGPU target - call for suggestions
Proposal
The initial proposal is comprised of seven operations and the address type -ideally this type would be included as a builtin type. These operations are:
- Constant operations:
constant
to create addresses from an index attribute, andtype_offset
to represent the offset needed to address a type -usually size in bytes. - Cast operations:
cast_int
,cast
to cast back and from integer types and between address spaces. - Address operations:
ptradd
adds a pointer and an integer. This op is modeled after [RFC] Replacing getelementptr with ptradd - Memref compatibility:
from_memref
andto_memref
to interoperate with the memref dialect.
One immediate consequence of introducing the address
dialect is that the bare pointer convention can now be applied as a pass in high-level dialects.
func.func @bar(%arg0: memref<i32>) -> memref<2xi32, strided<[1]>> {
%0 = call @foo(%arg0) : (memref<i32>) -> memref<2xi32, strided<[1]>>
return %0 : memref<2xi32, strided<[1]>>
}
// Gets transformed to:
func.func @bar(%arg0: !addr.address) -> !addr.address {
%0 = addr.to_memref %arg0 : memref<i32>
%1 = addr.from_memref [%0 : memref<i32>]
%2 = call @foo(%1) : (!addr.address) -> !addr.address
%3 = addr.to_memref %2 : memref<2xi32, strided<[1]>>
%4 = addr.from_memref [%3 : memref<2xi32, strided<[1]>>]
return %4 : !addr.address
}
// After canonicalization:
func.func @bar(%arg0: !addr.address) -> !addr.address {
%0 = call @foo(%arg0) : (!addr.address) -> !addr.address
return %0 : !addr.address
}
Most of these operations have trivial lowerings to LLVM, see AddrToLLVM.cpp. type_offset
is lowered as a GEP op with a null pointer.
Edit:
Type syntax:
address ::= `address` (`<` address-space^ `>`)?
address-space ::= attribute-value
Future work & directions
- Remove the
bare-ptr-convetion
option from all passes. - Create pointer aliasing analysis passes.
- Extend the dialect with more operations so that
memref
gets lowered to address instead of LLVM or SPIR-V, adding another level of possible optimizations.