Yet Another LLVM restrict
Support
Abstract
LLVM’s current support for the C restrict
qualifier is very limited and incomplete. In fact, LLVM only handles restrict
on function arguments (as a noalias
attribute). All other uses – such as restrict
on local or global variables, struct/union members, or in block scope – are completely ignored. This long-standing gap has been noted in the community (e.g. Jeroen Dobbelaere’s 2019 RFC) and discussed in LLVM dev meetings. For example, a 2017 LLVM developer talk observed that C99’s definition of “based on” for restrict pointers is “completely general”, making the compiler’s task “difficult in mostly non-useful ways”. In short, naïve mapping of restrict
to LLVM metadata has proven insufficient, and previous patch attempts (e.g. Hal Finkel’s “local restrict” patches) are stalled, partly because they rely on new intrinsics that inhibit optimizations. We therefore propose a new approach to fully support restrict
in LLVM without heavy intrinsics, by encoding lexical scope information into TBAA metadata.
This design addresses two main LLVM challenges: first, Clang IR doesn’t normally record where in the source each pointer was declared; second, the C standard’s “based on” definition is so complex that tracking it exactly in the compiler is very difficult. By encoding scope information directly into metadata, we sidestep expensive interprocedural “based on” tracking. (As a 2017 DevCon slide put it, pointer capturing across calls is “the root of all evil” in restrict analysis – it can make alias decisions undecidable.) Our scope-coding approach is tractable because it only needs local, lexical structure. In the rest of this proposal we explain the C rules for restrict
, illustrate the idea, and outline the implementation. All code examples from the original draft are preserved below for concreteness.
Some insights about restrict
behavior
The C standard’s restrict
rules can be summarized roughly as follows:
- Let
X
be an lvalue, and letP
be a restrict-qualified pointer initialized to point toX
. If a modification ofX
occurs within the blockD
is a block whereP
was declared, then any access toX
within this block must be performed exclusively through the pointerP
or through expressions based onP
.
In plain terms: If an object X
is modified in the same block where P
was declared, then all further accesses to X
in that block must be through P
(or something based on P
). If the object is never modified in the block, there is no aliasing restriction. We illustrate this with the following examples:
{
int x;
x = 42; // modification of x in the outer block
int y;
{ // inner block
int *restrict px = &x;
y = *px + x; // valid: x was modified in the outer block
}
}
Contrast with:
{
int x;
x = 42; // modification of x in this block
int y;
int *restrict px = &x;
y = *px + x; // invalid: access to x not exclusively through px
}
With these rules in mind, LLVM faces two obstacles:
- Lack of scope info in IR. Clang does not emit any IR metadata to tell LLVM which block a local or global pointer was declared in (unlike function-argument attrs). Without knowing the original scope, LLVM can’t enforce the “within the block” restriction directly.
- Tracking “based on” is hard. C’s definition of what counts as an expression “based on”
P
is notoriously tricky (as discussed in the 2017 slide).
To begin with, let’s consider the first point.
TBAA to the rescue
Our core idea is to leverage LLVM’s existing Type-Based Alias Analysis (TBAA) mechanism. We treat each restrict
pointer as having a unique type-domain ID, so that it does not alias with other types – but in a scope-sensitive way. Concretely, we will generate a TBAA tag (metadata) for each pointer (of type T* restrict
). Naïvely, one might think “just give each restrict
pointer a fresh TBAA ID (hence noalias with all others)”. That doesn’t work because of C’s nuances. Consider this code:
int *foo();
void bar() {
int *p = foo();
*p = 1; // First store
{ // new block
int *restrict rp = foo();
*rp = 2; // Second store
}
*p = 3; // Third store
}
This code is valid, but the stores clearly alias (foo()
presumably returns the same address). The compiler cannot reorder these stores arbitrarily. In particular, it must not treat *rp = 2
as independent of *p = 1
and *p = 3
, even though rp
is restrict
. The naive approach of tagging rp
with a new TBAA ID that aliases with nothing would forbid any interaction between *p
and *rp
, disallowing the valid memory dependency above.
Scope encoding
A C
-program is an N-ary scope tree. First of all, let’s notice that any tree can be represented as binary in LCRS (Left-child right-sibling) representation, so here and around we consider binary trees.
To encode “parent-child” relationships between scopes effectively we decide to use Huffman coding: Root is encoded with “1”. Right child add zero to right, left child – add one to the right.
If there is at least one restrict
pointer of type T
in the scope body, we must also encode other pointers in inner scopes.
This representation makes it straightforward to determine whether pointers originate from:
-
Parent-child scopes, or
-
Sibling scopes
The next step is mark all restrict
pointers as restrict with the unique id.
Provenance (pointer copies)
One subtle point is pointer assignment and “provenance.” Consider:
int *restrict x;
int *y = x; // Is y now 'based on' x?
According to the strictest reading of C99 examples, one might worry: does copying a restrict pointer into another imply that y
is “based on” x
and thus must itself be used restrictively? In practice, almost all guidance and compiler implementations say no: an unrestricted pointer y
that is merely assigned from a restrict pointer x
does not inherit the alias restriction. TI’s documentation explicitly states that “A pointer expression based on a copy of P is not based on P, since the copy is not affected by a change to P”, and that code like
void f(short *restrict p1) {
short *p2 = p1;
*p1 = ...; // and later *p2 = ...
}
is undefined
It seems to me they are right. Also I write a corresponded proposal to the C language standard and waiting for review for now.
Conclusion
I believe it’s possible to support restrict finally. We plan to prototype this patch soon and welcome feedback and design review from the LLVM community. If you see any potential issues, corner cases, or have alternative suggestions, please don’t hesitate to share your thoughts. Your insights will help us refine this design and move toward a complete and practical implementation of restrict
support in LLVM.
We look forward to your comments and suggestions.
References
- LLVM dev post: LLVM mostly ignores the ‘restrict’ qualifier
- Jeroen Dobbelaere’s 2019 RFC: Full restrict support in LLVM
- LLVM Dev Meeting 2017 – Restrict-Qualified Pointers in LLVM (PDF)
- C2y Working Draft (WG14 N3220), ISO/IEC JTC1 SC22 WG14
- TI whitepaper: Performance Tuning with the RESTRICT Keyword
- WG14 Defect Report N3659 – Considering expressions based on restrict pointers as pure rvalue expressions