This is an archive of the discontinued LLVM Phabricator instance.

[Attributes] Add IntrinsicLoweredToCall attribute.
Needs ReviewPublic

Authored by mattd on Apr 3 2018, 11:21 AM.

Details

Summary

Many intrinsics are not lowered to a function call; however, there is currently no way of knowing whether any given intrinsic is lowered to a call.
The function TargetTransformInfoImpl::isLoweredToCall was originally implemented to return 'false' for all intrinsics, suggesting that there is not the case
of any intrinsic being lowered to a call. The patch introduced here adds an attribute to indicate that a particular intrinsic will be lowered to a call instruction.
The aforementioned function has been updated to check the IsLoweredToCall attribute when it encounters an intrinsic.

This patch was inspired by the discussion at the following URL:
https://reviews.llvm.org/D41104

The semantics of lowering is preserved with this change, but I anticipate,
in the future, some intrinsics might be added or updated to reflect that they
truly be come a call.

Diff Detail

Event Timeline

mattd created this revision.Apr 3 2018, 11:21 AM

I don't IntrLoweredToCall is the attribute you want.

There are some intrinsics which are essentially always lowered to calls. But there are a lot of intrinsics which may or may not be lowered to a call, depending on the exact capabilities of the target. For example, llvm.trunc.f32() is lowered to a single instruction on some processor variants, and a call to trunc() on others. So it's not a fundamental property of the intrinsic; we have to actually query the target. (Getting this right would probably be helpful for the inliner.)

That said, that isn't what you want when you're trying to deduce norecurse; you don't actually care whether the intrinsic is lowered to a call instruction, just whether the lowering calls another user-defined function.

mattd added a comment.Apr 3 2018, 11:51 AM

Thanks for the feedback @eli.friedman.

Aside from the norecurse cases, there are other places that could probably benefit from knowing if a particular intrinsic is either a call to some user code, just an instruction, or metadata. As you mentioned, this is target specific. I think it makes sense to make isLoweredToCall a virtual function in TargetLowering. Each target-specific implementation of TargetLowering could override that routine to handle specific instructions that may/may-not be lowered to a user call. As an initial pass we could take the routine as it is now and make that the default behavior, which doesn't change functionality, but opens the doors for other targets to make use of this functionality later.

Yes, isLoweredToCall() should probably be virtual, so targets can handle target-specific intrinsics. We should probably also add more target-independent handling (llvm.dbg.value is never a call, llvm.trunc isn't a call if ISD::FTRUNC is legal, etc.). The attribute doesn't really make sense.

Sorry, had to refresh my memory about how TargetTransformInfo is implemented. isLoweredToCall is already virtual; TargetTransformInfo is just using some some funny template magic to reduce the abstraction penalty. You can override it by adding an implementation to BasicTTIImpl.h/ARMTargetTransformInfo.h/etc.

mattd added a comment.Apr 5 2018, 10:34 AM

Thanks again for the reply, much appreciated! I suppose there is nothing that really needs to change with how the isLoweredToCall predicate is presented to the rest of the codebase. At one point I wanted to move it from TTI to TargetLowering (TLI), because the predicate is about lowering semantics, and it seemed to make sense that isLoweredToCall could live there too. TargetLowering doesn't rely on the CRTP like TTI, so if we did move the predicate, we'd probably make it virtual. But for now, I don't see an advantage in moving it; however, thanks for pointing out the template magic, CRTP, and I'm now convinced that moving the predicate isn't really necessary, in other words it can still be overriden by a target implementation based on TTI.

Regarding the attribute: I was a bit biased towards implementing this functionality as an attribute, given discussion on the previous review that brought the limitation of isLoweredToCall to my attention: https://reviews.llvm.org/D41104. However, you put forward a good point regarding 'trunc.' and a handful of other intrinsics.

Let me step back for a minute and more generally address what I am trying to solve, in the norecurse case, and for potentially other cases as well. What I really want to know is if an intrinsic is just metadata or not. If the intrinsic is only an "information passer" used for communicating with other parts of the compiler (e.g., dbg.value, lifetime.start, etc). While that will not solve isLoweredToCall, I wouldn't necessarily need to use isLoweredToCall in the cases I am interested in. I believe this will solve the issue with norecurse making decisions on 'metadata' intrinsics (llvm.dbg.). If adding an attribute to denote that an intrinsic will only be used as metadata sounds like a reasonable change, then I'd be happy to rename the changes in this patch to something more appropriate, such as: "Attribute::MetadataIntrinsic", and then update the obvious metadata specific intrinsics in Intrinsics.td.

I feel like we've been going around in circles. You're been repeatedly coming up with various distinctions which are not actually the distinction you want, specifically that you can infer norecurse for functions which call a given intrinsic, i.e. whether there is a potential control flow path from a particular callsite to a particular function. This is not whether the intrinsic is lowered to a call instruction, this is not whether an intrinsic is "metadata".

mattd added a comment.Apr 5 2018, 6:33 PM

This is not whether the intrinsic is lowered to a call instruction, this is not whether an intrinsic is "metadata".

Correct, but having that data can be useful. I'm not trying to necessarily
change how norecurse is decided, rather I just want to prevent
certain intrinsics from having an influence. What I envisioned was
to use such a "is metadata" attribute to completely ignore intrinsics
that we know should not have an influence on norecurse. There are
other cases in the compiler where I'd also like to have this
"is metadata" information.

For norecurse, I would think that checking such an "is metadata"
attribute in the for-loop of the following function would help prevent
debug intrinsics from influencing the placement of norecurse
attributes on a function:
https://llvm.org/doxygen/FunctionAttrs_8cpp_source.html#l01260

for (Instruction &I : instructions(*F))
  if (auto CS = CallSite(&I)) {
    Function *Callee = CS.getCalledFunction();
    if (Callee && Callee->isAMetadataIntrinsic())  // Just an idea...
      continue;
    if (!Callee || Callee == F || !Callee->doesNotRecurse())
      // Function calls a potentially recursive function.
      return false;
  }

We have isa<DbgInfoIntrinsic> to check for all debug info.

More generally, I'm not sure there's a definition of "metadata" that would suit enough passes to make it worthwhile. There's a function for estimating the code size of a call: getIntrinsicCost in TTI returns TCC_Free for a bunch of intrinsics. llvm::isAssumeLikeIntrinsic checks... well, I'm actually not quite sure what it checks; it currently looks for metadata-ish intrinsics, but the documentation for it doesn't really match that meaning. And we have isLoweredToCall, which you were looking into changing. So I guess there's demand for something, but I'm not quite sure what it looks like.