Hello everyone!
As part of the RFC “Switching the LLVM dialect and dialect lowerings to opaque-pointers” all in-tree conversion passes are now capable of emitting opaque pointers instead of typed pointers.
This means that all users of MLIR are now able to switch their downstream projects to using opaque-pointers. Since LLVM 17 officially does not support typed pointers anymore it is important for any downstream projects to switch to using opaque pointers.
For immediate source compatiblity, opaque-pointers are currently turned off by default, therefore requiring users to opt-in at the time or writing. Use of opaque-pointers will be turned on by default at a later time however. See timeline at the end of this post.
Migration guide for users of in-tree conversion passes and patterns
All in-tree conversion passes that make use of patterns have a new pass option called use-opaque-pointers
. When enabled, it automatically causes the pass and conversion patterns used by the path to emit opaque pointers instead of typed pointers.
Example calling mlir-opt
in the testsuite:
// RUN: mlir-opt %s -convert-vector-to-llvm='use-opaque-pointers=1' | FileCheck %s
When adding such a pass in C++ you can enable this option by setting the field useOpaquePointers
of the passes option struct to true
.
Example making use of a generic enableOpaquePointers
lambda:
auto enableOpaquePointers = [](auto options) {
// Enables opaque pointers in any struct with the option.
options.useOpaquePointers = true;
return options;
};
// Creates the passes with the default values for options, except enabling opaque pointers.
// Note: The option struct is auto-generated by TableGen. See
// https://mlir.llvm.org/docs/PassManagement/#declarative-pass-specification for more details.
passManager.addPass(createLowerHostCodeToLLVMPass(
enableOpaquePointers(LowerHostCodeToLLVMPassOptions{})));
passManager.addPass(createConvertSPIRVToLLVMPass(
enableOpaquePointers(ConvertSPIRVToLLVMPassOptions{})));
Users of conversion patterns instead of passes can switch to using opaque pointers through the simple use of a flag in LowerToLLVMOptions
, which must then be passed to the LLVMTypeConverter
. All in-tree conversion patterns will automatically emit opaque-pointers when the LLVMTypeConverter
they were constructed with has the option enabled.
Example configuring the LLVMTypeConverter
:
LowerToLLVMOptions options(&getContext());
// Enables the use of opaque pointers.
options.useOpaquePointers = true;
// Don't forget to construct the `LLVMTypeConverter` using `options`!
LLVMTypeConverter converter(&getContext(), options);
// The patterns in `FinalizeMemRefToLLVM` will now emit opaque-pointers.
populateFinalizeMemRefToLLVMConversionPatterns(converter, patterns);
“I only make use of in-tree conversion passes, can I just wait for the default to be switched?”
I’d highly encourage you to enable opaque pointers within your pass pipeline as soon as possible to find any regressions as fast as possible. Ideally you should not notice any difference when turning on opaque pointers (except when dumping the IR).
Migration guide for users of the LLVM Dialect
The bulk of the changes required have to be done by any users of the LLVM Dialect. For architectural differences I’d like to refer you to LLVMs documentation of opaque pointers: Opaque Pointers — LLVM 17.0.0git documentation
Changes in Dialect conversions
See the paragraph about the use of LowerToLLVMOptions
and LLVMTypeConverter
above. This causes the LLVMTypeConverter
to emit opaque pointers for all built in type conversions.
Changes in C++ code
- To create an opaque pointer type instead of a typed pointer type use the
LLVMPointerType::get(MLIRContext* context, unsigned addressSpace = 0)
method. -
LLVM::LLVMPointerType::getElementType()
will return a null attribute for opaque pointers. In an opaque-pointer world, you should never call this method. You must get the element type from elsewhere. During lowering of e.g. ops using MemRefs, this can be done by using the type converter on the element type of the MemRef. Analog for other composite types. - You must now use the
build
methods ofGEPOp
with explicitbasePtrType
parameter as second parameter. SinceGEPOp
does indexing operations based on the pointers element type, the element type that was previously part of the pointer type has to now be passed as additional type parameter toGEPOp
. - You must now use the
build
method ofLoadOp
with the explicit return type. Since the pointer passed toLoadOp
has no element type anymore, the type that should be loaded from the pointer has to be passed explicitly as the result type of the op. - You must now use the
build
method ofAllocaOp
with the explicitelementType
as second parameter. Since the result type of theAllocaOp
no longer contains an element type it is necessary to pass the type that should be allocated as the second parameter explicitly. - For the time being you mustn’t use the
build
methods ofAddressOfOp
takingGlobalOp
orLLVMFuncOp
. These currently lead to typed pointers being used as result types. Once opaque-pointers are the default, they’ll be changed to using opaque-pointers as result types. -
BitcastOp
from a pointer to another pointer type are now noops.
Note that all the build
that should be used are also compatible with typed pointers. You can therefore do these changes in a more incremental fashion as well. The old build
methods of GEP, load
and alloca
will be marked deprecated in C++ as soon as possible.
“I’d like to support both opaque-pointers and typed pointers for the time being. Is this possible?”
For code wishing to support both for the time being LLVMTypeConverter
offers the getPointerType
method to create either a typed or opaque pointer depending on the LowerToLLVMOptions
used at construction time.
The useOpaquePointers()
method can be used to check whether opaque pointers are currently active. This is e.g. useful to avoid creating noop bitcasts when using opaque pointers.
Changes in IR Syntax
Typed Pointers | Opaque Pointers | Description |
---|---|---|
!llvm.ptr<i32>, !llvm.ptr<f32, 5> |
!llvm.ptr, !llvm.ptr<5> |
The new syntax for pointer types no longer contains the element type and only requires brackets for non-default address spaces |
%0 = llvm.load %ptr : !llvm.ptr<i32> |
%0 = llvm.load %ptr : !llvm.ptr -> i32 |
The new syntax for llvm.load now has an explicit result type, replacing the element type of the typed pointer |
llvm.store %val, %ptr : !llvm.ptr<f32> |
llvm.store %val, %ptr : f32, !llvm.ptr |
The new syntax for llvm.store now explicitly states the type of the value being stored. This is now necessary since the pointer element type is no longer equal. |
llvm.getelementptr %ptr[%idx] : (!llvm.ptr<f32>, i64) -> !llvm.ptr<f32> |
llvm.getelementptr %ptr[%idx] : (!llvm.ptr, i64) -> !llvm.ptr, f32 |
The element type used for indexing purposes by GEP is now printed behind the result type. It should be equal to the element type of %ptr previously. |
Note that the changes to the pointer type syntax will also be visible in every operation printing the type.
Migration Timeline
As of writing this, the use-opaque-pointers
options of passes and corresponding field in LowerToLLVMOptions
is still turned off for immediate source compatibility. Following changes will happen in the future:
- In roughly two weeks
use-opaque-pointers
andLowerToLLVMOptions
will default to being turned on. Any users still relying on typed pointers will have to either switch or explicitly disable the use of opaque-pointers. - One month after the default switch, typed pointers will be unsupported within the LLVM Dialect entirely.
(Exact time of these may vary depending on regressions or other unforssen consequences)
I will be posting additional PSAs for each of these steps.
Thanks for reading!