Noalias and nested pointers

Hey, I am trying to understand how noalias works and need some clarification.
The current Zig documentation sadly isn’t very helpful:

The noalias keyword.
TODO add documentation for noalias

The LLVM docs state the following about noalias:

This indicates that memory locations accessed via pointer values based on the argument or return value are not also accessed, during the execution of the function, via pointer values not based on the argument or return value. This guarantee only holds for memory locations that are modified, by any means, during the execution of the function.

and about pointer aliasing:

A pointer value is associated with the addresses associated with any value it is based on.

A pointer value formed from a scalar getelementptr operation is based on the pointer-typed operand of the getelementptr.
The pointer in lane l of the result of a vector getelementptr operation is based on the pointer in lane l of the vector-of-pointers-typed operand of the getelementptr.

This is where I am a little confused. In contrast to C restrict, Zig noalias is per-parameter, not per-pointer.
For example what’s happening here?

export fn aliasing(noalias ptr: *const [3]*[16]u64) u64 {
    var res: u64 = 0;
    for (ptr) |p| {
        for (p) |i| {
            res += i;
        }
    }
    return res;
}

Does noalias only cover the actual pointer values pointed to by ptr or does it also cover the u64 these pointers point to recusively? Are the u64 arrays still based on ptr?

Looking at the LLVM IR:

define dso_local i64 @aliasing(ptr noalias nocapture nonnull readonly align 8 %0) local_unnamed_addr {
Entry:
  %1 = load ptr, ptr %0, align 8
  %2 = load <16 x i64>, ptr %1, align 8
  %3 = getelementptr inbounds nuw i8, ptr %0, i64 8
  %4 = load ptr, ptr %3, align 8
  %5 = getelementptr inbounds nuw i8, ptr %0, i64 16
  %6 = load ptr, ptr %5, align 8
  %7 = load <16 x i64>, ptr %4, align 8
  %8 = load <16 x i64>, ptr %6, align 8
  [...]

are the load <16 x i64> instructions still based on ptr?
In other words, is noalias applied recursively to nested pointers, as if they were all marked restrict, or only to the top level pointer?

I tried this on godbolt and it seems that noalias only applies to the top-level pointer, since it only optimizes away the top level pointer access in my example case.

1 Like

Yeah, seems like it. Thanks for the example! Interestingly marking every pointer level as restricted in equivalent C code doesn’t seem to change that (godbolt) (and Zig seems to assume -fno-strict-aliasing by default).

noalias is LLVM’s implementation of the restrict qualifier of C99. And that qualifier applies to any memory pointed by the pointer, regardless of the depth of the access.

From cppreference:

During each execution of a block in which a restricted pointer P is declared (typically each execution of a function body in which P is a function parameter), if some object that is accessible through P (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined:

I think what happened in @IntegratedQuantum 's snippet was just a missed optimization. It would still be undefined to modify memory that is overlapped by both parameters.

1 Like

Thanks for the clarification!