jeudi 27 juillet 2023

Mfence would be inserted by compiler to function using non atomic pointer storing value of atomic pointer

I am reading cppreference of carries_dependency attribute. It seems to me that the following codes snippet from above link is telling that if carries_dependency attribute is not added to print2 function, because of passing the non-atomic pointer int* local which is storing the value of atomic pointer std::atomic<int*> p to print2, compiler would insert a mfence.

I tried to verify above on https://godbolt.org/z/TTE4bM9d6 where has the exact same number of instructions as https://godbolt.org/z/K4Wd4sEG4 that means mfence was not inserted. I can understand because of the x86-64 gcc I used. I tried to verify the same with ARM gcc trunk on https://godbolt.org/z/K6KM4nssK comparing with https://godbolt.org/z/jGYsae7dz. However, I have found got the same conclusion.

So my question is, is my understanding correct that from following snippet cppreference page is telling us mfence should be inserted by compiler if carries_dependency attribute is not added to print2 function? If so, why I can't see get that from above tests on Compiler Explorer? If mfence should be inserted in this case, is it because local is a pointer which compiler takes it as a reference? However, the local pointer and p atomic pointer are pointing at address of x which is not an atomic variable. Why compiler would still insert mfence in this case?

PS I understand I am asking more than 1 question and I am expected to only ask 1 question per post, however, all questions above are closely related to each other. The background information would be redundant if I split each question per post.

#include <atomic>
#include <iostream>
 
void print(int* val)
{
    std::cout << *val << std::endl;
}
 
void print2(int* val [[carries_dependency]])
{
    std::cout << *val << std::endl;
}
 
int main()
{
    int x{42};
    std::atomic<int*> p = &x;
    int* local = p.load(std::memory_order_consume);
 
    if (local)
    {
        // The dependency is explicit, so the compiler knows that local is
        // dereferenced, and that it must ensure that the dependency chain
        // is preserved in order to avoid a fence (on some architectures).
        std::cout << *local << std::endl;
    }
 
    if (local)
    {
        // The definition of print is opaque (assuming it is not inlined),
        // so the compiler must issue a fence in order to ensure that
        // reading *p in print returns the correct value.
        print(local);
    }
 
    if (local)
    {
        // The compiler can assume that although print2 is also opaque then
        // the dependency from the parameter to the dereferenced value is
        // preserved in the instruction stream, and no fence is necessary (on
        // some architectures). Obviously, the definition of print2 must actually
        // preserve this dependency, so the attribute will also impact the
        // generated code for print2.
        print2(local);
    }
}

Aucun commentaire:

Enregistrer un commentaire