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/>
restrict.man
Description: Unix manual page
restrict.pdf
Description: Adobe PDF document
signature.asc
Description: PGP signature
