inttoptr->add->ptrtoint capturing pointer?

I have an example:

foo(float * restrict y, int off1, int off2) {
float * restrict x;
for (…) {
for (…) {
x = y+off1;
}
x = (const float *)((int)x+off2))

I’m not sure why this should be capturing the pointer?

For instance, looking at scoped noalias dbg info:

SNA: Capture check for B/CSB UO: %54 = inttoptr i32 %add83 to float*, !dbg !101
SNA: Pointer %1 = call float* @llvm.noalias.p0_float(float* %0, metadata !38), !dbg !41 might be captured!

Is this implying that the noalias intrinsic might be capturing the pointer? It doesn’t look like “noalias” intrinsic has NoCapture property on the pointer:

def int_noalias : Intrinsic<[llvm_anyptr_ty],
[LLVMMatchType<0>, llvm_metadata_ty],
[IntrArgMemOnly, Returned<0>]>;

It should though right? From the definition of capture it is returning the pointer; however, we know nothing is happening here.

I’m on llvm 10 with Hal’s restrict patches.

Thanks.

I think you are working with a custom llvm here but I will
make a few general statements that might help:

- The noalias intrinsic as you've shown it captures, unfortunately.
We don't have the `nocapture_maybe_returned` attribute in IR yet that the Attributor uses for these situations,
IIRC, Juneyoung is working on making it an LLVM-IR enum attribute already.

- int2ptr is assumed to capture in basically every analysis I've seen. It doesn't intrinsically but it is really
hard to get it right. That said, we could allow *very* special patterns but I would argue those should be recognized
in instcombine and replaced there.

- Philip and I are working to define capture better for the lang ref, we might want to include some examples and
rational around int2ptr when we have a coherent model.

I've CC'ed people that might correct me or augment my answer, hope this helps :slight_smile:

~ Johannes

Wrt to Hal Finkel patches, as far as I remember, the capture was intentionally. But as mentioned before,
those patches have a number of shortcomings.

- The current top-of-tree has the 'llvm.experimental.noalias.scope.decl', which solves the issue of not knowing
where a restrict variable was inlined. It does not yet support local restrict variables.
- a more complete version for full restrict is available here: ⚙ D68484 [PATCH 01/27] [noalias] LangRef: noalias intrinsics and ptr_provenance documentation.
  (still no rebase to the current top-of-tree though)
- the 'restrict specification' says:
-- 'Note146: that "based" is defined only for expressions with pointer types.'(See [0]) -> anything can happen
-- it also specifies that 'x = y + off1' triggers undefined behavior, as both x and y are restrict pointers in the same scope.
  (but for this case, the full restrict implementation should be doing the expected thing, as long as 'y' is not dereferenced).

Greetings,

Jeroen Dobbelaere

[0] 6.7.3.1 Formal definition of restrict from n2573.pdf (See http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2573.pdf)

Juneyoung said he hadn’t started working on it yet, so I’m going to take a look at it also.

How will inttoptr work with the new restrict patches? Certainly the int2ptr capturing shouldn’t nullify the restrict qualifier.

Thanks.

How will inttoptr work with the new restrict patches? Certainly the int2ptr capturing shouldn’t nullify the restrict qualifier.

The full restrict patches see through ‘inttoptr(ptrtoint( x ) )’, Besides that, the analysis stops at ‘ptr2int(x)’ and ‘anything can happen’.

Because of this, all other ‘inttoptr’ usages will never introduce a restrict provenance.

(aka, a restrict pointer converted to an int + some computations and then converted back to a pointer will normally not retain the ‘restrictness’ and that can trigger undefined behavior)

Greetings,

Jeroen Dobbelaere

How will inttoptr work with the new restrict patches? Certainly the int2ptr capturing shouldn't nullify the restrict qualifier.

The full restrict patches see through 'inttoptr(ptrtoint( x ) )', Besides that, the analysis stops at 'ptr2int(x)' and 'anything can happen'.
Because of this, all other 'inttoptr' usages will never introduce a restrict provenance.

(aka, a restrict pointer converted to an int + some computations and then converted back to a pointer will normally not retain the 'restrictness' and that can trigger undefined behavior)

I'm very concerned about looking through `int2ptr(ptr2int(x))`. I'd need to go back to our old conversations and examples though.
From memory, I would think there can be a problem even if the pattern is as simple as that, e.g., if the ptr2int has more users
(which might only be other int2ptr's).

This is a very delicate part that has the tendency to break all our "smarts".

~ Johannes

So if some restrict pointer ‘x’ is casted to int, adds 1, then casted back to pointer, it nullifies the restrict qualifier, despite the results having no other uses?

Thanks.

So if some restrict pointer ‘x’ is casted to int, adds 1, then casted back to pointer, it nullifies the restrict qualifier, despite the results having no other uses?

yes.

int * restrict x = …;

bad usage:

(int)((int)x + 1) = 42;

valid usage:

x = (int*)((int)x + 1);

*x = 42;

Greetings,

Jeroen Dobbelaere

Ok, just clarifying.

Am I interrupting 6.7.3.1.4 incorrectly?

Seems like as long as the pointer is based on the restrict pointer (and no other pointer)and follows the constraints, it to is restrict qualified?

During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: T shall not be const-qualified. Every other lvalue used to access the value of X shall also have its address based on P. Every access that modifies X shall be considered also to modify P, for the purposes of this subclause.

Thanks.

Seems like as long as the pointer is based on the restrict pointer (and no other pointer)and follows the constraints, it to is restrict qualified?

as long as the pointer expression is based on a restrict pointer… its accesses are associated to that restrict pointer (and assumed to not alias with other restrict pointers in the same scope).

Greetings,

Jeroen Dobbelaere

Yes, so if you have an int2ptr->add+(non-pointer)->ptr2int that should retain restrict qualifier, but you are saying it doesn’t, or that the new patches won’t support that, correct?

Thanks.

Hi,

I think what Jeroen says is about the behavior of C programs.
In C, an expression can be restrict-qualified, which isn’t the case in LLVM IR; there is no ‘i8* restrict’ type or something like that.
I guess noalias intrinsics are inserted to preserve the information when translating restrict pointers in C to IR.

To summarize, the return value of inttoptr (which is an IR instruction) itself won’t work as a restrict-qualified pointer because there is no restrict qualifier in IR.
If it is somehow used by the noalias intrinsics, it should be able to gain the power to work as a restrict pointer.

x = (const float *)((int)x+off2))

I’m not sure why this should be capturing the pointer?

The reason is that (int)x + off2 may accidentally point into a different object.
A possible workaround is to use (const float*)((const char*)x+off2), which is guaranteed to preserve the provenance.
clang-tidy has an option to detect integer-to-pointer casts to warn about possible performance degradation: https://clang.llvm.org/extra/clang-tidy/checks/performance-no-int-to-ptr.html

Juneyoung

Juneyoung,

It seems from the spec the intention (in best practice) is to allow restrict to follow through to any pointers based on the original restrict pointer providing
that the new pointer is based on the original restrict pointer and only the original restrict pointer (ie, no other pointers), so on and so forth (ie propagated).
So in the example, the original pointer’s restrict qualifier, in this case, should follow through the ptr2int->add->int2ptr to the new pointer.

Yes, currently (and with the new patches) this doesn’t work, as int2ptr assumes capture, which prevents noalias, but should that be correct? It doesn’t seem
like it from the spec.

Thanks.

I think inttoptr should drop noalias-ness of the input pointer because it may collide with existing optimizations.

int i = ptr2int(p) + ofs

int j = ptr2int(p’) + ofs’
if (i == j) use(int2ptr(i));

Optimization can transform use(int2ptr(i)) inside the branch into use(int2ptr(j)) because i == j holds.
If int2ptr preserves noalias-ness of the input pointer, this transformation becomes invalid.
For example, if p was a normal pointer and p’ was a noalias pointer, the replacement makes the program more undefined.
In general, it is hard to detect whether an integer variable is derived from a pointer or not; imagine that i and j were complex expressions.

In terms of correctness, it is okay for LLVM to define the inttoptr cast drop the noalias information because it makes the program more defined.noalias (and restrict as well) guarantees disjointness of memory accesses by defining its violation as undefined behavior. Removing noalias is thus okay.

For what its worth:

  • for the full restrict implementation, I tried to follow the specification as close as possible.

Given that, refinements and tuning to the llvm-ir level behavior can be done/discussed.

  • my interpretation of the standard is that ‘an integer computation’ (ak ptr2int) loses the ‘based on’ property. A ‘int2ptr’ will not bring it back (*).

  • If you have code like:

int * restrict p;

int q=(int)p); // losses restrict based on

p[1]=42;

(int)(q+4) = 43; // accesses same object as p[1], but not based on ‘p’ → undefined behavior

return p[1];

the final access using ‘q’ will result in undefined behavior.

  • (*) I made one exception to this: as llvm is (was?) sometimes introducing ptr2int /int2ptr conversions itself, I ensured that we look through these

in order to track the ‘based on’ relationship.

Optimization can transform use(int2ptr(i)) inside the branch into use(int2ptr(j)) because i == j holds.

[0] is a good read for these cases. This is a situation that the full restrict patches is not trying to fix.

But, the ‘ptr_provenance’ infrastructure it introduces is something that can be useful to tackle it.

As was discussed during last LLVM AA Technical call[1], this infrastructure is the next step forward to introduce

into the main llvm, as it will help solving these kind of issues.

Greetings,

Jeroen Dobbelaere

[0] pointer provenance proposal http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2577.pdf

[1] LLVM AA Technical Call https://docs.google.com/document/d/1ybwEKDVtIbhIhK50qYtwKsL50K-NvB6LfuBsfepBZ9Y/edit

Juneyoung,

I don’t think it’s a valid argument to say we shouldn’t propagate the restrict qualifier because then we can’t do transformations
because the reverse argument can easily be made: if we don’t propagate the restrict qualifier then we can’t do transformations.
By definition, I think that transformation should be invalid if either i or j are restrict qualified. The restrict qualifier should
probably be thought of as a contract between the programmer and the compiler. I think the programmer should expect the
compiler to adhere to the restrict qualifier in best effort. The compiler should also not try to outthink the programmer, in that
if the programmer accesses memory on, or based on, a restrict qualified pointer with another pointer, the behavior is undefined.

Jeron,

I appreciate it. I have added myself to the next AA call and added int2ptr capturing as an agenda I hope we can briefly discuss.

Thanks,

Ryan