[llvm] [FunctionAttrs] Add the "initializes" attribute inference (PR #97373)
Haopeng Liu via llvm-commits
llvm-commits at lists.llvm.org
Sun Nov 10 09:47:20 PST 2024
================
@@ -580,6 +582,206 @@ struct ArgumentUsesTracker : public CaptureTracker {
const SCCNodeSet &SCCNodes;
};
+// A struct of argument use: a Use and the offset it accesses. This struct
+// is to track uses inside function via GEP. If GEP has a non-constant index,
+// the Offset field is nullopt.
+struct ArgumentUse {
+ Use *U;
+ std::optional<int64_t> Offset;
+};
+
+// A struct of argument access info. "Unknown" accesses are the cases like
+// unrecognized instructions, instructions that have more than one use of
+// the argument, or volatile memory accesses. "Unknown" implies "IsClobber"
+// and an empty access range.
+// Write or Read accesses can be clobbers as well for example, a Load with
+// scalable type.
+struct ArgumentAccessInfo {
+ enum class AccessType : uint8_t { Write, Read, Unknown };
+ AccessType ArgAccessType;
+ bool IsClobber = false;
+ ConstantRangeList AccessRanges;
+};
+
+// A struct to wrap the argument use info per block.
+struct UsesPerBlockInfo {
+ SmallDenseMap<Instruction *, ArgumentAccessInfo, 4> Insts;
+ bool HasWrites = false;
+ bool HasClobber = false;
+};
+
+// A struct to summarize the argument use info in a function.
+struct ArgumentUsesSummary {
+ bool HasAnyWrite = false;
+ bool HasWriteOutsideEntryBB = false;
+ SmallDenseMap<const BasicBlock *, UsesPerBlockInfo, 16> UsesPerBlock;
+};
+
+ArgumentAccessInfo GetArgmentAccessInfo(const Instruction *I,
+ const ArgumentUse &ArgUse,
+ const DataLayout &DL) {
+ auto GetTypeAccessRange =
+ [&DL](Type *Ty,
+ std::optional<int64_t> Offset) -> std::optional<ConstantRange> {
+ auto TypeSize = DL.getTypeStoreSize(Ty);
+ if (!TypeSize.isScalable() && Offset) {
+ int64_t Size = TypeSize.getFixedValue();
+ return ConstantRange(APInt(64, *Offset, true),
+ APInt(64, *Offset + Size, true));
+ }
+ return std::nullopt;
+ };
+ auto GetConstantIntRange =
+ [](Value *Length,
+ std::optional<int64_t> Offset) -> std::optional<ConstantRange> {
+ auto *ConstantLength = dyn_cast<ConstantInt>(Length);
+ if (ConstantLength && Offset)
+ return ConstantRange(
+ APInt(64, *Offset, true),
+ APInt(64, *Offset + ConstantLength->getSExtValue(), true));
+ return std::nullopt;
+ };
+ if (auto *SI = dyn_cast<StoreInst>(I)) {
+ if (!SI->isVolatile() && &SI->getOperandUse(1) == ArgUse.U) {
+ // Get the fixed type size of "SI". Since the access range of a write
+ // will be unioned, if "SI" doesn't have a fixed type size, we just set
+ // the access range to empty.
+ ConstantRangeList AccessRanges;
+ if (auto TypeAccessRange =
+ GetTypeAccessRange(SI->getAccessType(), ArgUse.Offset))
+ AccessRanges.insert(*TypeAccessRange);
+ return {ArgumentAccessInfo::AccessType::Write,
+ /*IsClobber=*/false, AccessRanges};
+ }
+ } else if (auto *LI = dyn_cast<LoadInst>(I)) {
+ if (!LI->isVolatile()) {
+ assert(&LI->getOperandUse(0) == ArgUse.U);
+ // Get the fixed type size of "LI". Different from Write, if "LI"
+ // doesn't have a fixed type size, we conservatively set as a clobber
+ // with an empty access range.
+ if (auto TypeAccessRange =
+ GetTypeAccessRange(LI->getAccessType(), ArgUse.Offset))
+ return {ArgumentAccessInfo::AccessType::Read,
+ /*IsClobber=*/false,
+ {*TypeAccessRange}};
+ return {ArgumentAccessInfo::AccessType::Read, /*IsClobber=*/true, {}};
+ }
+ } else if (auto *MemSet = dyn_cast<MemSetInst>(I)) {
+ if (!MemSet->isVolatile()) {
+ ConstantRangeList AccessRanges;
+ if (auto AccessRange =
+ GetConstantIntRange(MemSet->getLength(), ArgUse.Offset))
+ AccessRanges.insert(*AccessRange);
+ return {ArgumentAccessInfo::AccessType::Write,
+ /*IsClobber=*/false, AccessRanges};
+ }
+ } else if (auto *MTI = dyn_cast<MemTransferInst>(I)) {
+ if (!MTI->isVolatile()) {
+ if (&MTI->getOperandUse(0) == ArgUse.U) {
+ ConstantRangeList AccessRanges;
+ if (auto AccessRange =
+ GetConstantIntRange(MTI->getLength(), ArgUse.Offset))
+ AccessRanges.insert(*AccessRange);
+ return {ArgumentAccessInfo::AccessType::Write,
+ /*IsClobber=*/false, AccessRanges};
+ } else if (&MTI->getOperandUse(1) == ArgUse.U) {
+ if (auto AccessRange =
+ GetConstantIntRange(MTI->getLength(), ArgUse.Offset))
+ return {ArgumentAccessInfo::AccessType::Read,
+ /*IsClobber=*/false,
+ {*AccessRange}};
+ return {ArgumentAccessInfo::AccessType::Read, /*IsClobber=*/true, {}};
+ }
+ }
+ } else if (auto *CB = dyn_cast<CallBase>(I)) {
+ if (CB->isArgOperand(ArgUse.U)) {
+ unsigned ArgNo = CB->getArgOperandNo(ArgUse.U);
+ bool IsInitialize = CB->paramHasAttr(ArgNo, Attribute::Initializes);
+ // Argument is only not clobbered when parameter is writeonly/readnone
+ // and nocapture.
+ bool IsClobber = !(CB->onlyWritesMemory(ArgNo) &&
+ CB->paramHasAttr(ArgNo, Attribute::NoCapture));
+ ConstantRangeList AccessRanges;
+ if (IsInitialize && ArgUse.Offset) {
+ Attribute Attr = CB->getParamAttr(ArgNo, Attribute::Initializes);
+ ConstantRangeList CBCRL = Attr.getValueAsConstantRangeList();
+ for (ConstantRange &CR : CBCRL)
+ AccessRanges.insert(ConstantRange(CR.getLower() + *ArgUse.Offset,
+ CR.getUpper() + *ArgUse.Offset));
+ return {ArgumentAccessInfo::AccessType::Write, IsClobber, AccessRanges};
+ }
+ }
+ }
+ // Unrecognized instructions are considered clobbers.
+ return {ArgumentAccessInfo::AccessType::Unknown, /*IsClobber=*/true, {}};
+}
+
+// Collect the uses of argument "A" in "F".
+ArgumentUsesSummary CollectArgumentUsesPerBlock(Argument &A, Function &F) {
+ auto &DL = F.getParent()->getDataLayout();
+ auto PointerSize =
----------------
haopliu wrote:
Done.
https://github.com/llvm/llvm-project/pull/97373
More information about the llvm-commits
mailing list