[llvm-dev] Ambiguity in the nofree function attribute

Philip Reames via llvm-dev llvm-dev at lists.llvm.org
Tue Apr 13 14:38:07 PDT 2021


On 4/12/21 10:58 AM, Nuno Lopes wrote:
>
> I like option 2. I agree that allowing functions to allocate & 
> deallocate memory is useful.
>
> Option 1 is super hard to infer. Plus it necessarily hits fewer cases, 
> as the whole call-graph would need to consist of nofree calls. Option 
> 2 doesn’t have such requirement.
>
I think the consensus seems to be in favor of option #1.
>
> Nofree is most useful for callers to know that the dereferenceability 
> of any pointer they have is kept across the call. In general, function 
> attributes are there to help callers. Otherwise they would be at most 
> a cache for analyses that you can do locally (ok, plus information 
> that frontends can give, but clang can’t give you nofree I guess).
>
It's that last clause that's the entire difference between the two 
approaches.  :)
>
> I quickly scanned the test cases affected by your patch, and those 
> seem to be “easily” recoverable. Those functions don’t have any call 
> to free nor are those pointers passed to other non-nofree calls, so 
> you can assume that any dereferenceable argument remains so throughout 
> the whole function. Requires a bit more work, but doable.
>
Two points in response to your last paragraph:

1) I think you're over analyzing deliberately reduced minimal test case 
for the current logic and assuming from that the original examples look 
exactly like the reduced cases.

2) I do think the need for context sensitive logic is a major increase 
in difficulty over context insensitive.  Your point about being able to 
special case a context insensitive analysis when all callees are nofree 
is a good one, and probably worth implementing.
>
> Nuno
>
> *From:* Philip Reames <listmail at philipreames.com>
> *Sent:* 09 April 2021 20:05
> *To:* llvm-dev at lists.llvm.org
> *Cc:* Johannes Doerfert <johannesdoerfert at gmail.com>; Artur Pilipenko 
> <llvmlistbot at llvm.org>; Nuno Lopes <nunoplopes at sapo.pt>
> *Subject:* Ambiguity in the nofree function attribute
>
> I've stumbled across a case related to the nofree attribute where we 
> seem to have inconsistent interpretations of the attribute semantic in 
> tree.  I'd like some input from others as to what the "right" semantic 
> should be.
>
> The basic question is does the presence of nofree prevent the callee 
> from allocating and freeing memory entirely within it's dynamic 
> scope?  At first, it seems obvious that it does, but that turns out to 
> be a bit inconsistent with other attributes and leads to some 
> surprising results.
>
> For reference in the following discussion, here is the current wording 
> for the nofree function attribute in LangRef:
>
>     "This function attribute indicates that the function does not,
>     directly or indirectly, call a memory-deallocation function (free,
>     for example). As a result, uncaptured pointers that are known to
>     be dereferenceable prior to a call to a function with the nofree
>     attribute are still known to be dereferenceable after the call
>     (the capturing condition is necessary in environments where the
>     function might communicate the pointer to another thread which
>     then deallocates the memory)."
>
> For discussion purposes, please assume the concurrency case has been 
> separately proven.  That's not the point I'm getting at here.
>
> The two possible semantics as I see them are:
>
> *Option 1* - nofree implies no call to free, period
>
> This is the one that to me seems most consistent with the current 
> wording, but it prevents the callee from allocating storage and 
> freeing it entirely within it's scope.  This is, for instance, a 
> reasonable thing a target might want to do when lowering large 
> allocs.  This requires transforms to be careful in stripping the 
> attribute, but isn't entirely horrible.
>
> The more surprising bit is that it means we can not infer nofree from 
> readonly or readnone.  Why?  Because both are specified only in terms 
> of memory effects visible to the caller.  As a result, a readnone 
> function can allocate storage, write to it, and still be readonly.  
> Our current inference rules for readnone and readonly do exploit this 
> flexibility.
>
> The optimizer does currently assume that readonly implies nofree.  
> (See the accessor on Function)  Removing this substantially weakens 
> our ability to infer nofree when faced with a function declaration 
> which hasn't been explicitly annotated for nofree.  We can get most of 
> this back by adding appropriate annotations to intrinsics, but not all.
>
> *Option 2* - nofree applies to memory visible to the caller
>
> In this case, we'd add wording to the nofree definition analogous to 
> that in the readonly/readnone specification. (There's a subtlety about 
> the precise definition of visible here, but for the moment, let's hand 
> wave in the same way we do for the other attributes.)
>
> This allows us to infer nofree from readonly, but essentially cripples 
> our ability to drive transformations within an annotated function.  
> We'd have to restrict all transforms and inference to cases where we 
> can prove that the object being affected is visible to the caller.
>
> The benefit is that this makes it slightly easier to infer nofree in 
> some cases.  The main impact of this is improving ability to reason 
> about dereferenceability for uncaptured objects over calls to 
> functions for which we inferred nofree.
>
> The downside of this is that we essentially loose all ability to 
> reason about nofree in a context free manner.  For a specific example 
> of the impact of this, it means we can't infer dereferenceability for 
> an object allocated in F, and returned (e.g. not freed), in the scope 
> of F.
>
> This breaks hoisting and vectorization improvements (e.g. 
> unconditional loads instead of predicated ones) I've checked in over 
> the last few months, and makes the ongoing deref redefinition work 
> substantially harder. https://reviews.llvm.org/D100141 
> <https://reviews.llvm.org/D100141> shows what this looks like code wise.
>
> *My Take*
>
> At first, I was strongly convinced that option 1 was the right 
> choice.  So much so in fact that I nearly didn't bother to post this 
> question.  However, after giving it more thought, I've come to 
> distrust my own response a bit.  I definitely have a conflict of 
> interest here.  Option 2 requires me to effectively cripple several 
> recent optimizer enhancements, and maybe even revert some code which 
> becomes effectively useless.  It also makes a project I'm currently 
> working on (deref redef) substantially harder.
>
> On the other hand, the inconsistency with readonly and readnone is 
> surprising.  I can see an argument for that being the right overall 
> approach long term.
>
> So essentially, this email is me asking for a sanity check. Do folks 
> think option 1 is the right option?  Or am I forcing it to be the 
> right option because it makes things easier for me?
>
> Philip
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20210413/aa239a98/attachment.html>


More information about the llvm-dev mailing list