Forced Atomics

In D130621 and LLVM Atomic Instructions and Concurrency Guide it is stated that when enabling forced-atomics it is stated that “responsibility for providing the __sync libcalls lies with the user”.

I assume these should be defined in compiler-rt. However, I’m not sure where these functions should be defined for a particular target. Is there further information as to where to define them?
Thanks for any additional information on this.

I assume you’re targeting a device that doesn’t have the atomics extension? In that case, I’d expect the __sync_* libcall implementation risks being too specific to the system to be upstreamed into compiler-rt and you’d be best off providing them in your own library. There’s some relevant discussion about the forced-atomics feature in this Rust issue that might provide further background.

There’s some discussion here of the various ways you may want to disable interrupts in your in order to implement these primitives on a system without native atomics. I might have expected a full reference implementation would be floating around somewhere but I haven’t come across one so far. Perhaps @nikic knows of one?

1 Like

Thanks Alex, you are correct about the device being targeted. I am clear on how I wish to implement each __sync* function. Yes it seems better to implement these in a library and not compiler-rt. I’m just not clear on how to structure and place the library so that custom __sync* implementations are used when compiling.

Thanks for the links, I’ll study them and hopefully I can understand how to correctly implement this. I have looked for a reference implementation but I haven’t found anything so far.

Unless I’m misunderstanding your question, I think you just want to put them in a .o or .a and link with it, so the __sync_* symbols will be found in your lib.

1 Like

Initially I didn’t think it could be that simple, I thought __sync functions were special in some way and that would not work. I’m writing the library now. Thanks for your help, much appreciated. I was overthinking.

1 Like

Sadly, when I build the functions I get a ‘cannot redeclare builtin function’ errors from clang e.g.

In file included from ./atomics.cpp:5:
./atomics.h:3:9: error: cannot redeclare builtin function ‘__sync_fetch_and_add_8’
3 | int64_t __sync_fetch_and_add_8(int64_t *Ptr, int64_t Val);
| ^
./atomics.h:3:9: note: ‘__sync_fetch_and_add_8’ is a builtin with type ‘long long (volatile long long *, long long, …) noexcept’
./atomics.cpp:7:9: error: cannot redeclare builtin function ‘__sync_fetch_and_add_8’
7 | int64_t __sync_fetch_and_add_8(int64_t *Ptr, int64_t Val) {
| ^
./atomics.h:3:9: note: ‘__sync_fetch_and_add_8’ is a builtin with type ‘long long (volatile long long *, long long, …) noexcept’
3 | int64_t __sync_fetch_and_add_8(int64_t *Ptr, int64_t Val);
| ^
2 errors generated.

I’ve looked at clang’s implementation for detecting and rejecting certain builtins and I can think of a workaround, but it’s not particularly nice.

Looking into the compiler-rt source, ARM have overridden some of the atomic __sync functions, but I don’t quite understand how yet. The actual implementations have different names, so I suspect there might be an ifdef or similar somewhere that redirects to the functions in e.g.

llvm-project/compiler-rt/lib/builtins/arm/sync_fetch_and_add_8.S

To avoid that conflict you can specify eventual function name (symbol name) in assembler using __ asm __ keyword in function declaration.
So declare sth like:
int64_t sync_fetch_and_add_8(int64_t *Ptr, int64_t Val) __ asm __ (“__sync_fetch_and_add_8”);

1 Like

Thank you for the help. Unfortunately, this doesn’t quite work verbatim. I get:

./atomics.h:3:57: error: expected function body after function declarator
    3 | int64_t sync_fetch_and_add_8(int64_t *Ptr, int64_t Val) __ asm __ (“__sync_fetch_and_add_8”);
      |                                                         ^
./atomics.h:3:68: error: unexpected character <U+201C>
    3 | int64_t sync_fetch_and_add_8(int64_t *Ptr, int64_t Val) __ asm __ (“__sync_fetch_and_add_8”);
      |                                                                    ^
./atomics.h:3:93: error: character <U+201D> not allowed in an identifier
    3 | int64_t sync_fetch_and_add_8(int64_t *Ptr, int64_t Val) __ asm __ (“__sync_fetch_and_add_8”);

I’m sure it’s close to the correct syntax as I also found gcc/Asm-Labels. Although this also does not compile.

I’m working on solving this now.

Interestingly, the example here compiles just fine: llvm Attribute reference guide works, so I’m making an error somewhere.

I deleted the declaration and rewrote it from scratch:

int64_t sync_fetch_and_add_8(int64_t *Ptr, int64_t Val)  asm("__sync_fetch_and_add_8");

and all is well now. The build completes and llvm-objdump displays the desired name specified by asm(). I’m not sure what was wrong, a rogue character perhaps !!?

Thanks again.

Great to hear that!
I’ve forgotten to add that I couldn’t write underscore+underscore+“asm”+underscore+underscore
as it is displayed as bold asm without underscore marks around “asm”.
That is way I’ve added white spaces between underscores and “asm”.

1 Like

Markdown can be so frustrating.