Skip to content

Conversation

@steelman
Copy link

@steelman steelman commented Jul 10, 2025

The current definition of size_t as unsigned long causes many problems with recent versions of gcc and glibc* and other software packages. When size_t is defined like this gcc issues warnings/errors when a pointer to a size_t variable is to be assigned to a uint32_t * variable (uint32_t is defined as unsigned int) even though both unsigned long int and unsigned int are the same size (4 bytes).

Two other ILP32 ABIs supported in the gcc and glibc (i.e. MIPS n32 and Intel x32) define size_t as unsigned int.

* both need to be patched to support AArch64 ILP32, but the problems creep
into more platform independent code

The current definition[1] of `size_t` as `unsigned long` causes many
problems with recent versions of gcc and glibc[*] and other software
packages. When `size_t` is defined link this gcc issues warnings/errors
when a pointer to a `size_t` variable is to be assigned to a
`uint32_t *` variable (`uint32_t` is defined[2][3] as `unsigned int`)
even though both `unsigned long int` and `unsigned int` are the same
size (4 bytes).

Two other ILP32 ABIs supported in the gcc and glibc (i.e. MIPS n32[4]
and Intel x32[5]) define `size_t` as `unsigned int`.

[1] https://github.com/ARM-software/abi-aa/blob/ee6b627a58988b56a761d1a3d545fc01b9b78241/aapcs64/aapcs64.rst?plain=1#L2954
[2] https://sourceware.org/git?p=glibc.git;a=blob;f=posix/bits/types.h;hb=d6c2760ef7f7cdeab912767f04db4b14632fbb5f#l42
[3] https://sourceware.org/git?p=glibc.git;a=blob;f=bits/stdint-uintn.h;hb=d6c2760ef7f7cdeab912767f04db4b14632fbb5f#l26
[4] https://gcc.gnu.org/git?p=gcc.git;a=blob;f=gcc/config/mips/mips.h;hb=2f2e9bcfb0fd9cbf46e2d0d03b3f32f7df8d4fff#l3112
[5] https://gcc.gnu.org/git?p=gcc.git;a=blob;f=gcc/config/i386/x86-64.h;hb=2f2e9bcfb0fd9cbf46e2d0d03b3f32f7df8d4fff#l40

[*] both need to be patched to support ILP32, but the problems creep
    into more platform independent code

Signed-off-by: Łukasz Stelmach <[email protected]>
@smithp35
Copy link
Contributor

Unfortunately I don't think we can change the type from long to int as this will change the name mangling of C++ functions with size_t as a parameter and that is a breaking change.

unsigned int is j and unsigned long is m
https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin

I sympathize with the request, but we would have to find a way of making it without breaking existing objects.

@steelman
Copy link
Author

steelman commented Jul 10, 2025

we would have to find a way of making it without breaking existing objects

Even if the ILP32 ABI is currently marked as (Beta), which allows such changes?

@smithp35
Copy link
Contributor

It has been in Beta since 2016, mainly due to the lack of acceptance into the mainstream linux kernel and glibc. So while yes in theory it is in Beta which permits changes, in practice there is 9 years worth of legacy out there.

@Wilco1
Copy link
Contributor

Wilco1 commented Jul 10, 2025

And after a long time of not being actively used or maintained, ILP32 has been deprecated in GCC15 and may be removed in a future release.

@steelman
Copy link
Author

As you wrote, the ABI hasn't been accepted neither in linux nor in glibc and was recently deprecated in gcc. I don't know how much legacy binaries are out there, but recent toolchains available from Arm don't come with ILP32 glibc/libstdc++, so I guess their owners of AArch64 ILP32 binaries fully control those deployments and rebuilding them would be completely possible though annoying, I agree.

The reason I propose this change is that code as simple as this

#include <stdint.h>
#include <stdio.h>

int main(int ac, char* av[]) {
        size_t foo = (size_t)ac;
        uint32_t *bar = &foo;
        printf("uint32_t: %zx\n", sizeof(uint32_t));
        printf("size_t:   %zx\n", sizeof(size_t));
        return 0;
}

fails to build for ILP32 using Arm's own toolchain.

$ ./arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc --version
aarch64-none-linux-gnu-gcc (Arm GNU Toolchain 14.3.Rel1 (Build arm-14.174)) 14.3.1 20250623
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ ./arm-gnu-toolchain-14.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc -c -mabi=ilp32 pointers.c -o pointers.o 
pointers.c: In function ‘main’:
pointers.c:6:25: error: initialization of ‘uint32_t *’ {aka ‘unsigned int *’} from incompatible pointer type ‘size_t *’ {aka ‘long unsigned int *’} [-Wincompatible-pointer-types]
    6 |         uint32_t *bar = &foo;
      |                         ^

I believe there is a lot of source code that assumes, and rightly so, that on a 32-bit system these pointers are equivalent. These assumptions lead very far and cannot be worked around easily. For example there is this code in kmod

static inline bool uadd32_overflow(uint32_t a, uint32_t b, uint32_t *res)
{
#if (HAVE___BUILTIN_UADD_OVERFLOW && __SIZEOF_INT__ == 4)
	return __builtin_uadd_overflow(a, b, res);
#else
	*res = a + b;
	return UINT32_MAX - a < b;
#endif
}

static inline bool uaddsz_overflow(size_t a, size_t b, size_t *res)
{
#if __SIZEOF_SIZE_T__ == 8
	return uadd64_overflow(a, b, res);
#elif __SIZEOF_SIZE_T__ == 4
	return uadd32_overflow(a, b, res);
#else
#error "Unknown sizeof(size_t)"
#endif
}

which builds fine on AArch64 and AArch32 but not on AArch64 ILP32 and it is impossible to fix (without an explicit cast like uadd32_overflow(a, b, (uint32_t *)res) that is) because even if you redefine __uint32_t as unsigned long int, __builtin_uadd_overflow requires unsigned int * argument.

In my recent work of building different packages I found more similar cases of spaghetti dependencies where you are pulling on one end and need to dig through twisted knots and sometimes like here you hit a wall anyway.

Indeed, my proposal breaks ABI, but current definition of size_t for ILP32 breaks APIs. IMHO there is now more code out there that doesn't build properly (without -Wno-error=incompatible-pointer-types that is which isn't even the default setting anymore) because of how size_t is defined in AArch64 ILP32 ABI than there are legacy binaries that can't be rebuilt.

@Wilco1
Copy link
Contributor

Wilco1 commented Jul 12, 2025

It's generally wrong to make assumptions about size_t, it might be unsigned, unsigned long or even unsigned long long, so the most portable way is to use type generic code like this: https://godbolt.org/z/crPdazchK. In case bad assumptions have been baked in deeply, I would suggest to turn off that error.

Btw do you actually use ILP32? So far nobody has objected the deprecation.

@steelman
Copy link
Author

steelman commented Jul 14, 2025

It's generally wrong to make assumptions about size_t

Alas these assumptions are already everywhere starting with glibc, gcc and llvm.

do you actually use ILP32?

We are building a full distro to evaluate it. We are rebuilding all the packages we have built for armv7l and aarch64 and that is how we stumble upon such problems.

So far nobody has objected the deprecation.

I figure it's been, because everyone, who needed to run 32-bit code on aarch64 for any reason (memory savings, porting issues) has used armv7l. And now some vendors phase out it from their SoCs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants