Question:
There are patterns (such as the one here C#/CLR: MemoryBarrier and torn reads ) that can execute torn reads, but never use the resulting value if a torn read may have occurred.
Is this undefined behavior in C#?
Related: How could I determine for myself if this is undefined behavior or not?
Failed attempt to determine the answer:
My understanding (correct my if I'm wrong) is that in C++11 this would be undefined behavior because its not defined by the memory model which was added in C++11 (in older versions all multi-threading was implementation specific behavior). Theoretically I could determine this from the spec.
I tried to do this for C# and failed. I could not find the memory model in the spec. Below are some notes from my unsuccessful journey through the C# language specification (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf ) attempting to track down the semantics of assigning one struct to another in the case where there might be a data race (one reader copying, one writer writing to the data being copied). I failed to find even the semantics for assigning to or reading from any variable in any scenario. Either I missed something (almost certainly the case) or using variables is undefined behavior.
My notes while reading relevant parts of the C# Specification:
The C# language specification says what types are atomic but never what it meas to do atomic and non-atomic reads and writes:
12.5 Atomicity of variable references
Reads and writes of the following data types shall be atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list shall also be atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, need not be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.
It does not say if "atomic" (which it does not define) here implies any fences (so I assume not), but more relevantly for this question, it does not even define what read and write do (Which is non-trivial in multi-threaded programs).
Looking 14.14.1 Simple assignment
, this seems to be the extent of the specification of write (explaining "x = y"):
The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.
8.3 Variables and parameters
states:
A variable shall be assigned before its value can be obtained.
but does not define obtaining a value. No where do I see a spec for what reading produces (One would assume that last thing you wrote in the single threaded case, so I can't find that in the spec).
10.10 Execution order
seems to unnecessarily constrain (compared to normal acquire and release semantics used in the MSDN article linked below) volatile with respect to writes (no writes can move in either direction across a reference to a volatile) while under constraining reads (they can move in both direction across volatile operations. It also makes no mention of Thread.MemoryBarrier (Who's documentation seems prevent processor but not compiler reordering so its too weak). It also makes no reference to what loading from a variable/field means, so lots of nasty offtopic issues there, but no answers.
I've read all the parts of the spec that I could find that are relevant. Nowhere is reading from a field/memory/variable (looks like "variable" is the proper term here) defined.
Maybe somewhere in the language spec there is a spec for the behavior of read/load and write/store of variables (aka: the memory model), but if there is (I couldn't find it), it does not reference "atomic" (I searched it for atomic: section 12.5 is the only use). I don't see how any C# code can possibly be defined, so clearly I'm missing something: I don't think a valid C# implementation could just exit (due to it being undefined behavior!) on any read or write of a value.
If multi-threaded (and maybe even single threaded) C# is actually horribly under-specified with no memory-model, is there a go-to place to see specifications for particular implementations? Ex: If C# does not define the semantics of read and write, perhaps Microsoft's various C# compilers (There are at least 3 now) provide specifications, and Mono as well? Is this safe in any of the implementations and whats a good way to tell?
Maybe there are some informal (not in the spec) rules that are considered safe to go by (all major implementations comply)? That would be scary, but if that's all we get, I'll take it.
Some relevant but insufficient sources:
I found this article claiming to be about the C# memory model, but it is implementation specific (Refers to the CLR) and does not cover the case in question. It also provides a nice clean explanation of what I'd like the volatile semantics to be (C++11 style acquire an release), but is stronger in some ways and weaker in other than 10.10 Execution order
from the language spec, so I think its wrong: https://msdn.microsoft.com/magazine/jj863136
This article seems to have a lot of good information comparing C# memory model to C++11's, but also does not cover this specific issue as far as I can tell: http://blog.alexrp.com/2014/03/30/dot-net-atomics-and-memory-model-semantics/
A nice article about reordering issues. The update at the end mentions that "A volatile read can be moved backwards in time with respect to a volatile write" which disagrees with 10.10 Execution order
from the spec, but agrees with what most people see to claim the semantics should be (meaning it is in agreement with the MSDN article and the Volatile class, but not the keyword as far as I can tell): http://blog.coverity.com/2014/03/12/can-skip-lock-reading-integer/#.VoSyfhXhDGg
From a quick look this article mostly covers volatile and how it prevents redundant load elimination and why that's needed: https://blogs.msdn.microsoft.com/ericlippert/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/