Search code examples
c#multithreadingthread-safetyvolatilememory-barriers

Is it safe to assume volatile semantics when reassigning a struct field with a volatile field inside?


Take a look at the example below:

struct MyStruct
{
     private volatile int _field;
     public void Set(int v) => _field = v;
}

class MyClass
{
    private MyStruct _myStruct;
    // is it same as _myStruct.Set(0)?
    public void SomeMethod() => _myStruct = new MyStruct();
}

Basically the question is whether reassigning the whole struct with a volatile field inside uses the same volatile semantics as assigning the field directly.


Solution

  • The volatile prefix is implemented using a modreq compiler attribute, and is only enforced by the compiler if access is done directly through the field that is marked volatile.

    ECMA-335 specification does not actually have any volatile attribute for fields as such, and the CLR does not enforce volatile access to such a field, so it is up to the compiler to issue a volatile. prefix in the generated IL. It only does this if you access the field directly.

    You can see this easily by decompiling the following code

    public class C {
        static s s1;
        static s s2;
        public void M() {
            s1 = new s();
            s2 = s1;
            s2.i = s1.i;
        }
    }
    struct s{
         public volatile int i;   
    }
    
    .class public auto ansi beforefieldinit C
        extends [System.Private.CoreLib]System.Object
    {
        // Fields
        .field private static valuetype s s1
        .field private static valuetype s s2
    
        // Methods
        .method public hidebysig 
            instance void M () cil managed 
        {
            // Method begins at RVA 0x2050
            // Code size 47 (0x2f)
            .maxstack 8
    
            IL_0000: nop
            IL_0001: ldsflda valuetype s C::s1
            IL_0006: initobj s
            IL_000c: ldsfld valuetype s C::s1
            IL_0011: stsfld valuetype s C::s2
            IL_0016: ldsflda valuetype s C::s2
            IL_001b: ldsflda valuetype s C::s1
            IL_0020: volatile.
            IL_0022: ldfld int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) s::i
            IL_0027: volatile.
            IL_0029: stfld int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) s::i
            IL_002e: ret
        } // end of method C::M
    
        .method public hidebysig specialname rtspecialname 
            instance void .ctor () cil managed 
        {
            // Method begins at RVA 0x2080
            // Code size 8 (0x8)
            .maxstack 8
    
            IL_0000: ldarg.0
            IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
            IL_0006: nop
            IL_0007: ret
        } // end of method C::.ctor
    
    } // end of class C
    
    .class private sequential ansi sealed beforefieldinit s
        extends [System.Private.CoreLib]System.ValueType
    {
        // Fields
        .field public int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) i
    
    } // end of class s
    

    Sharplab link