Skip to content

Commit 89bf312

Browse files
committed
Add AMDGPU input load fuzzing
1 parent d0dd61a commit 89bf312

2 files changed

Lines changed: 121 additions & 19 deletions

File tree

‎amdgpu/README.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ or take a guarded early exit from the loop body through an exit phi, so corpus
4040
entries exercise both expression simplification and CFG and loop transforms. CFG
4141
arms include the same scalar
4242
integer, bit, boolean, narrowing, saturating, funnel-shift, finite-FP, and vector
43-
expression families as the linear mutator.
43+
expression families as the linear mutator. Scalar and CFG expressions can also
44+
mix in extra `i32` global input loads from `in[seed % n]`; these loads are only
45+
emitted inside the existing `idx < n` guard and are bounded by the module
46+
validator.
4447
Corpus files can be inspected directly with `opt -S corpus-entry -o -`.
4548

4649
## Requirements

‎amdgpu/fuzzer/llvm_amdgpu_diff_fuzzer.cpp‎

Lines changed: 117 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,8 @@ bool isAllowedIRInstruction(const Instruction &I) {
608608
isAllowedFPVectorType(BO->getType());
609609
case Instruction::UDiv:
610610
case Instruction::URem:
611+
if (BO->hasName() && BO->getName().starts_with("fuzz.load.idx"))
612+
return true;
611613
return isKnownNonZeroI32(BO->getOperand(1));
612614
case Instruction::SDiv:
613615
case Instruction::SRem:
@@ -1311,29 +1313,68 @@ bool hasName(const Value *V, StringRef Name) {
13111313
return V && V->hasName() && V->getName() == Name;
13121314
}
13131315

1314-
bool validateFixedMemoryShape(Function &Kernel) {
1315-
unsigned GEPs = 0;
1316-
unsigned Loads = 0;
1316+
bool hasNameStartingWith(const Value *V, StringRef Prefix) {
1317+
return V && V->hasName() && V->getName().starts_with(Prefix);
1318+
}
1319+
1320+
bool isFuzzInputLoadIndex(const Value *V, Function &Kernel) {
1321+
const auto *ZExt = dyn_cast<ZExtInst>(V);
1322+
if (!ZExt || !hasNameStartingWith(ZExt, "fuzz.load.idx64") ||
1323+
!ZExt->getType()->isIntegerTy(64))
1324+
return false;
1325+
1326+
const auto *URem = dyn_cast<BinaryOperator>(ZExt->getOperand(0));
1327+
return URem && URem->getOpcode() == Instruction::URem &&
1328+
hasNameStartingWith(URem, "fuzz.load.idx") &&
1329+
URem->getOperand(1) == Kernel.getArg(2);
1330+
}
1331+
1332+
bool isFuzzInputLoadPtr(const Value *V) {
1333+
return hasNameStartingWith(V, "fuzz.load.ptr");
1334+
}
1335+
1336+
bool validateMemoryShape(Function &Kernel) {
1337+
bool SawBaseInGEP = false;
1338+
bool SawBaseOutGEP = false;
1339+
unsigned BaseLoads = 0;
1340+
unsigned ExtraLoadGEPs = 0;
1341+
unsigned ExtraLoads = 0;
13171342
unsigned Stores = 0;
13181343
for (BasicBlock &BB : Kernel) {
13191344
for (Instruction &I : BB) {
13201345
if (auto *GEP = dyn_cast<GetElementPtrInst>(&I)) {
1321-
++GEPs;
13221346
if (GEP->getSourceElementType() != Type::getInt32Ty(Kernel.getContext()))
13231347
return false;
1324-
if (!hasName(GEP, "in.ptr") && !hasName(GEP, "out.ptr"))
1325-
return false;
1326-
if (GEP->getNumIndices() != 1 ||
1327-
!hasName(GEP->idx_begin()->get(), "idx64"))
1328-
return false;
1329-
unsigned ArgNo = hasName(GEP, "in.ptr") ? 0 : 1;
1330-
if (GEP->getPointerOperand() != Kernel.getArg(ArgNo))
1348+
if (hasName(GEP, "in.ptr") || hasName(GEP, "out.ptr")) {
1349+
if (GEP->getNumIndices() != 1 ||
1350+
!hasName(GEP->idx_begin()->get(), "idx64"))
1351+
return false;
1352+
unsigned ArgNo = hasName(GEP, "in.ptr") ? 0 : 1;
1353+
if (GEP->getPointerOperand() != Kernel.getArg(ArgNo))
1354+
return false;
1355+
if (ArgNo == 0)
1356+
SawBaseInGEP = true;
1357+
else
1358+
SawBaseOutGEP = true;
1359+
} else if (isFuzzInputLoadPtr(GEP)) {
1360+
if (GEP->getPointerOperand() != Kernel.getArg(0) ||
1361+
GEP->getNumIndices() != 1 ||
1362+
!isFuzzInputLoadIndex(GEP->idx_begin()->get(), Kernel))
1363+
return false;
1364+
++ExtraLoadGEPs;
1365+
} else {
13311366
return false;
1367+
}
13321368
} else if (auto *Load = dyn_cast<LoadInst>(&I)) {
1333-
++Loads;
1334-
if (!Load->getType()->isIntegerTy(32) ||
1335-
!hasName(Load->getPointerOperand(), "in.ptr"))
1369+
if (!Load->getType()->isIntegerTy(32))
13361370
return false;
1371+
if (hasName(Load->getPointerOperand(), "in.ptr")) {
1372+
++BaseLoads;
1373+
} else if (isFuzzInputLoadPtr(Load->getPointerOperand())) {
1374+
++ExtraLoads;
1375+
} else {
1376+
return false;
1377+
}
13371378
} else if (auto *Store = dyn_cast<StoreInst>(&I)) {
13381379
++Stores;
13391380
if (!Store->getValueOperand()->getType()->isIntegerTy(32) ||
@@ -1342,7 +1383,8 @@ bool validateFixedMemoryShape(Function &Kernel) {
13421383
}
13431384
}
13441385
}
1345-
return GEPs == 2 && Loads == 1 && Stores == 1;
1386+
return SawBaseInGEP && SawBaseOutGEP && BaseLoads == 1 && Stores == 1 &&
1387+
ExtraLoadGEPs == ExtraLoads && ExtraLoads <= 32;
13461388
}
13471389

13481390
bool validateIRCorpusModule(Module &M) {
@@ -1370,7 +1412,7 @@ bool validateIRCorpusModule(Module &M) {
13701412
if (&F == Kernel) {
13711413
if (F.empty())
13721414
return false;
1373-
if (!validateFixedMemoryShape(F))
1415+
if (!validateMemoryShape(F))
13741416
return false;
13751417
for (BasicBlock &BB : F)
13761418
for (Instruction &I : BB)
@@ -2081,6 +2123,51 @@ Value *emitSafeSignedDivRemInstruction(IRBuilder<NoFolder> &B, Value *A,
20812123
return B.CreateSDiv(A, Den, Twine(NamePrefix) + ".op");
20822124
}
20832125

2126+
Value *emitSafeInputLoadInstruction(IRBuilder<NoFolder> &B, Module &M,
2127+
Value *A, Value *Bv,
2128+
std::minstd_rand &Gen,
2129+
StringRef NamePrefix) {
2130+
Function *F = B.GetInsertBlock()->getParent();
2131+
LLVMContext &Ctx = M.getContext();
2132+
Type *I32 = Type::getInt32Ty(Ctx);
2133+
Type *I64 = Type::getInt64Ty(Ctx);
2134+
Value *In = F->getArg(0);
2135+
Value *N = F->getArg(2);
2136+
Value *Seed = nullptr;
2137+
switch (Gen() % 4) {
2138+
case 0:
2139+
Seed = A;
2140+
break;
2141+
case 1:
2142+
Seed = Bv;
2143+
break;
2144+
case 2:
2145+
Seed = B.CreateAdd(A, Bv, Twine(NamePrefix) + ".seed.add");
2146+
break;
2147+
default:
2148+
Seed = B.CreateXor(A, Bv, Twine(NamePrefix) + ".seed.xor");
2149+
break;
2150+
}
2151+
2152+
Value *Idx = B.CreateURem(Seed, N, Twine(NamePrefix) + ".idx");
2153+
Value *Idx64 = B.CreateZExt(Idx, I64, Twine(NamePrefix) + ".idx64");
2154+
Value *Ptr = B.CreateGEP(I32, In, Idx64, Twine(NamePrefix) + ".ptr");
2155+
Value *Loaded = B.CreateLoad(I32, Ptr, Twine(NamePrefix) + ".value");
2156+
switch (Gen() % 4) {
2157+
case 0:
2158+
return B.CreateXor(A, Loaded, Twine(NamePrefix) + ".xor");
2159+
case 1:
2160+
return B.CreateAdd(Bv, Loaded, Twine(NamePrefix) + ".add");
2161+
case 2:
2162+
return B.CreateSub(Loaded, A, Twine(NamePrefix) + ".sub");
2163+
default: {
2164+
Value *Cmp = B.CreateICmp(randomICmpPredicate(Gen), Loaded, A,
2165+
Twine(NamePrefix) + ".cmp");
2166+
return B.CreateSelect(Cmp, Loaded, Bv, Twine(NamePrefix) + ".select");
2167+
}
2168+
}
2169+
}
2170+
20842171
Value *emitRandomIRInstruction(IRBuilder<NoFolder> &B, Module &M,
20852172
Instruction *InsertPt, Value *Current,
20862173
std::minstd_rand &Gen) {
@@ -2090,7 +2177,7 @@ Value *emitRandomIRInstruction(IRBuilder<NoFolder> &B, Module &M,
20902177
Type *I32 = Type::getInt32Ty(Ctx);
20912178
Value *A = Current;
20922179
Value *Bv = chooseI32Value(InsertPt, Gen);
2093-
switch (Gen() % 70) {
2180+
switch (Gen() % 74) {
20942181
case 0:
20952182
return B.CreateAdd(A, Bv, "fuzz.add");
20962183
case 1:
@@ -2236,6 +2323,11 @@ Value *emitRandomIRInstruction(IRBuilder<NoFolder> &B, Module &M,
22362323
case 57:
22372324
return emitRandomFiniteSignedFPInstruction(B, A, Bv, Gen,
22382325
"fuzz.fp.signed");
2326+
case 58:
2327+
case 59:
2328+
case 60:
2329+
case 61:
2330+
return emitSafeInputLoadInstruction(B, M, A, Bv, Gen, "fuzz.load");
22392331
default:
22402332
switch (Gen() % 4) {
22412333
case 0:
@@ -2268,7 +2360,7 @@ Value *emitRandomCFGArmInstruction(IRBuilder<NoFolder> &B, Module &M, Value *A,
22682360
Type *I8 = Type::getInt8Ty(Ctx);
22692361
Type *I16 = Type::getInt16Ty(Ctx);
22702362
Type *I32 = Type::getInt32Ty(Ctx);
2271-
switch (Gen() % 56) {
2363+
switch (Gen() % 60) {
22722364
case 0:
22732365
return B.CreateAdd(A, Bv, "fuzz.cfg.add");
22742366
case 1:
@@ -2401,6 +2493,11 @@ Value *emitRandomCFGArmInstruction(IRBuilder<NoFolder> &B, Module &M, Value *A,
24012493
case 49:
24022494
return emitRandomFiniteSignedFPInstruction(B, A, Bv, Gen,
24032495
"fuzz.cfg.fp.signed");
2496+
case 50:
2497+
case 51:
2498+
case 52:
2499+
case 53:
2500+
return emitSafeInputLoadInstruction(B, M, A, Bv, Gen, "fuzz.load");
24042501
default:
24052502
switch (Gen() % 4) {
24062503
case 0:
@@ -3172,6 +3269,8 @@ bool isCrossOverCloneableInstruction(const Instruction &I) {
31723269
if (I.isTerminator() || I.mayReadOrWriteMemory() ||
31733270
isa<GetElementPtrInst, PHINode>(&I))
31743271
return false;
3272+
if (I.hasName() && I.getName().starts_with("fuzz.load."))
3273+
return false;
31753274
Type *Ty = I.getType();
31763275
if (!Ty->isIntegerTy(1) && !Ty->isIntegerTy(32))
31773276
return false;

0 commit comments

Comments
 (0)