|
|
Subscribe / Log in / New account

ACCESS_ONCE() and compiler bugs

By Jonathan Corbet
December 3, 2014
The ACCESS_ONCE() macro is used throughout the kernel to ensure that code generated by the compiler will access the indicated variable once (and only once); see this article for details on how it works and when its use is necessary. When that article was written (2012), there were 200 invocations of ACCESS_ONCE() in the kernel; now there are over 700 of them. Like many low-level techniques for concurrency management, ACCESS_ONCE() relies on trickery that is best hidden from view. And, like such techniques, it may break if the compiler changes behavior or, as has been seen recently, contains a bug.

Back in November, Christian Borntraeger posted a message regarding the interactions between ACCESS_ONCE() and an obscure GCC bug. To understand the problem, it is worth looking at the macro, which is defined simply in current kernels (in <linux/compiler.h>):

    #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

In short, ACCESS_ONCE() forces the variable to be treated as being a volatile type, even though it (like almost all variables in the kernel) is not declared that way. The problem reported by Christian is that GCC 4.6 and 4.7 will drop the volatile modifier if the variable passed into it is not of a scalar type. It works fine if x is an int, for example, but not if x has a more complicated type. For example, ACCESS_ONCE() is often used with page table entries, which are defined as having the pte_t type:

    typedef struct {
	unsigned long pte;
    } pte_t;

In this case, the volatile semantics will be lost in buggy compilers, leading to buggy kernels. Christian started by looking for ways to work around the problem, only to be informed that normal kernel practice is to avoid working around compiler bugs whenever possible; instead, the buggy versions should simply be blacklisted in the kernel build system. But 4.6 and 4.7 are installed on a lot of systems; blacklisting them would inconvenience many users. And, as Linus put it, there can be reasons for approaches other than blacklisting:

So I do agree with Heiko that we generally don't want to work around compiler bugs if we can avoid it. But sometimes the compiler bugs do end up saying "you're doing something very fragile". Maybe we should try to be less fragile here.

One way of being less fragile would be to change the affected ACCESS_ONCE() calls to point to the scalar parts of the relevant non-scalar types. So, if code does something like:

    pte_t p = ACCESS_ONCE(pte);

It could be changed to something like:

    unsigned long p = ACCESS_ONCE(pte->pte);

This type of change requires auditing all ACCESS_ONCE() calls, though, to find the ones using non-scalar types; that would be a lengthy and error-prone process that would not prevent the addition of new bugs in the future.

Another approach to the problem explored by Christian was to remove a number of problematic ACCESS_ONCE() calls and just put in a compiler barrier with barrier() instead. In many cases, a barrier is sufficient, but in others it is not. Once again, a detailed audit is required, and there is nothing preventing new code from adding buggy ACCESS_ONCE() calls.

So Christian headed down the path of changing ACCESS_ONCE() to simply disallow the use of non-scalar types altogether. In the most recent version of the patch set, ACCESS_ONCE() looks like this:

    #define __ACCESS_ONCE(x) ({ \
	       __maybe_unused typeof(x) __var = 0; \
	       (volatile typeof(x) *)&(x); })
    #define ACCESS_ONCE(x) (*__ACCESS_ONCE(x))

This version will cause compilation failures if a non-scalar type is passed into the macro. But what about the situations where a non-scalar type needs to be used? For these cases, Christian has introduced two new macros, READ_ONCE() and ASSIGN_ONCE(). The definition of the former looks like this:

    static __always_inline void __read_once_size(volatile void *p, void *res, int size)
    {
    	switch (size) {
    	case 1: *(u8 *)res = *(volatile u8 *)p; break;
    	case 2: *(u16 *)res = *(volatile u16 *)p; break;
    	case 4: *(u32 *)res = *(volatile u32 *)p; break;
    #ifdef CONFIG_64BIT
    	case 8: *(u64 *)res = *(volatile u64 *)p; break;
    #endif
        }
    }
    
    #define READ_ONCE(p) \
          ({ typeof(p) __val; __read_once_size(&p, &__val, sizeof(__val)); __val; })

Essentially, it works by forcing the use of scalar types, even if the variable passed in does not have such a type. Providing a single access macro that worked on both the left-hand and right-hand sides of an assignment turned out to not be trivial, so the separate ASSIGN_ONCE() was provided for the left-hand side case.

Christian's patch set replaces ACCESS_ONCE() calls with READ_ONCE() or ASSIGN_ONCE() in cases where the latter are needed. Comments in the code suggest that those macros should be preferred to ACCESS_ONCE() in the future, but most existing ACCESS_ONCE() calls have not been changed. Developers using ACCESS_ONCE() to access non-scalar types in the future will get an unpleasant surprise from the compiler, though.

This version of the patch has received few comments and seems likely to make it into the mainline in the near future; backports to the stable series are also probably on the agenda. There are times when it is best to simply avoid versions of the compiler with known bugs altogether. But, as can be seen here, compiler bugs can also be seen as a signal that things could be done better in the kernel, leading to more robust code overall.

Index entries for this article
KernelACCESS_ONCE()


to post comments

Strict aliasing violation?

Posted Dec 4, 2014 10:03 UTC (Thu) by cesarb (subscriber, #6266) [Link] (2 responses)

I'm not sure I really understand C's aliasing rules, but isn't this a strict aliasing violation?

> *(u32 *)res = *(volatile u32 *)p;

Unless "p" points to an int or unsigned int, or on 32-bit long or unsigned long, I believe the compiler is allowed to think "it's not the same variable", and reorder the accesses or even delete your code.

As a degenerate example, if you have

struct two_shorts { u16 s[2] };
struct two_shorts left, right;
int global;

void bozo(void)
{
global = 1;
*(u32 *)&left = *(volatile u32 *)&right;
}

If my understanding is correct, the compiler is free to assume that, since accessing an u16 as an u32 is not possible due to the strict aliasing rules, that line can never be reached (because it's undefined code), and so the function could be optimized to:

void bozo(void)
{
}

What's wrong with my analysis here?

(If my analysis is correct, my workaround would be to sprinkle a few OPTIMIZER_HIDE_VAR, because OPTIMIZER_HIDE_VAR is my hammer, and this problem looks like a nail.)

Strict aliasing violation?

Posted Dec 4, 2014 11:01 UTC (Thu) by cborni (subscriber, #12949) [Link]

The kernel is always build with strict-aliasing disabled (fno-strict-aliasing)

Strict aliasing violation?

Posted Dec 5, 2014 7:40 UTC (Fri) by kleptog (subscriber, #1183) [Link]

Strict aliasing refers to accessing the same area of memory via pointers of two different types. So if you assign you an int pointer and to a short pointer, the compiler is allowed to assume the pointers are distinct and hence reorder the assignments. char and void pointers are considered to alias with any other pointer. Example:

void foo(int *a, short *b) {
*a = 1;
*b = 2;
}

Strict aliasing means the compiler is allowed to reorder those two writes since they clearly can't be the same pointer. If one of the two were a char or void pointer then the compiler cannot assume this.

Where this usually trips people up is when people try to optimise structure copying by casting the pointer to an int* and looping. Since the compiler sees an int* and a struct* it assumes the pointers are distinct and chaos occasionally ensues.

So your program is fine, because type casts are legal. But if you're doing tricky pointer arithmetic with non-char/void pointers you need to be careful.

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 13:39 UTC (Thu) by epa (subscriber, #39769) [Link] (5 responses)

It's surprising that there isn't something equivalent to ACCESS_ONCE in the latest C standard. Have the standards committee considered it?

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 15:30 UTC (Thu) by sorokin (guest, #88478) [Link] (1 responses)

> Have the standards committee considered it?

Both C and C++ standards already has mechanism for that. You just need to mark your data as volatile or atomic, depending on your use case.

I don't understand why in Linux kernel there is a need in using ACCESS_ONCE instead of marking corresponding variable as volatile or atomic.

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 15:47 UTC (Thu) by corbet (editor, #1) [Link]

See The trouble with volatile from 2007.

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 21:51 UTC (Thu) by pbonzini (subscriber, #60935) [Link] (2 responses)

There is. It's atomic_load and atomic_store with relaxed memory ordering.

ACCESS_ONCE() and compiler bugs

Posted Dec 5, 2014 8:47 UTC (Fri) by epa (subscriber, #39769) [Link] (1 responses)

So... obvious follow-up question... what is the reason why the Linux code doesn't use these primitives instead of casting to and from volatile?

ACCESS_ONCE() and compiler bugs

Posted Dec 5, 2014 8:58 UTC (Fri) by pbonzini (subscriber, #60935) [Link]

Because the Linux kernel has needed these way before they were standardized. The primitives are also new and many details would have to be hashed out before Linux could use them. See this LWN article: http://lwn.net/Articles/586838/

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 21:56 UTC (Thu) by pbonzini (subscriber, #60935) [Link] (3 responses)

I wonder if ACCESS_ONCE could be defined as
#define ACCESS_ONCE(x)                                             \
   __builtin_choose_expr(sizeof(x) == 1, *(volatile u8 *)&x,       \
        __builtin_choose_expr(sizeof(x) == 2, *(volatile u16 *)&x, \
        __builtin_choose_expr(sizeof(x) == 4, *(volatile u32 *)&x, \
        __builtin_choose_expr(sizeof(x) == 8, *(volatile u64 *)&x, \
        (void)0))))

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 23:27 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (2 responses)

Why not put an error in the tail-end case?

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 23:30 UTC (Thu) by pbonzini (subscriber, #60935) [Link] (1 responses)

A void expression will give an error if used as either lvalue or rvalue.

ut I found the gotcha after posting: my definition is not type safe when used with non-scalar types, unlike the old ACCESS_ONCE.

ACCESS_ONCE() and compiler bugs

Posted Dec 4, 2014 23:44 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

Ah, right. For some reason I read it as "(void*)0"…which is also an error for non-pointer types.

ACCESS_ONCE() and compiler bugs

Posted Dec 6, 2014 19:31 UTC (Sat) by giraffedata (guest, #1954) [Link]

Maybe we should try to be less fragile here.
One way of being less fragile

I don't see these approaches as making the kernel less fragile. This is just a workaround for a particular compiler bug. A bug that matters because the kernel is fragile, perhaps.

If you have a glass bicycle, you make it less fragile by replacing the frame with a steel one, not by riding around a bump in the road.


Copyright © 2014, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds