This is an archive of the discontinued LLVM Phabricator instance.

[PATCH 01/27] [noalias] LangRef: noalias intrinsics and ptr_provenance documentation.
Needs ReviewPublic

Authored by jeroen.dobbelaere on Oct 4 2019, 1:59 PM.
Tokens
"Like" token, awarded by Dushistov.

Details

Reviewers
hfinkel
jdoerfert
Summary

This patch is the first of a series that introduces full restrict support
in LLVM and clang. The full support is based on the original local restrict
patches from Hal Finkel and is an implementation of the
'RFC: Full 'restrict' support in LLVM' [1].

In order to show the dependencies, in what follows, most of the time
a non-functional rebased patch from Hal Finkel is provided, followed
by a patch that enhances the full restrict support and makes everything
compile and run again.

[1] https://lists.llvm.org/pipermail/llvm-dev/2019-October/135672.html

Notes:

  • The mechanism with the ptr_provenance is such that passes that don't know about it will either work (and maybe miss certain optimizations) or crash. This is considered to be better than producing wrong code.
  • This set of patches is at the moment not complete. It is tested and works for the use cases of my company. But it is to be expected that some optimization passes will not interact well with it. In our experience, a number of optimization passes do have problems with the optional extra argument for load and store instructions, and they are normally easy to fix. It is possible that we did not yet catch all of those in passes that we don't use.
  • One item that is known to be missing, is LLVM IR bitcode support for the noalias_sidechannel of the load/store instruction (ascii LLVM IR is supported).
  • The new pass manager support has been fixed (D68507).
  • SLPVectorizer issues also have been fixed. (D68517)
  • The options enabling/disabling full restrict have been improved. (D68484)
  • A latent problem where invalid llvm-ir was produced during inlining has been fixed. (D68509)
  • A bug were the noalias depends-on relationship was lost has been fixed. (D68512 and D68521)

Added with the drop of 2020/06/12:

  • Renaming of 'side channel' to provenance, ptr_provenance
  • Incorporating Hal Finkel's changes. This should make it easier to review. It also reduces the number of patches to 26.
  • Handling of llvm.noalias.copy.guard during SROA has been improved.
  • Handling of Loop Unrolling has been improved.
  • Fixed a case in -fno-full-restrict where the new annotations were still produced.

Added with the drop of 2020/09/07

  • llvm-IR bitcode support
  • DeadArgumentElimination support

Added with the drop of 2021/05/18

  • coexists with llvm.experimental.noalias.scope.decl
  • some small improvements/fixes

Notes:

  • NoAliasInfo.rst describes the noalias intrinsics infrastructure.

This set of patches is based on f8dbd61074176bae92ec360a093ac7bc498c9321 (May 18, 2021)

A convenience patch is available at D69542.

Diff Detail

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
jeroen.dobbelaere edited the summary of this revision. (Show Details)Oct 5 2019, 2:34 AM

The patches in this patch series are not generated with full context (-U99999) (and not fully clang-formatted) :/

This mentions something it calls <channel> but as far as i can tell it is not actually explained *what* that is, what type it is.

This mentions something it calls <channel> but as far as i can tell it is not actually explained *what* that is, what type it is.

As the syntax description shows, it is the same as the type of the pointer operand <ty>*.
I should maybe have mentioned it explicitly.

Document the type of the noalias_sidechannel for load and store.

craig.topper added inline comments.
llvm/docs/LangRef.rst
18720

Extra space after "path"

18723

in -> into?

18740

"allows to track" reads funny. Maybe "allows tracking of"?

18746

intrinsics*

lzutao added a subscriber: lzutao.Oct 7 2019, 1:14 PM

Thank you very much for working on this and putting all this into motion!

I started to look at this patch in isolation but with the rough idea of the approach in mind.
I did add various comments, from small wording changes to proposals on how we conceptually describe things.
Given that many comments would be repeated for each intrinsic, I stopped after the first and want to see what people think.

llvm/docs/LangRef.rst
18683

Nit:

of load and store instructions.

The "of" sounds weird to me.


The documentation below explains how it works, with `restrict` in mind.

I personally dislike sentences like this and would just remove it.

The intrinsics can also be used to specify alias assumptions that are not restrict based.

Arguably that is always true. The section describes the semantics of the alias stuff and how that can be used to model restrict. It is implied that other things can be modeled as well.

18700

I think you mix the "templated" definition (XXX) with instantiations (i8**, %struct.FOO*, ...). I would prefer we pick either. Precedence says you replace XXX with the types of that instantiation.

18726

introduces alias assumptions

plural vs singular

in the normal computation path

this isn't a "known" term for me (see below)

of a pointer and it will be opaque for most optimizations

this is the hope but it is questionable if it is true and why it is here

I would replace the first sentence with:
"The `llvm.noalias` intrinsic attaches alias assumptions to its first argument."


The whole pass thing and splitting comes to early (IMHO). I don't know yet what these intrinsics mean but I learn that they are transformed. That said, llvm.side.noalias is not described here.

18732

Nit: remove "is used to", just "identifies"
Nit: remove "exact" (what does it mean given that we actually move stuff around under the normal "as if" rules)

It's not "done inside" and loops are only one example of this. What you want to say is more general:
"Whenever a llvm.noalias.decl intrinsic is duplicated through code transformations, care must be taken to duplicate and uniquify the scopes and intrinsics. These steps are described in the following."
To be honest, I'm not sure if it makes sense to say something like that here already.

18737

Not: stray "this" in 16284
The inlining sentence does not really clear up anything here, partially because we don't know what is happening.

18741

remove "a blob of"

18752

Either a real object, a constant where the value is relative to 0 or `null`.

There is a word missing and 0 is null.

18754

This seems odd, why introduce two things that do the same thing.

18757

"entries with a single element each."

It represents the variable declaration that contains one or more restrict pointers.

I do not understand this sentence.

18761

For both items above:
No need for "points to".

a restrict variable.

Maybe more specific: "the restrict pointer %p."

18765

Maybe:
"the address of an object with at least one restrict pointer constituent.

18768

I did not understand the above wording.

18785

Nit: "by the following"

18789

"an extra number" is not helpful
" related to " -> "describing"/"identifying"

18804

"related to" -> "referencing" or "scoped in"

18934

I mentioned that before, the lady of the lake says XXX is specialized here:
https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic

(do we add attributes here? if so, we need attributes here, e.g., nocapture).

18943

I dislike the loop body and alloca sentence because they do not convey information. To be honest, I don't know what the second sentence is saying. What I would prefer is to say something like:
"The intrinsic identifies the scope of the restrict restrict pointer through a virtual side-effect that ensures the control dependences are preserved. This virtual side-effect will also keep allocations alive and explicit."
(I guessed what you want to say wrt. alloca)

(allocas and mallocs should not be treated differently anyway, we have a heap-2-stack transformation now and for the purpose of this discussion there should not be a difference anyway)

18947

The above reads funny, maybe:
"The returned value is a handle to track dependences on the declaration. There is no explicit relationship to the value of the arguments."
Also, why do we want an i8* then? We have tokens and we have i32, I'd prefer either over an i8* which is more confusing in this context full of i8* that are actually pointers (IMHO).

18956
  1. I would love to remove this duplication, is that possible?
  2. Why do we need to talk about alloca and "optimized away"? Can't we say:

"The first argument %p.alloca points to an object in memory with one or more restrict pointers constituents or null."

18960

Is it a list with one element or a list with entries that have one element each? What I read earlier sounded different from what I read here.

18969

Copy and paste from somewhere above. I'd avoid duplication if possible in favor of references.

18975

The above sentence is broken somewhere (I think). Maybe make it two.

jeroen.dobbelaere marked 4 inline comments as done.Oct 7 2019, 3:38 PM

Thanks for all the feedback ! I added some explanations.

llvm/docs/LangRef.rst
18700

That's true, but imho the full intrinsic name become very long, cluttering the display., For clarity I replaced the type encodings with XXX. This makes it easier to focus on the intrinsics and the actual arguments. I agree that this is not perfect.

18754

The original idea was to treat '%p.addr' sometimes as a pointer to an object and sometimes as an offset. Later it needed to be separated: SROA first splits alloca's into multiple smaller alloca's. Each separate restrict pointer now points to its own alloca (%p.addr), and there is no place to put the offset. You can differentiate by splitting the p.scope, but that would imply duplicating scopes all over the place. The p.objId serves as a convenient and less costly solution to differentiate the pointers in this case.

18757

hmm. Not sure how to explain it further. What I want to say is (shown with an example:)

int *restrict A;  // one !p.scope, one restrict pointer
int *restrict B[10]; // another (single) !p.scope, ten restrict pointers
struct FOO { int* restrict mA; int * mB; int* restrict mC; } C; // yet another !p.scope, 2 restrict pointers
18947

I think a token has to many restrictions (no PHI, no select). i32 might do. I didn't think too much about it and just settled on i8*.

jdoerfert added inline comments.Oct 7 2019, 4:11 PM
llvm/docs/LangRef.rst
18754

So objId is an offset into p.addr? If so, let's document it that way.

How does this work if there are multiple restrict pointers in the object, e.g. struct { restrict *a; restrict *b }? Maybe it would help if you point me towards the place where I can see this intrinsic in action. At least then I might be able to provide better feedback on the wording.

18757

In that example, how doe the p.scopes look like? Or, asked differently, is the p.scope a consequence of the declaration, hence does it uniquely identifies a declaration?

18947

If the token is too restrictive I'd still prefer an i32 (or similar) to avoid confusion with all the i8 pointers that fly around. The wording will then make it clear that these are tokens.

jeroen.dobbelaere marked 3 inline comments as done.Oct 8 2019, 8:24 AM

Here is an example test.c:

struct FOO {
  int* restrict pA;
  int* pB;
  int* restrict pC;
};

void bar(int* a, int* b, int* c) {
  struct FOO tmp = { a, b, c };
  *tmp.pA=42;
  *tmp.pB=43;
  *tmp.pC=44;
}

Compiled as:

clang -mllvm --print-before-all -mllvm -debug -emit-llvm -O2 test.c -S -o -

Before SROA:

%tmp = alloca %struct.FOO, align 8
...
%1 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
...
%pA1 = getelementptr inbounds %struct.FOO, %struct.FOO* %tmp, i32 0, i32 0
%5 = load i32*, i32** %pA1, align 8, !tbaa !9, !noalias !6
%6 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %5, i8* %1, i32** %pA1, i64 0, metadata !6), !tbaa !9, !noalias !6
...
%pC3 = getelementptr inbounds %struct.FOO, %struct.FOO* %tmp, i32 0, i32 2
%8 = load i32*, i32** %pC3, align 8, !tbaa !12, !noalias !6
%9 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %8, i8* %1, i32** %pC3, i64 0, metadata !6), !tbaa !12, !noalias !6

During SROA: Notice how llvm.noalias.decl and llvm.noalias is split, using 0, 8 and 16 for the p.objId :

...
Rewriting alloca partition [0,8) to:   %tmp.sroa.0 = alloca i32*
Found llvm.noalias.decl:   %1 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
New   llvm.noalias.decl:   %1 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.0, i64 0, metadata !6)
 [...]
  rewriting [0,8) slice #2
    original:   %7 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.0.0., i8* %2, i32** %pA1, i64 0, metadata !6), !tbaa !9, !noalias !6
          to:   %7 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.0.0., i8* %1, i32** %tmp.sroa.0, i64 0, metadata !6), !tbaa !9, !noalias !6
 [...]
  rewriting split [0,24) slice #4 (splittable)
    original:   %2 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
          to:   %1 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.0, i64 0, metadata !6)
 [...]
Rewriting alloca partition [8,16) to:   %tmp.sroa.6 = alloca i32*
Found llvm.noalias.decl:   %2 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
New   llvm.noalias.decl:   %2 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.6, i64 8, metadata !6)
  [...]
  rewriting split [0,24) slice #4 (splittable)
    original:   %3 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
          to:   %2 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.6, i64 8, metadata !6)
  [...]
  rewriting [8,16) slice #7
    original:   %9 = load i32*, i32** %pB2, align 8, !tbaa !11, !noalias !6
          to:   %tmp.sroa.6.8. = load i32*, i32** %tmp.sroa.6, !tbaa !11, !noalias !6
Rewriting alloca partition [16,24) to:   %tmp.sroa.8 = alloca i32*
Found llvm.noalias.decl:   %3 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
New   llvm.noalias.decl:   %3 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.8, i64 16, metadata !6)
  [...]
  rewriting split [0,24) slice #4 (splittable)
    original:   %4 = call i8* @llvm.noalias.decl.p0i8.p0s_struct.FOOs.i64(%struct.FOO* %tmp, i64 0, metadata !6)
          to:   %3 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.8, i64 16, metadata !6)
  [...]
  rewriting [16,24) slice #10
    original:   %12 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.8.16., i8* %4, i32** %pC3, i64 0, metadata !6), !tbaa !12, !noalias !6
          to:   %12 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.8.16., i8* %3, i32** %tmp.sroa.8, i64 16, metadata !6), !tbaa !12, !noalias !6
  Speculating PHIs
  Speculating Selects

Then later on:

Promoting allocas with mem2reg...
Zeoring noalias.decl dep:   %0 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.0, i64 0, metadata !6)
Zeroing operand 2 of   %3 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.0.0., i8* %0, i32** %tmp.sroa.0, i64 0, metadata !6), !tbaa !9, !noalias !6
[...]
Zeoring noalias.decl dep:   %2 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.8, i64 16, metadata !2)
Zeroing operand 2 of   %4 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %tmp.sroa.8.16., i8* %2, i32** %tmp.sroa.8, i64 16, metadata !2), !tbaa !10, !noalias !2
[...]
Zeoring noalias.decl dep:   %1 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** %tmp.sroa.6, i64 8, metadata !2)
[...]

(aargh, 'Zeoring' should of course be 'Zeroing' ;) )

After this pass, we get:

define dso_local void @bar(i32* %a, i32* %b, i32* %c) #0 {
entry:
  %0 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** null, i64 0, metadata !2)
  %1 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** null, i64 8, metadata !2)  ; This one will be removed later on, as it is not used anywhere.
  %2 = call i8* @llvm.noalias.decl.p0i8.p0p0i32.i64(i32** null, i64 16, metadata !2)
  %3 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %a, i8* %0, i32** null, i64 0, metadata !2), !tbaa !5, !noalias !2
  store i32 42, i32* %3, align 4, !tbaa !10, !noalias !2
  store i32 43, i32* %b, align 4, !tbaa !10, !noalias !2
  %4 = call i32* @llvm.noalias.p0i32.p0i8.p0p0i32.i64(i32* %c, i8* %2, i32** null, i64 16, metadata !2), !tbaa !12, !noalias !2
  store i32 44, i32* %4, align 4, !tbaa !10, !noalias !2
  ret void
}
[...]
!2 = !{!3} ; p.scope: 'tmp' in function 'bar', also recycled by !noalias, as it is the only restrict declaration
!3 = distinct !{!3, !4, !"bar: tmp"}
!4 = distinct !{!4, !"bar"}
llvm/docs/LangRef.rst
18754

This is the confusing part for me for the LangRef vs the usage: should the LangRef describe only the high level effect, or can it also describe how llvm treats/optimizes stuff internally ? I have somehow the feeling the we might want to have a separate restrict handling document, describing how the intrincs and metadata work together. Or do you think such a thing also belongs to the LangRef ?

18757

Yes, the p.scope is a result of the declaration and uniquely identifies one.

18947

ok. We can consider that.

a.elovikov added inline comments.
llvm/docs/LangRef.rst
18692

I find it strange to see %side.p on both left and right sides. Is it a typo or does it have some special meaning?

After reading till the intrinsics' description I believe it should be just "%p" on the right side.

19026

the `noalias_sidechannel` path

Not sure about terminology, but are @llvm.noalias.arg.guard/@llvm.noalias.copy.guard considered as noalias_sidechannel? I'd suggest not to use the spelling from the load/store instructions and have a more general moved onto the "side" path (if my understanding is correct here).

19066

No explicit "or null" here. Is that intentional?

jeroen.dobbelaere marked 3 inline comments as done.Oct 10 2019, 1:34 PM
jeroen.dobbelaere added inline comments.
llvm/docs/LangRef.rst
18692

yes, that's a typo. the second %side.p should be %p:

%side.p = i8* @llvm.side.noalias.XXX(i8* %p, ...)
19026

The @llvm.noalias.arg.guard combines the normal path with the noalias_sidechannel path. The @llvm.noalias.copy.guard resides on the normal path and adds extra information to a copy operation (memcpy, load/store).
I tried to be consistent in terminology when referring to the 'noalias_sidechannel' path. (but I could also use the 'noalias side channel' or something similar).

19066

It can be 'null'

a.elovikov added inline comments.Oct 10 2019, 1:46 PM
llvm/docs/LangRef.rst
19026

How about this:

It will be transformed into a `llvm.side.noalias` intrinsic and moved onto
the `noalias_sidechannel` path for loads/stores and fed into the @llvm.noalias.arg.guard/@llvm.noalias.copy.guard intrinsics for function boundaries/copies respectively.

jeroen.dobbelaere marked an inline comment as done.Oct 10 2019, 1:53 PM
jeroen.dobbelaere added inline comments.
llvm/docs/LangRef.rst
19026

... and fed into the @llvm.noalias.arg.guard intrinsics for function boundaries.

(The @llvm.noalias.copy.guard is generated by the clang frontend)

izik1 added a subscriber: izik1.Oct 18 2019, 11:39 AM
jeroen.dobbelaere edited the summary of this revision. (Show Details)
aqjune added a subscriber: aqjune.Oct 30 2019, 7:52 PM
uenoku added a subscriber: uenoku.Nov 2 2019, 6:48 AM
simoll added a subscriber: simoll.Nov 5 2019, 6:29 AM
CryZe added a subscriber: CryZe.Feb 28 2020, 7:46 AM
jeroen.dobbelaere edited the summary of this revision. (Show Details)Mar 6 2020, 2:52 AM
alex added a subscriber: alex.May 27 2020, 9:49 PM

Note: I am working on an updated version of the patches, rebased to a more recent version of the tree; including some bug fixes and taking into account the rename of noalias_sidechannel to ptr_provenance etc.

jeroen.dobbelaere retitled this revision from [PATCH 01/38] [noalias] LangRef: noalias intrinsics and noalias_sidechannel documentation. to [PATCH 01/26] [noalias] LangRef: noalias intrinsics and noalias_sidechannel documentation..
jeroen.dobbelaere edited the summary of this revision. (Show Details)

If I'm understanding correctly, llvm.noalias.arg.guard(p, q) is equivalent to getelementptr q, p-q? And load i8, i8* %p, i8* %p_prov is equivalent to load(llvm.noalias.arg.guard(p, p_prov))? And llvm.provenance.noalias([...], %p.addr, <type>** %prov.p.addr, [...] is equivalent to llvm.noalias([...], llvm.noalias.arg.guard(p, p_prov), [...])? And llvm.noalias.copy.guard is equivalent to loading a pointer, applying llvm.noalias to it, and storing it back to the same address?

So really, these are the new concepts in the IR:

  1. llvm.noalias.decl: this introduces a new "scope" for aliasing.
  2. llvm.noalias: this associates a pointer with the llvm.noalias.decl.

And the rest can be expressed in terms of those intrinsics and basic IR instructions.

It took me a long time to parse this out; I think the description here needs to be reorganized. It really needs to separate out the semantic core from the detailed dive into the various intrinsics. Maybe into five sections: how noalias scopes work, how separating provenance from pointer values works, a high-level description of the intrinsics, the suggested lowering of the C "restrict", and the detailed description of the individual intrinsics.


Before optimizations, there is the declaration of the restrict pointer and `llvm.noalias` is used whenever the value of the restrict pointer is read.

Maybe explain why you're suggesting this, as opposed to using llvm.noalias when the value is written. (I guess it has something to do with the C standard's definition of "based on"?)

jeroen.dobbelaere edited the summary of this revision. (Show Details)Jun 12 2020, 11:54 AM

If I'm understanding correctly, llvm.noalias.arg.guard(p, q) is equivalent to getelementptr q, p-q?

This is not correct: the 'llvm.noalias.arg.guard(p,ptr_provenance)' combines the 'value of the pointer' (p) with the 'provenance of the pointer' (ptr_provenance).
The ptr_provenance does not has a real 'value'. It is more like a dependency.

When you follow both, they should come together at some point, like at the input argument of a function :

  • the ptr_provenance purpose is to track the llvm.provenance.noalias information (and its dependencies). Normally there are no computations on this path.
  • the normal 'p' path, should in the best case only contain computations.

Due to inlining, it is possible that somewhere in the flow, the normal 'p' path also contains noalias information. The propagation pass should flatten that out.

And load i8, i8* %p, i8* %p_prov is equivalent to load(llvm.noalias.arg.guard(p, p_prov))?

Yes, this is correct. The ptr_provenance path was added explicitly to the load/store instructions, in order to get the llvm.noalias.arg.guard out of the way of most optimizations.
This makes is it much easier to keep the code correct in the presence of optimizations.

And llvm.provenance.noalias([...], %p.addr, <type>** %prov.p.addr, [...] is equivalent to llvm.noalias([...], llvm.noalias.arg.guard(p, p_prov), [...])?

Yes. llvm.provenance.noalias and llvm.noalias are equivalent. The former does track more information, as it is itself also treated like a 'memory instruction', so that we llvm.noalias.arg.guard is not needed.

And llvm.noalias.copy.guard is equivalent to loading a pointer, applying llvm.noalias to it, and storing it back to the same address?

No.

llvm.noalias.copy.guard tells that the pointer it returns has restrict pointers as specified by the struct indices (encoded in the metadata value).

So really, these are the new concepts in the IR:

  1. llvm.noalias.decl: this introduces a new "scope" for aliasing.
  2. llvm.noalias: this associates a pointer with the llvm.noalias.decl.
  1. llvm.noalias.arg.guard: combines a pointer (computation) path with a ptr_provenance path
  2. llvm.noalias.copy.guard: indicates on what indices in memory a restrict pointer is located

And the rest can be expressed in terms of those intrinsics and basic IR instructions.

Yes.
llvm.provenance.noalias was introduced as a 'safeguard', to make it clear that it always must be on the 'ptr_provenance' operand side.
The ptr_provenance operand was introduced to keep the information out of the way of most optimization passes.

It took me a long time to parse this out; I think the description here needs to be reorganized. It really needs to separate out the semantic core from the detailed dive into the various intrinsics. Maybe into five sections: how noalias scopes work, how separating provenance from pointer values works, a high-level description of the intrinsics, the suggested lowering of the C "restrict", and the detailed description of the individual intrinsics.

Yes, that makes sense. I am in the process of putting all of this in a separate document, but I didn't want to wait to get the updated patches out ;)
I hope to update this 01/26 patch early next week with the next iteration of the documentation. This is already very useful input for it !


Before optimizations, there is the declaration of the restrict pointer and `llvm.noalias` is used whenever the value of the restrict pointer is read.

Maybe explain why you're suggesting this, as opposed to using llvm.noalias when the value is written. (I guess it has something to do with the C standard's definition of "based on"?)

Yes. I hope that the updated documentation will make this easier to understand.

Thanks !

Jeroen Dobbelaere

jeroen.dobbelaere edited the summary of this revision. (Show Details)Jun 12 2020, 12:23 PM

If I'm understanding correctly, llvm.noalias.arg.guard(p, q) is equivalent to getelementptr q, p-q?

This is not correct: the 'llvm.noalias.arg.guard(p,ptr_provenance)' combines the 'value of the pointer' (p) with the 'provenance of the pointer' (ptr_provenance).
The ptr_provenance does not has a real 'value'. It is more like a dependency.

When you follow both, they should come together at some point, like at the input argument of a function :

  • the ptr_provenance purpose is to track the llvm.provenance.noalias information (and its dependencies). Normally there are no computations on this path.
  • the normal 'p' path, should in the best case only contain computations.

Due to inlining, it is possible that somewhere in the flow, the normal 'p' path also contains noalias information. The propagation pass should flatten that out.

getelementptr q, (ptrtoint(p)-ptrtoint(q)) should return a pointer with provenance of q, and the value of p. (http://llvm.org/docs/LangRef.html#pointer-aliasing-rules). I can't see how it isn't equivalent... unless noalias provenance is somehow different from the usual aliasing rules.

Does the presence of provenance markings fix https://bugs.llvm.org/show_bug.cgi?id=35229 ?

And llvm.noalias.copy.guard is equivalent to loading a pointer, applying llvm.noalias to it, and storing it back to the same address?

No.

llvm.noalias.copy.guard tells that the pointer it returns has restrict pointers as specified by the struct indices (encoded in the metadata value).

Oh, I see, it only applies the provenance to loads derived from that pointer, not all loads from the memory.

getelementptr q, (ptrtoint(p)-ptrtoint(q)) should return a pointer with provenance of q, and the value of p. (http://llvm.org/docs/LangRef.html#pointer-aliasing-rules). I can't see how it isn't equivalent... unless noalias provenance is somehow different from the usual aliasing rules.

I see now. It is indeed somewhat equivalent. The separate intrinsic makes it easier to convey the specific purpose of the construct and to control the kind of optimizations that we want to allow.
A generalized version of the 'llvm.noalias.arg.guard', maybe something like 'llvm.ptr.provenance %pValue, %pProv1 [, %pProv_i]*', could convey the same information, and could be a help for fixing the bug you mentions.
But, this is not the goal of the full restrict patches, and I would rather start with the current focused set of intrinsics, before trying to expand on it.

Does the presence of provenance markings fix https://bugs.llvm.org/show_bug.cgi?id=35229 ?

No, that problem is not fixed with the full restrict patches.

And llvm.noalias.copy.guard is equivalent to loading a pointer, applying llvm.noalias to it, and storing it back to the same address?

No.

llvm.noalias.copy.guard tells that the pointer it returns has restrict pointers as specified by the struct indices (encoded in the metadata value).

Oh, I see, it only applies the provenance to loads derived from that pointer, not all loads from the memory.

yes.

MSxDOS added a subscriber: MSxDOS.Jun 15 2020, 10:49 PM

I see now. It is indeed somewhat equivalent. The separate intrinsic makes it easier to convey the specific purpose of the construct and to control the kind of optimizations that we want to allow.

Sure, I wasn't suggesting that you'd want to actually use the getelementptr version, just trying to understand the intended meaning.

A generalized version of the 'llvm.noalias.arg.guard', maybe something like 'llvm.ptr.provenance %pValue, %pProv1 [, %pProv_i]*', could convey the same information, and could be a help for fixing the bug you mentions.

Is there some semantic difference between llvm.noalias.arg.guard and something like llvm.ptr.provenance? Or is it just a difference in the intended use?

A generalized version of the 'llvm.noalias.arg.guard', maybe something like 'llvm.ptr.provenance %pValue, %pProv1 [, %pProv_i]*', could convey the same information, and could be a help for fixing the bug you mentions.

Is there some semantic difference between llvm.noalias.arg.guard and something like llvm.ptr.provenance? Or is it just a difference in the intended use?

The llvm.noalias.arg.guard is intended to only track noalias dependencies. The llvm.ptr.provenance could be used to track provenance in a more general way (Like pointing to the original alloca).

jeroen.dobbelaere edited the summary of this revision. (Show Details)

Initial version of 'NoAliasInfo.rst', describing the noalias intrinsics infrastructure.

Notes:

  • in a future version 'llvm.noalias' and 'llvm.provenance.noalias' will be merged into a single intrinsic.
  • any feedback is welcome !
Matt added a subscriber: Matt.Jun 29 2020, 12:41 PM
jeroen.dobbelaere retitled this revision from [PATCH 01/26] [noalias] LangRef: noalias intrinsics and noalias_sidechannel documentation. to [PATCH 01/26] [noalias] LangRef: noalias intrinsics and ptr_provenance documentation..Jul 7 2020, 3:14 AM
jeroen.dobbelaere edited the summary of this revision. (Show Details)
jeroen.dobbelaere edited the summary of this revision. (Show Details)
jeroen.dobbelaere edited the summary of this revision. (Show Details)Jul 10 2020, 9:13 AM

Notes:

  • in a future version 'llvm.noalias' and 'llvm.provenance.noalias' will be merged into a single intrinsic.

I was thinking of merging llvm.noalias and llvm.provenance.noalias but now decided to not do it:

  • llvm.noalias is a convenience shortcut to llvm.provenance.noalias + llvm.noalias.arg.guard
  • keeping the convenience intrinsic reduces the amount of generated code and makes tracking tbaa on the intrinsics easier.
jeroen.dobbelaere edited the summary of this revision. (Show Details)

Updated NoAliasInfo.rst to explain the relationship between @llvm.noalias and @llvm.provenance.noalias, @llvm.noalias.arg.guard

Rebased to c06b7e2ab5167ad031745a706204abed1aefd823 (July 14, 2020)

jeroen.dobbelaere edited the summary of this revision. (Show Details)

Rebased to 9fb46a452d4e5666828c95610ceac8dcd9e4ce16 (September 7, 2020)

Hmm. If anybody knows how to hide the inline comments from an older revision..

jeroen.dobbelaere retitled this revision from [PATCH 01/26] [noalias] LangRef: noalias intrinsics and ptr_provenance documentation. to [PATCH 01/27] [noalias] LangRef: noalias intrinsics and ptr_provenance documentation..Sep 7 2020, 2:37 PM

The effect of the patches on the compile time can be found here: https://llvm-compile-time-tracker.com/index.php?branch=dobbelaj-snps/perf/full_restrict-20200907
For some the regressions, I already have some ideas on how reduce the impact. I propose to have the discussion at the respective patches.

yaxunl added a subscriber: yaxunl.Sep 15 2020, 12:48 PM

ping

Any feedback on this patch ?

Note: On some architectures, you might want to use -mllvm -enable-aa-sched-mi to make use of alias information when scheduling the machine instructions.

nikic added a subscriber: nikic.Nov 3 2020, 2:25 PM

As promised, I've started testing this patch set in rust. Unfortunately I quickly ran into an assertion failure on the following reduced test case:

%0 = type { i32 }
%1 = type { i32 }

define internal void @foo0(%0* noalias %ptr) {
    store %0 zeroinitializer, %0* %ptr
    ret void
}

define internal void @foo1(%1* noalias %ptr) {
    store %1 zeroinitializer, %1* %ptr
    ret void
}

define void @bar(%0* %ptr0, %1* %ptr1) {
    call void @foo0(%0* noalias %ptr0)
    call void @foo1(%1* noalias %ptr1)
    ret void
}

Run opt -inline:

opt: /home/nikic/rust/src/llvm-project/llvm/include/llvm/Support/Casting.h:269: typename llvm::cast_retty<X, Y*>::ret_type llvm::cast(Y*) [with X = llvm::Function; Y = llvm::Value; typename llvm::cast_retty<X, Y*>::ret_type = llvm::Function*]: Assertion `isa<X>(Val) && "cast<Ty>() argument of incompatible type!"' failed.
PLEASE submit a bug report to https://bugs.llvm.org/ and include the crash backtrace.
Stack dump:
0.	Program arguments: build/x86_64-unknown-linux-gnu/llvm/bin/opt -S -inline 
1.	Running pass 'CallGraph Pass Manager' on module '<stdin>'.
 #0 0x0000557429562c40 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x28a8c40)
 #1 0x00005574295608e4 llvm::sys::RunSignalHandlers() (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x28a68e4)
 #2 0x0000557429560a28 SignalHandler(int) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x28a6a28)
 #3 0x00007fcfc4eb43c0 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x153c0)
 #4 0x00007fcfc498418b raise /build/glibc-ZN95T4/glibc-2.31/signal/../sysdeps/unix/sysv/linux/raise.c:51:1
 #5 0x00007fcfc4963859 abort /build/glibc-ZN95T4/glibc-2.31/stdlib/abort.c:81:7
 #6 0x00007fcfc4963729 get_sysdep_segment_value /build/glibc-ZN95T4/glibc-2.31/intl/loadmsgcat.c:509:8
 #7 0x00007fcfc4963729 _nl_load_domain /build/glibc-ZN95T4/glibc-2.31/intl/loadmsgcat.c:970:34
 #8 0x00007fcfc4974f36 (/lib/x86_64-linux-gnu/libc.so.6+0x36f36)
 #9 0x0000557428c60829 (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x1fa6829)
#10 0x0000557428c6b554 llvm::IRBuilderBase::CreateNoAliasDeclaration(llvm::Value*, llvm::Value*, llvm::Value*) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x1fb1554)
#11 0x00005574295f3783 AddNoAliasIntrinsics(llvm::CallBase&, llvm::ValueMap<llvm::Value const*, llvm::WeakTrackingVH, llvm::ValueMapConfig<llvm::Value const*, llvm::sys::SmartMutex<false> > >&, llvm::MDNode*&) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x2939783)
#12 0x00005574295f4699 llvm::InlineFunction(llvm::CallBase&, llvm::InlineFunctionInfo&, llvm::AAResults*, bool, llvm::Function*) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x293a699)
#13 0x0000557428e49c48 llvm::LegacyInlinerBase::inlineCalls(llvm::CallGraphSCC&) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x218fc48)
#14 0x00005574283ac72e (anonymous namespace)::CGPassManager::runOnModule(llvm::Module&) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x16f272e)
#15 0x0000557428cc1503 llvm::legacy::PassManagerImpl::run(llvm::Module&) (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x2007503)
#16 0x000055742734a7e2 main (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x6907e2)
#17 0x00007fcfc49650b3 __libc_start_main /build/glibc-ZN95T4/glibc-2.31/csu/../csu/libc-start.c:342:3
#18 0x00005574273e4b5e _start (build/x86_64-unknown-linux-gnu/llvm/bin/opt+0x72ab5e)

I believe this is a known problem with the name mangling for pointers to anonymous types. I think @fhahn may know more about this, IIRC this came up as a problem with PredicateInfo as well.

As promised, I've started testing this patch set in rust. Unfortunately I quickly ran into an assertion failure on the following reduced test case:

Thank you for trying this out ! D91250 should resolve that problem. Can you try again with that patch applied ?

Thanks !

Jeroen Dobbelaere

A few comments inline.

llvm/docs/LangRef.rst
18689

Should this be:
%prov.p = i8* @llvm.provenance.noalias.XXX(i8* %p, i8* %p.decl, ?
Or could you clarify the %prov.p on both sides?

llvm/docs/NoAliasInfo.rst
266 ↗(On Diff #290342)

This seems like it may be useful to see a fragment/sample of the metadata declarations here?
Similar for the OutOfLoop snippet.

362 ↗(On Diff #290342)

IIUC, this example looks like a good place to clarify the distinction between p.alloca and p.addr in the LangRef description, along the lines of "in this example the address is the alloca, but in general they may refer to different locations; the alloca could be a struct and the address could point to a member of the struct".

373 ↗(On Diff #290342)

Expand with explanation on what does not alias in the example.

jryans added a subscriber: jryans.Dec 12 2020, 8:15 AM
penzn added a subscriber: penzn.Jan 5 2021, 10:25 AM
troyj added a subscriber: troyj.Jan 22 2021, 8:16 AM

Note for those that have not been following the LLVM AA Technical Calls: we have introduced part of the infrastructure needed for full restrict by focusing on fixing https://bugs.llvm.org/show_bug.cgi?id=39282 (See D93039, D93040, D92887, D94306).

Next steps involve looking at providing the ptr_provenance infrastructure.

I also try to provide an update of at least the single patch (D69542) in the coming weeks.

jeroen.dobbelaere edited the summary of this revision. (Show Details)
ychen added a subscriber: ychen.Jun 13 2021, 2:41 PM

I updated and rebased the convenience patch to 8924ba3bf8c6b0e8d14dff455e4e449a426a2700 (November 17, 2021) (See D69542)

anemet added a subscriber: anemet.Jan 21 2022, 9:39 PM
nlopes added a subscriber: nlopes.Jan 30 2022, 4:47 AM

Let me make a proposal for a simpler IR:

declare i8* @llvm.noalias(i8*) noalias argmemonly
declare void @llvm.noalias.end(i8*, i8*) argmemonly

define @foo(i8* %ptr) {

%p2 = call i8* @llvm.noalias(i8* %ptr)

use %p2..

call @llvm.noalias.end(i8* %ptr, i8* %p2)

use %ptr..
}

Access to restrict pointer is bounded by the noalias & noalias.end calls.
Advantages:

  • Much simpler than the current proposal.
  • Perf improvements from day one: by tagging the intrinsics properly, LLVM's AA algorithms can already decide that %p2 doesn't alias anything else.
  • Safe from day one: no memory operations can be moved across the barriers. This is enforced by LLVM IR semantics already, no changes needed!

Further perf improvements can be made, like hoisting llvm.alias intrinsics, teach LLVM that these intrinsics don't actually write to memory, etc.

Essentially, I don't see a need to track provenance explicitly with metadata. It's already easily accessible. Explicit tracking adds overhead, so it has to be very well justified. Right now I don't understand the motivation.

Please let me know what you think, especially what use case wouldn't work with the proposal above. Thanks!

Access to restrict pointer is bounded by the noalias & noalias.end calls.
Advantages:

  • Much simpler than the current proposal.
  • Perf improvements from day one: by tagging the intrinsics properly, LLVM's AA algorithms can already decide that %p2 doesn't alias anything else.
  • Safe from day one: no memory operations can be moved across the barriers. This is enforced by LLVM IR semantics already, no changes needed!

Further perf improvements can be made, like hoisting llvm.alias intrinsics, teach LLVM that these intrinsics don't actually write to memory, etc.

Essentially, I don't see a need to track provenance explicitly with metadata. It's already easily accessible. Explicit tracking adds overhead, so it has to be very well justified. Right now I don't understand the motivation.

Please let me know what you think, especially what use case wouldn't work with the proposal above. Thanks!

Hi @nlopes, thanks for looking into this.

I am not sure what you expect the semantics of @llvm.noalias and @llvm.noalias.end to be. Having examples on how this is supposed to work and and to allow us to implement C99 restrict would be useful.

The current implementation is what it is because of:

  • for a C99 restrict implementation, simpler is not necessarily 'correct' :(
  • the need to implement the 'based on' relationship, also in a a way that clang is producing code, where this dependency is not easily seen. The same restrict pointer usage can appear in different blocks at different places. They will not be using the same '@llvm.nolias' intrinsic.
  • One of the aims is also to allow memory operations to be moved across (certain) barriers as much as possible. The used intrinsics should (in the end) get completely out of the way of optimizations. The original @llvm.noalias is opaque, but gets converted into a @llvm.provenance.noalias later on which is put on the ptr_provenance path for that specific reason.

I am not sure what you expect the semantics of @llvm.noalias and @llvm.noalias.end to be. Having examples on how this is supposed to work and and to allow us to implement C99 restrict would be useful.

I believe these two are sufficient:

declare i8* @llvm.noalias(i8*) noalias argmemonly
declare void @llvm.noalias.end(i8*, i8*) argmemonly

noalias creates a new object with data aliasing that of the input and same size. noalias.end() doesn't do anything. It's there to prevent memory operations to cross the boundary.
These intrinsics delimit the lexical scope of the restrict variables. They are also introduced when inlining a function with noalias paramater attributes.

So:

{
  restrict *q = p;
  use(q);
}
use(p);

is represented as:

%q = call @llvm.noalias(%p)
use(%q)
call @llvm.noalias.end(%p, %q)

use(%p)

The two uses cannot cross the barriers. This is enforced by LLVM out-of-the-box. Of course one can implement transformations to widen or eliminate the barriers.

  • the need to implement the 'based on' relationship, also in a a way that clang is producing code, where this dependency is not easily seen. The same restrict pointer usage can appear in different blocks at different places.

You need to elaborate this a bit more, otherwise I don't understand what you mean.

  • One of the aims is also to allow memory operations to be moved across (certain) barriers as much as possible. The used intrinsics should (in the end) get completely out of the way of optimizations. The original @llvm.noalias is opaque, but gets converted into a @llvm.provenance.noalias later on which is put on the ptr_provenance path for that specific reason.

Sure, but barriers exist and cannot be removed. restrict creates a new memory block in a well-defined region. Barriers can be widened and even removed (by giving up on the restrict information). But whether you use intrinsics as barriers or some metadata doesn't matter.
Intrinsics as the ones proposed above have the advantage of being correct from day one and without missing places that need to be updated to learn about the new metadata. This is a huge plus.

  • the need to implement the 'based on' relationship, also in a a way that clang is producing code, where this dependency is not easily seen. The same restrict pointer usage can appear in different blocks at different places.

You need to elaborate this a bit more, otherwise I don't understand what you mean.

How would you map (before _any_ optimizations; aka, what kind of code should clang produce):

// int *p, *q,  *s,  *t;
{
   int *restrict rp = p;
   int *restrict rq = q;
   int *restrict rs = s;

   int * based_on_rp1 = rp + index1;

   use(rp); 
   use(based_on_rp1); // aliases with rp
   use(rq); // only aliases with rq
   use(rs);
   int * based_on_something;
   if (some_input) {
     based_on_something = based_on_rp1;
   } else {
     based_on_something = rs;
   }
   use(based_on_something); // might alias with rs, or rp
   int * based_on_rp2 = rp + index2;
   use(based_on_rp2);  // aliases with rp
   use(rp);
   use(t); // will not alias with anything above
 }
 use(t+index3); // might alias with everything above
  • One of the aims is also to allow memory operations to be moved across (certain) barriers as much as possible. The used intrinsics should (in the end) get completely out of the way of optimizations. The original @llvm.noalias is opaque, but gets converted into a @llvm.provenance.noalias later on which is put on the ptr_provenance path for that specific reason.

Sure, but barriers exist and cannot be removed. restrict creates a new memory block in a well-defined region. Barriers can be widened and even removed (by giving up on the restrict information).
But whether you use intrinsics as barriers or some metadata doesn't matter.

In some way, it does matter. Intrinsics result in real barriers that can only be left out if done explicitly. (aka, dropping them might result in wrong code).
Metadata can be dropped at will, it should not influence correctness. That is the reason why the full restrict implementation uses both: intrinsics for adding the based-on relationship; metadata for the scope.
The goal of the patches is not just to provide restrict support; The goals is to provide restrict support _and_ good optimizations making use of this knowledge.

Intrinsics as the ones proposed above have the advantage of being correct from day one and without missing places that need to be updated to learn about the new metadata. This is a huge plus.

Were you able to check the initial description ? (https://lists.llvm.org/pipermail/llvm-dev/2019-October/135672.html) As well as the talk I gave last LLVM Dev conference ?
They should give a decent explanation on why the different concepts are introduced, keeping correctness and optimizations in mind.

Thanks,

Jeroen

  • the need to implement the 'based on' relationship, also in a a way that clang is producing code, where this dependency is not easily seen. The same restrict pointer usage can appear in different blocks at different places.

You need to elaborate this a bit more, otherwise I don't understand what you mean.

How would you map (before _any_ optimizations; aka, what kind of code should clang produce):

// int *p, *q,  *s,  *t;
{
   int *restrict rp = p;
   int *restrict rq = q;
   int *restrict rs = s;

   int * based_on_rp1 = rp + index1;

   use(rp); 
   use(based_on_rp1); // aliases with rp
   use(rq); // only aliases with rq
   use(rs);
   int * based_on_something;
   if (some_input) {
     based_on_something = based_on_rp1;
   } else {
     based_on_something = rs;
   }
   use(based_on_something); // might alias with rs, or rp
   int * based_on_rp2 = rp + index2;
   use(based_on_rp2);  // aliases with rp
   use(rp);
   use(t); // will not alias with anything above
 }
 use(t+index3); // might alias with everything above

Thank you for the example. I don't see any complication because what I've proposed can handle these implicitly and leverage the LLVM's AA reasoning as-is.
So your example would be compiled to:

%rp = call @llvm.noalias(%p)
%rq = call @llvm.noalias(%q)
%rs = call @llvm.noalias(%s)

%based_on_rp1 = gep %rp, %index1

use(%rp)
use(%based_on_rp1)
use(%rq)  ; only aliases with rq; LLVM gets it automatically
use(%rs)

if (some_input) {
  %based_on_something0 = %based_on_rp1
} else {
  %based_on_something1 = %rs
}
%based_on_something = phi(based_on_something0, based_on_something1)
use(based_on_something)  ; may-alias with rs, or rp

%based_on_rp2 = gep %rp, index2
use(%based_on_rp2)  ;aliases with rp
use(%rp)
use(%t)  ; will not alias with anything above; LLVM knows that for free

call llvm.alias.end(%p, %rp)
call llvm.alias.end(%q, %rq)
call llvm.alias.end(%s, %rs)

The translation is pretty straightforward AFAICT. And the aliasing properties you want to establish are given for free by the current AA.

I'm sorry I'm late to the party, which I'm sure is frustrating for you, but only recently someone called my attention to this proposal and asked me to review it.

Herald added a project: Restricted Project. · View Herald TranscriptJun 7 2022, 1:48 AM
vzakhari added inline comments.
llvm/docs/NoAliasInfo.rst
41 ↗(On Diff #346108)

typo: associate d

362 ↗(On Diff #346108)

Please add parenthesis, so that it looks like a call instruction. Same at line 365.

374 ↗(On Diff #346108)

Can you please expand on the meaning of "must block optimizations"? Maybe list some (important) optimizations that are blocked and insert a link to Optimization passes section below for optimizations that are not blocked but must handle the scopes properly?

398 ↗(On Diff #346108)

Should and here be or/and?

I think it might be useful to put an example somewhere that shows what happens when a pointer provenance cannot be proven not to be based on a restrict pointer. For example:

void unknown();
extern int *gp1;
extern int *gp2;
void test(int *restrict p1) {
  gp1 = p1;
  unknown();
  for (int i = 0; i < 100; ++i)
    gp2[i] = p1[i] + 1;
}

As I understand, the store instruction inside the loop uses a non-restrict pointer (gp2) with unknown/missing provenance and since p1 escapes, gp2 might be based on p1. Thus, the load and the store pontentially alias. I would be great to see an explanation of the logic used to compute the MayAlias result here.

457 ↗(On Diff #346108)

Please add parenthesis for the calls.

603 ↗(On Diff #346108)

Can you please add some example(s)? As I understand, this describes cases with global restrict pointer and with indirect loads of restrict pointers (e.g. int *restrict *restrict).

624 ↗(On Diff #346108)

Do we need to represent the aggregate type as an extra argument to make it work with opaque pointers?

721 ↗(On Diff #346108)

Is it important that the unknown scope has the function scope as its "parent"?

!6 = distinct !{!6, !7, !"test: unknown scope"}
!7 = distinct !{!7, !"test"}
890 ↗(On Diff #346108)

typo: "the the"

1019 ↗(On Diff #346108)

Is this correct?

1250 ↗(On Diff #346108)

past -> passed?

1384 ↗(On Diff #346108)

Can you please add notes that ScopedNoAliasAA is the alias analysis that is using the noalias and ptr_provenance information?
Do you also have estimation of the complexity of alias queries with the new representation? How is it affected by the number of scopes in the scope lists attached to the load/store, by the length of the provenance chain, etc.?

vzakhari added inline comments.Dec 2 2022, 6:40 PM
llvm/docs/NoAliasInfo.rst
1367 ↗(On Diff #346108)

Does noalias attribute becomes redundant for arguments, since clang homes it with llvm.noalias.decl and loads from it with llvm.noalias?