Search code examples
cparameter-passingrestrictfunction-parameterpointer-aliasing

Is it enough to only restrict the "out"-(pointer-)parameters of a function?


Suppose I have a function which takes some pointer parameters - some non-const, through which it may write, and some const through which it only reads. Example:

void f(int * a, int const *b);

Suppose also that the function does not otherwise write to memory (i.e. doesn't use global variables, fixed addresses, recasting the const pointers as non-const and such tricks).

Now, is it sufficient (as per the C language standard), for achieving the benefits of restrict for all reads within f(), to only restrict the output parameters? i.e. in the example, restrict a but not b?

A simple test (GodBolt) suggests such restriction should be sufficient. This source:

int f(int * restrict a, int const * b) {
    a[0] += b[0];
    return a[0] + b[0];
}

int all_restricted(int * restrict a, int const * restrict b) {
    a[0] += b[0];
    return a[0] + b[0];
}

int unrestricted(int * a, int const * b) {
    a[0] += b[0];
    return a[0] + b[0];
}

Produces the same object code for x86_64:

f:
        mov     eax, DWORD PTR [rsi]
        mov     edx, DWORD PTR [rdi]
        add     edx, eax
        mov     DWORD PTR [rdi], edx
        add     eax, edx
        ret
all_restricted:
        mov     eax, DWORD PTR [rsi]
        mov     edx, DWORD PTR [rdi]
        add     edx, eax
        mov     DWORD PTR [rdi], edx
        add     eax, edx
        ret
unrestricted:
        mov     eax, DWORD PTR [rsi]
        add     eax, DWORD PTR [rdi]
        mov     DWORD PTR [rdi], eax
        add     eax, DWORD PTR [rsi]
        ret

but that's not a general guarantee.


Solution

  • No, it is not enough.

    Suppose also that the function does not otherwise write to memory (i.e. doesn't use global variables, fixed addresses, recasting the const pointers as non-const and such tricks).

    This supposition is insufficient. It would also be necessary that the compiler see the function does not otherwise write to memory that the pointer points to (including any memory that could be accessed via the pointer, such as b[18]). For example, if the calls bar(b);, and the compiler cannot see bar, then it cannot know whether memory that b points to is modified during execution of f, even if it is not.

    Give this additional premise, that the compiler can see there are no modifications to any memory pointed to via b, then it does not matter for optimization whether b is declared with const and/or restrict: The compiler knows everything about the memory, and telling it anything more does not add information.

    However, it is often not the case that code satisfies this premise. (And even when it does, it may be a nuisance for a programmer to be sure of it.) So let’s consider a case when we do not have the additional premise:

    void f(int * restrict a, int const *b)
    {
        printf("%d\n", *b);
        bar();
        printf("%d\n", *b);
    }
    

    When bar is called, the compiler does not know whether *b is modified. Even though this function does not pass b to bar, bar might access some external object that is *b or has a pointer to where *b is, and so bar can change the object that is *b. Therefore, the compiler must reload *b from memory for the second printf.

    If instead we declare the function void f(int * restrict a, int const * restrict b), then restrict asserts that, if *b is modified during execution of f (including indirectly, inside bar), then every access to it will be via b (directly, as in *b, or indirectly, as through a pointer visibly copied or calculated from b). Since the compiler can see bar does not receive b, it knows bar does not contain any accesses to *b that are based on b, and therefore it may assume bar does not change *b.

    Therefore, adding restrict to a parameter that is a pointer to a const-qualified type may enable some optimizations, even if all other parameters are also declared restrict.