| # Opaque Pointers | |
| ## The Opaque Pointer Type | |
| Traditionally, LLVM IR pointer types have contained a pointee type. For example, | |
| `i32*` is a pointer that points to an `i32` somewhere in memory. However, | |
| due to a lack of pointee type semantics and various issues with having pointee | |
| types, there is a desire to remove pointee types from pointers. | |
| The opaque pointer type project aims to replace all pointer types containing | |
| pointee types in LLVM with an opaque pointer type. The new pointer type is | |
| represented textually as `ptr`. | |
| Some instructions still need to know what type to treat the memory pointed to by | |
| the pointer as. For example, a load needs to know how many bytes to load from | |
| memory and what type to treat the resulting value as. In these cases, | |
| instructions themselves contain a type argument. For example the load | |
| instruction from older versions of LLVM | |
| ```llvm | |
| load i64* %p | |
| ``` | |
| becomes | |
| ```llvm | |
| load i64, ptr %p | |
| ``` | |
| Address spaces are still used to distinguish between different kinds of pointers | |
| where the distinction is relevant for lowering (e.g. data vs function pointers | |
| have different sizes on some architectures). Opaque pointers are not changing | |
| anything related to address spaces and lowering. For more information, see | |
| {ref}`DataLayout <langref_datalayout>`. Opaque pointers in non-default | |
| address space are spelled `ptr addrspace(N)`. | |
| This was proposed all the way back in | |
| [2015](https://lists.llvm.org/pipermail/llvm-dev/2015-February/081822.html). | |
| ## Issues with explicit pointee types | |
| LLVM IR pointers can be cast back and forth between pointers with different | |
| pointee types. The pointee type does not necessarily represent the actual | |
| underlying type in memory. In other words, the pointee type carries no real | |
| semantics. | |
| Historically LLVM was some sort of type-safe subset of C. Having pointee types | |
| provided an extra layer of checks to make sure that the Clang frontend matched | |
| its frontend values/operations with the corresponding LLVM IR. However, as other | |
| languages like C++ adopted LLVM, the community realized that pointee types were | |
| more of a hindrance for LLVM development and that the extra type checking with | |
| some frontends wasn't worth it. | |
| LLVM's type system was [originally designed](https://llvm.org/pubs/2003-05-01-GCCSummit2003.html) to support high-level | |
| optimization. However, years of LLVM implementation experience have demonstrated | |
| that the pointee type system design does not effectively support | |
| optimization. Memory optimization algorithms, such as SROA, GVN, and AA, | |
| generally need to look through LLVM's struct types and reason about the | |
| underlying memory offsets. The community realized that pointee types hinder LLVM | |
| development, rather than helping it. Some of the initially proposed high-level | |
| optimizations have evolved into [TBAA](https://llvm.org/docs/LangRef.html#tbaa-metadata) due to limitations with | |
| representing higher-level language information directly via SSA values. | |
| Pointee types provide some value to frontends because the IR verifier uses types | |
| to detect straightforward type confusion bugs. However, frontends also have to | |
| deal with the complexity of inserting bitcasts everywhere that they might be | |
| required. The community consensus is that the costs of pointee types | |
| outweigh the benefits, and that they should be removed. | |
| Many operations do not actually care about the underlying type. These | |
| operations, typically intrinsics, usually end up taking an arbitrary pointer | |
| type `i8*` and sometimes a size. This causes lots of redundant no-op bitcasts | |
| in the IR to and from a pointer with a different pointee type. | |
| No-op bitcasts take up memory/disk space and also take up compile time to look | |
| through. However, perhaps the biggest issue is the code complexity required to | |
| deal with bitcasts. When looking up through def-use chains for pointers it's | |
| easy to forget to call `Value::stripPointerCasts()` to find the true underlying | |
| pointer obfuscated by bitcasts. And when looking down through def-use chains | |
| passes need to iterate through bitcasts to handle uses. Removing no-op pointer | |
| bitcasts prevents a category of missed optimizations and makes writing LLVM | |
| passes a little bit easier. | |
| Fewer no-op pointer bitcasts also reduces the chances of incorrect bitcasts in | |
| regards to address spaces. People maintaining backends that care a lot about | |
| address spaces have complained that frontends like Clang often incorrectly | |
| bitcast pointers, losing address space information. | |
| An analogous transition that happened earlier in LLVM is integer signedness. | |
| Currently there is no distinction between signed and unsigned integer types, but | |
| rather each integer operation (e.g. add) contains flags to signal how to treat | |
| the integer. Previously LLVM IR distinguished between unsigned and signed | |
| integer types and ran into similar issues of no-op casts. The transition from | |
| manifesting signedness in types to instructions happened early on in LLVM's | |
| timeline to make LLVM easier to work with. | |
| ## Opaque Pointers Mode | |
| During the transition phase, LLVM can be used in two modes: In typed pointer | |
| mode all pointer types have a pointee type and opaque pointers cannot be used. | |
| In opaque pointers mode (the default), all pointers are opaque. The opaque | |
| pointer mode can be disabled using `-opaque-pointers=0` in | |
| LLVM tools like `opt`, or `-Xclang -no-opaque-pointers` in clang. | |
| Additionally, opaque pointer mode is automatically disabled for IR and bitcode | |
| files that explicitly mention `i8*` style typed pointers. | |
| In opaque pointer mode, all typed pointers used in IR, bitcode, or created | |
| using `PointerType::get()` and similar APIs are automatically converted into | |
| opaque pointers. This simplifies migration and allows testing existing IR with | |
| opaque pointers. | |
| ```llvm | |
| define i8* @test(i8* %p) { | |
| %p2 = getelementptr i8, i8* %p, i64 1 | |
| ret i8* %p2 | |
| } | |
| ; Is automatically converted into the following if -opaque-pointers | |
| ; is enabled: | |
| define ptr @test(ptr %p) { | |
| %p2 = getelementptr i8, ptr %p, i64 1 | |
| ret ptr %p2 | |
| } | |
| ``` | |
| ## Migration Instructions | |
| In order to support opaque pointers, two types of changes tend to be necessary. | |
| The first is the removal of all calls to `PointerType::getElementType()` and | |
| `Type::getPointerElementType()`. | |
| In the LLVM middle-end and backend, this is usually accomplished by inspecting | |
| the type of relevant operations instead. For example, memory access related | |
| analyses and optimizations should use the types encoded in the load and store | |
| instructions instead of querying the pointer type. | |
| Here are some common ways to avoid pointer element type accesses: | |
| - For loads, use `getType()`. | |
| - For stores, use `getValueOperand()->getType()`. | |
| - Use `getLoadStoreType()` to handle both of the above in one call. | |
| - For getelementptr instructions, use `getSourceElementType()`. | |
| - For calls, use `getFunctionType()`. | |
| - For allocas, use `getAllocatedType()`. | |
| - For globals, use `getValueType()`. | |
| - For consistency assertions, use | |
| `PointerType::isOpaqueOrPointeeTypeEquals()`. | |
| - To create a pointer type in a different address space, use | |
| `PointerType::getWithSamePointeeType()`. | |
| - To check that two pointers have the same element type, use | |
| `PointerType::hasSameElementTypeAs()`. | |
| - While it is preferred to write code in a way that accepts both typed and | |
| opaque pointers, `Type::isOpaquePointerTy()` and | |
| `PointerType::isOpaque()` can be used to handle opaque pointers specially. | |
| `PointerType::getNonOpaquePointerElementType()` can be used as a marker in | |
| code-paths where opaque pointers have been explicitly excluded. | |
| - To get the type of a byval argument, use `getParamByValType()`. Similar | |
| method exists for other ABI-affecting attributes that need to know the | |
| element type, such as byref, sret, inalloca and preallocated. | |
| - Some intrinsics require an `elementtype` attribute, which can be retrieved | |
| using `getParamElementType()`. This attribute is required in cases where | |
| the intrinsic does not naturally encode a needed element type. This is also | |
| used for inline assembly. | |
| Note that some of the methods mentioned above only exist to support both typed | |
| and opaque pointers at the same time, and will be dropped once the migration | |
| has completed. For example, `isOpaqueOrPointeeTypeEquals()` becomes | |
| meaningless once all pointers are opaque. | |
| While direct usage of pointer element types is immediately apparent in code, | |
| there is a more subtle issue that opaque pointers need to contend with: A lot | |
| of code assumes that pointer equality also implies that the used load/store | |
| type or GEP source element type is the same. Consider the following examples | |
| with typed and opaque pointers: | |
| ```llvm | |
| define i32 @test(i32* %p) { | |
| store i32 0, i32* %p | |
| %bc = bitcast i32* %p to i64* | |
| %v = load i64, i64* %bc | |
| ret i64 %v | |
| } | |
| define i32 @test(ptr %p) { | |
| store i32 0, ptr %p | |
| %v = load i64, ptr %p | |
| ret i64 %v | |
| } | |
| ``` | |
| Without opaque pointers, a check that the pointer operand of the load and | |
| store are the same also ensures that the accessed type is the same. Using a | |
| different type requires a bitcast, which will result in distinct pointer | |
| operands. | |
| With opaque pointers, the bitcast is not present, and this check is no longer | |
| sufficient. In the above example, it could result in store to load forwarding | |
| of an incorrect type. Code making such assumptions needs to be adjusted to | |
| check the accessed type explicitly: | |
| `LI->getType() == SI->getValueOperand()->getType()`. | |
| ### Frontends | |
| Frontends need to be adjusted to track pointee types independently of LLVM, | |
| insofar as they are necessary for lowering. For example, clang now tracks the | |
| pointee type in the `Address` structure. | |
| Frontends using the C API through an FFI interface should be aware that a | |
| number of C API functions are deprecated and will be removed as part of the | |
| opaque pointer transition: | |
| ``` | |
| LLVMBuildLoad -> LLVMBuildLoad2 | |
| LLVMBuildCall -> LLVMBuildCall2 | |
| LLVMBuildInvoke -> LLVMBuildInvoke2 | |
| LLVMBuildGEP -> LLVMBuildGEP2 | |
| LLVMBuildInBoundsGEP -> LLVMBuildInBoundsGEP2 | |
| LLVMBuildStructGEP -> LLVMBuildStructGEP2 | |
| LLVMBuildPtrDiff -> LLVMBuildPtrDiff2 | |
| LLVMConstGEP -> LLVMConstGEP2 | |
| LLVMConstInBoundsGEP -> LLVMConstInBoundsGEP2 | |
| LLVMAddAlias -> LLVMAddAlias2 | |
| ``` | |
| Additionally, it will no longer be possible to call `LLVMGetElementType()` | |
| on a pointer type. | |
| It is possible to control whether opaque pointers are used (if you want to | |
| override the default) using `LLVMContext::setOpaquePointers`. | |
| ## Temporarily disabling opaque pointers | |
| In LLVM 15, opaque pointers are enabled by default, but it it still possible to | |
| use typed pointers using a number of opt-in flags. | |
| For users of the clang driver interface, it is possible to temporarily restore | |
| the old default using the `-DCLANG_ENABLE_OPAQUE_POINTERS=OFF` cmake option, | |
| or by passing `-Xclang -no-opaque-pointers` to a single clang invocation. | |
| For users of the clang cc1 interface, `-no-opaque-pointers` can be passed. | |
| Note that the `CLANG_ENABLE_OPAQUE_POINTERS` cmake option has no effect on | |
| the cc1 interface. | |
| Usage for LTO can be disabled by passing `-Wl,-plugin-opt=no-opaque-pointers` | |
| to the clang driver. | |
| For users of LLVM as a library, opaque pointers can be disabled by calling | |
| `setOpaquePointers(false)` on the `LLVMContext`. | |
| For users of LLVM tools like opt, opaque pointers can be disabled by passing | |
| `-opaque-pointers=0`. | |
| ## Version Support | |
| **LLVM 14:** Supports all necessary APIs for migrating to opaque pointers and deprecates/removes incompatible APIs. However, using opaque pointers in the optimization pipeline is **not** fully supported. This release can be used to make out-of-tree code compatible with opaque pointers, but opaque pointers should **not** be enabled in production. | |
| **LLVM 15:** Opaque pointers are enabled by default. Typed pointers are still | |
| supported. | |
| **LLVM 16:** Opaque pointers are enabled by default. Typed pointers are | |
| supported on a best-effort basis only and not tested. | |
| **LLVM 17:** Only opaque pointers are supported. Typed pointers are not | |
| supported. | |
| ## Transition State | |
| As of July 2023: | |
| Typed pointers are **not** supported on the `main` branch. | |
| The following typed pointer functionality has been removed: | |
| - The `CLANG_ENABLE_OPAQUE_POINTERS` cmake flag is no longer supported. | |
| - The `-no-opaque-pointers` cc1 clang flag is no longer supported. | |
| - The `-opaque-pointers` opt flag is no longer supported. | |
| - The `-plugin-opt=no-opaque-pointers` LTO flag is no longer supported. | |
| - C APIs that do not support opaque pointers (like `LLVMBuildLoad`) are no | |
| longer supported. | |
| The following typed pointer functionality is still to be removed: | |
| - Various APIs that are no longer relevant with opaque pointers. |