Here's a proposal for adding a function attribute for replacing
the restrict restrict qualifier.  It's v0.3 of n3294 (now we have a
document number).

I was going to name it [[noalias()]], but I thought that it would be
possible to mark several pointers as possibly referencing the same
object, and then the name [[restrict()]] made more sense.

It's based on a proposal I sent to Martin recently in this discussion.

Do you have any feedback for this?

I've attached the man(7) source and the resulting PDF, and below goes
a plain text rendering (formatting is lost).


Have a lovely night!
Alex

---

N3294 (WG14)                Proposal for C2y               N3294 (WG14)

Name
     n3294  - The [[restrict()]] function attribute as a replacement of
     the restrict qualifier

Category
     Feature and deprecation.

Author
     Alejandro  Colomar  Andres;  maintainer  of  the  Linux  man-pages
     project.

   Cc
     GNU C library
     GNU Compiler Collection
     Linux man‐pages
     Paul Eggert
     Xi Ruoyao
     Jakub Jelinek
     Martin Uecker
     LIU Hao
     Jonathan Wakely
     Richard Earnshaw
     Sam James
     Emanuele Torre
     Ben Boeckel
     "Eissfeldt, Heiko"
     David Malcolm

Description
   restrict qualifier
     The  restrict  qualifier is not useful for diagnostics.  Being de‐
     fined in terms of accesses, the API is not enough for a caller  to
     know what the function will do with the objects it receives.

     That is, a caller cannot know if the following call is correct:

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

            f(a, &a);

     Having  no way to determine if a call will result in Undefined Be‐
     havior makes it a dangerous qualifier.

     The reader might notice that this prototype and call is very simi‐
     lar to the prototype of strtol(3), and the use reminds of a  rela‐
     tively common use of that function.

   Diagnostics
     A good replacement of the restrict qualifier should allow to spec‐
     ify  in  the  API of the following function that it doesn’t accept
     pointers that alias.

            void
            replace(const T *restrict new, T **restrict ls, size_t pos)
            {
                 memcpy(ls[pos], new, sizeof(T));
            }

     This proposal suggests the following:

            [[restrict(1)]] [[restrict(2)]]
            void
            replace(const T *restrict new, T **restrict ls, size_t pos);

            replace(arr[3], arr, 2);  // UB; can be diagnosed

   Qualifiers
     It is also unfortunate that restrict  is  a  qualifier,  since  it
     doesn’t  follow  the  rules  that  apply  to all other qualifiers.
     While it is discarded easily, its  semantics  make  it  as  if  it
     couldn’t be discarded.

   Function attribute
     The purpose of restrict is to

     •  Allow functions to optimize based on the knowledge that certain
        objects are not accessed by any other object in the same scope;
        usually a function boundary, which is the most opaque boundary,
        and where this information is not otherwise available.

     •  Diagnose  calls  that  would result in Undefined Behavior under
        this memory model.

     Qualifiers don’t seem to be good for  carrying  this  information,
     but  function attributes are precisely for adding information that
     cannot be expressed by just using the type system.

     An attribute would need to be more strict than the restrict quali‐
     fier to allow diagnosing non‐trivial cases, such as the call shown
     above.

     A caller only knows what the callee receives,  not  what  it  does
     with  it.  Thus, for diagnostics to work, the semantics of a func‐
     tion attribute should be specified in terms of what a function  is
     allowed to receive.

   [[restrict]]
     The  [[restrict]] function attribute specifies that the pointer to
     which it applies is the only reference  to  the  array  object  to
     which  it  points (except that a pointer to one past the last ele‐
     ment may overlap another object).

     If the number of elements is specified with array  notation  or  a
     compiler‐specific  attribute, the array object to be considered is
     a subobject of the original array object, which is limited by  the
     number of elementsspecified in the function prototype.

     For the following prototype:

            [[restrict(1)]] [[restrict(2)]]
            void add_inplace(size_t n, int a[n], const int b[n]);

     In  the following calls, the caller is able to determine with cer‐
     tainty if the behavior is defined or undefined:

            char  a[100] = ...;
            char  b[50] = ...;

            add_inplace(50, a, a + 50);  // Ok
            add_inplace(50, a, b);       // Ok
            add_inplace(50, a, a);       // UB

     In the first of the three calls, the parameters don’t alias inside
     the function, since the subobjects of 50 elements do  not  overlap
     each  other,  even  though they are one single array object to the
     outer function.

   Optimizations
     This function attribute allows similar  optimizations  than  those
     allowed by the restrict qualifier.

     strtol(3)
            In  some  cases, such as the strtol(3) function, the proto‐
            type will be different, since this  attribute  is  stricter
            than restrict, and can’t be applied to the same parameters.
            For example, the prototype for strtol(3) would be

                   [[restrict(2)]]
                   long
                   strtol(const char *str, char **endp, int base);

            This  could  affect optimizations, since now it’s not clear
            to the implementation that str is not modified by any other
            reference.   Compiler‐specific  attributes  can  help  with
            that.   For example, the [[gnu::access()]] attribute can be
            used in this function to give more information:

                   [[restrict(2)]]
                   [[gnu::access(read_only, 1)]]
                   [[gnu::access(write_only, 2)]]
                   long
                   strtol(const char *str, char **endp, int base);

            The fact that endp is write‐only  lets  the  callee  deduce
            that *endp cannot be used to write to the string (since the
            callee is not allowed to inspect *endp).

            Another  concern  is  that  a global variable such as errno
            might alias the string.  This is already a concern in  sev‐
            eral  ISO  C  calls, such as rename(2).  But in the case of
            strtol(3), it would be a regression.   There  are  ways  to
            overcome  that, such as designing helper functions in a way
            that the attribute can be applied to add extra information.

            It is important that diagnostics are easy to determine,  to
            avoid  false negatives and false positives, so that code is
            easily safe.  Optimizations, while important, need  not  be
            as  easy  to  apply  as  diagnostics.  If an implementation
            wants to be optimal, it will do the extra  work  for  being
            fast.

     Multiple aliasing pointers
            In  some cases, it might be useful to allow specifying that
            some pointers may alias each other, but not others.

     Strings
            Another way to determine that str cannot be aliased by  any
            other  object  such  as  errno would be to use an attribute
            that marks  str  as  a  string.   An  object  of  type  int
            shouldn’t  be  allowed to represent a string, so regardless
            of character types being allowed to alias any  other  type,
            an  attribute such as [[gnu::null_terminated_string_arg()]]
            might be used to determine that the global errno  does  not
            alias the string.

   Deprecation
     The restrict qualifier would be deprecated by this attribute, sim‐
     ilar  to how the noreturn function specifier was superseded by the
     [[noreturn]] function attribute.

   Backwards compatibility
     Removing the restrict qualifier from function prototypes does  not
     cause  problems  in  most functions.  Only functions with restrict
     applied to a pointee would  have  incompatible  definitions.   The
     only standard functions where this would happen are:

     tmpfile_s()
     fopen_s()
     freopen_s()

     Those  functions  are  not  widely  adopted,  so the problem would
     likely be minimal.

Proposal
   6.7.13.x The restrict function attribute
     Constraints
            The restrict attribute shall be applied to a function.

            A 1‐based index can be specified in an  attribute  argument
            clause,  to  associate the attribute with the corresponding
            parameter of the function, which must be of a pointer type.

            (Optional.)  Several indices can be specified, separated by
            commas.

            The attribute can be applied  several  times  to  the  same
            function, to mark several parameters with the attribute.

            (Optional.)   The argument attribute clause may be omitted,
            which is equivalent to specifying the  attribute  once  for
            each parameter that is a pointer.

     Semantics
            If  a  function is defined with the restrict attribute, the
            corresponding parameter shall be the only reference to  the
            array  object  that it points to.  If the function receives
            another reference to the same array object, the behavior is
            undefined.  If  the  function  accesses  the  array  object
            through  an  lvalue  that is not derived from that pointer,
            the behavior is undefined.

            (Optional.)  If more than one parameters are  specified  in
            the  same  attribute  argument  clause,  then  all of those
            pointers are allowed to point to the same array object.

            If the number of elements is specified with array  notation
            (or  a compiler‐specific attribute), the array object to be
            considered for aliasing is a sub‐object of the original ar‐
            ray object, limited by the number  of  elements  specifiedr
            [1].

     [1]  For the following prototype:

                 [[restrict(1)]] [[restrict(2)]]
                 void f(size_t n, int a[n], const int b[n]);

          In  the  the following calls, the caller is able to determine
          if the behavior is defined or undefined:

                 char a[100] = /*...*/;
                 char b[50] = /*...*/;

                 f(50, a, a + 50);  // Ok
                 f(50, a, b);       // UB; a diagnostic is recommended
                 f(50, a, a + 2);   // UB; a diagnostic is recommended

History
     Revisions of this paper:

     0.1    Original draft for removing restrict from the first parame‐
            ter of strtol(3).

     0.2    Incorporate feedback from glibc and gcc mailing lists.

     0.3    Re‐purpose,  to  deprecate  restrict  and   propose   [[re‐
            strict()]] instead.

See also
     The original discussion about restrict and strtol(3).

ISO/IEC 9899                   2024‐07‐09                  N3294 (WG14)


-- 
<https://www.alejandro-colomar.es/>

Attachment: restrict.man
Description: Unix manual page

Attachment: restrict.pdf
Description: Adobe PDF document

Attachment: signature.asc
Description: PGP signature

Reply via email to