Skip to contentSkip to conversation

document volatile atomic_read() behavior

PATCH In review Chris Snook 3 participants 8 messages Aug 9, 2007

Subsystems (5)
lore
Share & tools
lore
#0 lore raw

PATCH 24/24 document volatile atomic_read() behavior

From: Chris Snook <[email protected]>

Update atomic_ops.txt to reflect the newly consistent behavior of atomic_read(), and to note that volatile (in declarations) is now considered harmful.

Documentation/atomic_ops.txt +11 -3
@@ -12,7 +12,7 @@
 C integer type will fail.  Something like the following should
 suffice:
 
-	typedef struct { volatile int counter; } atomic_t;
+	typedef struct { int counter; } atomic_t;
 
 	The first operations to implement for atomic_t's are the
 initializers and plain reads.
@@ -38,9 +38,17 @@
 
 Next, we have:
 
-	#define atomic_read(v)	((v)->counter)
+	#define atomic_read(v)	(*(volatile int *)&(v)->counter)
 
-which simply reads the current value of the counter.
+which reads the counter as though it were volatile.  This prevents the
+compiler from optimizing away repeated atomic_read() invocations without
+requiring a more expensive barrier().  Historically this has been
+accomplished by declaring the counter itself to be volatile, but the
+ambiguity of the C standard on the semantics of volatile make this practice
+vulnerable to overly creative interpretation by compilers.  Explicit
+casting in atomic_read() ensures consistent behavior across architectures
+and compilers.  Even with this convenience in atomic_read(), busy-waiters
+should call cpu_relax().
 
 Now, we move onto the actual atomic operation interfaces.
 
#1 lore raw
Quoted 5 lines

Historically this has been

(quoted diff) +2 -0
+accomplished by declaring the counter itself to be volatile, but the
+ambiguity of the C standard on the semantics of volatile make this 

practice
+vulnerable to overly creative interpretation by compilers.

It's even worse when accessing through a volatile casted pointer; see for example the recent(*) GCC bugs in that area.

(*) Well, not _all_ that recent. No one should be using the 3.x series anymore, right?

Quoted 4 lines

Explicit
+casting in atomic_read() ensures consistent behavior across architectures
+and compilers.

Even modulo compiler bugs, what makes you believe that?

Segher

#2 lore raw
Quoted 12 lines · Segher Boessenkool
Quoted 5 lines

Historically this has been

(quoted diff) +2 -0
+accomplished by declaring the counter itself to be volatile, but the
+ambiguity of the C standard on the semantics of volatile make this 

practice
+vulnerable to overly creative interpretation by compilers.

It's even worse when accessing through a volatile casted pointer; see for example the recent(*) GCC bugs in that area.

(*) Well, not _all_ that recent. No one should be using the 3.x series anymore, right?

Quoted 6 lines
Quoted 4 lines

Explicit
+casting in atomic_read() ensures consistent behavior across architectures
+and compilers.

Even modulo compiler bugs, what makes you believe that?

When you declare a variable volatile, you don't actually tell the compiler where you want to override its default optimization behavior, giving it some freedom to guess your intentions incorrectly. When you put the cast on the data access itself, there is no question about precisely where in the code you want to override the compiler's default optimization behavior. If the compiler doesn't do what you want with a volatile declaration, it might have a plausible excuse in the ambiguity of the C standard. If the compiler doesn't do what you want in a cast specific to a single dereference, it's just plain broken. We try to be compatible with plausibly correct compilers, but if they're completely broken, we're screwed no matter what.

-- Chris

#3 lore raw
Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Explicit
+casting in atomic_read() ensures consistent behavior across architectures
+and compilers.

Even modulo compiler bugs, what makes you believe that?

When you declare a variable volatile, you don't actually tell the compiler where you want to override its default optimization behavior, giving it some freedom to guess your intentions incorrectly. When you put the cast on the data access itself, there is no question about precisely where in the code you want to override the compiler's default optimization behavior.

...except for the small point that this isn't how volatile works.

Rule of thumb: even people who know the semantics of volatile shouldn't use it.

Quoted 1 line

If the compiler doesn't do what you want with a volatile declaration, it might have a plausible excuse in the ambiguity of the C standard. If the compiler doesn't do what you want in a cast specific to a single dereference, it's just plain broken.

The other way around. "volatile" has pretty weak semantics, and certainly not the semantics you think it has, or that you wish it had; but *(volatile XX *) doesn't have *any* semantics. However much you cast that pointer it still doesn't point to a volatile object.

GCC will generally take the path of least surprise and perform a volatile access anyway, but this has only be decided recently (a year ago or so), and there very likely still are some bugs in that area.

Quoted 1 line

We try to be compatible with plausibly correct compilers, but if they're completely broken, we're screwed no matter what.

If you don't know what to expect, you're screwed for sure.

Anyway, what's the supposed advantage of *(volatile *) vs. using a real volatile object? That you can access that same object in a non-volatile way?

Segher

#4 lore raw
Quoted 26 lines · Segher Boessenkool
Quoted 7 lines
Quoted 5 lines
Quoted 4 lines

Explicit
+casting in atomic_read() ensures consistent behavior across architectures
+and compilers.

Even modulo compiler bugs, what makes you believe that?

When you declare a variable volatile, you don't actually tell the compiler where you want to override its default optimization behavior, giving it some freedom to guess your intentions incorrectly. When you put the cast on the data access itself, there is no question about precisely where in the code you want to override the compiler's default optimization behavior.

...except for the small point that this isn't how volatile works.

Rule of thumb: even people who know the semantics of volatile shouldn't use it.

Quoted 1 line

If the compiler doesn't do what you want with a volatile declaration, it might have a plausible excuse in the ambiguity of the C standard. If the compiler doesn't do what you want in a cast specific to a single dereference, it's just plain broken.

The other way around. "volatile" has pretty weak semantics, and certainly not the semantics you think it has, or that you wish it had; but *(volatile XX *) doesn't have *any* semantics. However much you cast that pointer it still doesn't point to a volatile object.

GCC will generally take the path of least surprise and perform a volatile access anyway, but this has only be decided recently (a year ago or so), and there very likely still are some bugs in that area.

Quoted 8 lines
Quoted 1 line

We try to be compatible with plausibly correct compilers, but if they're completely broken, we're screwed no matter what.

If you don't know what to expect, you're screwed for sure.

Anyway, what's the supposed advantage of *(volatile *) vs. using a real volatile object? That you can access that same object in a non-volatile way?

You'll have to take that up with Linus and the minds behind Volatile Considered Harmful, but the crux of it is that volatile objects are prone to compiler bugs too, and if we have to track down a compiler bug, it's a lot easier when we know exactly where the load is supposed to be because we deliberately put it there, rather than letting the compiler re-order everything that lacks a strict data dependency and trying to figure out where in a thousand lines of assembler the compiler should have put the load for the volatile object.

If we're going to assume that the compiler has bugs we'll never be able to find, we all need to find new careers. If we're going to assume that it has bugs we *can* find, then let's use code that makes it easier to do that.

I initially proposed a patch that made all the objects volatile, on the grounds that this was a special case where there wasn't much room to have a misunderstanding that resulted in anything worse than wasted loads. Linus objected, and now that I've seen all the responses to the new patchset, I understand exactly why. If our compilers really suck as much as everyone says they do, it'll be much easier to detect that with volatile casts than with volatile declarations.

-- Chris

#5 lore raw
Quoted 5 lines
Quoted 3 lines

Anyway, what's the supposed advantage of *(volatile *) vs. using a real volatile object? That you can access that same object in a non-volatile way?

You'll have to take that up with Linus and the minds behind Volatile Considered Harmful, but the crux of it is that volatile objects are prone to compiler bugs too, and if we have to track down a compiler bug, it's a lot easier when we know exactly where the load is supposed to be because we deliberately put it there, rather than letting the compiler re-order everything that lacks a strict data dependency and trying to figure out where in a thousand lines of assembler the compiler should have put the load for the volatile object.

So, why not do the access explicitly via an inline asm? It generates the same code, it's obviously correct, and it's even *actually* correct. Plus, you get good compiler support (and compiler people support).

Quoted 1 line

If we're going to assume that the compiler has bugs we'll never be able to find, we all need to find new careers.

If we cannot find the bug in finite time, we cannot observe the bug in finite time either, so either way that's fine :-)

Quoted 1 line

If we're going to assume that it has bugs we *can* find, then let's use code that makes it easier to do that.

And I'm saying this is a step in the wrong direction for that.

Quoted 1 line

I initially proposed a patch that made all the objects volatile, on the grounds that this was a special case where there wasn't much room to have a misunderstanding that resulted in anything worse than wasted loads. Linus objected, and now that I've seen all the responses to the new patchset, I understand exactly why. If our compilers really suck as much as everyone says they do, it'll be much easier to detect that with volatile casts than with volatile declarations.

Except that accesses via volatile pointer casts open up a whole new can of worms.

Segher

#6 lore raw
In reply to Segher Boessenkool
Quoted 3 lines · Segher Boessenkool

Anyway, what's the supposed advantage of *(volatile *) vs. using a real volatile object? That you can access that same object in a non-volatile way?

That's my understanding. That way accesses where you don't care about volatility may be optimised.

For instance, in cases where there are already other things controlling visibility (as are needed for atomic increment, for example) you don't need to make the access itself volatile.

Chris

#7 lore raw
Quoted 5 lines
Quoted 3 lines

Anyway, what's the supposed advantage of *(volatile *) vs. using a real volatile object? That you can access that same object in a non-volatile way?

That's my understanding. That way accesses where you don't care about volatility may be optimised.

But those accesses might be done non-atomically then (for example, if the compiler knows it only needs to write one byte, it might not bother writing the rest), so that's no good if you want to read the thing atomically too.

Quoted 1 line

For instance, in cases where there are already other things controlling visibility (as are needed for atomic increment, for example) you don't need to make the access itself volatile.

Hrm, you mean within a lock or similar? You'll get the same semantics as volatile anyway there.

Segher