[AA] Model operand bundles more precisely

Based on D130896, we can model operand bundles more precisely. In
addition to the baseline ModRefBehavior, a reading/clobbering operand
bundle may also read/write all locations. For example, a memcpy with
deopt bundle can read any memory, but only write argument memory.

This means that getModRefInfo() for memcpy with a pointer that does
not alias the arguments results in Ref, rather than ModRef, without
the need to implement any special handling.

Differential Revision: https://reviews.llvm.org/D130980
This commit is contained in:
Nikita Popov 2022-09-01 16:51:00 +02:00
parent cc61c822e0
commit ab25ea6d35
3 changed files with 37 additions and 56 deletions

View File

@ -235,35 +235,37 @@ ModRefInfo AAResults::getModRefInfo(const CallBase *Call,
// Try to refine the mod-ref info further using other API entry points to the
// aggregate set of AA results.
auto MRB = getModRefBehavior(Call);
if (MRB.onlyAccessesInaccessibleMem())
// We can completely ignore inaccessible memory here, because MemoryLocations
// can only reference accessible memory.
auto MRB = getModRefBehavior(Call).getWithoutLoc(
FunctionModRefBehavior::InaccessibleMem);
if (MRB.doesNotAccessMemory())
return ModRefInfo::NoModRef;
// TODO: Exclude inaccessible memory location here.
Result &= MRB.getModRef();
if (MRB.onlyAccessesArgPointees() || MRB.onlyAccessesInaccessibleOrArgMem()) {
ModRefInfo ArgMR = MRB.getModRef(FunctionModRefBehavior::ArgMem);
ModRefInfo OtherMR =
MRB.getWithoutLoc(FunctionModRefBehavior::ArgMem).getModRef();
if ((ArgMR | OtherMR) != OtherMR) {
// Refine the modref info for argument memory. We only bother to do this
// if ArgMR is not a subset of OtherMR, otherwise this won't have an impact
// on the final result.
ModRefInfo AllArgsMask = ModRefInfo::NoModRef;
if (MRB.doesAccessArgPointees()) {
for (const auto &I : llvm::enumerate(Call->args())) {
const Value *Arg = I.value();
if (!Arg->getType()->isPointerTy())
continue;
unsigned ArgIdx = I.index();
MemoryLocation ArgLoc =
MemoryLocation::getForArgument(Call, ArgIdx, TLI);
AliasResult ArgAlias = alias(ArgLoc, Loc, AAQI);
if (ArgAlias != AliasResult::NoAlias)
AllArgsMask |= getArgModRefInfo(Call, ArgIdx);
}
for (const auto &I : llvm::enumerate(Call->args())) {
const Value *Arg = I.value();
if (!Arg->getType()->isPointerTy())
continue;
unsigned ArgIdx = I.index();
MemoryLocation ArgLoc = MemoryLocation::getForArgument(Call, ArgIdx, TLI);
AliasResult ArgAlias = alias(ArgLoc, Loc, AAQI);
if (ArgAlias != AliasResult::NoAlias)
AllArgsMask |= getArgModRefInfo(Call, ArgIdx);
}
// Return NoModRef if no alias found with any argument.
if (isNoModRef(AllArgsMask))
return ModRefInfo::NoModRef;
// Logical & between other AA analyses and argument analysis.
Result &= AllArgsMask;
ArgMR &= AllArgsMask;
}
Result &= ArgMR | OtherMR;
// If Loc is a constant memory location, the call definitely could not
// modify the memory location.
if (isModSet(Result) && pointsToConstantMemory(Loc, AAQI, /*OrLocal*/ false))

View File

@ -769,12 +769,16 @@ FunctionModRefBehavior BasicAAResult::getModRefBehavior(const CallBase *Call) {
else if (Call->onlyAccessesInaccessibleMemOrArgMem())
Min = FunctionModRefBehavior::inaccessibleOrArgMemOnly(MR);
// If the call has operand bundles then aliasing attributes from the function
// it calls do not directly apply to the call. This can be made more precise
// in the future.
if (!Call->hasOperandBundles())
if (const Function *F = Call->getCalledFunction())
Min &= getBestAAResults().getModRefBehavior(F);
if (const Function *F = Call->getCalledFunction()) {
FunctionModRefBehavior FMRB = getBestAAResults().getModRefBehavior(F);
// Operand bundles on the call may also read or write memory, in addition
// to the behavior of the called function.
if (Call->hasReadingOperandBundles())
FMRB |= FunctionModRefBehavior::readOnly();
if (Call->hasClobberingOperandBundles())
FMRB |= FunctionModRefBehavior::writeOnly();
Min &= FMRB;
}
return Min;
}
@ -976,26 +980,6 @@ ModRefInfo BasicAAResult::getModRefInfo(const CallBase *Call,
return ModRefInfo::NoModRef;
}
// Ideally, there should be no need to special case for memcpy/memove
// intrinsics here since general machinery (based on memory attributes) should
// already handle it just fine. Unfortunately, it doesn't due to deficiency in
// operand bundles support. At the moment it's not clear if complexity behind
// enhancing general mechanism worths it.
// TODO: Consider improving operand bundles support in general mechanism.
if (auto *Inst = dyn_cast<AnyMemTransferInst>(Call)) {
AliasResult SrcAA =
getBestAAResults().alias(MemoryLocation::getForSource(Inst), Loc, AAQI);
AliasResult DestAA =
getBestAAResults().alias(MemoryLocation::getForDest(Inst), Loc, AAQI);
// It's also possible for Loc to alias both src and dest, or neither.
ModRefInfo rv = ModRefInfo::NoModRef;
if (SrcAA != AliasResult::NoAlias || Call->hasReadingOperandBundles())
rv |= ModRefInfo::Ref;
if (DestAA != AliasResult::NoAlias || Call->hasClobberingOperandBundles())
rv |= ModRefInfo::Mod;
return rv;
}
// Guard intrinsics are marked as arbitrarily writing so that proper control
// dependencies are maintained but they never mods any particular memory
// location.

View File

@ -33,19 +33,14 @@ define i32 @test2(i32* %P, i32* noalias %P2) {
; heap, but they don't write to any location in the heap if the callee does not
; deoptimize the caller. This fact, combined with the fact that
; @argmemonly_function is, well, an argmemonly function, can be used to conclude
; that %P is not written to at the callsite. However LLVM currently cannot
; describe the "does not write to non-args, and reads the entire heap" effect on
; a callsite.
; that %P is not written to at the callsite.
; CHECK-LABEL: @test2(
%v1 = load i32, i32* %P
; CHECK: %v1 = load i32, i32* %P
call void @argmemonly_function(i32* %P2) [ "deopt"() ]
; CHECK: call void @argmemonly_function(
%v2 = load i32, i32* %P
; CHECK: %v2 = load i32, i32* %P
%diff = sub i32 %v1, %v2
; CHECK: %diff = sub i32 %v1, %v2
ret i32 %diff
; CHECK: ret i32 %diff
; CHECK: ret i32 0
}