Skip to content

Fix test failures when -fshort-enums is used#145

Closed
midnightveil wants to merge 2 commits intoseL4:masterfrom
midnightveil:short-enums-fix
Closed

Fix test failures when -fshort-enums is used#145
midnightveil wants to merge 2 commits intoseL4:masterfrom
midnightveil:short-enums-fix

Conversation

@midnightveil
Copy link
Copy Markdown
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>
Comment thread apps/sel4test-tests/src/tests/faults.c
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

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

@Indanz
Copy link
Copy Markdown
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
Copy Markdown
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.

@lsf37
Copy link
Copy Markdown
Member

lsf37 commented Apr 5, 2026

I've also hit this now (trying to build without docker for a change), and I don't see a way to remove the warning without just suppressing them, which does not sound like a good idea.

Overall, I'd actually be happy with the casts as in the original PR. From everything we've been discussing, they are the lowest impact change. Trying to re-architect what test_eq does looks like a deeper rabbit hole to me, and it's not like the casts are doing anything wrong. If at all, they are a bit of documentation on what is going on. Maybe we should add a comment that explains why we have them in a way that can be discovered via grep if someone else is hitting the same problem elsewhere in sel4test.

@Indanz
Copy link
Copy Markdown
Contributor

Indanz commented Apr 6, 2026

My original objections still stand:

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.

The root cause is the wrong assumption in the check macros, anything else is one work-around or another for that problem.

@lsf37
Copy link
Copy Markdown
Member

lsf37 commented Apr 6, 2026

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

The root cause is the wrong assumption in the check macros, anything else is one work-around or another for that problem.

So you'd remove the size assertion in here? https://github.com/seL4/seL4_libs/blob/4ce71fca2adbf1c2add7eabf5670633a70177a2f/libsel4test/include/sel4test/test.h#L239-L266

Wouldn't that make it more likely that something even more obscure is going wrong in all these TYPES_COMPATIBLE dispatch checks?

@midnightveil
Copy link
Copy Markdown
Contributor Author

I think we'd need to add two pairs of TYPES_COMPATIBLE checks then: one for a and one for b. That or I guess we really should be checking TYPES_COMPATIBLE(a, b) which I don't know why we didn't do originally instead of the size. [ It's not, strictly speaking, transitive, because whilst it might be the case that "enum X" ~ "int" and "enum Y" ~ "int", it is not the case that "enum X" ~ "enum Y" ]. Otherwise we'd run into the issue now I think where we see the short enum on the left, use the short enum printf specifier, but then also use it for the int on the right, since dispatch goes off the type of A. I think this is fine because varargs puts everything on the stack as an int, but I don't know if that's technically UB to do that or not.

We really want 2 things:

  1. the typeof(a), then the typeof(b), for the right printf format string. This can be solved by making test_op do the printf formatting for a & b separately. This can likely be improved by using C11 _Generic to go from type -> "%llu" etc, and so that twice as arguments. (I say _Generic of the chain of if types compatible because then we can compile time error if we don't know how to print a particular type).

  2. typeof(a) and typeof(b) are either both signed or unsigned, for inequality. (see: a = "int" and b = "unsigned int" has edge cases). Checking types compatible of a & b would be good enough except it's also too restrictive, as it misses the short-enums enum compatible with int issue. However, I don't think anywhere in the current code really cares about this, and if it did it's broken currently anyway since we were previously assuming that if it's the same size it's fine; and most of the time we use equality anyway in these test suites.

In most other cases e.g. equality of numbers the default C promotion to int, or to the larger of a and b for binary operators should be fine.

tldr: I think it's mostly about the printing. This would be a rework of the test_op macro, but it wouldn't break users, I don't think.

@Indanz
Copy link
Copy Markdown
Contributor

Indanz commented Apr 6, 2026

So you'd remove the size assertion in here? https://github.com/seL4/seL4_libs/blob/4ce71fca2adbf1c2add7eabf5670633a70177a2f/libsel4test/include/sel4test/test.h#L239-L266

Yes.

Wouldn't that make it more likely that something even more obscure is going wrong in all these TYPES_COMPATIBLE dispatch checks?

Maybe, but very unlikely. Worst case the top bits of b get discarded in the value print, making debugging slightly harder.

I find all that macro code overly complex for no gain. It does all the type checking, and then it still needs to cast things.

The sad thing is, because of those macros, you get punished for using the right enum type instead of int. The reason this problem is fixed by just changing those four lines is because that are the only places using seL4_Error and test_result_t instead of int.

If you don't want to remove the size check, changing these locations to also use int would be more consistent with all the other code.

@lsf37
Copy link
Copy Markdown
Member

lsf37 commented Apr 6, 2026

Ok, fine, let's fix the macro. In our own code, these macros are used exclusively in sel4test, libsel4test and libsel4serialserver (also for testing), we probably don't need to be extra super careful about compatibility.

@midnightveil, your plan sounds good to me. Do you have time to give this a go?

@midnightveil
Copy link
Copy Markdown
Contributor Author

@midnightveil, your plan sounds good to me. Do you have time to give this a go?

Yeah, I can give this a shot today.

(I think this PR is the easier fix, it's not like this code needs to be perfect, but I can fix it properly too)

midnightveil added a commit to au-ts/seL4_libs that referenced this pull request Apr 7, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil added a commit to au-ts/seL4_libs that referenced this pull request Apr 7, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil added a commit to au-ts/seL4_libs that referenced this pull request Apr 7, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

I've verified locally that this allows me to compile locally with
a toolchain that sets -fshort-enums, and also that simulation
tests for {x86_64,aarch64,aarch32,riscv64} all pass with these
changes. I've also tested that when it does fail it does manage
to print an error correctly.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
@midnightveil
Copy link
Copy Markdown
Contributor Author

Closing this in favour of seL4/seL4_libs#111.

@midnightveil midnightveil deleted the short-enums-fix branch April 7, 2026 04:06
midnightveil added a commit to au-ts/seL4_libs that referenced this pull request Apr 7, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

I've verified locally that this allows me to compile locally with
a toolchain that sets -fshort-enums, and also that simulation
tests for {x86_64,aarch64,aarch32,riscv64} all pass with these
changes. I've also tested that when it does fail it does manage
to print an error correctly.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
midnightveil added a commit to au-ts/seL4_libs that referenced this pull request Apr 8, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

I've verified locally that this allows me to compile locally with
a toolchain that sets -fshort-enums, and also that simulation
tests for {x86_64,aarch64,aarch32,riscv64} all pass with these
changes. I've also tested that when it does fail it does manage
to print an error correctly.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
lsf37 pushed a commit to seL4/seL4_libs that referenced this pull request Apr 8, 2026
Ref: seL4/sel4test#145 (comment)

This will fix the code's assumption that a & b are the same type,
instead preferring to just format each separately, and also
to compare with the operator naively, which should work for
our purposes.

I've verified locally that this allows me to compile locally with
a toolchain that sets -fshort-enums, and also that simulation
tests for {x86_64,aarch64,aarch32,riscv64} all pass with these
changes. I've also tested that when it does fail it does manage
to print an error correctly.

Signed-off-by: Julia Vassiliki <julia.vassiliki@unsw.edu.au>
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