Skip to content

Conversation

@midnightveil
Copy link
Contributor

@midnightveil midnightveil commented Jul 23, 2025

When compiled with arm-none-eabi-gcc (13.3.0, and others), the default ABI has -fshort-enums on by default. This seem to also be the case for x86 with gcc (GCC) 15.1.1 20250521 (Red Hat 15.1.1-2).

Due to C's arcane integer promotion rules, the enum members such as SUCCESS or seL4_NoError are of type "int" (generally signed 32 bit), whereas the size of seL4_Error or test_result_t is 8 bits (a char).

For reference: https://godbolt.org/z/zoKzhqxc7

This produces test output like:

Error: res (size 1) != SUCCESS (size 4), use of test_eq incorrect

Test with: seL4/seL4_libs#105

When compiled with arm-none-eabi-gcc (13.3.0, and others), the
default ABI has -fshort-enums on by default. This seem to also be
the case for x86 with gcc (GCC) 15.1.1 20250521 (Red Hat 15.1.1-2).

Due to C's arcane integer promotion rules, the enum members such as
SUCCESS or seL4_NoError are of type "int" (generally signed 32 bit),
whereas the size of seL4_Error or test_result_t is 8 bits (a char).

For reference: https://godbolt.org/z/zoKzhqxc7

This produces test output like:

    Error: res (size 1) != SUCCESS (size 4), use of test_eq incorrect

Signed-off-by: julia <git.ts@trainwit.ch>
api_make_guard_skip_word(guard),
env->page_directory, seL4_NilData);
test_eq(err, 0);
test_eq(err, (seL4_Error)seL4_NoError);
Copy link
Member

@lsf37 lsf37 Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other occurrences of seL4_NoError in this repo are just test_eq(err, seL4_NoError). The type of seL4_NoError is already the enum, so it looks to me like we should not need an explicit cast here. Or is there something special going on in this one?

(we would need to cast the 0, since that would be a literal with C's own arcane rules for that)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same for SUCCESS, really, unless there is a #define layer that I have overlooked)

Copy link
Contributor Author

@midnightveil midnightveil Jul 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other cases have err defined as type int, so the RHS matches the same type as the LHS. These ones are special because err is seL4_Error which then has size 1, not 4.

res has the type of test_result_t and not int.

I don't know if there's a better way to solve this generally, this seems really annoying because of C integer promotion rules.

Copy link
Contributor

@Indanz Indanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I acknowledge the problem, but I disagree with the solution: Surely the solution is to remove the sizeof check in the test_eq macro? What does that check add?

@midnightveil
Copy link
Contributor Author

IDK, but probably a mix of:

  • things like inequality (>) have odd behaviour when done on types of mix signedness e.g. (int)(-1) < (unsigned int)(1) can return false since -1 becomes 255.

  • size checking produces a better error when types aren't compatible (especially now since it can be compiled time checked)

I also didn't want to make changes that might break many more things, so took the simplest solution...

@Indanz
Copy link
Contributor

Indanz commented Jul 23, 2025

* things like inequality (>) have odd behaviour when done on types of mix signedness e.g. (int)(-1) < (unsigned int)(1) can return false since -1 becomes 255.

But then compilers warn about it, at least with warning enabled (it's part of -Wextra). Checking size doesn't help at all for mixed signedness problems.

* size checking produces a better error when types aren't compatible (especially now since it can be compiled time checked)

But different sized values are compatible when they have the same sign and are integers. AFAIK only floating point values are incompatible when they have different sizes, small chance the test accidentally pass. We can add a check for comparing floats with doubles instead of the size check.

I also didn't want to make changes that might break many more things, so took the simplest solution...

We have a wrong assumption in the check macro's (because they're part of the same code base that defined SUCCESS), which extends to all comparisons with enums, so to me the simple, low risk solution seems to remove those checks. That won't break any existing code and I don't see how it can cause future code to break.

If we keep that arbitrary size check, all users will stumble into this subtle enum size thing on some platforms with some toolchains. The static assert won't catch problems before they get into the code, only afterwards. So all it does is break the tests now and then for users for no good reason, like what happened to you now. The static assert just makes it easier to debug, it doesn't avoid the problem.

Removing the size checks does avoid and solve the problem, now and in the future.

@midnightveil
Copy link
Contributor Author

But different sized values are compatible when they have the same sign and are integers. AFAIK only floating point values are incompatible when they have different sizes, small chance the test accidentally pass. We can add a check for comparing floats with doubles instead of the size check.

https://godbolt.org/z/bf8K6Krer

enum value { A, B } is never compatible with its int even if the sizeof fits, that wouldn't make sense. Also, two different enum types are not compatible either. We'd have to add support for every enum we'd ever want to use to this type compatibility test. (And, as a bonus, since we use this compatibility test, and not say, _Generic, we can't even make this fail to compile).

@Indanz
Copy link
Contributor

Indanz commented Aug 4, 2025

https://godbolt.org/z/bf8K6Krer

enum value { A, B } is never compatible with its int even if the sizeof fits, that wouldn't make sense. Also, two different enum types are not compatible either. We'd have to add support for every enum we'd ever want to use to this type compatibility test. (And, as a bonus, since we use this compatibility test, and not say, _Generic, we can't even make this fail to compile).

Okay, enums are weird in C, we can't fix that (and apparently my use of compatible is different than the official use). But using __builtin_types_compatible_p doesn't fix the original problem:

_Static_assert(__builtin_types_compatible_p(typeof(SUCCESS), typeof(xxx_value)));

fails too, so it has exactly the same problems as the sizeof tests does.

So the rest of what I said still applies. Extra debug checks should help find real bugs, not cause false positives and subtle problems themselves. If you can't make them work for enums, remove them.

@midnightveil
Copy link
Contributor Author

Okay, enums are weird in C, we can't fix that (and apparently my use of compatible is different than the official use). But using __builtin_types_compatible_p doesn't fix the original problem:

The code is using it already for type-based dispatch; my PR to seL4_libs did make it work. I appear to have confused myself slightly here; the type of the enum was never "int". (oops).

https://godbolt.org/z/1j48TG9cj

^ Needed to be unsigned int / unsigned char.

So the rest of what I said still applies. Extra debug checks should help find real bugs, not cause false positives and subtle problems themselves. If you can't make them work for enums, remove them.

But actually — the existing checks seem to be needed for type-based dispatch. I guess I'd need to think about it more and see if it's possible, but I don't see how you'd easily have a test_eq(a, b) print out the values of a and b without this type-dispatch, and unless we hardcode all the possibilities? Like, you could just pass in the format specifier at every callsite, and maybe that's nicer, but also it's significantly more effort than these 5 fixes.

@midnightveil
Copy link
Contributor Author

The other alternative is we just pass through -fno-short-enums through the build system?

@Indanz
Copy link
Contributor

Indanz commented Dec 13, 2025

The other alternative is we just pass through -fno-short-enums through the build system?

Agreed, not sure why we didn't think of that before.

@midnightveil
Copy link
Contributor Author

Hm.

/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/arch/arm/arch.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/main.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/interrupt.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/smmu.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/syscall.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/timer.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/testtypes.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/nix/store/fj2jmvq3azd8lqqa6jw7qkbjnq33rdn0-arm-none-eabi-binutils-2.43.1/bin/arm-none-eabi-ld: warning: apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/timer.c.obj uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail

This happens for every object linked in, regardless of if I add -fno-short-enums to cmake-tool or to sel4test (the latter makes it only complain about sel4test objects), or even if I add -mabi=aapcs-linux.

 /nix/store/yn7f2hdccdzclaz43zz7pi3akrn6v2gw-arm-none-eabi-gcc-13.3.0/bin/arm-none-eabi-gcc --sysroot=/home/julia/ts/sel4test-manifest-5/build  -march=armv7ve -marm  -D__KERNEL_32__ -g -D__KERNEL_32__   -static -nostdlib -z max-page-size=0x1000    -Wl,-u_sel4_start -Wl,-e_sel4_start
  -Wl,-T /home/julia/ts/sel4test-manifest-5/tools/seL4/cmake-tool/helpers/tls_rootserver.lds   -Wl,-umuslcsys_init_muslc /home/julia/ts/sel4test-manifest-5/build/lib/crt0.o /home/julia/ts/sel4test-manifest-5/build/lib/crti.o /nix/store/yn7f2hdccdzclaz43zz7pi3akrn6v2gw-arm-none-eabi-gcc-13.3.0/lib/gcc/arm-none-eabi/13.3.0/crtbegin.o apps/sel4test-driver/archive.o apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/arch/arm/arch.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/main.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/interrupt.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/smmu.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/syscall.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/tests/timer.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/testtypes.c.obj apps/sel4test-driver/CMakeFiles/sel4test-driver.dir/src/timer.c.obj -Wl,--start-group         -lgcc  libsel4/libsel4.a  apps/sel4test-driver/sel4runtime/libsel4runtime.a  apps/sel4test-driver/seL4_libs/libsel4allocman/libsel4allocman.a  apps/sel4test-driver/seL4_libs/libsel4vka/libsel4vka.a  apps/sel4test-driver/seL4_libs/libsel4utils/libsel4utils.a  apps/sel4test-driver/sel4_projects_libs/libsel4rpc/libsel4rpc.a  apps/sel4test-driver/seL4_libs/libsel4test/libsel4test.a  apps/sel4test-driver/seL4_libs/libsel4platsupport/libsel4platsupport.a  apps/sel4test-driver/seL4_libs/libsel4muslcsys/libsel4muslcsys.a  apps/sel4test-driver/libsel4testsupport/libsel4testsupport.a  -Wl,-u -Wl,__vsyscall_ptr  apps/sel4test-driver/seL4_libs/libsel4test/libsel4test.a  apps/sel4test-driver/sel4_projects_libs/libsel4rpc/libsel4rpc.a  apps/sel4test-driver/sel4_projects_libs/libsel4nanopb/libsel4nanopb.a  apps/sel4test-driver/sel4_projects_libs/libsel4nanopb/libnanopb.a  apps/sel4test-driver/seL4_libs/libsel4sync/libsel4sync.a  apps/sel4test-driver/seL4_libs/libsel4serialserver/libsel4serialserver.a  apps/sel4test-driver/seL4_libs/libsel4utils/libsel4utils.a  apps/sel4test-driver/util_libs/libelf/libelf.a  apps/sel4test-driver/util_libs/libcpio/libcpio.a  apps/sel4test-driver/seL4_libs/libsel4platsupport/libsel4platsupport.a  apps/sel4test-driver/sel4runtime/libsel4runtime.a  apps/sel4test-driver/util_libs/libplatsupport/libplatsupport.a  apps/sel4test-driver/util_libs/libfdt/libfdt.a  -Wl,--undefined=arm_gic_ptr,--undefined=tegra_ictlr_ptr,--undefined=arm_gicv3_ptr,--undefined=fsl_avic_ptr,--undefined=ti_omap3_ptr  apps/sel4test-driver/seL4_libs/libsel4simple-default/libsel4simple-default.a  apps/sel4test-driver/seL4_libs/libsel4vspace/libsel4vspace.a  apps/sel4test-driver/seL4_libs/libsel4simple/libsel4simple.a  apps/sel4test-driver/seL4_libs/libsel4vka/libsel4vka.a  apps/sel4test-driver/seL4_libs/libsel4debug/libsel4debug.a  libsel4/libsel4.a  apps/sel4test-driver/util_libs/libutils/libutils.a  apps/sel4test-driver/musllibc/build-temp/stage/lib/libc.a -Wl,--end-group /nix/store/yn7f2hdccdzclaz43zz7pi3akrn6v2gw-arm-none-eabi-gcc-13.3.0/lib/gcc/arm-none-eabi/13.3.0/crtend.o /home/julia/ts/sel4test-manifest-5/build/lib/crtn.o -o apps/sel4test-driver/sel4test-driver -Wl,--verbose

There's a way to hide this (--no-enum-size-warning) but I don't know if that's necessarily correct. From the looks of things we're asking for -lgcc so libgcc would likely be a source of complaints: /nix/store/yn7f2hdccdzclaz43zz7pi3akrn6v2gw-arm-none-eabi-gcc-13.3.0/lib/gcc/arm-none-eabi/13.3.0/libgcc.a. It looks like BFD picks up the output attributes from the first input file it's given, which happens to be crt0.o.

Not sure if I like that approach now.

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