https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127626
Bug ID: 127626
Summary: arm: __sync_val_compare_and_swap calls generated with
wrong signedness
Product: gcc
Version: 14.3.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: target
Assignee: unassigned at gcc dot gnu.org
Reporter: campbell+gcc-bugzilla at mumble dot net
Target Milestone: ---
Created attachment 65694
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65694&action=edit
comparison of __sync_val_compare_and_swap_1 with normal functions of similar
prototypes
The builtin __sync_val_compare_and_swap_1 has an implicit prototype of
unsigned char __sync_val_compare_and_swap_1(volatile void *, unsigned char,
unsigned char);
as you can verify by supplying the wrong definition and observing the
error message:
armatomic.c:8:9: error: conflicting types for built-in function
'__sync_val_compare_and_swap_1'; expected 'unsigned char(volatile void *,
unsigned char, unsigned char)' [-Werror=builtin-declaration-mismatch]
8 | uint8_t __sync_val_compare_and_swap_1(volatile char *, uint8_t,
uint8_t);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
However, for Arm architecture options that require an out-of-line
library call (in this case, -march=armv5t -mfloat-abi=soft), gcc
generates calls to the library function _as if_ it were declared with
signed char or int8_t. For example, here is a fragment of code
generated for __sync_val_compare_and_swap(p, 0x88, 0xf0) and stored in
a uint8_t:
0: e3e01077 mvn r1, #119 @ 0x77
4: e52de004 push {lr} @ (str lr, [sp, #-4]!)
8: e24dd00c sub sp, sp, #12
c: e3e0200f mvn r2, #15
10: e28d0007 add r0, sp, #7
14: e5cd1007 strb r1, [sp, #7]
18: ebfffffe bl 0 <__sync_val_compare_and_swap_1>
18: R_ARM_CALL __sync_val_compare_and_swap_1
1c: e1a02000 mov r2, r0
20: e3a01088 mov r1, #136 @ 0x88
24: e20220ff and r2, r2, #255 @ 0xff
The `mvn r1 #0x77' puts ~0x77 = 0xffffff88 in r1, the `mvn r2, #0xf'
puts ~0x0f = 0xfffffff0 in r2, and the `and r2, r2, #0xff' at the end
clears the high-order bits of a sign-extended 8-bit quantity, as if the
old, new, and result all had signed type int8_t instead of unsigned
type uint8_t.
The attached atomic.c shows the corresponding code generation for
normal non-builtin functions having otherwise identical prototypes,
both signed or unsigned, and the code gcc generates for
__sync_val_compare_and_swap_1 matches the signed one.
In contrast, on sh3 and sparc, gcc generates out-of-line library calls
to the same __sync_val_compare_and_swap_1 symbol, but as if they are
unsigned -- matching the implicit prototype shown in the error message:
uint8_t res = __sync_val_compare_and_swap(p, 0x88, 0xf0);
printf(..., 0x88, res);
6: e0 88 mov #-120,r0
8: 98 10 mov.w 2c <main__+0x2c>,r8 ! 88
a: 64 f3 mov r15,r4
c: 80 f3 mov.b r0,@(3,r15)
e: 65 83 mov r8,r5
10: d0 07 mov.l 30 <main__+0x30>,r0 ! 0 <main__>
12: 96 0c mov.w 2e <main__+0x2e>,r6 ! f0
14: 40 0b jsr @r0
16: 74 03 add #3,r4
18: 66 03 mov r0,r6
1a: d0 06 mov.l 34 <main__+0x34>,r0 ! 0 <main__>
1c: d4 06 mov.l 38 <main__+0x38>,r4 ! 0 <main__>
1e: 40 0b jsr @r0
...
2c: 00 88 .word 0x0088
2e: 00 f0 .word 0x00f0
...
30: R_SH_DIR32 __sync_val_compare_and_swap_1
34: R_SH_DIR32 printf
Note that the inputs (0x88 in r5, 0xf0 in r6) are not sign-extended
(requiring a 16-bit encoding in sh3 since there is no zero-extended
mov.b to load them), and the output is not explicitly zero-extended
with extu.b when promoted to int as a printf argument as it would be
with a uint8_t prototype, because the codegen assumes the return value
is already zero-extended.
In contrast, with a signed prototype for a normal function call, the
generated code is:
82: d0 08 mov.l a4 <main_s8+0x2c>,r0 ! 0 <main__>
84: e6 f0 mov #-16,r6
86: e5 88 mov #-120,r5
88: 40 0b jsr @r0
8a: 74 03 add #3,r4
8c: 66 0c extu.b r0,r6
8e: d0 06 mov.l a8 <main_s8+0x30>,r0 ! 0 <main__>
90: 95 07 mov.w a2 <main_s8+0x2a>,r5 ! 88
92: d4 06 mov.l ac <main_s8+0x34>,r4 ! 0 <main__>
94: 40 0b jsr @r0
...
a2: 00 88 .word 0x0088
...
a4: R_SH_DIR32 s8_sync_val_compare_and_swap_1
a8: R_SH_DIR32 printf
Note that the inputs _are_ sign-extended (-120 = 0xffffff88 in r5, -16
= 0xfffffff0 in r6), and the output _is_ explicitly zero-extended with
extu.b, when the function is declared with signed inputs and outputs.
Similarly for sparc -- with the builtin:
8: 94 10 20 f0 mov 0xf0, %o2
c: c2 2f bf ff stb %g1, [ %fp + -1 ]
10: 92 10 20 88 mov 0x88, %o1
14: 40 00 00 00 call 14 <main__+0x14>
14: R_SPARC_WDISP30 __sync_val_compare_and_swap_1
18: 90 07 bf ff add %fp, -1, %o0
1c: 92 10 20 88 mov 0x88, %o1
20: 94 10 00 08 mov %o0, %o2
24: b0 10 20 00 clr %i0
28: 11 00 00 00 sethi %hi(0), %o0
28: R_SPARC_HI22 .rodata.str1.8
2c: 40 00 00 00 call 2c <main__+0x2c>
2c: R_SPARC_WDISP30 printf
Zero-extended inputs, no adjustment of output.
With a normal function declared with signed inputs and outputs:
80: 94 10 3f f0 mov -16, %o2
84: c2 2f bf ff stb %g1, [ %fp + -1 ]
88: 92 10 3f 88 mov -120, %o1
8c: 40 00 00 00 call 8c <main_s8+0x14>
8c: R_SPARC_WDISP30 s8_sync_val_compare_and_swap_1
90: 90 07 bf ff add %fp, -1, %o0
94: 92 10 20 88 mov 0x88, %o1
98: 94 0a 20 ff and %o0, 0xff, %o2
9c: b0 10 20 00 clr %i0
a0: 11 00 00 00 sethi %hi(0), %o0
a0: R_SPARC_HI22 .rodata.str1.8
a4: 40 00 00 00 call a4 <main_s8+0x2c>
a4: R_SPARC_WDISP30 printf
Inputs are sign-extended (-120 = 0xffffff88 in %o1, -16 = 0xfffffff0 in
%o2), and output is adjusted with `and %o0, 0xff, %o2' to zero-extend
it.
NetBSD bug reports:
PR lib/56839: GCC emits wrong codes for compare_and_swap_1 bultins on
armv5 (el & eb)
https://gnats.NetBSD.org/56839
When we switched the libc symbols to have signed prototypes, that broke
sh3 and sparc:
PR port-sh3/60773: __sync_val_compare_and_swap_1 test failures
https://gnats.NetBSD.org/60773
Also, it sure would be nice if the ABI contract for these symbols were
documented somewhere!