NetBSD Problem Report #60773
From martin@last-of-the-heroes.duskware.de Wed Sep 23 13:42:52 2026
Return-Path: <martin@last-of-the-heroes.duskware.de>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits)
client-signature RSA-PSS (2048 bits))
(Client CN "mail.netbsd.org", Issuer "YR1" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id C59E11A923B
for <gnats-bugs@gnats.NetBSD.org>; Wed, 23 Sep 2026 13:42:51 +0000 (UTC)
Message-Id: <20260923134248.47C8AE1FFBB@mail.duskware.de>
Date: Wed, 23 Sep 2026 15:42:43 +0200 (CEST)
From: martin@NetBSD.org
Reply-To: martin@NetBSD.org
To: gnats-bugs@NetBSD.org
Subject: __sync_val_compare_and_swap_1 test failures
X-Send-Pr-Version: 3.95
X-From4GNATS: "martin@NetBSD.org via gnats" <gnats-admin@NetBSD.org>
>Number: 60773
>Notify-List: skrll, rin
>Category: toolchain
>Synopsis: __sync_val_compare_and_swap_1 test failures
>Confidential: no
>Severity: critical
>Priority: high
>Responsible: toolchain-manager
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Wed Sep 23 13:45:00 +0000 2026
>Last-Modified: Sat Sep 26 14:30:01 +0000 2026
>Originator: Martin Husemann
>Release: NetBSD 11.99.8
>Organization:
The NetBSD Foundation, Inc.
>Environment:
System: NetBSD last-of-the-heroes.duskware.de 11.99.8 NetBSD 11.99.8 (GENERIC) #412: Tue Sep 22 09:43:24 CEST 2026 martin@seven-days-to-the-wolves.aprisoft.de:/work/src/sys/arch/landisk/compile/GENERIC landisk
Architecture: sh3el
Machine: landisk
>Description:
Some CAS test cases go wrong on the landisk tests
https://www.NetBSD.org/~martin/landisk-atf
>How-To-Repeat:
cd /usr/tests/lib/libc/atomic && ./t___sync_compare_and_swap __sync_val_compare_and_swap_1
note:
failed: /work/src/tests/lib/libc/atomic/t___sync_compare_and_swap.c:189: successful case: res expects 0x88 but 0xffffff88
>Fix:
n/a
>Release-Note:
>Audit-Trail:
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc:
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 03:18:03 +0300
On Wed, Sep 23, 2026 at 13:45:00 +0000, martin@NetBSD.org via gnats wrote:
> failed: /work/src/tests/lib/libc/atomic/t___sync_compare_and_swap.c:189: successful case: res expects 0x88 but 0xffffff88
common/lib/libc/atomic/atomic_cas_8_cas.c defines
sync_val_compare_and_swap_1 to return signed int8_t
_atomic_cas_8 returns correct uin8_t value 0x88, then
sync_val_compare_and_swap_1 sign extends it before returning.
The change was in common/lib/libc/atomic/atomic_init_testset.c
revision 1.22
date: 2026-05-03 14:48:02 +0300; author: skrll; state: Exp; lines: +40 -5; commitid: z77RclIjL3bYwmEG;
PR/56839 GCC emits wrong codes for compare_and_swap_1 bultins on armv5 (el & eb)
There is mismatch in signedness of the GCC builtin __sync_* function arguments
and the _atomic_* functions so we cannot directly alias them. Instead write
the __sync_* functions with signed arguments and pass them the unsigned
_atomics_* functions.
So the testcase and the function declaration do not match.
PS: Please, can someone write a manual page for these? It's scores of
function names that all look alike with a sprinkle of scrabble bag
spillover randomly glued in unexpected places.
gcc info only talks about "__sync_val_compare_and_swap" and refers to
Intel's Itanium psABI, which is probably printed on fine leafs of
unobtanium (if you are lucky - with a microfiche copy on archive.org
w/out an OCR layer). Anyway, finding this information shouldn't be a
wild goose chase or an exercise in RTFS and repo archeology.
-uwe
Responsible-Changed-From-To: port-sh3-maintainer->toolchain-manager
Responsible-Changed-By: riastradh@NetBSD.org
Responsible-Changed-When: Thu, 24 Sep 2026 00:47:41 +0000
Responsible-Changed-Why:
not sh3-specific, also applies to sparc
From: Taylor R Campbell <riastradh@NetBSD.org>
To: gnats-bugs@netbsd.org
Cc: port-sh3-maintainer@netbsd.org, gnats-admin@netbsd.org,
netbsd-bugs@netbsd.org, martin@NetBSD.org, skrll@NetBSD.org,
uwe@NetBSD.org
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 00:50:36 +0000
Maybe we should just close this as a duplicate of PR 56839 (GCC emits
wrong codes for compare_and_swap_1 bultins on armv5 (el & eb))?
The same issue is turning up on sparc:
FAILED: /tmp/build/2026.09.22.23.22.21-sparc/src/tests/lib/libc/atomic/t___=
sync_compare_and_swap.c:189: successful case: res expects 0x88 but 0xffffff=
88
https://releng.netbsd.org/b5reports/sparc/2026/2026.09.22.23.22.21/test.htm=
l#lib_libc_atomic_t___sync_compare_and_swap___sync_val_compare_and_swap_1
But not on any other architectures I checked.
> Date: Thu, 24 Sep 2026 03:18:03 +0300
> From: Valery Ushakov <uwe@stderr.spb.ru>
>=20
> PS: Please, can someone write a manual page for these? It's scores of
> function names that all look alike with a sprinkle of scrabble bag
> spillover randomly glued in unexpected places.
If we do that, we will still have to be careful to distinguish:
(a) the compiler builtins that gcc/clang recognize
from
(b) the elf library symbols that gcc/clang generate calls to.
And I suspect they are not the same...
> gcc info only talks about "__sync_val_compare_and_swap" and refers to
> Intel's Itanium psABI, which is probably printed on fine leafs of
> unobtanium (if you are lucky - with a microfiche copy on archive.org
> w/out an OCR layer). Anyway, finding this information shouldn't be a
> wild goose chase or an exercise in RTFS and repo archeology.
Didn't seem too hard to find a plausible referent with a web search
for `intel itanium psabi':
https://refspecs.linuxfoundation.org/elf/IA64-SysV-psABI.pdf
We should archive this ourselves, and at least link to it from
<https://wiki.NetBSD.org/abi>.
Unfortunately, it is not entirely clear on signedness, also appears to
be more about the compiler builtins than the elf library symbols
judging by the ellipsis part... (I also have no idea what the
sentence about the ellipsis is supposed to mean!)
7.4.3 Atomic Compare-and-swap Operation
"int __sync_bool_compare_and_swap (type* ptr, type oldvalue, type
newvalue, ...)"
"type __sync_val_compare_and_swap (type* ptr, type oldvalue, type
newvalue, ...)"
Where type may be one of int, long, long long, unsigned int,
unsigned long, unsigned long long. The ellipsis (...) refers to
an optional list of variables protected by the memory barrier.
I just did a quick search for the string `__sync_val'; perhaps if you
actually read the rest of the document it will say how the symbols are
supposed to be defined.
But I bet it's just ambiguous and got randomly chosen differently on
different architectures for gcc's codegen.
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: Taylor R Campbell <riastradh@NetBSD.org>,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 13:34:24 +0300
On Thu, Sep 24, 2026 at 00:50:36 +0000, Taylor R Campbell wrote:
> Maybe we should just close this as a duplicate of PR 56839 (GCC emits
> wrong codes for compare_and_swap_1 bultins on armv5 (el & eb))?
That makes sense probably.
I'd still love to see this documented. I did have Itanium psABI
stashed, apparently, (from my Intel days), so I looked up what is
written there and the gcc info seems to be just a TLDR sumamry for
that.
Which leaves me with the question, what is the relation between
__sync_val_compare_and_swap and __sync_val_compare_and_swap_1 &c? Is
__sync_val_compare_and_swap supposed to be a kind of type-generic
macro? Or if the "type" in its signature is implementaion defined and
there's only one instance of it, with that specific type? Etc, etc...
We already have atomic*(3), but nothing for __sync*. I'll do the mdoc
part if given the prose for the manpage.
-uwe
From: "Taylor R Campbell" <riastradh@netbsd.org>
To: gnats-bugs@gnats.NetBSD.org
Cc:
Subject: PR/60773 CVS commit: src/tests/lib/libc/atomic
Date: Thu, 24 Sep 2026 12:07:53 +0000
Module Name: src
Committed By: riastradh
Date: Thu Sep 24 12:07:53 UTC 2026
Modified Files:
src/tests/lib/libc/atomic: t___sync_compare_and_swap.c
Log Message:
t___sync_compare_and_swap: Check both signednesses of everything.
PR 60773: sh3 __sync_val_compare_and_swap_1 test failures
To generate a diff of this commit:
cvs rdiff -u -r1.5 -r1.6 \
src/tests/lib/libc/atomic/t___sync_compare_and_swap.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: Taylor R Campbell <riastradh@NetBSD.org>,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 15:14:33 +0300
Tangential to the topic at hand, but since I had to read the code, I
might as well ask - why is atomic_init_testset.c statically
initializes the _fn variables to _up functions only immediately to set
them to _mp functions in __libc_atomic_init (and later set them back
to _up if we are on a UP system and rasctl(2) was successful)? Any
reason not to init the vars to _mp directly? _mp functions use the
locks array that is statically initialized, so it should be safe to
call them before __libc_atomic_init, shouldn't it?
If there's a reason why it's done in this way, it probably warrants a
comment.
TIA.
-uwe
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 12:37:51 +0000
> Date: Thu, 24 Sep 2026 15:14:33 +0300
> From: Valery Ushakov <uwe@stderr.spb.ru>
>=20
> Tangential to the topic at hand, but since I had to read the code, I
> might as well ask - why is atomic_init_testset.c statically
> initializes the _fn variables to _up functions only immediately to set
> them to _mp functions in __libc_atomic_init (and later set them back
> to _up if we are on a UP system and rasctl(2) was successful)? Any
> reason not to init the vars to _mp directly? _mp functions use the
> locks array that is statically initialized, so it should be safe to
> call them before __libc_atomic_init, shouldn't it?
The code was originally added with no initializer by ad@ in 2008,
under the assumption the constructor would always run before use, and
the constructor started with the always-safe _mp variants before
deciding on the basis of sysctl whether to use the cheaper _up
variants if safe:
https://mail-index.netbsd.org/source-changes/2008/02/09/msg002110.html
https://nxr.netbsd.org/xref/src/common/lib/libc/atomic/atomic_init_testset.=
c?r=3D1.1#63
skrll@ added the initializer because the constructor didn't always run
before use, but it's unclear why he chose _up:
https://mail-index.netbsd.org/source-changes/2009/01/30/msg216500.html
https://nxr.netbsd.org/xref/src/common/lib/libc/atomic/atomic_init_testset.=
c?r=3D1.6#63
This actually caused some trouble for us later on with the _up
variants getting inadvertently used in ld.elf_so until martin@ changed
it to call __libc_atomic_init in 2023:
PR lib/57628: multithread programs may deadlock in ld.elf_so (sparc on
sparc64)
https://gnats.NetBSD.org/57628
https://mail-index.netbsd.org/source-changes/2023/10/03/msg147830.html
https://nxr.netbsd.org/xref/src/libexec/ld.elf_so/rtld.c?r=3D1.216#72
> If there's a reason why it's done in this way, it probably warrants a
> comment.
I think the reason is `hysterical raisins' and this warrants a commit
to make the static initializers use _mp and then delete the
now-redundant initial assignments in __libc_atomic_init.
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: Taylor R Campbell <riastradh@NetBSD.org>,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Thu, 24 Sep 2026 20:17:00 +0300
On Thu, Sep 24, 2026 at 00:20:02 +0000, Valery Ushakov via gnats wrote:
> common/lib/libc/atomic/atomic_cas_8_cas.c defines
> sync_val_compare_and_swap_1 to return signed int8_t
>
> _atomic_cas_8 returns correct uin8_t value 0x88, then
> sync_val_compare_and_swap_1 sign extends it before returning.
For the record, just to make it explicit. Consider simplified:
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#define OLDVAL (0x1122334455667788UL)
#define NEWVAL (0x8090a0b0c0d0e0f0UL)
int
main()
{
volatile uint8_t val;
uint8_t oldval;
uint8_t newval;
uint8_t expval;
uint8_t expres;
uint8_t res;
val = (uint8_t)OLDVAL;
oldval = (uint8_t)OLDVAL;
newval = (uint8_t)NEWVAL;
expval = (uint8_t)NEWVAL;
expres = (uint8_t)OLDVAL;
res = __sync_val_compare_and_swap_1(&val, oldval, newval);
printf("expected %x, res = %x\n", expres, res);
return 0;
}
=> 0x7a095778 <_atomic_cas_8+16>: jsr @r0
(gdb) si
0x7a09563c in ?? () from /usr/lib/libc.so.12
1: x/i $pc
=> 0x7a09563c: extu.b r5,r5
We have entered _atomic_cas_8_up (static, so no symbol):
(gdb) finish
Run till exit from #0 0x7a09563c in ?? () from /usr/lib/libc.so.12
0x7a09577c in _atomic_cas_8 () from /usr/lib/libc.so.12
1: x/i $pc
=> 0x7a09577c <_atomic_cas_8+20>: lds.l @r15+,pr
(gdb) p/x $r0
$1 = 0x88
We've got correct old uint8_t value (unsigned)
(gdb) si
0x7a09577e in _atomic_cas_8 () from /usr/lib/libc.so.12
1: x/i $pc
=> 0x7a09577e <_atomic_cas_8+22>: rts
(gdb)
0x7a031536 in __sync_val_compare_and_swap_1 () from /usr/lib/libc.so.12
1: x/i $pc
=> 0x7a031536 <__sync_val_compare_and_swap_1+18>: exts.b r0,r0
(gdb) si
0x7a031538 in __sync_val_compare_and_swap_1 () from /usr/lib/libc.so.12
1: x/i $pc
=> 0x7a031538 <__sync_val_compare_and_swap_1+20>: lds.l @r15+,pr
(gdb) p/x $r0
$2 = 0xffffff88
Now we are back to __sync_val_compare_and_swap_1 and it needs to
return int8_t (signed) value so it converts the uint8_t result of
_atomic_cas_8_up by sign-extending (exts.b) the r0.
-uwe
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>
Cc: gnats-bugs@netbsd.org, Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 14:41:44 +0000
> Date: Thu, 24 Sep 2026 20:17:00 +0300
> From: Valery Ushakov <uwe@stderr.spb.ru>
>=20
> For the record, just to make it explicit. Consider simplified:
> [...]
There's probably some shenanigans going on because __sync_* are not
normal functions but magic builtins with implicit prototypes. Is
gcc's implicit prototype signed or unsigned?
uint8_t u8_sync_val_compare_and_swap_1(volatile uint8_t *, uint8_t, uint8_t=
);
int8_t s8_sync_val_compare_and_swap_1(volatile int8_t *, int8_t, int8_t);
Under these prototypes, I compared three functions:
- main__ calls the __sync_* builtin
- main_u8 calls u8_sync_*
- main_s8 calls s8_sync_*
All three actually use uint8_t for all local variables, just like
t___sync_compare_and_swap.c does.
(I don't natively speak sh3, so please correct me if I've guessed any
of the meaning wrong.)
Here are the relevant excerpts -- in each case, the first jsr calls
the sync function, and the second calls printf, so what's interesting
is what happens between the two:
00000000 <main__>:
...
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
...
30: R_SH_DIR32 __sync_val_compare_and_swap_1
34: R_SH_DIR32 printf
...
0000003c <main_u8>:
...
4c: d0 07 mov.l 6c <main_u8+0x30>,r0 ! 0 <main__>
4e: 96 0c mov.w 6a <main_u8+0x2e>,r6 ! f0
50: 40 0b jsr @r0
52: 74 03 add #3,r4
54: 66 03 mov r0,r6
56: d0 06 mov.l 70 <main_u8+0x34>,r0 ! 0 <main__>
58: d4 06 mov.l 74 <main_u8+0x38>,r4 ! 0 <main__>
5a: 40 0b jsr @r0
...
6c: R_SH_DIR32 u8_sync_val_compare_and_swap_1
70: R_SH_DIR32 printf
...
00000078 <main_s8>:
...
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
...
a4: R_SH_DIR32 s8_sync_val_compare_and_swap_1
a8: R_SH_DIR32 printf
...
Note that main_s8 has an additional extu.b instruction in it! I'm
guessing this means `extend unsigned byte', i.e., zero-extend result
in r0 to printf argument in r6. So it looks like gcc treats
__sync_val_compare_and_swap_1 as if it has the signed prototype:
main__ matches main_u8, not main_s8.
If I disassemble t___sync_compare_and_swap.o, I see there's also an
extu.b instruction:
000009b8 <atfu___sync_val_compare_and_swap_1_body>:
...
// Load expval 0xf0 into r11, which is callee-saves so
// __sync_val_compare_and_swap_1 should preserve it:
9d0: 9b 52 mov.w a78 <atfu___sync_val_compare_and_sw=
ap_1_body+0xc0>,r11 ! f0
...
// Load expres 0x88 into r10, which is also callee-saves so
// __sync_val_compare_and_swap_1 should preserve it:
9d4: 9a 51 mov.w a7a <atfu___sync_val_compare_and_sw=
ap_1_body+0xc2>,r10 ! 88
...
// Load __sync_val_compare_and_swap_1 function into r1 to
// call:
9d8: d1 2a mov.l a84 <atfu___sync_val_compare_and_sw=
ap_1_body+0xcc>,r1 ! 98
...
9e8: 01 03 bsrf r1
// res in r0. Move res to r9, reuse r0 for the stored val,
// move stored val to r6, and check whether the stored val is
// what we expect:
9ea: 64 d3 mov r13,r4
9ec: 69 03 mov r0,r9
9ee: 84 8f mov.b @(15,r8),r0
9f0: 66 0c extu.b r0,r6
// val =3D=3D expval?
9f2: 36 b0 cmp/eq r11,r6
9f4: 8d 0b bt.s a0e <atfu___sync_val_compare_and_sw=
ap_1_body+0x56>
// res =3D=3D expres?
9f6: 39 a0 cmp/eq r10,r9
...
a78: 00 f0 .word 0x00f0
a7a: 00 88 .word 0x0088
...
a84: 00 00 .word 0x0000
a84: R_SH_PLT32 __sync_val_compare_and_swap_1
Now I'm a little lost with the sh3 branch delay slot shenanigans (is
that two comparisons fed into a single conditional branch with a delay
slot??), but what I see here is that the _stored_ val is zero-extended
with extu.b, but the _returned_ res is not! That's in stark contrast
to the attached program I examined, where the returned result _is_
zero-extended with extu.b.
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>,
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 15:51:47 +0000
This is a multi-part message in MIME format.
--=_8xiQiGqtxVZ2LhiPkIgdNm2J38qsNMwx
Content-Transfer-Encoding: quoted-printable
Oops, attached now...
> Date: Fri, 25 Sep 2026 14:41:44 +0000
> From: Taylor R Campbell <riastradh@NetBSD.org>
>=20
> > Date: Thu, 24 Sep 2026 20:17:00 +0300
> > From: Valery Ushakov <uwe@stderr.spb.ru>
> >=20
> > For the record, just to make it explicit. Consider simplified:
> > [...]
>=20
> There's probably some shenanigans going on because __sync_* are not
> normal functions but magic builtins with implicit prototypes. Is
> gcc's implicit prototype signed or unsigned?
>=20
> uint8_t u8_sync_val_compare_and_swap_1(volatile uint8_t *, uint8_t, uint8=
_t);
> int8_t s8_sync_val_compare_and_swap_1(volatile int8_t *, int8_t, int8_t);
>=20
> Under these prototypes, I compared three functions:
>=20
> - main__ calls the __sync_* builtin
> - main_u8 calls u8_sync_*
> - main_s8 calls s8_sync_*
>=20
> All three actually use uint8_t for all local variables, just like
> t___sync_compare_and_swap.c does.
>=20
> (I don't natively speak sh3, so please correct me if I've guessed any
> of the meaning wrong.)
>=20
> Here are the relevant excerpts -- in each case, the first jsr calls
> the sync function, and the second calls printf, so what's interesting
> is what happens between the two:
>=20
> 00000000 <main__>:
> ...
> 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
> ...
> 30: R_SH_DIR32 __sync_val_compare_and_swap_1
> 34: R_SH_DIR32 printf
> ...
> 0000003c <main_u8>:
> ...
> 4c: d0 07 mov.l 6c <main_u8+0x30>,r0 ! 0 <main__>
> 4e: 96 0c mov.w 6a <main_u8+0x2e>,r6 ! f0
> 50: 40 0b jsr @r0
> 52: 74 03 add #3,r4
> 54: 66 03 mov r0,r6
> 56: d0 06 mov.l 70 <main_u8+0x34>,r0 ! 0 <main__>
> 58: d4 06 mov.l 74 <main_u8+0x38>,r4 ! 0 <main__>
> 5a: 40 0b jsr @r0
> ...
> 6c: R_SH_DIR32 u8_sync_val_compare_and_swap_1
> 70: R_SH_DIR32 printf
> ...
> 00000078 <main_s8>:
> ...
> 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
> ...
> a4: R_SH_DIR32 s8_sync_val_compare_and_swap_1
> a8: R_SH_DIR32 printf
> ...
>=20
> Note that main_s8 has an additional extu.b instruction in it! I'm
> guessing this means `extend unsigned byte', i.e., zero-extend result
> in r0 to printf argument in r6. So it looks like gcc treats
> __sync_val_compare_and_swap_1 as if it has the signed prototype:
> main__ matches main_u8, not main_s8.
>=20
> If I disassemble t___sync_compare_and_swap.o, I see there's also an
> extu.b instruction:
>=20
> 000009b8 <atfu___sync_val_compare_and_swap_1_body>:
> ...
>=20
> // Load expval 0xf0 into r11, which is callee-saves so
> // __sync_val_compare_and_swap_1 should preserve it:
> 9d0: 9b 52 mov.w a78 <atfu___sync_val_compare_and_=
swap_1_body+0xc0>,r11 ! f0
> ...
> // Load expres 0x88 into r10, which is also callee-saves so
> // __sync_val_compare_and_swap_1 should preserve it:
> 9d4: 9a 51 mov.w a7a <atfu___sync_val_compare_and_=
swap_1_body+0xc2>,r10 ! 88
> ...
> // Load __sync_val_compare_and_swap_1 function into r1 to
> // call:
> 9d8: d1 2a mov.l a84 <atfu___sync_val_compare_and_=
swap_1_body+0xcc>,r1 ! 98
> ...
> 9e8: 01 03 bsrf r1
> // res in r0. Move res to r9, reuse r0 for the stored val,
> // move stored val to r6, and check whether the stored val is
> // what we expect:
> 9ea: 64 d3 mov r13,r4
> 9ec: 69 03 mov r0,r9
> 9ee: 84 8f mov.b @(15,r8),r0
> 9f0: 66 0c extu.b r0,r6
> // val =3D=3D expval?
> 9f2: 36 b0 cmp/eq r11,r6
> 9f4: 8d 0b bt.s a0e <atfu___sync_val_compare_and_=
swap_1_body+0x56>
> // res =3D=3D expres?
> 9f6: 39 a0 cmp/eq r10,r9
> ...
> a78: 00 f0 .word 0x00f0
> a7a: 00 88 .word 0x0088
> ...
> a84: 00 00 .word 0x0000
> a84: R_SH_PLT32 __sync_val_compare_and_swap_1
>=20
> Now I'm a little lost with the sh3 branch delay slot shenanigans (is
> that two comparisons fed into a single conditional branch with a delay
> slot??), but what I see here is that the _stored_ val is zero-extended
> with extu.b, but the _returned_ res is not! That's in stark contrast
> to the attached program I examined, where the returned result _is_
> zero-extended with extu.b.
--=_8xiQiGqtxVZ2LhiPkIgdNm2J38qsNMwx
Content-Type: text/plain; charset="ISO-8859-1"; name="sh3atomic"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="sh3atomic.c"
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#define OLDVAL (0x1122334455667788UL)
#define NEWVAL (0x8090a0b0c0d0e0f0UL)
uint8_t u8_sync_val_compare_and_swap_1(volatile uint8_t *, uint8_t, uint8_t=
);
int8_t s8_sync_val_compare_and_swap_1(volatile int8_t *, int8_t, int8_t);
int
main__()
{
volatile uint8_t val;
uint8_t oldval;
uint8_t newval;
uint8_t expval;
uint8_t expres;
uint8_t res;
val =3D (uint8_t)OLDVAL;
oldval =3D (uint8_t)OLDVAL;
newval =3D (uint8_t)NEWVAL;
expval =3D (uint8_t)NEWVAL;
expres =3D (uint8_t)OLDVAL;
res =3D __sync_val_compare_and_swap_1(&val, oldval, newval);
printf("expected %x, res =3D %x\n", expres, res);
return 0;
}
int
main_u8()
{
volatile uint8_t val;
uint8_t oldval;
uint8_t newval;
uint8_t expval;
uint8_t expres;
uint8_t res;
val =3D (uint8_t)OLDVAL;
oldval =3D (uint8_t)OLDVAL;
newval =3D (uint8_t)NEWVAL;
expval =3D (uint8_t)NEWVAL;
expres =3D (uint8_t)OLDVAL;
res =3D u8_sync_val_compare_and_swap_1(&val, oldval, newval);
printf("expected %x, res =3D %x\n", expres, res);
return 0;
}
int
main_s8()
{
volatile uint8_t val;
uint8_t oldval;
uint8_t newval;
uint8_t expval;
uint8_t expres;
uint8_t res;
val =3D (uint8_t)OLDVAL;
oldval =3D (uint8_t)OLDVAL;
newval =3D (uint8_t)NEWVAL;
expval =3D (uint8_t)NEWVAL;
expres =3D (uint8_t)OLDVAL;
res =3D s8_sync_val_compare_and_swap_1(&val, oldval, newval);
printf("expected %x, res =3D %x\n", expres, res);
return 0;
}
--=_8xiQiGqtxVZ2LhiPkIgdNm2J38qsNMwx--
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: Taylor R Campbell <riastradh@NetBSD.org>,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 18:52:34 +0300
On Fri, Sep 25, 2026 at 14:41:44 +0000, Taylor R Campbell wrote:
> There's probably some shenanigans going on because __sync_* are not
> normal functions but magic builtins with implicit prototypes. Is
> gcc's implicit prototype signed or unsigned?
init_sync_libfuncs_1 in external/gpl3/gcc/dist/gcc/optabs-libfuncs.cc
seems to be the place where they are materialized. My gcc-fu is too
weak to understand what's going on there. It seems that it is
eventually used in expand_atomic_compare_and_swap in optabs.cc. I'm
lost after that.
-uwe
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 16:14:25 +0000
> Date: Fri, 25 Sep 2026 14:41:44 +0000
> From: Taylor R Campbell <riastradh@NetBSD.org>
>=20
> Note that main_s8 has an additional extu.b instruction in it! I'm
> guessing this means `extend unsigned byte', i.e., zero-extend result
> in r0 to printf argument in r6. So it looks like gcc treats
> __sync_val_compare_and_swap_1 as if it has the signed prototype:
> main__ matches main_u8, not main_s8.
> [...]
> Now I'm a little lost with the sh3 branch delay slot shenanigans (is
> that two comparisons fed into a single conditional branch with a delay
> slot??), but what I see here is that the _stored_ val is zero-extended
> with extu.b, but the _returned_ res is not! That's in stark contrast
> to the attached program I examined, where the returned result _is_
> zero-extended with extu.b.
Err, no, I just confused myself; it's consistent: in both cases gcc
treats __sync_val_compare_and_swap as if it returned uint8_t, not
int8_t, because so there's no extu.b instruction involved to convert
int8_t to uint8_t.
Here's how the builtin is declared:
209 DEF_SYNC_BUILTIN (BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_1,
210 "__sync_val_compare_and_swap_1",
211 BT_FN_I1_VPTR_I1_I1, ATTR_NOTHROWCALL_LEAF_LIST)
https://nxr.netbsd.org/xref/src/external/gpl3/gcc/dist/gcc/sync-builtins.de=
f?r=3D1.1.1.12#209
I'm a little fuzzy on what BT_FN_I1_VPTR_I1_I1 means. It appears to
be defined like this:
209 DEF_PRIMITIVE_TYPE (BT_I1, builtin_type_for_size (BITS_PER_UNIT*1, =
1))
...
787 DEF_FUNCTION_TYPE_3 (BT_FN_I1_VPTR_I1_I1, BT_I1, BT_VOLATILE_PTR, B=
T_I1, BT_I1)
https://nxr.netbsd.org/xref/src/external/gpl3/gcc/dist/gcc/builtin-types.de=
f?r=3D1.1.1.12#787
Note that BT_I1 is not clearly defined to be either int8_t _or_
uint8_t, for which there are BT_INT8 and BT_UINT8:
70 DEF_PRIMITIVE_TYPE (BT_INT8, signed_char_type_node)
...
72 DEF_PRIMITIVE_TYPE (BT_UINT8, unsigned_char_type_node)
But builtin_type_for_size is defined here and perhaps it illuminates:
7234 tree
7235 builtin_type_for_size (int size, bool unsignedp)
7236 {
7237 tree type =3D c_common_type_for_size (size, unsignedp);
7238 return type ? type : error_mark_node;
7239 }
https://nxr.netbsd.org/xref/src/external/gpl3/gcc/dist/gcc/c-family/c-commo=
n.cc?r=3D1.1.1.3#7228
2332 tree
2333 c_common_type_for_size (unsigned int bits, int unsignedp)
2334 {
...
2340 if (bits =3D=3D TYPE_PRECISION (signed_char_type_node))
2341 return unsignedp ? unsigned_char_type_node : signed_char_type_n=
ode;
https://nxr.netbsd.org/xref/src/external/gpl3/gcc/dist/gcc/c-family/c-commo=
n.cc?r=3D1.1.1.3#2328
That suggests to me that gcc will treat BT_I1 as unsigned char, and
thus always treat __sync_val_compare_and_swap_1 as if it had the
prototype
uint8_t __sync_val_compare_and_swap_1(volatile uint8_t *, uint8_t, uint8_t);
Which makes the arm bug of PR lib/56839 (GCC emits wrong codes for
compare_and_swap_1 bultins on armv5 (el & eb)) all the more puzzling!
However, I haven't connected the notation in sync-builtins.def to the
logic that gcc actually uses for code generation. So perhaps there is
more magic yet to be discovered down that path.
From: Valery Ushakov <uwe@stderr.spb.ru>
To: gnats-bugs@netbsd.org
Cc: Taylor R Campbell <riastradh@NetBSD.org>,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 19:32:04 +0300
On Fri, Sep 25, 2026 at 14:41:44 +0000, Taylor R Campbell wrote:
> 00000078 <main_s8>:
> ...
> 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
[...]
>
> Note that main_s8 has an additional extu.b instruction in it! I'm
> guessing this means `extend unsigned byte', i.e., zero-extend result
> in r0 to printf argument in r6. So it looks like gcc treats
> __sync_val_compare_and_swap_1 as if it has the signed prototype:
> main__ matches main_u8, not main_s8.
You are right. sh3 psABI specifies that unsigned char is returned in
the LSB of r0 and is zero-extended, while signed char is returned
sign-extended. So that extu.b does the coercion of res to its
declared uint8_t type and it can be elided for main_u8, b/c ABI
already requires r0 to be zero-extended.
> If I disassemble t___sync_compare_and_swap.o, I see there's also an
> extu.b instruction:
>
> 000009b8 <atfu___sync_val_compare_and_swap_1_body>:
> ...
>
> // Load expval 0xf0 into r11, which is callee-saves so
> // __sync_val_compare_and_swap_1 should preserve it:
> 9d0: 9b 52 mov.w a78 <atfu___sync_val_compare_and_swap_1_body+0xc0>,r11 ! f0
> ...
> // Load expres 0x88 into r10, which is also callee-saves so
> // __sync_val_compare_and_swap_1 should preserve it:
> 9d4: 9a 51 mov.w a7a <atfu___sync_val_compare_and_swap_1_body+0xc2>,r10 ! 88
> ...
Side note, there's no mov.b variant for pc-relative load. mov.w
sign-extends, but 0x0088 is positive, so we get the the right uint8_t
value in r10.
> // Load __sync_val_compare_and_swap_1 function into r1 to
> // call:
> 9d8: d1 2a mov.l a84 <atfu___sync_val_compare_and_swap_1_body+0xcc>,r1 ! 98
> ...
> 9e8: 01 03 bsrf r1
> 9ea: 64 d3 mov r13,r4
> // res in r0. Move res to r9, reuse r0 for the stored val,
> // move stored val to r6, and check whether the stored val is
> // what we expect:
> 9ec: 69 03 mov r0,r9
> 9ee: 84 8f mov.b @(15,r8),r0
> 9f0: 66 0c extu.b r0,r6
Side note - on sh3 mov.b from memory (and mov.w) can only load to R0,
hence the extra song and dance. Both implicitly sign-extend R0.
> // val == expval?
> 9f2: 36 b0 cmp/eq r11,r6
> 9f4: 8d 0b bt.s a0e <atfu___sync_val_compare_and_swap_1_body+0x56>
> // res == expres?
> 9f6: 39 a0 cmp/eq r10,r9
> ...
Right, and r10 is (uint8_t)0x88, and r9 (returned as uint8_t) is
expected to be zero-extended b/c the ABI.
As we know from the observation - it's not, b/c the actual function
was defined as returning int8_t and hence it retutned sign-extended
value, but the caller expects otherwise.
> Now I'm a little lost with the sh3 branch delay slot shenanigans (is
> that two comparisons fed into a single conditional branch with a
> delay slot??),
We do the first comparison and branch on its result. We do the second
comarison in the delay slot of that branch to not waste a perfectly
good clock cycle, the value of that comparison will be used later.
> but what I see here is that the _stored_ val is zero-extended
> with extu.b, but the _returned_ res is not! That's in stark contrast
> to the attached program I examined, where the returned result _is_
> zero-extended with extu.b.
As I mentioned above, the loaded value must be zero-extended b/c the
instruction sign-extends it on load.
-uwe
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>,
gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org,
Nick Hudson <skrll@NetBSD.org>
Cc:
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Fri, 25 Sep 2026 19:47:19 +0000
I did the same for Arm with -march=armv5t -mfloat-abi=soft, and here's
how it came out -- gcc treats __sync_* as signed, not unsigned (note
the `and r2, r2, #255' in main__ and main_u8 but not main_s8):
00000000 <main__>:
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
28: e59f000c ldr r0, [pc, #12] @ 3c <main__+0x3c>
2c: ebfffffe bl 0 <printf>
...
00000040 <main_u8>:
40: e3e03077 mvn r3, #119 @ 0x77
44: e52de004 push {lr} @ (str lr, [sp, #-4]!)
48: e24dd00c sub sp, sp, #12
4c: e3a020f0 mov r2, #240 @ 0xf0
50: e3a01088 mov r1, #136 @ 0x88
54: e28d0007 add r0, sp, #7
58: e5cd3007 strb r3, [sp, #7]
5c: ebfffffe bl 0 <u8_sync_val_compare_and_swap_1>
5c: R_ARM_CALL u8_sync_val_compare_and_swap_1
60: e3a01088 mov r1, #136 @ 0x88
64: e1a02000 mov r2, r0
68: e59f000c ldr r0, [pc, #12] @ 7c <main_u8+0x3c>
6c: ebfffffe bl 0 <printf>
...
00000080 <main_s8>:
80: e3e01077 mvn r1, #119 @ 0x77
84: e52de004 push {lr} @ (str lr, [sp, #-4]!)
88: e24dd00c sub sp, sp, #12
8c: e3e0200f mvn r2, #15
90: e28d0007 add r0, sp, #7
94: e5cd1007 strb r1, [sp, #7]
98: ebfffffe bl 0 <s8_sync_val_compare_and_swap_1>
98: R_ARM_CALL s8_sync_val_compare_and_swap_1
9c: e1a02000 mov r2, r0
a0: e3a01088 mov r1, #136 @ 0x88
a4: e20220ff and r2, r2, #255 @ 0xff
a8: e59f000c ldr r0, [pc, #12] @ bc <main_s8+0x3c>
ac: ebfffffe bl 0 <printf>
ac: R_ARM_CALL printf
...
On sparc, though, as on sh3, __sync_* is treated as unsigned (note the
`and %o0, 0xff, %o2' in main_s8 but not in main__ or main_u8):
00000000 <main__>:
main__():
0: 9d e3 bf 98 save %sp, -104, %sp
4: 82 10 3f 88 mov -120, %g1
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
30: 90 12 20 00 mov %o0, %o0 ! 0 <main__>
30: R_SPARC_LO10 .rodata.str1.8
34: 81 c7 e0 08 ret
38: 81 e8 00 00 restore
0000003c <main_u8>:
main_u8():
3c: 9d e3 bf 98 save %sp, -104, %sp
40: 82 10 3f 88 mov -120, %g1
44: 94 10 20 f0 mov 0xf0, %o2
48: c2 2f bf ff stb %g1, [ %fp + -1 ]
4c: 92 10 20 88 mov 0x88, %o1
50: 40 00 00 00 call 50 <main_u8+0x14>
50: R_SPARC_WDISP30 u8_sync_val_compare_and_swap_1
54: 90 07 bf ff add %fp, -1, %o0
58: 92 10 20 88 mov 0x88, %o1
5c: 94 10 00 08 mov %o0, %o2
60: b0 10 20 00 clr %i0
64: 11 00 00 00 sethi %hi(0), %o0
64: R_SPARC_HI22 .rodata.str1.8
68: 40 00 00 00 call 68 <main_u8+0x2c>
68: R_SPARC_WDISP30 printf
6c: 90 12 20 00 mov %o0, %o0 ! 0 <main__>
6c: R_SPARC_LO10 .rodata.str1.8
70: 81 c7 e0 08 ret
74: 81 e8 00 00 restore
00000078 <main_s8>:
main_s8():
78: 9d e3 bf 98 save %sp, -104, %sp
7c: 82 10 3f 88 mov -120, %g1
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
a8: 90 12 20 00 mov %o0, %o0 ! 0 <main__>
a8: R_SPARC_LO10 .rodata.str1.8
ac: 81 c7 e0 08 ret
b0: 81 e8 00 00 restore
So something screwy is going on with gcc's internal concept of what
signedness to use for __sync_*.
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Valery Ushakov <uwe@stderr.spb.ru>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org,
Nick Hudson <skrll@NetBSD.org>
Subject: Re: port-sh3/60773: sh3 __sync_val_compare_and_swap_1 test failures
Date: Sat, 26 Sep 2026 14:25:12 +0000
I filed a bug report upstream with GCC:
GCC bug 127626: arm: __sync_val_compare_and_swap calls generated with
wrong signedness
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127626
>Unformatted:
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.