[PATCH] D68484: [PATCH 01/38] [noalias] LangRef: noalias intrinsics and noalias_sidechannel documentation.

Jeroen Dobbelaere via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 8 08:26:07 PDT 2019


jeroen.dobbelaere marked 3 inline comments as done.
jeroen.dobbelaere added a comment.

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"}



================
Comment at: llvm/docs/LangRef.rst:16303
+- ``p.objId``: a number that can be used to differentiate different *object P*
+  when ``%p.addr`` is optimized away.
+- ``!p.scope``: metadata argument that refers to a list of alias.scope metadata
----------------
jdoerfert wrote:
> jeroen.dobbelaere wrote:
> > jdoerfert wrote:
> > > This seems odd, why introduce two things that do the same thing.
> > 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.
> 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.
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 ?


================
Comment at: llvm/docs/LangRef.rst:16306
+  entries that contains exactly one element. It represents the variable
+  declaration that contains one or more restrict pointers.
+- ``%p.decl``: points to the ``@llvm.noalias.decl`` intrinsic associated with
----------------
jdoerfert wrote:
> jeroen.dobbelaere wrote:
> > jdoerfert wrote:
> > > "entries with a single element each."
> > > > It represents the variable declaration that contains one or more restrict pointers.
> > > I do not understand this sentence.
> > 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
> > 
> > 
> > 
> > 
> 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?
Yes, the p.scope is a result of the declaration and uniquely identifies one.


================
Comment at: llvm/docs/LangRef.rst:16496
+not really represent a value. It is merely used to track a dependency on the
+declaration.
+
----------------
jdoerfert wrote:
> jeroen.dobbelaere wrote:
> > jdoerfert wrote:
> > > 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).
> > 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*.
> 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.
ok. We can consider that. 


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D68484/new/

https://reviews.llvm.org/D68484





More information about the llvm-commits mailing list