[FuzzMutate] introduce vector operations, select and fneg into InstInjectorStrategy

Reviewed By: arsenm

Differential Revision: https://reviews.llvm.org/D139894
This commit is contained in:
Peter Rong 2022-12-12 16:33:19 -08:00
parent 4d7e5163d9
commit c06adaeba6
6 changed files with 248 additions and 29 deletions

View File

@ -18,6 +18,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/Value.h"
#include <functional>
@ -117,6 +118,20 @@ static inline SourcePred anyIntType() {
return {Pred, Make};
}
static inline SourcePred anyIntOrVecIntType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
return V->getType()->isIntOrIntVectorTy();
};
return {Pred, std::nullopt};
}
static inline SourcePred boolOrVecBoolType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
return V->getType()->isIntOrIntVectorTy(1);
};
return {Pred, std::nullopt};
}
static inline SourcePred anyFloatType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
return V->getType()->isFloatingPointTy();
@ -125,6 +140,13 @@ static inline SourcePred anyFloatType() {
return {Pred, Make};
}
static inline SourcePred anyFloatOrVecFloatType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
return V->getType()->isFPOrFPVectorTy();
};
return {Pred, std::nullopt};
}
static inline SourcePred anyPtrType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
return V->getType()->isPointerTy() && !V->isSwiftError();
@ -161,6 +183,54 @@ static inline SourcePred sizedPtrType() {
return {Pred, Make};
}
static inline SourcePred matchFirstLengthWAnyType() {
auto Pred = [](ArrayRef<Value *> Cur, const Value *V) {
assert(!Cur.empty() && "No first source yet");
Type *This = V->getType(), *First = Cur[0]->getType();
VectorType *ThisVec = dyn_cast<VectorType>(This);
VectorType *FirstVec = dyn_cast<VectorType>(First);
if (ThisVec && FirstVec) {
return ThisVec->getElementCount() == FirstVec->getElementCount();
}
return (ThisVec == nullptr) && (FirstVec == nullptr) && (!This->isVoidTy());
};
auto Make = [](ArrayRef<Value *> Cur, ArrayRef<Type *> BaseTypes) {
assert(!Cur.empty() && "No first source yet");
std::vector<Constant *> Result;
ElementCount EC;
bool isVec = false;
if (VectorType *VecTy = dyn_cast<VectorType>(Cur[0]->getType())) {
EC = VecTy->getElementCount();
isVec = true;
}
for (Type *T : BaseTypes) {
if (VectorType::isValidElementType(T)) {
if (isVec)
// If the first pred is <i1 x N>, make the result <T x N>
makeConstantsWithType(VectorType::get(T, EC), Result);
else
makeConstantsWithType(T, Result);
}
}
assert(!Result.empty() && "No potential constants.");
return Result;
};
return {Pred, Make};
}
/// Match values that have the same type as the first source.
static inline SourcePred matchSecondType() {
auto Pred = [](ArrayRef<Value *> Cur, const Value *V) {
assert((Cur.size() > 1) && "No second source yet");
return V->getType() == Cur[1]->getType();
};
auto Make = [](ArrayRef<Value *> Cur, ArrayRef<Type *>) {
assert((Cur.size() > 1) && "No second source yet");
return makeConstantsWithType(Cur[1]->getType());
};
return {Pred, Make};
}
static inline SourcePred anyAggregateType() {
auto Pred = [](ArrayRef<Value *>, const Value *V) {
// We can't index zero sized arrays.

View File

@ -28,12 +28,16 @@ void describeFuzzerControlFlowOps(std::vector<fuzzerop::OpDescriptor> &Ops);
void describeFuzzerPointerOps(std::vector<fuzzerop::OpDescriptor> &Ops);
void describeFuzzerAggregateOps(std::vector<fuzzerop::OpDescriptor> &Ops);
void describeFuzzerVectorOps(std::vector<fuzzerop::OpDescriptor> &Ops);
void describeFuzzerUnaryOperations(std::vector<fuzzerop::OpDescriptor> &Ops);
void describeFuzzerOtherOps(std::vector<fuzzerop::OpDescriptor> &Ops);
/// @}
namespace fuzzerop {
/// Descriptors for individual operations.
/// @{
OpDescriptor selectDescriptor(unsigned Weight);
OpDescriptor fnegDescriptor(unsigned Weight);
OpDescriptor binOpDescriptor(unsigned Weight, Instruction::BinaryOps Op);
OpDescriptor cmpOpDescriptor(unsigned Weight, Instruction::OtherOps CmpOp,
CmpInst::Predicate Pred);
@ -44,6 +48,7 @@ OpDescriptor insertValueDescriptor(unsigned Weight);
OpDescriptor extractElementDescriptor(unsigned Weight);
OpDescriptor insertElementDescriptor(unsigned Weight);
OpDescriptor shuffleVectorDescriptor(unsigned Weight);
/// @}
} // namespace fuzzerop

View File

@ -15,6 +15,9 @@ using namespace fuzzerop;
void fuzzerop::makeConstantsWithType(Type *T, std::vector<Constant *> &Cs) {
if (auto *IntTy = dyn_cast<IntegerType>(T)) {
uint64_t W = IntTy->getBitWidth();
Cs.push_back(ConstantInt::get(IntTy, 0));
Cs.push_back(ConstantInt::get(IntTy, 1));
Cs.push_back(ConstantInt::get(IntTy, 42));
Cs.push_back(ConstantInt::get(IntTy, APInt::getMaxValue(W)));
Cs.push_back(ConstantInt::get(IntTy, APInt::getMinValue(W)));
Cs.push_back(ConstantInt::get(IntTy, APInt::getSignedMaxValue(W)));
@ -24,10 +27,24 @@ void fuzzerop::makeConstantsWithType(Type *T, std::vector<Constant *> &Cs) {
auto &Ctx = T->getContext();
auto &Sem = T->getFltSemantics();
Cs.push_back(ConstantFP::get(Ctx, APFloat::getZero(Sem)));
Cs.push_back(ConstantFP::get(Ctx, APFloat(Sem, 1)));
Cs.push_back(ConstantFP::get(Ctx, APFloat(Sem, 42)));
Cs.push_back(ConstantFP::get(Ctx, APFloat::getLargest(Sem)));
Cs.push_back(ConstantFP::get(Ctx, APFloat::getSmallest(Sem)));
} else
Cs.push_back(ConstantFP::get(Ctx, APFloat::getInf(Sem)));
Cs.push_back(ConstantFP::get(Ctx, APFloat::getNaN(Sem)));
} else if (VectorType *VecTy = dyn_cast<VectorType>(T)) {
std::vector<Constant *> EleCs;
Type *EltTy = VecTy->getElementType();
makeConstantsWithType(EltTy, EleCs);
ElementCount EC = VecTy->getElementCount();
for (Constant *Elt : EleCs) {
Cs.push_back(ConstantVector::getSplat(EC, Elt));
}
} else {
Cs.push_back(UndefValue::get(T));
Cs.push_back(PoisonValue::get(T));
}
}
std::vector<Constant *> fuzzerop::makeConstantsWithType(Type *T) {

View File

@ -67,11 +67,20 @@ void llvm::describeFuzzerFloatOps(std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(cmpOpDescriptor(1, Instruction::FCmp, CmpInst::FCMP_TRUE));
}
void llvm::describeFuzzerUnaryOperations(
std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(fnegDescriptor(1));
}
void llvm::describeFuzzerControlFlowOps(
std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(splitBlockDescriptor(1));
}
void llvm::describeFuzzerOtherOps(std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(selectDescriptor(1));
}
void llvm::describeFuzzerPointerOps(std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(gepDescriptor(1));
}
@ -88,6 +97,22 @@ void llvm::describeFuzzerVectorOps(std::vector<fuzzerop::OpDescriptor> &Ops) {
Ops.push_back(shuffleVectorDescriptor(1));
}
OpDescriptor llvm::fuzzerop::selectDescriptor(unsigned Weight) {
auto buildOp = [](ArrayRef<Value *> Srcs, Instruction *Inst) {
return SelectInst::Create(Srcs[0], Srcs[1], Srcs[2], "S", Inst);
};
return {Weight,
{boolOrVecBoolType(), matchFirstLengthWAnyType(), matchSecondType()},
buildOp};
}
OpDescriptor llvm::fuzzerop::fnegDescriptor(unsigned Weight) {
auto buildOp = [](ArrayRef<Value *> Srcs, Instruction *Inst) {
return UnaryOperator::Create(Instruction::FNeg, Srcs[0], "F", Inst);
};
return {Weight, {anyFloatOrVecFloatType()}, buildOp};
}
OpDescriptor llvm::fuzzerop::binOpDescriptor(unsigned Weight,
Instruction::BinaryOps Op) {
auto buildOp = [Op](ArrayRef<Value *> Srcs, Instruction *Inst) {
@ -107,13 +132,13 @@ OpDescriptor llvm::fuzzerop::binOpDescriptor(unsigned Weight,
case Instruction::And:
case Instruction::Or:
case Instruction::Xor:
return {Weight, {anyIntType(), matchFirstType()}, buildOp};
return {Weight, {anyIntOrVecIntType(), matchFirstType()}, buildOp};
case Instruction::FAdd:
case Instruction::FSub:
case Instruction::FMul:
case Instruction::FDiv:
case Instruction::FRem:
return {Weight, {anyFloatType(), matchFirstType()}, buildOp};
return {Weight, {anyFloatOrVecFloatType(), matchFirstType()}, buildOp};
case Instruction::BinaryOpsEnd:
llvm_unreachable("Value out of range of enum");
}
@ -129,9 +154,9 @@ OpDescriptor llvm::fuzzerop::cmpOpDescriptor(unsigned Weight,
switch (CmpOp) {
case Instruction::ICmp:
return {Weight, {anyIntType(), matchFirstType()}, buildOp};
return {Weight, {anyIntOrVecIntType(), matchFirstType()}, buildOp};
case Instruction::FCmp:
return {Weight, {anyFloatType(), matchFirstType()}, buildOp};
return {Weight, {anyFloatOrVecFloatType(), matchFirstType()}, buildOp};
default:
llvm_unreachable("CmpOp must be ICmp or FCmp");
}

View File

@ -91,10 +91,17 @@ TEST(OperationsTest, SourcePreds) {
Constant *s = ConstantStruct::get(StructType::create(Ctx, "OpaqueStruct"));
Constant *a =
ConstantArray::get(ArrayType::get(i32->getType(), 2), {i32, i32});
Constant *v8i1 = ConstantVector::getSplat(ElementCount::getFixed(8), i1);
Constant *v8i8 = ConstantVector::getSplat(ElementCount::getFixed(8), i8);
Constant *v4f16 = ConstantVector::getSplat(ElementCount::getFixed(4), f16);
Constant *p0i32 =
ConstantPointerNull::get(PointerType::get(i32->getType(), 0));
Constant *v8p0i32 =
ConstantVector::getSplat(ElementCount::getFixed(8), p0i32);
Constant *vni32 = ConstantVector::getSplat(ElementCount::getScalable(8), i32);
Constant *vnf64 = ConstantVector::getSplat(ElementCount::getScalable(8), f64);
Constant *vnp0i32 =
ConstantVector::getSplat(ElementCount::getScalable(8), p0i32);
auto OnlyI32 = onlyType(i32->getType());
EXPECT_TRUE(OnlyI32.matches({}, i32));
@ -126,6 +133,36 @@ TEST(OperationsTest, SourcePreds) {
AnyInt.generate({}, {i32->getType(), f16->getType(), v8i8->getType()}),
AllOf(SizeIs(Ge(1u)), Each(TypesMatch(i32))));
auto AnyIntOrVecInt = anyIntOrVecIntType();
EXPECT_TRUE(AnyIntOrVecInt.matches({}, i1));
EXPECT_TRUE(AnyIntOrVecInt.matches({}, i64));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, f32));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, v4f16));
EXPECT_TRUE(AnyIntOrVecInt.matches({}, v8i8));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, v4f16));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, v8p0i32));
EXPECT_TRUE(AnyIntOrVecInt.matches({}, vni32));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, vnf64));
EXPECT_FALSE(AnyIntOrVecInt.matches({}, vnp0i32));
EXPECT_THAT(AnyIntOrVecInt.generate({}, {v8i8->getType()}),
AllOf(Each(TypesMatch(v8i8))));
auto BoolOrVecBool = boolOrVecBoolType();
EXPECT_TRUE(BoolOrVecBool.matches({}, i1));
EXPECT_FALSE(BoolOrVecBool.matches({}, i64));
EXPECT_FALSE(BoolOrVecBool.matches({}, f32));
EXPECT_FALSE(BoolOrVecBool.matches({}, v4f16));
EXPECT_TRUE(BoolOrVecBool.matches({}, v8i1));
EXPECT_FALSE(BoolOrVecBool.matches({}, v4f16));
EXPECT_FALSE(BoolOrVecBool.matches({}, v8p0i32));
EXPECT_FALSE(BoolOrVecBool.matches({}, vni32));
EXPECT_FALSE(BoolOrVecBool.matches({}, vnf64));
EXPECT_FALSE(BoolOrVecBool.matches({}, vnp0i32));
EXPECT_THAT(BoolOrVecBool.generate({}, {v8i8->getType(), v8i1->getType()}),
AllOf(Each(TypesMatch(v8i1))));
auto AnyFP = anyFloatType();
EXPECT_TRUE(AnyFP.matches({}, f16));
EXPECT_TRUE(AnyFP.matches({}, f32));
@ -137,11 +174,30 @@ TEST(OperationsTest, SourcePreds) {
AnyFP.generate({}, {i32->getType(), f16->getType(), v8i8->getType()}),
AllOf(SizeIs(Ge(1u)), Each(TypesMatch(f16))));
auto AnyFPOrVecFP = anyFloatOrVecFloatType();
EXPECT_TRUE(AnyFPOrVecFP.matches({}, f16));
EXPECT_TRUE(AnyFPOrVecFP.matches({}, f32));
EXPECT_FALSE(AnyFPOrVecFP.matches({}, i16));
EXPECT_FALSE(AnyFPOrVecFP.matches({}, p0i32));
EXPECT_TRUE(AnyFPOrVecFP.matches({}, v4f16));
EXPECT_FALSE(AnyFPOrVecFP.matches({}, v8p0i32));
EXPECT_FALSE(AnyFPOrVecFP.matches({}, vni32));
EXPECT_TRUE(AnyFPOrVecFP.matches({}, vnf64));
EXPECT_FALSE(AnyFPOrVecFP.matches({}, vnp0i32));
EXPECT_THAT(AnyFPOrVecFP.generate(
{}, {i32->getType(), f16->getType(), v8i8->getType()}),
AllOf(SizeIs(Ge(1u)), Each(TypesMatch(f16))));
EXPECT_THAT(AnyFPOrVecFP.generate({}, {v4f16->getType()}),
AllOf(SizeIs(Ge(1u)), Each(TypesMatch(v4f16))));
auto AnyPtr = anyPtrType();
EXPECT_TRUE(AnyPtr.matches({}, p0i32));
EXPECT_FALSE(AnyPtr.matches({}, i8));
EXPECT_FALSE(AnyPtr.matches({}, a));
EXPECT_FALSE(AnyPtr.matches({}, v8i8));
EXPECT_FALSE(AnyPtr.matches({}, v8p0i32));
EXPECT_FALSE(AnyPtr.matches({}, vni32));
auto isPointer = [](Value *V) { return V->getType()->isPointerTy(); };
EXPECT_THAT(
@ -154,9 +210,12 @@ TEST(OperationsTest, SourcePreds) {
EXPECT_FALSE(AnyVec.matches({}, i8));
EXPECT_FALSE(AnyVec.matches({}, a));
EXPECT_FALSE(AnyVec.matches({}, s));
EXPECT_TRUE(AnyVec.matches({}, v8p0i32));
EXPECT_TRUE(AnyVec.matches({}, vni32));
EXPECT_TRUE(AnyVec.matches({}, vnf64));
EXPECT_TRUE(AnyVec.matches({}, vnp0i32));
EXPECT_THAT(AnyVec.generate({}, {v8i8->getType()}),
ElementsAre(TypesMatch(v8i8)));
EXPECT_THAT(AnyVec.generate({}, {v8i8->getType()}), Each(TypesMatch(v8i8)));
auto First = matchFirstType();
EXPECT_TRUE(First.matches({i8}, i8));
@ -167,6 +226,28 @@ TEST(OperationsTest, SourcePreds) {
EXPECT_THAT(First.generate({i8}, {}), Each(TypesMatch(i8)));
EXPECT_THAT(First.generate({f16}, {i8->getType()}), Each(TypesMatch(f16)));
EXPECT_THAT(First.generate({v8i8, i32}, {}), Each(TypesMatch(v8i8)));
auto FirstLength = matchFirstLengthWAnyType();
EXPECT_TRUE(FirstLength.matches({v8i8}, v8i1));
EXPECT_THAT(FirstLength.generate({v8i1}, {i8->getType()}),
Each(TypesMatch(v8i8)));
auto Second = matchSecondType();
EXPECT_TRUE(Second.matches({i32, i8}, i8));
EXPECT_TRUE(Second.matches({i8, f16}, f16));
EXPECT_THAT(Second.generate({v8i8, i32}, {}), Each(TypesMatch(i32)));
EXPECT_THAT(Second.generate({f32, f16}, {f16->getType()}),
Each(TypesMatch(f16)));
auto FirstScalar = matchScalarOfFirstType();
EXPECT_TRUE(FirstScalar.matches({v8i8}, i8));
EXPECT_TRUE(FirstScalar.matches({i8}, i8));
EXPECT_TRUE(FirstScalar.matches({v4f16}, f16));
EXPECT_THAT(FirstScalar.generate({v8i8}, {i8->getType()}),
Each(TypesMatch(i8)));
}
TEST(OperationsTest, SplitBlock) {
@ -301,13 +382,12 @@ TEST(OperationsTest, GEPPointerOperand) {
// Check that we only pick sized pointers for the GEP instructions
LLVMContext Ctx;
const char *SourceCode =
"%opaque = type opaque\n"
"declare void @f()\n"
"define void @test(%opaque %o) {\n"
" %a = alloca i64, i32 10\n"
" ret void\n"
"}";
const char *SourceCode = "%opaque = type opaque\n"
"declare void @f()\n"
"define void @test(%opaque %o) {\n"
" %a = alloca i64, i32 10\n"
" ret void\n"
"}";
auto M = parseAssembly(SourceCode, Ctx);
fuzzerop::OpDescriptor Descr = fuzzerop::gepDescriptor(1);

View File

@ -34,6 +34,17 @@ std::unique_ptr<IRMutator> createInjectorMutator() {
Type::getInt1Ty, Type::getInt8Ty, Type::getInt16Ty, Type::getInt32Ty,
Type::getInt64Ty, Type::getFloatTy, Type::getDoubleTy};
// Add vector 1, 2, 3, 4, and 8.
int VectorLength[] = {1, 2, 3, 4, 8};
std::vector<TypeGetter> BasicTypeGetters(Types);
for (auto typeGetter : BasicTypeGetters) {
for (int length : VectorLength) {
Types.push_back([typeGetter, length](LLVMContext &C) {
return VectorType::get(typeGetter(C), length, false);
});
}
}
std::vector<std::unique_ptr<IRMutationStrategy>> Strategies;
Strategies.push_back(std::make_unique<InjectorIRStrategy>(
InjectorIRStrategy::getDefaultOps()));
@ -78,6 +89,25 @@ void IterateOnSource(StringRef Source, IRMutator &Mutator) {
}
}
static void mutateAndVerifyModule(StringRef Source,
std::unique_ptr<IRMutator> &Mutator,
int repeat = 100) {
LLVMContext Ctx;
auto M = parseAssembly(Source.data(), Ctx);
std::mt19937 mt(Seed);
std::uniform_int_distribution<int> RandInt(INT_MIN, INT_MAX);
for (int i = 0; i < repeat; i++) {
Mutator->mutateModule(*M, RandInt(mt), Source.size(), Source.size() + 1024);
ASSERT_FALSE(verifyModule(*M, &errs()));
}
}
template <class Strategy>
static void mutateAndVerifyModule(StringRef Source, int repeat = 100) {
auto Mutator = createMutator<Strategy>();
ASSERT_TRUE(Mutator);
mutateAndVerifyModule(Source, Mutator, repeat);
}
TEST(InjectorIRStrategyTest, EmptyModule) {
// Test that we can inject into empty module
@ -92,6 +122,13 @@ TEST(InjectorIRStrategyTest, EmptyModule) {
EXPECT_TRUE(!verifyModule(*M, &errs()));
}
TEST(InjectorIRStrategyTest, LargeInsertion) {
StringRef Source = "";
auto Mutator = createInjectorMutator();
ASSERT_TRUE(Mutator);
mutateAndVerifyModule(Source, Mutator, 100);
}
TEST(InstDeleterIRStrategyTest, EmptyFunction) {
// Test that we don't crash even if we can't remove from one of the functions.
@ -385,21 +422,6 @@ TEST(InstModificationIRStrategy, FastMath) {
ASSERT_TRUE(p.second);
}
template <class Strategy>
static void mutateAndVerifyModule(StringRef Source, int repeat = 100) {
LLVMContext Ctx;
auto Mutator = createMutator<Strategy>();
ASSERT_TRUE(Mutator);
auto M = parseAssembly(Source.data(), Ctx);
std::mt19937 mt(Seed);
std::uniform_int_distribution<int> RandInt(INT_MIN, INT_MAX);
for (int i = 0; i < repeat; i++) {
Mutator->mutateModule(*M, RandInt(mt), Source.size(), Source.size() + 1024);
ASSERT_FALSE(verifyModule(*M, &errs()));
}
}
TEST(InsertCFGStrategy, CFG) {
StringRef Source = "\n\
define i32 @test(i1 %C1, i1 %C2, i1 %C3, i16 %S1, i16 %S2, i32 %I1) { \n\