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!
  • [Bug target/127626] N... campbell+gcc-bugzilla at mumble dot net via Gcc-bugs

Reply via email to