Mixed 32b/64b pointers in same translation unit

Dear community,

I am looking at trying to use clang in such a way that it is possible to use both 32-bit and 64-bit pointers in the same translation unit. Is this currently supported?

I would like to enable it on a per-pointer basis, and it would be ideal if it is part of the type. I am thinking that one would do something like…

[[ptr_32]] int* p;

…or the like to tell the compiler specifically that it is a 32-bit pointer (the rest will be 64-bit). I am compiling to a 64-bit target, so all pointers should natively be 64-bit.

I’ve been looking over the source a little, and see references to address space and an attribute address_space that enables one to choose the address space for variables. It seems that depending on the address space, different sizes for pointers can be used. I tried modifying the DataLayout::reset function (I realize I should probably modify the targets instead of the layout class itself) to add another entry to the Pointers vector:

setPointerAlignment(0, 8, 8, 8);

setPointerAlignment(4, 4, 4, 4);

…and using the address space extension to pick out the right size for some pointers:

attribute((address_space(0))) int* HiIAmA64BitPtr{ (int*)0xFFFF’FFFF’FFFF’FFFF };

attribute((address_space(4))) int* HiIAmA32BitPtr{ (attribute((address_space(4))) int*)0xFFFF’FFFF’FFFF’FFFF };

But that did not appear to work. When running the code and printing the size of the variables, I see that they are both 8 bytes:

std::cout << "Size of 64-bit ptr: " << sizeof(HiIAmA64BitPtr) << std::endl; // Prints 8

std::cout << "Size of 32-bit ptr: " << sizeof(HiIAmA32BitPtr) << std::endl; // Prints 8

Furthermore, for some strange reason, if I print the LLVM IR using clang++ -S -emit-llvm, I see that all pointers seem to be 32 bits long even without using the address space extension. Yet, a long long variable (called Test here) has a 64-bit size:

target triple = “x86_64-pc-windows-msvc19.0.0”

@"\01?HiIAmA64BitPtr@@3PEAHEA" = global i32* inttoptr (i64 -1 to i32*), align 8

@"\01?HiIAmA32BitPtr@@3PEAHEA" = global i32 addrspace(4)* inttoptr (i64 -1 to i32 addrspace(4)*), align 8

@"\01?g_Test@@3_KA" = global i64 0, align 8

This seems very odd to me. Can someone point me in the right direction on how to approach this?

My command line for compiling:

clang++ “Test.cpp” -std=c++1z -Wall -fms-compatibility-version=19 -c -o Test.o && “link.exe” -out:Test.exe -defaultlib:libcmt -nologo Test.o

(For some reason, clang is unable to invoke the linker correctly on its own.)

Regards,
Patrik Eklöf

I think you’re on the right track with address spaces, but you need to modify clang/lib/Basic/Targets.cpp to override TargetInfo::getPointerWidthV to give a different size.

If you go down this road, consider making __ptr32 and __ptr64 work better. MSVC accepts this code but clang rejects:

struct Foo { int *__ptr32 p; };
int *__ptr32 p;
int *__ptr64 q;
static_assert(sizeof(Foo) == 4, “Foo”);
static_assert(sizeof(p) == 4, “p”);
static_assert(sizeof(q) == 8, “q”);

Thanks for the answer, Reid.

I tried your suggestion and it worked on simpler types where I initialize pointers to some constant value, e.g.

constexpr attribute((address_space(0))) int* HiIAmA64BitPtr{ nullptr };

constexpr attribute((address_space(4))) int* HiIAmA32BitPtr{ nullptr };

int main()

{

static_assert(sizeof(HiIAmA32BitPtr) == 4);

static_assert(sizeof(HiIAmA64BitPtr) == 8);

}

But when I tried something more complex, it stops working. For example:

void foo() {}

__attribute((address_space(4))) void bar() {}

constexpr attribute((address_space(0))) auto* HiIAmA64BitPtr{ &foo };

constexpr attribute((address_space(4))) auto* HiIAmA32BitPtr{ &bar };

int main()

{

constexpr auto SizeOf32 = sizeof(HiIAmA32BitPtr);

constexpr auto SizeOf64 = sizeof(HiIAmA64BitPtr);

static_assert(sizeof(HiIAmA32BitPtr) == 4);

static_assert(sizeof(HiIAmA64BitPtr) == 8);

}

From what I can gather, it tries to take the address space of the parent type (or the dereferenced type of a pointer). My guess is that in the former experiment, the attribute binds to the dereferenced type and not the pointer? In this case, it doesn’t seem to work, though. Presumably because the attribute binds to the pointer type? I’m not sure. Regardless, it doesn’t work, and I’m still not sure why.

The assert that the 32-bit pointer is 4 bytes fails.

Any ideas?

Regards,
Patrik Eklöf

I wouldn’t be surprised if our attribute parsing does strange things around auto. We have a lot of heuristics to try to figure out which attributes apply to which part of the type or declaration. Address spaces are primarily used by opencl and cuda, so if you can write idiomatic cuda to do it, then it’ll probably work.

Well, it does the same thing if you type out the type too, which is why I left it as auto:

__attribute((address_space(4))) void bar() {}

constexpr attribute((address_space(4))) void(*HiIAmA32BitPtr)(){ &bar };

int main()

{

static_assert(sizeof(HiIAmA32BitPtr) == 4); // Assertion fails

}

Regards,

Patrik Eklöf

clang doesn’t currently support address spaces on function pointers. You’re getting a weird result because the address space is getting attached to the return type; clang should reject that, but currently doesn’t. -Eli

Useful information.

Thanks.

Regards,

Patrik Eklöf