llvm-project/llvm/unittests/Analysis/CaptureTrackingTest.cpp
Nikita Popov d35366bcca [CaptureTracking] Correctly handle multiple uses in one instruction
If the same value is used multiple times in the same instruction,
CaptureTracking may end up reporting the wrong use as being captured,
and/or report the same use as being captured multiple times.

Make sure that all checks take the use operand number into account,
rather than performing unreliable comparisons against the used value.

I'm not sure whether this can cause any problems in practice, but
at least some capture trackers (ArgUsesTracker, AACaptureUseTracker)
do care about which call argument is captured.
2020-11-07 11:31:20 +01:00

135 lines
4.9 KiB
C++

//=======- CaptureTrackingTest.cpp - Unit test for the Capture Tracking ---===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/Analysis/CaptureTracking.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/SourceMgr.h"
#include "gtest/gtest.h"
using namespace llvm;
TEST(CaptureTracking, MaxUsesToExplore) {
StringRef Assembly = R"(
; Function Attrs: nounwind ssp uwtable
declare void @doesnt_capture(i8* nocapture, i8* nocapture, i8* nocapture,
i8* nocapture, i8* nocapture)
; %arg has 5 uses
define void @test_few_uses(i8* %arg) {
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
ret void
}
; %arg has 50 uses
define void @test_many_uses(i8* %arg) {
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
call void @doesnt_capture(i8* %arg, i8* %arg, i8* %arg, i8* %arg, i8* %arg)
ret void
}
)";
LLVMContext Context;
SMDiagnostic Error;
auto M = parseAssemblyString(Assembly, Error, Context);
ASSERT_TRUE(M) << "Bad assembly?";
auto Test = [&M](const char *FName, unsigned FalseMaxUsesLimit,
unsigned TrueMaxUsesLimit) {
Function *F = M->getFunction(FName);
ASSERT_NE(F, nullptr);
Value *Arg = &*F->arg_begin();
ASSERT_NE(Arg, nullptr);
ASSERT_FALSE(PointerMayBeCaptured(Arg, true, true, FalseMaxUsesLimit));
ASSERT_TRUE(PointerMayBeCaptured(Arg, true, true, TrueMaxUsesLimit));
BasicBlock *EntryBB = &F->getEntryBlock();
DominatorTree DT(*F);
Instruction *Ret = EntryBB->getTerminator();
ASSERT_TRUE(isa<ReturnInst>(Ret));
ASSERT_FALSE(PointerMayBeCapturedBefore(Arg, true, true, Ret, &DT, false,
FalseMaxUsesLimit));
ASSERT_TRUE(PointerMayBeCapturedBefore(Arg, true, true, Ret, &DT, false,
TrueMaxUsesLimit));
};
Test("test_few_uses", 6, 4);
Test("test_many_uses", 50, 30);
}
struct CollectingCaptureTracker : public CaptureTracker {
SmallVector<const Use *, 4> Captures;
void tooManyUses() { }
bool captured(const Use *U) override {
Captures.push_back(U);
return false;
}
};
TEST(CaptureTracking, MultipleUsesInSameInstruction) {
StringRef Assembly = R"(
declare void @call(i8*, i8*, i8*)
define void @test(i8* %arg, i8** %ptr) {
call void @call(i8* %arg, i8* nocapture %arg, i8* %arg) [ "bundle"(i8* %arg) ]
cmpxchg i8** %ptr, i8* %arg, i8* %arg acq_rel monotonic
icmp eq i8* %arg, %arg
ret void
}
)";
LLVMContext Context;
SMDiagnostic Error;
auto M = parseAssemblyString(Assembly, Error, Context);
ASSERT_TRUE(M) << "Bad assembly?";
Function *F = M->getFunction("test");
Value *Arg = &*F->arg_begin();
BasicBlock *BB = &F->getEntryBlock();
Instruction *Call = &*BB->begin();
Instruction *CmpXChg = Call->getNextNode();
Instruction *ICmp = CmpXChg->getNextNode();
CollectingCaptureTracker CT;
PointerMayBeCaptured(Arg, &CT);
EXPECT_EQ(7u, CT.Captures.size());
// Call arg 1
EXPECT_EQ(Call, CT.Captures[0]->getUser());
EXPECT_EQ(0u, CT.Captures[0]->getOperandNo());
// Call arg 3
EXPECT_EQ(Call, CT.Captures[1]->getUser());
EXPECT_EQ(2u, CT.Captures[1]->getOperandNo());
// Operand bundle arg
EXPECT_EQ(Call, CT.Captures[2]->getUser());
EXPECT_EQ(3u, CT.Captures[2]->getOperandNo());
// Cmpxchg compare operand
EXPECT_EQ(CmpXChg, CT.Captures[3]->getUser());
EXPECT_EQ(1u, CT.Captures[3]->getOperandNo());
// Cmpxchg new value operand
EXPECT_EQ(CmpXChg, CT.Captures[4]->getUser());
EXPECT_EQ(2u, CT.Captures[4]->getOperandNo());
// ICmp first operand
EXPECT_EQ(ICmp, CT.Captures[5]->getUser());
EXPECT_EQ(0u, CT.Captures[5]->getOperandNo());
// ICmp second operand
EXPECT_EQ(ICmp, CT.Captures[6]->getUser());
EXPECT_EQ(1u, CT.Captures[6]->getOperandNo());
}