From acd569519fcac164117356a3ea826cc04b8f16c5 Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Sat, 20 Sep 2025 17:32:22 -0700 Subject: [PATCH 1/3] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20ch?= =?UTF-8?q?anges=20to=20main=20this=20commit=20is=20based=20on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created using spr 1.3.8-beta.1 [skip ci] --- llvm/docs/LangRef.rst | 130 ++++++++++++--- llvm/include/llvm/IR/DataLayout.h | 111 +++++++++++-- llvm/lib/IR/DataLayout.cpp | 48 ++++-- llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 27 +++- .../ConstProp/inttoptr-gep-index-width.ll | 4 +- .../Transforms/SimplifyCFG/nonintegral.ll | 102 +++++++++++- .../SimplifyCFG/switch_create-custom-dl.ll | 59 ++++++- llvm/unittests/IR/DataLayoutTest.cpp | 148 +++++++++++++++++- 8 files changed, 566 insertions(+), 63 deletions(-) diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst index e6713c827d6ab..a3e9f60dab515 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -660,42 +660,122 @@ Non-Integral Pointer Type Note: non-integral pointer types are a work in progress, and they should be considered experimental at this time. -LLVM IR optionally allows the frontend to denote pointers in certain address -spaces as "non-integral" via the :ref:`datalayout string`. -Non-integral pointer types represent pointers that have an *unspecified* bitwise -representation; that is, the integral representation may be target dependent or -unstable (not backed by a fixed integer). +For most targets, the pointer representation is a direct mapping from the +bitwise representation to the address of the underlying memory allocation. +Such pointers are considered "integral", and any pointers where the +representation is not just an integer address are called "non-integral". + +In most cases pointers with a non-integral representation behave exactly the +same as an integral pointer, the only difference is that it is not possible to +create a pointer just from an address unless all the non-address bits were +also recreated correctly in a target-specific way. +Since the address width of a non-integral pointer is not equal to the bitwise +representation, extracting the address will need to truncate to the index width +of the pointer. +An example of such a non-integral pointer representation are the AMDGPU buffer +descriptors which are a 128-bit fat pointer and a 32-bit offset. + +Additionally, LLVM IR optionally allows the frontend to denote pointers in +certain address spaces as "unstable" or having "external state" +(or combinations of these) via the :ref:`datalayout string`. + +The exact implications of these properties are target-specific, but the +following IR semantics and restrictions to optimization passes apply: + +Unstable pointer representation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Pointers in this address space have an *unspecified* bitwise representation +(i.e. not backed by a fixed integer). The bitwise pattern of such pointers is +allowed to change in a target-specific way. For example, this could be a pointer +type used with copying garbage collection where the garbage collector could +update the pointer at any time in the collection sweep. ``inttoptr`` and ``ptrtoint`` instructions have the same semantics as for integral (i.e., normal) pointers in that they convert integers to and from -corresponding pointer types, but there are additional implications to be -aware of. Because the bit-representation of a non-integral pointer may -not be stable, two identical casts of the same operand may or may not +corresponding pointer types, but there are additional implications to be aware +of. + +For "unstable" pointer representations, the bit-representation of the pointer +may not be stable, so two identical casts of the same operand may or may not return the same value. Said differently, the conversion to or from the -non-integral type depends on environmental state in an implementation +"unstable" pointer type depends on environmental state in an implementation defined manner. - If the frontend wishes to observe a *particular* value following a cast, the generated IR must fence with the underlying environment in an implementation defined manner. (In practice, this tends to require ``noinline`` routines for such operations.) From the perspective of the optimizer, ``inttoptr`` and ``ptrtoint`` for -non-integral types are analogous to ones on integral types with one +"unstable" pointer types are analogous to ones on integral types with one key exception: the optimizer may not, in general, insert new dynamic occurrences of such casts. If a new cast is inserted, the optimizer would need to either ensure that a) all possible values are valid, or b) appropriate fencing is inserted. Since the appropriate fencing is implementation defined, the optimizer can't do the latter. The former is challenging as many commonly expected properties, such as -``ptrtoint(v)-ptrtoint(v) == 0``, don't hold for non-integral types. +``ptrtoint(v)-ptrtoint(v) == 0``, don't hold for "unstable" pointer types. Similar restrictions apply to intrinsics that might examine the pointer bits, such as :ref:`llvm.ptrmask`. -The alignment information provided by the frontend for a non-integral pointer +The alignment information provided by the frontend for an "unstable" pointer (typically using attributes or metadata) must be valid for every possible representation of the pointer. +Non-integral pointers with external state +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A further special case of non-integral pointers is ones that include external +state (such as bounds information or a type tag) with a target-defined size. +An example of such a type is a CHERI capability, where there is an additional +validity bit that is part of all pointer-typed registers, but is located in +memory at an implementation-defined address separate from the pointer itself. +Another example would be a fat-pointer scheme where pointers remain plain +integers, but the associated bounds are stored in an out-of-band table. + +Unless also marked as "unstable", the bit-wise representation of pointers with +external state is stable and ``ptrtoint(x)`` always yields a deterministic +value. This means transformation passes are still permitted to insert new +``ptrtoint`` instructions. + +The following restrictions apply to IR level optimization passes: + +The ``inttoptr`` instruction does not recreate the external state and therefore +it is target dependent whether it can be used to create a dereferenceable +pointer. In general passes should assume that the result of such an inttoptr +is not dereferenceable. For example, on CHERI targets an ``inttoptr`` will +yield a capability with the external state (the validity tag bit) set to zero, +which will cause any dereference to trap. +The ``ptrtoint`` instruction also only returns the "in-band" state and omits +all external state. +These two properties mean that ``inttoptr(ptrtoint(x))`` cannot be folded to +``x`` since the ``ptrtoint`` operation does not include the external state +needed to reconstruct the original pointer and ``inttoptr`` cannot set it. + +When a ``store ptr addrspace(N) %p, ptr @dst`` of such a non-integral pointer +is performed, the external metadata is also stored to an implementation-defined +location. Similarly, a ``%val = load ptr addrspace(N), ptr @dst`` will fetch the +external metadata and make it available for all uses of ``%val``. +Similarly, the ``llvm.memcpy`` and ``llvm.memmove`` intrinsics also transfer the +external state. This is essential to allow frontends to efficiently emit copies +of structures containing such pointers, since expanding all these copies as +individual loads and stores would affect compilation speed and inhibit +optimizations. + +Notionally, these external bits are part of the pointer, but since +``inttoptr`` / ``ptrtoint``` only operate on the "in-band" bits of the pointer +and the external bits are not explicitly exposed, they are not included in the +size specified in the :ref:`datalayout string`. + +When a pointer type has external state, all roundtrips via memory must +be performed as loads and stores of the correct type since stores of other +types may not propagate the external data. +Therefore it is not legal to convert an existing load/store (or a +``llvm.memcpy`` / ``llvm.memmove`` intrinsic) of pointer types with external +state to a load/store of an integer type with same bitwidth, as that may drop +the external state. + + .. _globalvars: Global Variables @@ -3179,8 +3259,8 @@ as follows: ``A
`` Specifies the address space of objects created by '``alloca``'. Defaults to the default address space of 0. -``p[n]::[:[:]]`` - This specifies the properties of a pointer in address space ``n``. +``p[][]::[:[:]]`` + This specifies the properties of a pointer in address space ``as``. The ```` parameter specifies the size of the bitwise representation. For :ref:`non-integral pointers ` the representation size may be larger than the address width of the underlying address space (e.g. to @@ -3193,9 +3273,14 @@ as follows: default index size is equal to the pointer size. The index size also specifies the width of addresses in this address space. All sizes are in bits. - The address space, ``n``, is optional, and if not specified, - denotes the default address space 0. The value of ``n`` must be - in the range [1,2^24). + The address space, ````, is optional, and if not specified, denotes the + default address space 0. The value of ```` must be in the range [1,2^24). + The optional ```` are used to specify properties of pointers in this + address space: the character ``u`` marks pointers as having an unstable + representation, ``n`` marks pointers as non-integral (i.e. having + additional metadata), ``e`` marks pointers having external state + (``n`` must also be set). See :ref:`Non-Integral Pointer Types `. + ``i:[:]`` This specifies the alignment for an integer type of a given bit ````. The value of ```` must be in the range [1,2^24). @@ -3248,9 +3333,11 @@ as follows: this set are considered to support most general arithmetic operations efficiently. ``ni:
:
:
...`` - This specifies pointer types with the specified address spaces - as :ref:`Non-Integral Pointer Type ` s. The ``0`` - address space cannot be specified as non-integral. + This marks pointer types with the specified address spaces + as :ref:`non-integral and unstable `. + The ``0`` address space cannot be specified as non-integral. + It is only supported for backwards compatibility, the flags of the ``p`` + specifier should be used instead for new code. ```` is a lower bound on what is required for a type to be considered aligned. This is used in various places, such as: @@ -31402,4 +31489,3 @@ Semantics: The '``llvm.preserve.struct.access.index``' intrinsic produces the same result as a getelementptr with base ``base`` and access operands ``{0, gep_index}``. - diff --git a/llvm/include/llvm/IR/DataLayout.h b/llvm/include/llvm/IR/DataLayout.h index 5653ee7b6837d..d7574365b4351 100644 --- a/llvm/include/llvm/IR/DataLayout.h +++ b/llvm/include/llvm/IR/DataLayout.h @@ -77,12 +77,21 @@ class DataLayout { uint32_t BitWidth; Align ABIAlign; Align PrefAlign; + /// The index bit width also defines the address size in this address space. + /// If the index width is less than the representation bit width, the + /// pointer is non-integral and bits beyond the index width could be used + /// for additional metadata (e.g. AMDGPU buffer fat pointers with bounds + /// and other flags or CHERI capabilities that contain bounds+permissions. uint32_t IndexBitWidth; /// Pointers in this address space don't have a well-defined bitwise - /// representation (e.g. may be relocated by a copying garbage collector). - /// Additionally, they may also be non-integral (i.e. containing additional - /// metadata such as bounds information/permissions). - bool IsNonIntegral; + /// representation (e.g. they may be relocated by a copying garbage + /// collector and thus have different addresses at different times). + bool HasUnstableRepresentation; + /// Pointers in this address space have additional state bits that are + /// located at a target-defined location when stored in memory. An example + /// of this would be CHERI capabilities where the validity bit is stored + /// separately from the pointer address+bounds information. + bool HasExternalState; LLVM_ABI bool operator==(const PointerSpec &Other) const; }; @@ -149,7 +158,7 @@ class DataLayout { /// Sets or updates the specification for pointer in the given address space. void setPointerSpec(uint32_t AddrSpace, uint32_t BitWidth, Align ABIAlign, Align PrefAlign, uint32_t IndexBitWidth, - bool IsNonIntegral); + bool HasUnstableRepr, bool HasExternalState); /// Internal helper to get alignment for integer of given bitwidth. LLVM_ABI Align getIntegerAlignment(uint32_t BitWidth, bool abi_or_pref) const; @@ -355,19 +364,89 @@ class DataLayout { /// \sa DataLayout::getAddressSizeInBits unsigned getAddressSize(unsigned AS) const { return getIndexSize(AS); } - /// Return the address spaces containing non-integral pointers. Pointers in - /// this address space don't have a well-defined bitwise representation. - SmallVector getNonIntegralAddressSpaces() const { + /// Return the address spaces with special pointer semantics (such as being + /// unstable or non-integral). + SmallVector getNonStandardAddressSpaces() const { SmallVector AddrSpaces; for (const PointerSpec &PS : PointerSpecs) { - if (PS.IsNonIntegral) + if (PS.HasUnstableRepresentation || PS.HasExternalState || + PS.BitWidth != PS.IndexBitWidth) AddrSpaces.push_back(PS.AddrSpace); } return AddrSpaces; } + /// Returns whether this address space has a non-integral pointer + /// representation, i.e. the pointer is not just an integer address but some + /// other bitwise representation. When true, passes cannot assume that all + /// bits of the representation map directly to the allocation address. + /// NOTE: This also returns true for "unstable" pointers where the + /// representation may be just an address, but this value can change at any + /// given time (e.g. due to copying garbage collection). + /// Examples include AMDGPU buffer descriptors + /// with a 128-bit fat pointer and a 32-bit offset or CHERI capabilities that + /// contain bounds, permissions and an out-of-band validity bit. + /// + /// In general, more specialized functions such as shouldAvoidIntToPtr(), + /// shouldAvoidPtrToInt(), or hasExternalState() should be preferred over + /// this one when reasoning about the behavior of IR analysis/transforms. + /// TODO: should remove/deprecate this once all uses have migrated. bool isNonIntegralAddressSpace(unsigned AddrSpace) const { - return getPointerSpec(AddrSpace).IsNonIntegral; + const auto &PS = getPointerSpec(AddrSpace); + return PS.BitWidth != PS.IndexBitWidth || PS.HasUnstableRepresentation; + } + + /// Returns whether this address space has an "unstable" pointer + /// representation. The bitwise pattern of such pointers is allowed to change + /// in a target-specific way. For example, this could be used for copying + /// garbage collection where the garbage collector could update the pointer + /// value as part of the collection sweep. + bool hasUnstableRepresentation(unsigned AddrSpace) const { + return getPointerSpec(AddrSpace).HasUnstableRepresentation; + } + bool hasUnstableRepresentation(Type *Ty) const { + auto *PTy = dyn_cast(Ty->getScalarType()); + return PTy && hasUnstableRepresentation(PTy->getPointerAddressSpace()); + } + + /// Returns whether this address space has external state (implies having + /// a non-integral pointer representation). + /// These pointer types must be loaded and stored using appropriate + /// instructions and cannot use integer loads/stores as this would not + /// propagate the out-of-band state. An example of such a pointer type is a + /// CHERI capability that contain bounds, permissions and an out-of-band + /// validity bit that is invalidated whenever an integer/FP store is performed + /// to the associated memory location. + bool hasExternalState(unsigned AddrSpace) const { + return getPointerSpec(AddrSpace).HasExternalState; + } + bool hasExternalState(Type *Ty) const { + auto *PTy = dyn_cast(Ty->getScalarType()); + return PTy && hasExternalState(PTy->getPointerAddressSpace()); + } + + /// Returns whether passes should avoid introducing `inttoptr` instructions + /// for this address space. + /// + /// This is currently the case for non-integral pointer representations with + /// external state (hasExternalState()) since `inttoptr` cannot recreate the + /// external state bits. + /// New `inttoptr` instructions should also be avoided for "unstable" bitwise + /// representations (hasUnstableRepresentation()) unless the pass knows it is + /// within a critical section that retains the current representation. + bool shouldAvoidIntToPtr(unsigned AddrSpace) const { + return hasUnstableRepresentation(AddrSpace) || hasExternalState(AddrSpace); + } + + /// Returns whether passes should avoid introducing `ptrtoint` instructions + /// for this address space. + /// + /// This is currently the case for pointer address spaces that have an + /// "unstable" representation (hasUnstableRepresentation()) since the + /// bitwise pattern of such pointers could change unless the pass knows it is + /// within a critical section that retains the current representation. + bool shouldAvoidPtrToInt(unsigned AddrSpace) const { + return hasUnstableRepresentation(AddrSpace); } bool isNonIntegralPointerType(PointerType *PT) const { @@ -375,10 +454,20 @@ class DataLayout { } bool isNonIntegralPointerType(Type *Ty) const { - auto *PTy = dyn_cast(Ty); + auto *PTy = dyn_cast(Ty->getScalarType()); return PTy && isNonIntegralPointerType(PTy); } + bool shouldAvoidPtrToInt(Type *Ty) const { + auto *PTy = dyn_cast(Ty->getScalarType()); + return PTy && shouldAvoidPtrToInt(PTy->getPointerAddressSpace()); + } + + bool shouldAvoidIntToPtr(Type *Ty) const { + auto *PTy = dyn_cast(Ty->getScalarType()); + return PTy && shouldAvoidIntToPtr(PTy->getPointerAddressSpace()); + } + /// The size in bits of the pointer representation in a given address space. /// This is not necessarily the same as the integer address of a pointer (e.g. /// for fat pointers). diff --git a/llvm/lib/IR/DataLayout.cpp b/llvm/lib/IR/DataLayout.cpp index 77f9b997a2ebf..50b2599f075f9 100644 --- a/llvm/lib/IR/DataLayout.cpp +++ b/llvm/lib/IR/DataLayout.cpp @@ -151,7 +151,8 @@ bool DataLayout::PointerSpec::operator==(const PointerSpec &Other) const { return AddrSpace == Other.AddrSpace && BitWidth == Other.BitWidth && ABIAlign == Other.ABIAlign && PrefAlign == Other.PrefAlign && IndexBitWidth == Other.IndexBitWidth && - IsNonIntegral == Other.IsNonIntegral; + HasUnstableRepresentation == Other.HasUnstableRepresentation && + HasExternalState == Other.HasExternalState; } namespace { @@ -194,7 +195,7 @@ constexpr DataLayout::PrimitiveSpec DefaultVectorSpecs[] = { // Default pointer type specifications. constexpr DataLayout::PointerSpec DefaultPointerSpecs[] = { // p0:64:64:64:64 - {0, 64, Align::Constant<8>(), Align::Constant<8>(), 64, false}, + {0, 64, Align::Constant<8>(), Align::Constant<8>(), 64, false, false}, }; DataLayout::DataLayout() @@ -405,9 +406,29 @@ Error DataLayout::parsePointerSpec(StringRef Spec) { // Address space. Optional, defaults to 0. unsigned AddrSpace = 0; - if (!Components[0].empty()) - if (Error Err = parseAddrSpace(Components[0], AddrSpace)) - return Err; + bool ExternalState = false; + bool UnstableRepr = false; + StringRef AddrSpaceStr = Components[0]; + while (!AddrSpaceStr.empty()) { + char C = AddrSpaceStr.front(); + if (C == 'e') { + ExternalState = true; + } else if (C == 'u') { + UnstableRepr = true; + } else if (isAlpha(C)) { + return createStringError("'%c' is not a valid pointer specification flag", + C); + } else { + break; // not a valid flag, remaining must be the address space number. + } + AddrSpaceStr = AddrSpaceStr.drop_front(1); + } + if (!AddrSpaceStr.empty()) + if (Error Err = parseAddrSpace(AddrSpaceStr, AddrSpace)) + return Err; // Failed to parse the remaining characters as a number + if (AddrSpace == 0 && (ExternalState || UnstableRepr)) + return createStringError( + "address space 0 cannot be unstable or have external state"); // Size. Required, cannot be zero. unsigned BitWidth; @@ -440,8 +461,12 @@ Error DataLayout::parsePointerSpec(StringRef Spec) { return createStringError( "index size cannot be larger than the pointer size"); + if (ExternalState && BitWidth == IndexBitWidth) + return createStringError( + "pointers with external state must be non-integral"); + setPointerSpec(AddrSpace, BitWidth, ABIAlign, PrefAlign, IndexBitWidth, - false); + UnstableRepr, ExternalState); return Error::success(); } @@ -617,7 +642,7 @@ Error DataLayout::parseLayoutString(StringRef LayoutString) { // the spec for AS0, and we then update that to mark it non-integral. const PointerSpec &PS = getPointerSpec(AS); setPointerSpec(AS, PS.BitWidth, PS.ABIAlign, PS.PrefAlign, PS.IndexBitWidth, - true); + true, false); } return Error::success(); @@ -665,17 +690,20 @@ DataLayout::getPointerSpec(uint32_t AddrSpace) const { void DataLayout::setPointerSpec(uint32_t AddrSpace, uint32_t BitWidth, Align ABIAlign, Align PrefAlign, - uint32_t IndexBitWidth, bool IsNonIntegral) { + uint32_t IndexBitWidth, bool HasUnstableRepr, + bool HasExternalState) { auto I = lower_bound(PointerSpecs, AddrSpace, LessPointerAddrSpace()); if (I == PointerSpecs.end() || I->AddrSpace != AddrSpace) { PointerSpecs.insert(I, PointerSpec{AddrSpace, BitWidth, ABIAlign, PrefAlign, - IndexBitWidth, IsNonIntegral}); + IndexBitWidth, HasUnstableRepr, + HasExternalState}); } else { I->BitWidth = BitWidth; I->ABIAlign = ABIAlign; I->PrefAlign = PrefAlign; I->IndexBitWidth = IndexBitWidth; - I->IsNonIntegral = IsNonIntegral; + I->HasUnstableRepresentation = HasUnstableRepr; + I->HasExternalState = HasExternalState; } } diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp index a1f759dd1df83..5e719c6c8cbb7 100644 --- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -525,28 +525,33 @@ static bool dominatesMergePoint( static ConstantInt *getConstantInt(Value *V, const DataLayout &DL) { // Normal constant int. ConstantInt *CI = dyn_cast(V); - if (CI || !isa(V) || !V->getType()->isPointerTy() || - DL.isNonIntegralPointerType(V->getType())) + if (CI || !isa(V) || !V->getType()->isPointerTy()) return CI; + // It is not safe to look through inttoptr or ptrtoint when using unstable + // pointer types. + if (DL.hasUnstableRepresentation(V->getType())) + return nullptr; + // This is some kind of pointer constant. Turn it into a pointer-sized // ConstantInt if possible. - IntegerType *PtrTy = cast(DL.getIntPtrType(V->getType())); + IntegerType *IntPtrTy = cast(DL.getIntPtrType(V->getType())); // Null pointer means 0, see SelectionDAGBuilder::getValue(const Value*). if (isa(V)) - return ConstantInt::get(PtrTy, 0); + return ConstantInt::get(IntPtrTy, 0); - // IntToPtr const int. + // IntToPtr const int, we can look through this if the semantics of + // inttoptr for this address space are a simple (truncating) bitcast. if (ConstantExpr *CE = dyn_cast(V)) if (CE->getOpcode() == Instruction::IntToPtr) if (ConstantInt *CI = dyn_cast(CE->getOperand(0))) { // The constant is very likely to have the right type already. - if (CI->getType() == PtrTy) + if (CI->getType() == IntPtrTy) return CI; else return cast( - ConstantFoldIntegerCast(CI, PtrTy, /*isSigned=*/false, DL)); + ConstantFoldIntegerCast(CI, IntPtrTy, /*isSigned=*/false, DL)); } return nullptr; } @@ -866,10 +871,12 @@ Value *SimplifyCFGOpt::isValueEqualityComparison(Instruction *TI) { } } - // Unwrap any lossless ptrtoint cast. + // Unwrap any lossless ptrtoint cast (except for unstable pointers). if (CV) { if (PtrToIntInst *PTII = dyn_cast(CV)) { Value *Ptr = PTII->getPointerOperand(); + if (DL.hasUnstableRepresentation(Ptr->getType())) + return CV; if (PTII->getType() == DL.getIntPtrType(Ptr->getType())) CV = Ptr; } @@ -1427,6 +1434,8 @@ bool SimplifyCFGOpt::performValueComparisonIntoPredecessorFolding( Builder.SetInsertPoint(PTI); // Convert pointer to int before we switch. if (CV->getType()->isPointerTy()) { + assert(!DL.hasUnstableRepresentation(CV->getType()) && + "Should not end up here with unstable pointers"); CV = Builder.CreatePtrToInt(CV, DL.getIntPtrType(CV->getType()), "magicptr"); } @@ -5246,6 +5255,8 @@ bool SimplifyCFGOpt::simplifyBranchOnICmpChain(BranchInst *BI, Builder.SetInsertPoint(BI); // Convert pointer to int before we switch. if (CompVal->getType()->isPointerTy()) { + assert(!DL.hasUnstableRepresentation(CompVal->getType()) && + "Should not end up here with unstable pointers"); CompVal = Builder.CreatePtrToInt( CompVal, DL.getIntPtrType(CompVal->getType()), "magicptr"); } diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-index-width.ll b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-index-width.ll index 03056e8361e21..2049def9b59b7 100644 --- a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-index-width.ll +++ b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-index-width.ll @@ -6,7 +6,9 @@ target datalayout = "p:16:16:16:8" ; The GEP should only modify the low 8 bits of the pointer. define ptr @test() { ; CHECK-LABEL: define ptr @test() { -; CHECK-NEXT: ret ptr inttoptr (i16 -256 to ptr) +; We need to use finer-grained DataLayout properties for non-integral pointers +; FIXME: Should be: ret ptr inttoptr (i16 -256 to ptr) +; CHECK-NEXT: ret ptr getelementptr (i8, ptr inttoptr (i16 -1 to ptr), i8 1) ; %base = inttoptr i16 -1 to ptr %gep = getelementptr i8, ptr %base, i8 1 diff --git a/llvm/test/Transforms/SimplifyCFG/nonintegral.ll b/llvm/test/Transforms/SimplifyCFG/nonintegral.ll index 423ac4d1e69c1..74fe3bbeabb3a 100644 --- a/llvm/test/Transforms/SimplifyCFG/nonintegral.ll +++ b/llvm/test/Transforms/SimplifyCFG/nonintegral.ll @@ -1,12 +1,29 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6 ; RUN: opt -passes=simplifycfg -S < %s | FileCheck %s -target datalayout = "ni:1" +target datalayout = "pu1:64:64-pe2:64:64:64:32" +; We should not introduce ptrtoint instructions with unstable pointers define void @test_01(ptr addrspace(1) align 8 %ptr) { -; CHECK-LABEL: @test_01( -; CHECK-NOT: ptrtoint -; CHECK-NEXT: icmp eq ptr addrspace(1) %ptr, null -; CHECK-NOT: ptrtoint +; CHECK-LABEL: define void @test_01( +; CHECK-SAME: ptr addrspace(1) align 8 [[PTR:%.*]]) { +; CHECK-NEXT: [[COND1:%.*]] = icmp eq ptr addrspace(1) [[PTR]], null +; CHECK-NEXT: [[COND2:%.*]] = icmp eq ptr addrspace(1) [[PTR]], null +; CHECK-NEXT: br i1 [[COND1]], label %[[TRUE1:.*]], label %[[FALSE1:.*]] +; CHECK: [[TRUE1]]: +; CHECK-NEXT: br i1 [[COND2]], label %[[TRUE2:.*]], label %[[FALSE2:.*]] +; CHECK: [[FALSE1]]: +; CHECK-NEXT: store i64 1, ptr addrspace(1) [[PTR]], align 8 +; CHECK-NEXT: br label %[[TRUE1]] +; CHECK: [[COMMON_RET:.*]]: +; CHECK-NEXT: ret void +; CHECK: [[TRUE2]]: +; CHECK-NEXT: store i64 2, ptr addrspace(1) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET]] +; CHECK: [[FALSE2]]: +; CHECK-NEXT: store i64 3, ptr addrspace(1) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET]] +; %cond1 = icmp eq ptr addrspace(1) %ptr, null %cond2 = icmp eq ptr addrspace(1) %ptr, null br i1 %cond1, label %true1, label %false1 @@ -26,3 +43,78 @@ false2: store i64 3, ptr addrspace(1) %ptr, align 8 ret void } + +; This transformation is fine for pointers with external state. +; TODO: it would probably be better to just emit a pointer compare against null. +define void @test_02(ptr addrspace(2) align 8 %ptr) { +; CHECK-LABEL: define void @test_02( +; CHECK-SAME: ptr addrspace(2) align 8 [[PTR:%.*]]) { +; CHECK-NEXT: [[MAGICPTR:%.*]] = ptrtoint ptr addrspace(2) [[PTR]] to i64 +; CHECK-NEXT: [[COND:%.*]] = icmp eq i64 [[MAGICPTR]], 0 +; CHECK-NEXT: br i1 [[COND]], label %[[TRUE2:.*]], label %[[FALSE1:.*]] +; CHECK: [[FALSE1]]: +; CHECK-NEXT: store i64 1, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: store i64 3, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET:.*]] +; CHECK: [[COMMON_RET]]: +; CHECK-NEXT: ret void +; CHECK: [[TRUE2]]: +; CHECK-NEXT: store i64 2, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET]] +; + %cond1 = icmp eq ptr addrspace(2) %ptr, null + %cond2 = icmp eq ptr addrspace(2) %ptr, null + br i1 %cond1, label %true1, label %false1 + +true1: + br i1 %cond2, label %true2, label %false2 + +false1: + store i64 1, ptr addrspace(2) %ptr, align 8 + br label %true1 + +true2: + store i64 2, ptr addrspace(2) %ptr, align 8 + ret void + +false2: + store i64 3, ptr addrspace(2) %ptr, align 8 + ret void +} + +; This transformation is fine for pointers with external state (even with inttoptr). +define void @test_03(ptr addrspace(2) align 8 %ptr) { +; CHECK-LABEL: define void @test_03( +; CHECK-SAME: ptr addrspace(2) align 8 [[PTR:%.*]]) { +; CHECK-NEXT: [[MAGICPTR:%.*]] = ptrtoint ptr addrspace(2) [[PTR]] to i64 +; CHECK-NEXT: [[COND:%.*]] = icmp eq i64 [[MAGICPTR]], 4 +; CHECK-NEXT: br i1 [[COND]], label %[[TRUE2:.*]], label %[[FALSE1:.*]] +; CHECK: [[FALSE1]]: +; CHECK-NEXT: store i64 1, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: store i64 3, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET:.*]] +; CHECK: [[COMMON_RET]]: +; CHECK-NEXT: ret void +; CHECK: [[TRUE2]]: +; CHECK-NEXT: store i64 2, ptr addrspace(2) [[PTR]], align 8 +; CHECK-NEXT: br label %[[COMMON_RET]] +; + %cond1 = icmp eq ptr addrspace(2) %ptr, inttoptr (i32 4 to ptr addrspace(2)) + %cond2 = icmp eq ptr addrspace(2) %ptr, inttoptr (i32 4 to ptr addrspace(2)) + br i1 %cond1, label %true1, label %false1 + +true1: + br i1 %cond2, label %true2, label %false2 + +false1: + store i64 1, ptr addrspace(2) %ptr, align 8 + br label %true1 + +true2: + store i64 2, ptr addrspace(2) %ptr, align 8 + ret void + +false2: + store i64 3, ptr addrspace(2) %ptr, align 8 + ret void +} diff --git a/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll b/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll index 336fc5e14d758..8103124e3e5a6 100644 --- a/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll +++ b/llvm/test/Transforms/SimplifyCFG/switch_create-custom-dl.ll @@ -1,6 +1,6 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt -S -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 -switch-range-to-icmp < %s | FileCheck %s -target datalayout="p:40:64:64:32" +target datalayout="p:40:64:64:32-pe200:64:64:64:32-pu201:64:64:64:32" declare void @foo1() @@ -89,6 +89,63 @@ F: ; preds = %0 ret void } +; We also allow the transformation for pointers with external state +define void @test1_ptr_external_state(ptr addrspace(200) %V) { +; CHECK-LABEL: @test1_ptr_external_state( +; CHECK-NEXT: [[MAGICPTR:%.*]] = ptrtoint ptr addrspace(200) [[V:%.*]] to i64 +; CHECK-NEXT: switch i64 [[MAGICPTR]], label [[F:%.*]] [ +; CHECK-NEXT: i64 17, label [[T:%.*]] +; CHECK-NEXT: i64 4, label [[T]] +; CHECK-NEXT: ] +; CHECK: common.ret: +; CHECK-NEXT: ret void +; CHECK: T: +; CHECK-NEXT: call void @foo1() +; CHECK-NEXT: br label [[COMMON_RET:%.*]] +; CHECK: F: +; CHECK-NEXT: call void @foo2() +; CHECK-NEXT: br label [[COMMON_RET]] +; + %C1 = icmp eq ptr addrspace(200) %V, inttoptr (i32 4 to ptr addrspace(200)) + %C2 = icmp eq ptr addrspace(200) %V, inttoptr (i32 17 to ptr addrspace(200)) + %CN = or i1 %C1, %C2 ; [#uses=1] + br i1 %CN, label %T, label %F +T: ; preds = %0 + call void @foo1( ) + ret void +F: ; preds = %0 + call void @foo2( ) + ret void +} + +; But it is not permitted for unstable pointer representations +define void @test1_ptr_unstable(ptr addrspace(201) %V) { +; CHECK-LABEL: @test1_ptr_unstable( +; CHECK-NEXT: [[C1:%.*]] = icmp eq ptr addrspace(201) [[V:%.*]], inttoptr (i32 4 to ptr addrspace(201)) +; CHECK-NEXT: [[C2:%.*]] = icmp eq ptr addrspace(201) [[V]], inttoptr (i32 17 to ptr addrspace(201)) +; CHECK-NEXT: [[CN:%.*]] = or i1 [[C1]], [[C2]] +; CHECK-NEXT: br i1 [[CN]], label [[T:%.*]], label [[F:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret void +; CHECK: T: +; CHECK-NEXT: call void @foo1() +; CHECK-NEXT: br label [[COMMON_RET:%.*]] +; CHECK: F: +; CHECK-NEXT: call void @foo2() +; CHECK-NEXT: br label [[COMMON_RET]] +; + %C1 = icmp eq ptr addrspace(201) %V, inttoptr (i32 4 to ptr addrspace(201)) + %C2 = icmp eq ptr addrspace(201) %V, inttoptr (i32 17 to ptr addrspace(201)) + %CN = or i1 %C1, %C2 ; [#uses=1] + br i1 %CN, label %T, label %F +T: ; preds = %0 + call void @foo1( ) + ret void +F: ; preds = %0 + call void @foo2( ) + ret void +} + define void @test2(i32 %V) { ; CHECK-LABEL: @test2( ; CHECK-NEXT: switch i32 [[V:%.*]], label [[T:%.*]] [ diff --git a/llvm/unittests/IR/DataLayoutTest.cpp b/llvm/unittests/IR/DataLayoutTest.cpp index e0c0f35847f07..67481f6db131c 100644 --- a/llvm/unittests/IR/DataLayoutTest.cpp +++ b/llvm/unittests/IR/DataLayoutTest.cpp @@ -320,7 +320,8 @@ TEST(DataLayout, ParsePointerSpec) { "\"p[]::[:[:]]\"")); // address space - for (StringRef Str : {"p0x0:32:32", "px:32:32:32", "p16777216:32:32:32:32"}) + for (StringRef Str : + {"p0x0:32:32", "p10_000:32:32:32", "p16777216:32:32:32:32"}) EXPECT_THAT_EXPECTED( DataLayout::parse(Str), FailedWithMessage("address space must be a 24-bit integer")); @@ -401,6 +402,30 @@ TEST(DataLayout, ParsePointerSpec) { EXPECT_THAT_EXPECTED( DataLayout::parse(Str), FailedWithMessage("index size cannot be larger than the pointer size")); + + // Only 'e', 'u', and 'n' flags are valid. + EXPECT_THAT_EXPECTED( + DataLayout::parse("pa:32:32"), + FailedWithMessage("'a' is not a valid pointer specification flag")); + EXPECT_THAT_EXPECTED( + DataLayout::parse("puX:32:32"), + FailedWithMessage("'X' is not a valid pointer specification flag")); + // Flags must be before the address space number. + EXPECT_THAT_EXPECTED( + DataLayout::parse("p2n:32:32"), + FailedWithMessage("address space must be a 24-bit integer")); + + EXPECT_THAT_EXPECTED( + DataLayout::parse("pe2:64:64"), + FailedWithMessage("pointers with external state must be non-integral")); + + // AS0 cannot be non-integral. + for (StringRef Str : {"pe:64:64", "pu:64:64", "pue:64:64", "pe0:64:64", + "pu0:64:64", "peu0:64:64"}) + EXPECT_THAT_EXPECTED( + DataLayout::parse(Str), + FailedWithMessage( + "address space 0 cannot be unstable or have external state")); } TEST(DataLayoutTest, ParseNativeIntegersSpec) { @@ -556,18 +581,131 @@ TEST(DataLayout, GetPointerPrefAlignment) { } TEST(DataLayout, IsNonIntegralAddressSpace) { - DataLayout Default; - EXPECT_THAT(Default.getNonIntegralAddressSpaces(), ::testing::SizeIs(0)); + const DataLayout Default; + EXPECT_THAT(Default.getNonStandardAddressSpaces(), ::testing::SizeIs(0)); EXPECT_FALSE(Default.isNonIntegralAddressSpace(0)); EXPECT_FALSE(Default.isNonIntegralAddressSpace(1)); - DataLayout Custom = cantFail(DataLayout::parse("ni:2:16777215")); - EXPECT_THAT(Custom.getNonIntegralAddressSpaces(), + const DataLayout Custom = cantFail(DataLayout::parse("ni:2:16777215")); + EXPECT_THAT(Custom.getNonStandardAddressSpaces(), ::testing::ElementsAreArray({2U, 16777215U})); EXPECT_FALSE(Custom.isNonIntegralAddressSpace(0)); EXPECT_FALSE(Custom.isNonIntegralAddressSpace(1)); EXPECT_TRUE(Custom.isNonIntegralAddressSpace(2)); + EXPECT_TRUE(Custom.shouldAvoidIntToPtr(2)); + EXPECT_TRUE(Custom.shouldAvoidPtrToInt(2)); EXPECT_TRUE(Custom.isNonIntegralAddressSpace(16777215)); + EXPECT_TRUE(Custom.shouldAvoidIntToPtr(16777215)); + EXPECT_TRUE(Custom.shouldAvoidPtrToInt(16777215)); + + // Pointers are marked as non-integral if the address size != total size + for (const auto *Layout : {"p2:64:64:64:32", "p2:128:64:64:64"}) { + const DataLayout DL = cantFail(DataLayout::parse(Layout)); + EXPECT_TRUE(DL.isNonIntegralAddressSpace(2)); + EXPECT_FALSE(DL.hasUnstableRepresentation(2)); + EXPECT_FALSE(DL.hasExternalState(2)); + EXPECT_FALSE(DL.shouldAvoidIntToPtr(2)); + EXPECT_FALSE(DL.shouldAvoidPtrToInt(2)); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({2U})); + } + // Pointers can be marked as unstable using 'pu' + for (const auto *Layout : {"pu2:64:64:64:64", "pu2:64:64:64:32"}) { + const DataLayout DL = cantFail(DataLayout::parse(Layout)); + // Note: isNonIntegralAddressSpace returns true for even with index == + EXPECT_TRUE(DL.isNonIntegralAddressSpace(2)); + EXPECT_TRUE(DL.hasUnstableRepresentation(2)); + EXPECT_FALSE(DL.hasExternalState(2)); + EXPECT_TRUE(DL.shouldAvoidPtrToInt(2)); + EXPECT_TRUE(DL.shouldAvoidIntToPtr(2)); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({2U})); + } + + // Non-integral pointers with external state ('e' flag, requires index width + // != representation width). + for (const auto *Layout : {"pe2:64:64:64:32", "pe2:128:64:64:64"}) { + const DataLayout DL = cantFail(DataLayout::parse(Layout)); + EXPECT_TRUE(DL.isNonIntegralAddressSpace(2)); + EXPECT_TRUE(DL.hasExternalState(2)); + EXPECT_TRUE(DL.shouldAvoidIntToPtr(2)); + EXPECT_FALSE(DL.shouldAvoidPtrToInt(2)); + EXPECT_FALSE(DL.hasUnstableRepresentation(2)); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({2U})); + } + EXPECT_THAT_EXPECTED( + DataLayout::parse("pe2:64:64:64:64"), + FailedWithMessage("pointers with external state must be non-integral")); + + // It is also possible to have both unstable representation and external state + for (const auto *Layout : {"peu2:64:64:64:32", "pue2:128:64:64:64"}) { + const DataLayout DL = cantFail(DataLayout::parse(Layout)); + EXPECT_TRUE(DL.isNonIntegralAddressSpace(2)); + EXPECT_TRUE(DL.hasExternalState(2)); + EXPECT_TRUE(Custom.shouldAvoidIntToPtr(2)); + EXPECT_TRUE(Custom.shouldAvoidPtrToInt(2)); + EXPECT_TRUE(DL.hasUnstableRepresentation(2)); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({2U})); + } + + // For backwards compatibility, the ni DataLayout part overrides any + // p[e][u]. + for (const auto *Layout : + {"ni:2-p2:64:64:64:32", "ni:2-pu2:64:64:64:32", "ni:2-pu2:64:64:64:32", + "p2:64:64:64:32-ni:2", "pu2:64:64:64:32-ni:2", "pe2:64:64:64:32-ni:2", + "peeee2:64:64:64:32-pu2:64:64:64:32-ni:2"}) { + DataLayout DL = cantFail(DataLayout::parse(Layout)); + EXPECT_TRUE(DL.isNonIntegralAddressSpace(2)); + EXPECT_TRUE(DL.hasUnstableRepresentation(2)); + // The external state property is new and not expected for existing uses of + // non-integral pointers, so existing :ni data layouts should not set it. + EXPECT_FALSE(DL.hasExternalState(2)); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({2U})); + } +} + +TEST(DataLayout, NonIntegralHelpers) { + DataLayout DL = cantFail(DataLayout::parse( + "p1:128:128:128:64-pu2:32:32:32:32-pu3:64:64:64:32-pe4:64:64:64:32")); + EXPECT_THAT(DL.getNonStandardAddressSpaces(), + ::testing::ElementsAreArray({1u, 2u, 3u, 4u})); + struct Result { + unsigned Addrspace; + bool NonIntegral; + bool Unstable; + bool ExternalState; + unsigned Size; + } ExpectedResults[] = { + {0, false, false, false, 64}, {1, true, false, false, 128}, + {2, true, true, false, 32}, {3, true, true, false, 64}, + {4, true, false, true, 64}, + }; + LLVMContext Ctx; + for (const auto &Exp : ExpectedResults) { + EXPECT_EQ(Exp.NonIntegral, DL.isNonIntegralAddressSpace(Exp.Addrspace)); + EXPECT_EQ(Exp.Unstable, DL.hasUnstableRepresentation(Exp.Addrspace)); + EXPECT_EQ(Exp.ExternalState, DL.hasExternalState(Exp.Addrspace)); + bool AvoidIntToPtr = Exp.Unstable || Exp.ExternalState; + EXPECT_EQ(AvoidIntToPtr, DL.shouldAvoidIntToPtr(Exp.Addrspace)); + bool AvoidPtrToInt = Exp.Unstable; + EXPECT_EQ(AvoidPtrToInt, DL.shouldAvoidPtrToInt(Exp.Addrspace)); + Type *PtrTy = PointerType::get(Ctx, Exp.Addrspace); + Type *PtrVecTy = VectorType::get(PtrTy, 2, /*Scalable=*/false); + Type *ScalablePtrVecTy = VectorType::get(PtrTy, 1, /*Scalable=*/true); + for (Type *Ty : {PtrTy, PtrVecTy, ScalablePtrVecTy}) { + EXPECT_EQ(AvoidPtrToInt, DL.shouldAvoidPtrToInt(Ty)); + EXPECT_EQ(AvoidIntToPtr, DL.shouldAvoidIntToPtr(Ty)); + // The old API should return true for both unstable and non-integral. + EXPECT_EQ(Exp.Unstable || Exp.NonIntegral, + DL.isNonIntegralPointerType(Ty)); + } + // Both helpers gracefully handle non-pointer, non-vector-of-pointers: + EXPECT_FALSE(DL.shouldAvoidPtrToInt(IntegerType::getInt1Ty(Ctx))); + EXPECT_FALSE(DL.shouldAvoidIntToPtr(IntegerType::getInt1Ty(Ctx))); + } } TEST(DataLayoutTest, CopyAssignmentInvalidatesStructLayout) { From 690f3086eda24b46fc5cc6e9093327c675c0ce21 Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Sun, 21 Sep 2025 14:56:22 -0700 Subject: [PATCH 2/3] fix typo in comment Created using spr 1.3.8-beta.1 --- .../InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll index 16980b54f93ad..a9eeca1c2fa20 100644 --- a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll +++ b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll @@ -1,6 +1,6 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5 ; RUN: opt -S -passes=instsimplify < %s | FileCheck %s -;; Check that we do not create new inttoptr intstructions for unstable pointers +;; Check that we do not create new inttoptr instructions for unstable pointers ;; or pointers with external state (even if the values are all constants). ;; NOTE: for all but the zero address space, the GEP should only modify the low 8 bits of the pointer. target datalayout = "p:16:16:16:16-p1:16:16:16:8-pu2:16:16:16:8-pe3:16:16:16:8" From d18eb5ed5f5cdd70930669a722b919816506566a Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Sun, 21 Sep 2025 18:23:59 -0700 Subject: [PATCH 3/3] spelling Created using spr 1.3.8-beta.1 --- .../InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll index a9eeca1c2fa20..da413df013571 100644 --- a/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll +++ b/llvm/test/Transforms/InstSimplify/ConstProp/inttoptr-gep-nonintegral.ll @@ -2,7 +2,8 @@ ; RUN: opt -S -passes=instsimplify < %s | FileCheck %s ;; Check that we do not create new inttoptr instructions for unstable pointers ;; or pointers with external state (even if the values are all constants). -;; NOTE: for all but the zero address space, the GEP should only modify the low 8 bits of the pointer. +;; NOTE: for all but the zero address space, the GEP should only modify the +;; low 8 bits of the pointer. target datalayout = "p:16:16:16:16-p1:16:16:16:8-pu2:16:16:16:8-pe3:16:16:16:8" define ptr @test_null_base_normal() { @@ -22,7 +23,7 @@ define ptr @test_inttoptr_base_normal() { } ;; Transformation is fine for non-integral address space, but we can only change -;; the index bits (i8 -2 == i16 254) +;; the index bits: (i8 -2 == i16 254) define ptr addrspace(1) @test_null_base_nonintegral() { ; CHECK-LABEL: define ptr addrspace(1) @test_null_base_nonintegral() { ; CHECK-NEXT: ret ptr addrspace(1) inttoptr (i16 254 to ptr addrspace(1))