//===- TrackerTest.cpp ----------------------------------------------------===// // // 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/AsmParser/Parser.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Function.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Module.h" #include "llvm/SandboxIR/Function.h" #include "llvm/SandboxIR/Instruction.h" #include "llvm/Support/SourceMgr.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" using namespace llvm; struct TrackerTest : public testing::Test { LLVMContext C; std::unique_ptr M; void parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; M = parseAssemblyString(IR, Err, C); if (!M) Err.print("TrackerTest", errs()); } BasicBlock *getBasicBlockByName(Function &F, StringRef Name) { for (BasicBlock &BB : F) if (BB.getName() == Name) return &BB; llvm_unreachable("Expected to find basic block!"); } }; TEST_F(TrackerTest, SetOperand) { parseIR(C, R"IR( define void @foo(ptr %ptr) { %gep0 = getelementptr float, ptr %ptr, i32 0 %gep1 = getelementptr float, ptr %ptr, i32 1 %ld0 = load float, ptr %gep0 store float undef, ptr %gep0 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto &Tracker = Ctx.getTracker(); Tracker.save(); auto It = BB->begin(); auto *Gep0 = &*It++; auto *Gep1 = &*It++; auto *Ld = &*It++; auto *St = &*It++; St->setOperand(0, Ld); St->setOperand(1, Gep1); Ld->setOperand(0, Gep1); EXPECT_EQ(St->getOperand(0), Ld); EXPECT_EQ(St->getOperand(1), Gep1); EXPECT_EQ(Ld->getOperand(0), Gep1); Ctx.getTracker().revert(); EXPECT_NE(St->getOperand(0), Ld); EXPECT_EQ(St->getOperand(1), Gep0); EXPECT_EQ(Ld->getOperand(0), Gep0); } TEST_F(TrackerTest, SetUse) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %arg) { %ld = load i8, ptr %ptr %add = add i8 %ld, %arg ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); unsigned ArgIdx = 0; auto *Arg0 = F->getArg(ArgIdx++); auto *BB = &*F->begin(); auto &Tracker = Ctx.getTracker(); Tracker.save(); auto It = BB->begin(); auto *Ld = &*It++; auto *Add = &*It++; Ctx.save(); sandboxir::Use Use = Add->getOperandUse(0); Use.set(Arg0); EXPECT_EQ(Add->getOperand(0), Arg0); Ctx.revert(); EXPECT_EQ(Add->getOperand(0), Ld); } TEST_F(TrackerTest, SwapOperands) { parseIR(C, R"IR( define void @foo(i1 %cond) { bb0: br i1 %cond, label %bb1, label %bb2 bb1: ret void bb2: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); Ctx.createFunction(&LLVMF); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *BB1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb1"))); auto *BB2 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb2"))); auto &Tracker = Ctx.getTracker(); Tracker.save(); auto It = BB0->begin(); auto *Br = cast(&*It++); unsigned SuccIdx = 0; SmallVector ExpectedSuccs({BB2, BB1}); for (auto *Succ : Br->successors()) EXPECT_EQ(Succ, ExpectedSuccs[SuccIdx++]); // This calls User::swapOperandsInternal() internally. Br->swapSuccessors(); SuccIdx = 0; for (auto *Succ : reverse(Br->successors())) EXPECT_EQ(Succ, ExpectedSuccs[SuccIdx++]); Ctx.getTracker().revert(); SuccIdx = 0; for (auto *Succ : Br->successors()) EXPECT_EQ(Succ, ExpectedSuccs[SuccIdx++]); } TEST_F(TrackerTest, RUWIf_RAUW_RUOW) { parseIR(C, R"IR( define void @foo(ptr %ptr) { %ld0 = load float, ptr %ptr %ld1 = load float, ptr %ptr store float %ld0, ptr %ptr store float %ld0, ptr %ptr ret void } )IR"); llvm::Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); llvm::BasicBlock *LLVMBB = &*LLVMF.begin(); Ctx.createFunction(&LLVMF); auto *BB = cast(Ctx.getValue(LLVMBB)); auto It = BB->begin(); sandboxir::Instruction *Ld0 = &*It++; sandboxir::Instruction *Ld1 = &*It++; sandboxir::Instruction *St0 = &*It++; sandboxir::Instruction *St1 = &*It++; Ctx.save(); // Check RUWIf when the lambda returns false. Ld0->replaceUsesWithIf(Ld1, [](const sandboxir::Use &Use) { return false; }); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); // Check RUWIf when the lambda returns true. Ld0->replaceUsesWithIf(Ld1, [](const sandboxir::Use &Use) { return true; }); EXPECT_EQ(St0->getOperand(0), Ld1); EXPECT_EQ(St1->getOperand(0), Ld1); Ctx.revert(); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); // Check RUWIf user == St0. Ctx.save(); Ld0->replaceUsesWithIf( Ld1, [St0](const sandboxir::Use &Use) { return Use.getUser() == St0; }); EXPECT_EQ(St0->getOperand(0), Ld1); EXPECT_EQ(St1->getOperand(0), Ld0); Ctx.revert(); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); // Check RUWIf user == St1. Ctx.save(); Ld0->replaceUsesWithIf( Ld1, [St1](const sandboxir::Use &Use) { return Use.getUser() == St1; }); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld1); Ctx.revert(); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); // Check RAUW. Ctx.save(); Ld1->replaceAllUsesWith(Ld0); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); Ctx.revert(); EXPECT_EQ(St0->getOperand(0), Ld0); EXPECT_EQ(St1->getOperand(0), Ld0); // Check RUOW. Ctx.save(); St0->replaceUsesOfWith(Ld0, Ld1); EXPECT_EQ(St0->getOperand(0), Ld1); Ctx.revert(); EXPECT_EQ(St0->getOperand(0), Ld0); // Check accept(). Ctx.save(); St0->replaceUsesOfWith(Ld0, Ld1); EXPECT_EQ(St0->getOperand(0), Ld1); Ctx.accept(); EXPECT_EQ(St0->getOperand(0), Ld1); } // TODO: Test multi-instruction patterns. TEST_F(TrackerTest, EraseFromParent) { parseIR(C, R"IR( define void @foo(i32 %v1) { %add0 = add i32 %v1, %v1 %add1 = add i32 %add0, %v1 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Add1 = &*It++; sandboxir::Instruction *Ret = &*It++; Ctx.save(); // Check erase. Add1->eraseFromParent(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); EXPECT_EQ(Add0->getNumUses(), 0u); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); EXPECT_EQ(Add1->getOperand(0), Add0); // Same for the last instruction in the block. Ctx.save(); Ret->eraseFromParent(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(It, BB->end()); Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); } // TODO: Test multi-instruction patterns. TEST_F(TrackerTest, RemoveFromParent) { parseIR(C, R"IR( define i32 @foo(i32 %arg) { %add0 = add i32 %arg, %arg %add1 = add i32 %add0, %arg ret i32 %add1 } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *Arg = F->getArg(0); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Add1 = &*It++; sandboxir::Instruction *Ret = &*It++; Ctx.save(); // Check removeFromParent(). Add1->removeFromParent(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Removed instruction still be connected to operands and users. EXPECT_EQ(Add1->getOperand(0), Add0); EXPECT_EQ(Add1->getOperand(1), Arg); EXPECT_EQ(Add0->getNumUses(), 1u); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); EXPECT_EQ(Add1->getOperand(0), Add0); // Same for the last instruction in the block. Ctx.save(); Ret->removeFromParent(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(It, BB->end()); EXPECT_EQ(Ret->getOperand(0), Add1); Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); } // TODO: Test multi-instruction patterns. TEST_F(TrackerTest, MoveInstr) { parseIR(C, R"IR( define i32 @foo(i32 %arg) { %add0 = add i32 %arg, %arg %add1 = add i32 %add0, %arg ret i32 %add1 } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Add1 = &*It++; sandboxir::Instruction *Ret = &*It++; // Check moveBefore(Instruction *) with tracking enabled. Ctx.save(); Add1->moveBefore(Add0); It = BB->begin(); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Same for the last instruction in the block. Ctx.save(); Ret->moveBefore(Add0); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(It, BB->end()); Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check moveBefore(BasicBlock &, BasicBlock::iterator) with tracking enabled. Ctx.save(); Add1->moveBefore(*BB, Add0->getIterator()); It = BB->begin(); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Same for the last instruction in the block. Ctx.save(); Ret->moveBefore(*BB, Add0->getIterator()); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check moveAfter(Instruction *) with tracking enabled. Ctx.save(); Add0->moveAfter(Add1); It = BB->begin(); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Same for the last instruction in the block. Ctx.save(); Ret->moveAfter(Add0); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Add1); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); } // TODO: Test multi-instruction patterns. TEST_F(TrackerTest, InsertIntoBB) { parseIR(C, R"IR( define void @foo(i32 %arg) { %add0 = add i32 %arg, %arg ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Ret = &*It++; // Detach `Add0` before we save. Add0->removeFromParent(); // Check insertBefore(Instruction *) with tracking enabled. Ctx.save(); Add0->insertBefore(Ret); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check insertAfter(Instruction *) with tracking enabled. Ctx.save(); Add0->insertAfter(Ret); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check insertInto(BasicBlock *, BasicBlock::iterator) with tracking enabled. Ctx.save(); Add0->insertInto(BB, Ret->getIterator()); It = BB->begin(); EXPECT_EQ(&*It++, Add0); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // To make sure we don't leak memory insert `Add0` back into the BB before the // end of the test. Add0->insertBefore(Ret); } // TODO: Test multi-instruction patterns. TEST_F(TrackerTest, CreateAndInsertInst) { parseIR(C, R"IR( define void @foo(ptr %ptr) { %ld = load i8, ptr %ptr, align 64 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *Ptr = F->getArg(0); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Ld = cast(&*It++); auto *Ret = &*It++; Ctx.save(); // Check create(InsertBefore) with tracking enabled. sandboxir::LoadInst *NewLd = sandboxir::LoadInst::create( Ld->getType(), Ptr, Align(8), /*InsertBefore=*/Ld->getIterator(), Ctx, "NewLd"); It = BB->begin(); EXPECT_EQ(&*It++, NewLd); EXPECT_EQ(&*It++, Ld); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); // Check revert(). Ctx.revert(); It = BB->begin(); EXPECT_EQ(&*It++, Ld); EXPECT_EQ(&*It++, Ret); EXPECT_EQ(It, BB->end()); } TEST_F(TrackerTest, FenceInstSetters) { parseIR(C, R"IR( define void @foo() { fence syncscope("singlethread") seq_cst ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); sandboxir::Function *F = Ctx.createFunction(LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Fence = cast(&*It++); // Check setOrdering(). auto OrigOrdering = Fence->getOrdering(); auto NewOrdering = AtomicOrdering::Release; EXPECT_NE(NewOrdering, OrigOrdering); Ctx.save(); Fence->setOrdering(NewOrdering); EXPECT_EQ(Fence->getOrdering(), NewOrdering); Ctx.revert(); EXPECT_EQ(Fence->getOrdering(), OrigOrdering); // Check setSyncScopeID(). auto OrigSSID = Fence->getSyncScopeID(); auto NewSSID = SyncScope::System; EXPECT_NE(NewSSID, OrigSSID); Ctx.save(); Fence->setSyncScopeID(NewSSID); EXPECT_EQ(Fence->getSyncScopeID(), NewSSID); Ctx.revert(); EXPECT_EQ(Fence->getSyncScopeID(), OrigSSID); } TEST_F(TrackerTest, CallBaseSetters) { parseIR(C, R"IR( declare void @bar1(i8) declare void @bar2(i8) define void @foo(i8 %arg0, i8 %arg1) { call void @bar1(i8 %arg0) ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); unsigned ArgIdx = 0; auto *Arg0 = F->getArg(ArgIdx++); auto *Arg1 = F->getArg(ArgIdx++); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Call = cast(&*It++); [[maybe_unused]] auto *Ret = cast(&*It++); // Check setArgOperand(). Ctx.save(); Call->setArgOperand(0, Arg1); EXPECT_EQ(Call->getArgOperand(0), Arg1); Ctx.revert(); EXPECT_EQ(Call->getArgOperand(0), Arg0); auto *Bar1F = Call->getCalledFunction(); auto *Bar2F = Ctx.createFunction(M->getFunction("bar2")); // Check setCalledOperand(). Ctx.save(); Call->setCalledOperand(Bar2F); EXPECT_EQ(Call->getCalledOperand(), Bar2F); Ctx.revert(); EXPECT_EQ(Call->getCalledOperand(), Bar1F); // Check setCalledFunction(). Ctx.save(); Call->setCalledFunction(Bar2F); EXPECT_EQ(Call->getCalledFunction(), Bar2F); Ctx.revert(); EXPECT_EQ(Call->getCalledFunction(), Bar1F); } TEST_F(TrackerTest, InvokeSetters) { parseIR(C, R"IR( define void @foo(i8 %arg) { bb0: invoke i8 @foo(i8 %arg) to label %normal_bb unwind label %exception_bb normal_bb: ret void exception_bb: ret void other_bb: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *NormalBB = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "normal_bb"))); auto *ExceptionBB = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "exception_bb"))); auto *OtherBB = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "other_bb"))); auto It = BB0->begin(); auto *Invoke = cast(&*It++); // Check setNormalDest(). Ctx.save(); Invoke->setNormalDest(OtherBB); EXPECT_EQ(Invoke->getNormalDest(), OtherBB); Ctx.revert(); EXPECT_EQ(Invoke->getNormalDest(), NormalBB); // Check setUnwindDest(). Ctx.save(); Invoke->setUnwindDest(OtherBB); EXPECT_EQ(Invoke->getUnwindDest(), OtherBB); Ctx.revert(); EXPECT_EQ(Invoke->getUnwindDest(), ExceptionBB); // Check setSuccessor(). Ctx.save(); Invoke->setSuccessor(0, OtherBB); EXPECT_EQ(Invoke->getSuccessor(0), OtherBB); Ctx.revert(); EXPECT_EQ(Invoke->getSuccessor(0), NormalBB); Ctx.save(); Invoke->setSuccessor(1, OtherBB); EXPECT_EQ(Invoke->getSuccessor(1), OtherBB); Ctx.revert(); EXPECT_EQ(Invoke->getSuccessor(1), ExceptionBB); } TEST_F(TrackerTest, CatchSwitchInst) { parseIR(C, R"IR( define void @foo(i32 %cond0, i32 %cond1) { bb0: %cs0 = catchswitch within none [label %handler0, label %handler1] unwind to caller bb1: %cs1 = catchswitch within %cs0 [label %handler0, label %handler1] unwind label %cleanup handler0: ret void handler1: ret void cleanup: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *BB1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb1"))); auto *Handler0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "handler0"))); auto *Handler1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "handler1"))); auto *CS0 = cast(&*BB0->begin()); auto *CS1 = cast(&*BB1->begin()); // Check setParentPad(). auto *OrigPad = CS0->getParentPad(); auto *NewPad = CS1; EXPECT_NE(NewPad, OrigPad); Ctx.save(); CS0->setParentPad(NewPad); EXPECT_EQ(CS0->getParentPad(), NewPad); Ctx.revert(); EXPECT_EQ(CS0->getParentPad(), OrigPad); // Check setUnwindDest(). auto *OrigUnwindDest = CS1->getUnwindDest(); auto *NewUnwindDest = BB0; EXPECT_NE(NewUnwindDest, OrigUnwindDest); Ctx.save(); CS1->setUnwindDest(NewUnwindDest); EXPECT_EQ(CS1->getUnwindDest(), NewUnwindDest); Ctx.revert(); EXPECT_EQ(CS1->getUnwindDest(), OrigUnwindDest); // Check setSuccessor(). auto *OrigSuccessor = CS0->getSuccessor(0); auto *NewSuccessor = BB0; EXPECT_NE(NewSuccessor, OrigSuccessor); Ctx.save(); CS0->setSuccessor(0, NewSuccessor); EXPECT_EQ(CS0->getSuccessor(0), NewSuccessor); Ctx.revert(); EXPECT_EQ(CS0->getSuccessor(0), OrigSuccessor); // Check addHandler(). Ctx.save(); CS0->addHandler(BB0); EXPECT_EQ(CS0->getNumHandlers(), 3u); Ctx.revert(); EXPECT_EQ(CS0->getNumHandlers(), 2u); auto HIt = CS0->handler_begin(); EXPECT_EQ(*HIt++, Handler0); EXPECT_EQ(*HIt++, Handler1); } TEST_F(TrackerTest, LandingPadInstSetters) { parseIR(C, R"IR( define void @foo() { entry: invoke void @foo() to label %bb unwind label %unwind unwind: %lpad = landingpad { ptr, i32 } catch ptr null ret void bb: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); auto *LLVMUnwind = getBasicBlockByName(LLVMF, "unwind"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *Unwind = cast(Ctx.getValue(LLVMUnwind)); auto It = Unwind->begin(); auto *LPad = cast(&*It++); [[maybe_unused]] auto *Ret = cast(&*It++); // Check setCleanup(). auto OrigIsCleanup = LPad->isCleanup(); auto NewIsCleanup = true; EXPECT_NE(NewIsCleanup, OrigIsCleanup); Ctx.save(); LPad->setCleanup(NewIsCleanup); EXPECT_EQ(LPad->isCleanup(), NewIsCleanup); Ctx.revert(); EXPECT_EQ(LPad->isCleanup(), OrigIsCleanup); } TEST_F(TrackerTest, CatchReturnInstSetters) { parseIR(C, R"IR( define void @foo() { dispatch: %cs = catchswitch within none [label %catch] unwind to caller catch: %catchpad = catchpad within %cs [ptr @foo] catchret from %catchpad to label %continue continue: ret void catch2: %catchpad2 = catchpad within %cs [ptr @foo] ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); BasicBlock *LLVMCatch = getBasicBlockByName(LLVMF, "catch"); auto LLVMIt = LLVMCatch->begin(); [[maybe_unused]] auto *LLVMCP = cast(&*LLVMIt++); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *Catch = cast(Ctx.getValue(LLVMCatch)); auto *Catch2 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "catch2"))); auto It = Catch->begin(); [[maybe_unused]] auto *CP = cast(&*It++); auto *CR = cast(&*It++); auto *CP2 = cast(&*Catch2->begin()); // Check setCatchPad(). auto *OrigCP = CR->getCatchPad(); auto *NewCP = CP2; EXPECT_NE(NewCP, OrigCP); Ctx.save(); CR->setCatchPad(NewCP); EXPECT_EQ(CR->getCatchPad(), NewCP); Ctx.revert(); EXPECT_EQ(CR->getCatchPad(), OrigCP); // Check setSuccessor(). auto *OrigSucc = CR->getSuccessor(); auto *NewSucc = Catch; EXPECT_NE(NewSucc, OrigSucc); Ctx.save(); CR->setSuccessor(NewSucc); EXPECT_EQ(CR->getSuccessor(), NewSucc); Ctx.revert(); EXPECT_EQ(CR->getSuccessor(), OrigSucc); } TEST_F(TrackerTest, CleanupReturnInstSetters) { parseIR(C, R"IR( define void @foo() { dispatch: invoke void @foo() to label %throw unwind label %cleanup throw: ret void cleanup: %cleanuppad = cleanuppad within none [] cleanupret from %cleanuppad unwind label %cleanup2 cleanup2: %cleanuppad2 = cleanuppad within none [] ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); BasicBlock *LLVMCleanup = getBasicBlockByName(LLVMF, "cleanup"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *Throw = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "throw"))); auto *Cleanup = cast(Ctx.getValue(LLVMCleanup)); auto *Cleanup2 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "cleanup2"))); auto It = Cleanup->begin(); [[maybe_unused]] auto *CP = cast(&*It++); auto *CRI = cast(&*It++); auto *CP2 = cast(&*Cleanup2->begin()); // Check setCleanupPad(). auto *OrigCleanupPad = CRI->getCleanupPad(); auto *NewCleanupPad = CP2; EXPECT_NE(NewCleanupPad, OrigCleanupPad); Ctx.save(); CRI->setCleanupPad(NewCleanupPad); EXPECT_EQ(CRI->getCleanupPad(), NewCleanupPad); Ctx.revert(); EXPECT_EQ(CRI->getCleanupPad(), OrigCleanupPad); // Check setUnwindDest(). auto *OrigUnwindDest = CRI->getUnwindDest(); auto *NewUnwindDest = Throw; EXPECT_NE(NewUnwindDest, OrigUnwindDest); Ctx.save(); CRI->setUnwindDest(NewUnwindDest); EXPECT_EQ(CRI->getUnwindDest(), NewUnwindDest); Ctx.revert(); EXPECT_EQ(CRI->getUnwindDest(), OrigUnwindDest); } TEST_F(TrackerTest, SwitchInstSetters) { parseIR(C, R"IR( define void @foo(i32 %cond0, i32 %cond1) { entry: switch i32 %cond0, label %default [ i32 0, label %bb0 i32 1, label %bb1 ] bb0: ret void bb1: ret void default: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); auto *LLVMEntry = getBasicBlockByName(LLVMF, "entry"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *Cond1 = F.getArg(1); auto *Entry = cast(Ctx.getValue(LLVMEntry)); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *BB1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb1"))); auto *Switch = cast(&*Entry->begin()); // Check setCondition(). auto *OrigCond = Switch->getCondition(); auto *NewCond = Cond1; EXPECT_NE(NewCond, OrigCond); Ctx.save(); Switch->setCondition(NewCond); EXPECT_EQ(Switch->getCondition(), NewCond); Ctx.revert(); EXPECT_EQ(Switch->getCondition(), OrigCond); // Check setDefaultDest(). auto *OrigDefaultDest = Switch->getDefaultDest(); auto *NewDefaultDest = Entry; EXPECT_NE(NewDefaultDest, OrigDefaultDest); Ctx.save(); Switch->setDefaultDest(NewDefaultDest); EXPECT_EQ(Switch->getDefaultDest(), NewDefaultDest); Ctx.revert(); EXPECT_EQ(Switch->getDefaultDest(), OrigDefaultDest); // Check setSuccessor(). auto *OrigSucc = Switch->getSuccessor(0); auto *NewSucc = Entry; EXPECT_NE(NewSucc, OrigSucc); Ctx.save(); Switch->setSuccessor(0, NewSucc); EXPECT_EQ(Switch->getSuccessor(0), NewSucc); Ctx.revert(); EXPECT_EQ(Switch->getSuccessor(0), OrigSucc); // Check addCase(). auto *Zero = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 0); auto *One = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 1); auto *FortyTwo = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 42); Ctx.save(); Switch->addCase(FortyTwo, Entry); EXPECT_EQ(Switch->getNumCases(), 3u); EXPECT_EQ(Switch->findCaseDest(Entry), FortyTwo); EXPECT_EQ(Switch->findCaseValue(FortyTwo)->getCaseSuccessor(), Entry); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); Ctx.revert(); EXPECT_EQ(Switch->getNumCases(), 2u); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); // Check removeCase(). Ctx.save(); Switch->removeCase(Switch->findCaseValue(Zero)); EXPECT_EQ(Switch->getNumCases(), 1u); EXPECT_EQ(Switch->findCaseDest(BB1), One); Ctx.revert(); EXPECT_EQ(Switch->getNumCases(), 2u); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); } TEST_F(TrackerTest, SwitchInstPreservesSuccesorOrder) { parseIR(C, R"IR( define void @foo(i32 %cond0) { entry: switch i32 %cond0, label %default [ i32 0, label %bb0 i32 1, label %bb1 i32 2, label %bb2 ] bb0: ret void bb1: ret void bb2: ret void default: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); auto *LLVMEntry = getBasicBlockByName(LLVMF, "entry"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *Entry = cast(Ctx.getValue(LLVMEntry)); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *BB1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb1"))); auto *BB2 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb2"))); auto *Switch = cast(&*Entry->begin()); auto *DefaultDest = Switch->getDefaultDest(); auto *Zero = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 0); auto *One = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 1); auto *Two = sandboxir::ConstantInt::get(sandboxir::Type::getInt32Ty(Ctx), 2); // Check that we can properly revert a removeCase multiple positions apart // from the end of the operand list. Ctx.save(); Switch->removeCase(Switch->findCaseValue(Zero)); EXPECT_EQ(Switch->getNumCases(), 2u); Ctx.revert(); EXPECT_EQ(Switch->getNumCases(), 3u); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); EXPECT_EQ(Switch->findCaseDest(BB2), Two); EXPECT_EQ(Switch->getSuccessor(0), DefaultDest); EXPECT_EQ(Switch->getSuccessor(1), BB0); EXPECT_EQ(Switch->getSuccessor(2), BB1); EXPECT_EQ(Switch->getSuccessor(3), BB2); // Check that we can properly revert a removeCase of the last case. Ctx.save(); Switch->removeCase(Switch->findCaseValue(Two)); EXPECT_EQ(Switch->getNumCases(), 2u); Ctx.revert(); EXPECT_EQ(Switch->getNumCases(), 3u); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); EXPECT_EQ(Switch->findCaseDest(BB2), Two); EXPECT_EQ(Switch->getSuccessor(0), DefaultDest); EXPECT_EQ(Switch->getSuccessor(1), BB0); EXPECT_EQ(Switch->getSuccessor(2), BB1); EXPECT_EQ(Switch->getSuccessor(3), BB2); // Check order is preserved after reverting multiple removeCase invocations. Ctx.save(); Switch->removeCase(Switch->findCaseValue(One)); Switch->removeCase(Switch->findCaseValue(Zero)); Switch->removeCase(Switch->findCaseValue(Two)); EXPECT_EQ(Switch->getNumCases(), 0u); Ctx.revert(); EXPECT_EQ(Switch->getNumCases(), 3u); EXPECT_EQ(Switch->findCaseDest(BB0), Zero); EXPECT_EQ(Switch->findCaseDest(BB1), One); EXPECT_EQ(Switch->findCaseDest(BB2), Two); EXPECT_EQ(Switch->getSuccessor(0), DefaultDest); EXPECT_EQ(Switch->getSuccessor(1), BB0); EXPECT_EQ(Switch->getSuccessor(2), BB1); EXPECT_EQ(Switch->getSuccessor(3), BB2); } TEST_F(TrackerTest, SelectInst) { parseIR(C, R"IR( define void @foo(i1 %c0, i8 %v0, i8 %v1) { %sel = select i1 %c0, i8 %v0, i8 %v1 ret void } )IR"); llvm::Function *LLVMF = &*M->getFunction("foo"); sandboxir::Context Ctx(C); sandboxir::Function *F = Ctx.createFunction(LLVMF); auto *V0 = F->getArg(1); auto *V1 = F->getArg(2); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Select = cast(&*It++); // Check tracking for swapValues. Ctx.save(); Select->swapValues(); EXPECT_EQ(Select->getTrueValue(), V1); EXPECT_EQ(Select->getFalseValue(), V0); Ctx.revert(); EXPECT_EQ(Select->getTrueValue(), V0); EXPECT_EQ(Select->getFalseValue(), V1); } TEST_F(TrackerTest, ShuffleVectorInst) { parseIR(C, R"IR( define void @foo(<2 x i8> %v1, <2 x i8> %v2) { %shuf = shufflevector <2 x i8> %v1, <2 x i8> %v2, <2 x i32> ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *SVI = cast(&*It++); // Check setShuffleMask. SmallVector OrigMask(SVI->getShuffleMask()); Ctx.save(); SVI->setShuffleMask(ArrayRef({0, 0})); EXPECT_NE(SVI->getShuffleMask(), ArrayRef(OrigMask)); Ctx.revert(); EXPECT_EQ(SVI->getShuffleMask(), ArrayRef(OrigMask)); // Check commute. auto *Op0 = SVI->getOperand(0); auto *Op1 = SVI->getOperand(1); Ctx.save(); SVI->commute(); EXPECT_EQ(SVI->getOperand(0), Op1); EXPECT_EQ(SVI->getOperand(1), Op0); EXPECT_NE(SVI->getShuffleMask(), ArrayRef(OrigMask)); Ctx.revert(); EXPECT_EQ(SVI->getOperand(0), Op0); EXPECT_EQ(SVI->getOperand(1), Op1); EXPECT_EQ(SVI->getShuffleMask(), ArrayRef(OrigMask)); } TEST_F(TrackerTest, PossiblyDisjointInstSetters) { parseIR(C, R"IR( define void @foo(i8 %arg0, i8 %arg1) { %or = or i8 %arg0, %arg1 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *PDI = cast(&*It++); // Check setIsDisjoint(). auto OrigIsDisjoint = PDI->isDisjoint(); auto NewIsDisjoint = true; EXPECT_NE(NewIsDisjoint, OrigIsDisjoint); Ctx.save(); PDI->setIsDisjoint(NewIsDisjoint); EXPECT_EQ(PDI->isDisjoint(), NewIsDisjoint); Ctx.revert(); EXPECT_EQ(PDI->isDisjoint(), OrigIsDisjoint); } TEST_F(TrackerTest, PossiblyNonNegInstSetters) { parseIR(C, R"IR( define void @foo(i32 %arg) { %zext = zext i32 %arg to i64 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *PNNI = cast(&*It++); // Check setNonNeg(). auto OrigNonNeg = PNNI->hasNonNeg(); auto NewNonNeg = true; EXPECT_NE(NewNonNeg, OrigNonNeg); Ctx.save(); PNNI->setNonNeg(NewNonNeg); EXPECT_EQ(PNNI->hasNonNeg(), NewNonNeg); Ctx.revert(); EXPECT_EQ(PNNI->hasNonNeg(), OrigNonNeg); } TEST_F(TrackerTest, AtomicRMWSetters) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %arg) { %atomicrmw = atomicrmw add ptr %ptr, i8 %arg acquire, align 128 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *RMW = cast(&*It++); // Check setAlignment(). Ctx.save(); auto OrigAlign = RMW->getAlign(); Align NewAlign(1024); EXPECT_NE(NewAlign, OrigAlign); RMW->setAlignment(NewAlign); EXPECT_EQ(RMW->getAlign(), NewAlign); Ctx.revert(); EXPECT_EQ(RMW->getAlign(), OrigAlign); // Check setVolatile(). Ctx.save(); auto OrigIsVolatile = RMW->isVolatile(); bool NewIsVolatile = true; EXPECT_NE(NewIsVolatile, OrigIsVolatile); RMW->setVolatile(NewIsVolatile); EXPECT_EQ(RMW->isVolatile(), NewIsVolatile); Ctx.revert(); EXPECT_EQ(RMW->isVolatile(), OrigIsVolatile); // Check setOrdering(). Ctx.save(); auto OrigOrdering = RMW->getOrdering(); auto NewOrdering = AtomicOrdering::SequentiallyConsistent; EXPECT_NE(NewOrdering, OrigOrdering); RMW->setOrdering(NewOrdering); EXPECT_EQ(RMW->getOrdering(), NewOrdering); Ctx.revert(); EXPECT_EQ(RMW->getOrdering(), OrigOrdering); // Check setSyncScopeID(). Ctx.save(); auto OrigSSID = RMW->getSyncScopeID(); auto NewSSID = SyncScope::SingleThread; EXPECT_NE(NewSSID, OrigSSID); RMW->setSyncScopeID(NewSSID); EXPECT_EQ(RMW->getSyncScopeID(), NewSSID); Ctx.revert(); EXPECT_EQ(RMW->getSyncScopeID(), OrigSSID); } TEST_F(TrackerTest, AtomicCmpXchgSetters) { parseIR(C, R"IR( define void @foo(ptr %ptr, i8 %cmp, i8 %new) { %cmpxchg = cmpxchg ptr %ptr, i8 %cmp, i8 %new monotonic monotonic, align 128 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *CmpXchg = cast(&*It++); // Check setAlignment(). Ctx.save(); auto OrigAlign = CmpXchg->getAlign(); Align NewAlign(1024); EXPECT_NE(NewAlign, OrigAlign); CmpXchg->setAlignment(NewAlign); EXPECT_EQ(CmpXchg->getAlign(), NewAlign); Ctx.revert(); EXPECT_EQ(CmpXchg->getAlign(), OrigAlign); // Check setVolatile(). Ctx.save(); auto OrigIsVolatile = CmpXchg->isVolatile(); bool NewIsVolatile = true; EXPECT_NE(NewIsVolatile, OrigIsVolatile); CmpXchg->setVolatile(NewIsVolatile); EXPECT_EQ(CmpXchg->isVolatile(), NewIsVolatile); Ctx.revert(); EXPECT_EQ(CmpXchg->isVolatile(), OrigIsVolatile); // Check setWeak(). Ctx.save(); auto OrigIsWeak = CmpXchg->isWeak(); bool NewIsWeak = true; EXPECT_NE(NewIsWeak, OrigIsWeak); CmpXchg->setWeak(NewIsWeak); EXPECT_EQ(CmpXchg->isWeak(), NewIsWeak); Ctx.revert(); EXPECT_EQ(CmpXchg->isWeak(), OrigIsWeak); // Check setSuccessOrdering(). Ctx.save(); auto OrigSuccessOrdering = CmpXchg->getSuccessOrdering(); auto NewSuccessOrdering = AtomicOrdering::SequentiallyConsistent; EXPECT_NE(NewSuccessOrdering, OrigSuccessOrdering); CmpXchg->setSuccessOrdering(NewSuccessOrdering); EXPECT_EQ(CmpXchg->getSuccessOrdering(), NewSuccessOrdering); Ctx.revert(); EXPECT_EQ(CmpXchg->getSuccessOrdering(), OrigSuccessOrdering); // Check setFailureOrdering(). Ctx.save(); auto OrigFailureOrdering = CmpXchg->getFailureOrdering(); auto NewFailureOrdering = AtomicOrdering::SequentiallyConsistent; EXPECT_NE(NewFailureOrdering, OrigFailureOrdering); CmpXchg->setFailureOrdering(NewFailureOrdering); EXPECT_EQ(CmpXchg->getFailureOrdering(), NewFailureOrdering); Ctx.revert(); EXPECT_EQ(CmpXchg->getFailureOrdering(), OrigFailureOrdering); // Check setSyncScopeID(). Ctx.save(); auto OrigSSID = CmpXchg->getSyncScopeID(); auto NewSSID = SyncScope::SingleThread; EXPECT_NE(NewSSID, OrigSSID); CmpXchg->setSyncScopeID(NewSSID); EXPECT_EQ(CmpXchg->getSyncScopeID(), NewSSID); Ctx.revert(); EXPECT_EQ(CmpXchg->getSyncScopeID(), OrigSSID); } TEST_F(TrackerTest, AllocaInstSetters) { parseIR(C, R"IR( define void @foo(i8 %arg) { %alloca = alloca i32, align 64 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *Alloca = cast(&*It++); // Check setAllocatedType(). Ctx.save(); auto *OrigTy = Alloca->getAllocatedType(); auto *NewTy = sandboxir::Type::getInt64Ty(Ctx); EXPECT_NE(NewTy, OrigTy); Alloca->setAllocatedType(NewTy); EXPECT_EQ(Alloca->getAllocatedType(), NewTy); Ctx.revert(); EXPECT_EQ(Alloca->getAllocatedType(), OrigTy); // Check setAlignment(). Ctx.save(); auto OrigAlign = Alloca->getAlign(); Align NewAlign(128); EXPECT_NE(NewAlign, OrigAlign); Alloca->setAlignment(NewAlign); EXPECT_EQ(Alloca->getAlign(), NewAlign); Ctx.revert(); EXPECT_EQ(Alloca->getAlign(), OrigAlign); // Check setUsedWithInAlloca(). Ctx.save(); auto OrigWIA = Alloca->isUsedWithInAlloca(); bool NewWIA = true; EXPECT_NE(NewWIA, OrigWIA); Alloca->setUsedWithInAlloca(NewWIA); EXPECT_EQ(Alloca->isUsedWithInAlloca(), NewWIA); Ctx.revert(); EXPECT_EQ(Alloca->isUsedWithInAlloca(), OrigWIA); } TEST_F(TrackerTest, CallBrSetters) { parseIR(C, R"IR( define void @foo(i8 %arg) { bb0: callbr void @foo(i8 %arg) to label %bb1 [label %bb2] bb1: ret void bb2: ret void other_bb: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *OtherBB = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "other_bb"))); auto It = BB0->begin(); auto *CallBr = cast(&*It++); // Check setDefaultDest(). Ctx.save(); auto *OrigDefaultDest = CallBr->getDefaultDest(); CallBr->setDefaultDest(OtherBB); EXPECT_EQ(CallBr->getDefaultDest(), OtherBB); Ctx.revert(); EXPECT_EQ(CallBr->getDefaultDest(), OrigDefaultDest); // Check setIndirectDest(). Ctx.save(); auto *OrigIndirectDest = CallBr->getIndirectDest(0); CallBr->setIndirectDest(0, OtherBB); EXPECT_EQ(CallBr->getIndirectDest(0), OtherBB); Ctx.revert(); EXPECT_EQ(CallBr->getIndirectDest(0), OrigIndirectDest); } TEST_F(TrackerTest, FuncletPadInstSetters) { parseIR(C, R"IR( define void @foo() { dispatch: %cs = catchswitch within none [label %handler0] unwind to caller handler0: %catchpad = catchpad within %cs [ptr @foo] ret void handler1: %cleanuppad = cleanuppad within %cs [ptr @foo] ret void bb: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); [[maybe_unused]] auto &F = *Ctx.createFunction(&LLVMF); auto *Dispatch = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "dispatch"))); auto *Handler0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "handler0"))); auto *Handler1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "handler1"))); auto *CP = cast(&*Handler0->begin()); auto *CLP = cast(&*Handler1->begin()); for (auto *FPI : {static_cast(CP), static_cast(CLP)}) { // Check setParentPad(). auto *OrigParentPad = FPI->getParentPad(); auto *NewParentPad = Dispatch; EXPECT_NE(NewParentPad, OrigParentPad); Ctx.save(); FPI->setParentPad(NewParentPad); EXPECT_EQ(FPI->getParentPad(), NewParentPad); Ctx.revert(); EXPECT_EQ(FPI->getParentPad(), OrigParentPad); // Check setArgOperand(). auto *OrigArgOperand = FPI->getArgOperand(0); auto *NewArgOperand = Dispatch; EXPECT_NE(NewArgOperand, OrigArgOperand); Ctx.save(); FPI->setArgOperand(0, NewArgOperand); EXPECT_EQ(FPI->getArgOperand(0), NewArgOperand); Ctx.revert(); EXPECT_EQ(FPI->getArgOperand(0), OrigArgOperand); } } TEST_F(TrackerTest, PHINodeSetters) { parseIR(C, R"IR( define void @foo(i8 %arg0, i8 %arg1, i8 %arg2) { bb0: br label %bb2 bb1: %phi = phi i8 [ %arg0, %bb0 ], [ %arg1, %bb1 ] br label %bb1 bb2: ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); unsigned ArgIdx = 0; auto *Arg0 = F.getArg(ArgIdx++); auto *Arg1 = F.getArg(ArgIdx++); auto *Arg2 = F.getArg(ArgIdx++); auto *BB0 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb0"))); auto *BB1 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb1"))); auto *BB2 = cast( Ctx.getValue(getBasicBlockByName(LLVMF, "bb2"))); auto *PHI = cast(&*BB1->begin()); // Check setIncomingValue(). Ctx.save(); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); PHI->setIncomingValue(0, Arg2); EXPECT_EQ(PHI->getIncomingValue(0), Arg2); Ctx.revert(); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check setIncomingBlock(). Ctx.save(); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); PHI->setIncomingBlock(0, BB2); EXPECT_EQ(PHI->getIncomingBlock(0), BB2); Ctx.revert(); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check addIncoming(). Ctx.save(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); PHI->addIncoming(Arg1, BB2); EXPECT_EQ(PHI->getNumIncomingValues(), 3u); EXPECT_EQ(PHI->getIncomingBlock(2), BB2); EXPECT_EQ(PHI->getIncomingValue(2), Arg1); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check removeIncomingValue(1). Ctx.save(); PHI->removeIncomingValue(1); EXPECT_EQ(PHI->getNumIncomingValues(), 1u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check removeIncomingValue(0). Ctx.save(); PHI->removeIncomingValue(0u); EXPECT_EQ(PHI->getNumIncomingValues(), 1u); EXPECT_EQ(PHI->getIncomingBlock(0), BB1); EXPECT_EQ(PHI->getIncomingValue(0), Arg1); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check removeIncomingValueIf(FromBB1). Ctx.save(); PHI->removeIncomingValueIf( [&](unsigned Idx) { return PHI->getIncomingBlock(Idx) == BB1; }); EXPECT_EQ(PHI->getNumIncomingValues(), 1u); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); // Check removeIncomingValue() remove all. Ctx.save(); PHI->removeIncomingValue(0u); EXPECT_EQ(PHI->getNumIncomingValues(), 1u); EXPECT_EQ(PHI->getIncomingBlock(0), BB1); EXPECT_EQ(PHI->getIncomingValue(0), Arg1); PHI->removeIncomingValue(0u); EXPECT_EQ(PHI->getNumIncomingValues(), 0u); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); // Check removeIncomingValue(BasicBlock *). Ctx.save(); PHI->removeIncomingValue(BB1); EXPECT_EQ(PHI->getNumIncomingValues(), 1u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); Ctx.revert(); EXPECT_EQ(PHI->getNumIncomingValues(), 2u); EXPECT_EQ(PHI->getIncomingBlock(0), BB0); EXPECT_EQ(PHI->getIncomingValue(0), Arg0); EXPECT_EQ(PHI->getIncomingBlock(1), BB1); EXPECT_EQ(PHI->getIncomingValue(1), Arg1); } void checkCmpInst(sandboxir::Context &Ctx, sandboxir::CmpInst *Cmp) { Ctx.save(); auto OrigP = Cmp->getPredicate(); auto NewP = Cmp->getSwappedPredicate(); Cmp->setPredicate(NewP); EXPECT_EQ(Cmp->getPredicate(), NewP); Ctx.revert(); EXPECT_EQ(Cmp->getPredicate(), OrigP); Ctx.save(); auto OrigOp0 = Cmp->getOperand(0); auto OrigOp1 = Cmp->getOperand(1); Cmp->swapOperands(); EXPECT_EQ(Cmp->getPredicate(), NewP); EXPECT_EQ(Cmp->getOperand(0), OrigOp1); EXPECT_EQ(Cmp->getOperand(1), OrigOp0); Ctx.revert(); EXPECT_EQ(Cmp->getPredicate(), OrigP); EXPECT_EQ(Cmp->getOperand(0), OrigOp0); EXPECT_EQ(Cmp->getOperand(1), OrigOp1); } TEST_F(TrackerTest, CmpInst) { SCOPED_TRACE("TrackerTest sandboxir::CmpInst tests"); parseIR(C, R"IR( define void @foo(i64 %i0, i64 %i1, float %f0, float %f1) { %foeq = fcmp ogt float %f0, %f1 %ioeq = icmp uge i64 %i0, %i1 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *FCmp = cast(&*It++); checkCmpInst(Ctx, FCmp); auto *ICmp = cast(&*It++); checkCmpInst(Ctx, ICmp); } TEST_F(TrackerTest, GlobalValueSetters) { parseIR(C, R"IR( define void @foo() { call void @foo() ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto *Call = cast(&*BB->begin()); auto *GV = cast(Call->getCalledOperand()); // Check setUnnamedAddr(). auto OrigUnnamedAddr = GV->getUnnamedAddr(); auto NewUnnamedAddr = sandboxir::GlobalValue::UnnamedAddr::Global; EXPECT_NE(NewUnnamedAddr, OrigUnnamedAddr); Ctx.save(); GV->setUnnamedAddr(NewUnnamedAddr); EXPECT_EQ(GV->getUnnamedAddr(), NewUnnamedAddr); Ctx.revert(); EXPECT_EQ(GV->getUnnamedAddr(), OrigUnnamedAddr); // Check setVisibility(). auto OrigVisibility = GV->getVisibility(); auto NewVisibility = sandboxir::GlobalValue::VisibilityTypes::ProtectedVisibility; EXPECT_NE(NewVisibility, OrigVisibility); Ctx.save(); GV->setVisibility(NewVisibility); EXPECT_EQ(GV->getVisibility(), NewVisibility); Ctx.revert(); EXPECT_EQ(GV->getVisibility(), OrigVisibility); } TEST_F(TrackerTest, GlobalIFuncSetters) { parseIR(C, R"IR( declare external void @bar() @ifunc = ifunc void(), ptr @foo define void @foo() { call void @ifunc() call void @bar() ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *Call0 = cast(&*It++); auto *Call1 = cast(&*It++); // Check classof(), creation. auto *IFunc = cast(Call0->getCalledOperand()); auto *Bar = cast(Call1->getCalledOperand()); // Check setResolver(). auto *OrigResolver = IFunc->getResolver(); auto *NewResolver = Bar; EXPECT_NE(NewResolver, OrigResolver); Ctx.save(); IFunc->setResolver(NewResolver); EXPECT_EQ(IFunc->getResolver(), NewResolver); Ctx.revert(); EXPECT_EQ(IFunc->getResolver(), OrigResolver); } TEST_F(TrackerTest, GlobalVariableSetters) { parseIR(C, R"IR( @glob0 = global i32 42 @glob1 = global i32 43 define void @foo() { %ld0 = load i32, ptr @glob0 %ld1 = load i32, ptr @glob1 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *Ld0 = cast(&*It++); auto *Ld1 = cast(&*It++); // Check classof(), creation. auto *GV0 = cast(Ld0->getPointerOperand()); auto *GV1 = cast(Ld1->getPointerOperand()); // Check setInitializer(). auto *OrigInitializer = GV0->getInitializer(); auto *NewInitializer = GV1->getInitializer(); EXPECT_NE(NewInitializer, OrigInitializer); Ctx.save(); GV0->setInitializer(NewInitializer); EXPECT_EQ(GV0->getInitializer(), NewInitializer); Ctx.revert(); EXPECT_EQ(GV0->getInitializer(), OrigInitializer); // Check setConstant(). bool OrigIsConstant = GV0->isConstant(); bool NewIsConstant = !OrigIsConstant; Ctx.save(); GV0->setConstant(NewIsConstant); EXPECT_EQ(GV0->isConstant(), NewIsConstant); Ctx.revert(); EXPECT_EQ(GV0->isConstant(), OrigIsConstant); // Check setExternallyInitialized(). bool OrigIsExtInit = GV0->isExternallyInitialized(); bool NewIsExtInit = !OrigIsExtInit; Ctx.save(); GV0->setExternallyInitialized(NewIsExtInit); EXPECT_EQ(GV0->isExternallyInitialized(), NewIsExtInit); Ctx.revert(); EXPECT_EQ(GV0->isExternallyInitialized(), OrigIsExtInit); } TEST_F(TrackerTest, GlobalAliasSetters) { parseIR(C, R"IR( @alias = dso_local alias void(), ptr @foo declare void @bar(); define void @foo() { call void @alias() call void @bar() ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *Call0 = cast(&*It++); auto *Call1 = cast(&*It++); auto *Callee1 = cast(Call1->getCalledOperand()); auto *Alias = cast(Call0->getCalledOperand()); // Check setAliasee(). auto *OrigAliasee = Alias->getAliasee(); auto *NewAliasee = Callee1; EXPECT_NE(NewAliasee, OrigAliasee); Ctx.save(); Alias->setAliasee(NewAliasee); EXPECT_EQ(Alias->getAliasee(), NewAliasee); Ctx.revert(); EXPECT_EQ(Alias->getAliasee(), OrigAliasee); } TEST_F(TrackerTest, SetVolatile) { parseIR(C, R"IR( define void @foo(ptr %arg0, i8 %val) { %ld = load i8, ptr %arg0, align 64 store i8 %val, ptr %arg0, align 64 ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); auto *Load = cast(&*It++); auto *Store = cast(&*It++); EXPECT_FALSE(Load->isVolatile()); Ctx.save(); Load->setVolatile(true); EXPECT_TRUE(Load->isVolatile()); Ctx.revert(); EXPECT_FALSE(Load->isVolatile()); EXPECT_FALSE(Store->isVolatile()); Ctx.save(); Store->setVolatile(true); EXPECT_TRUE(Store->isVolatile()); Ctx.revert(); EXPECT_FALSE(Store->isVolatile()); } TEST_F(TrackerTest, Flags) { parseIR(C, R"IR( define void @foo(i32 %arg, float %farg) { %add = add i32 %arg, %arg %fadd = fadd float %farg, %farg %udiv = udiv i32 %arg, %arg ret void } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto &F = *Ctx.createFunction(&LLVMF); auto *BB = &*F.begin(); auto It = BB->begin(); auto *Add = &*It++; auto *FAdd = &*It++; auto *UDiv = &*It++; #define CHECK_FLAG(I, GETTER, SETTER) \ { \ Ctx.save(); \ bool OrigFlag = I->GETTER(); \ bool NewFlag = !OrigFlag; \ I->SETTER(NewFlag); \ EXPECT_EQ(I->GETTER(), NewFlag); \ Ctx.revert(); \ EXPECT_EQ(I->GETTER(), OrigFlag); \ } CHECK_FLAG(Add, hasNoUnsignedWrap, setHasNoUnsignedWrap); CHECK_FLAG(Add, hasNoSignedWrap, setHasNoSignedWrap); CHECK_FLAG(FAdd, isFast, setFast); CHECK_FLAG(FAdd, hasAllowReassoc, setHasAllowReassoc); CHECK_FLAG(UDiv, isExact, setIsExact); CHECK_FLAG(FAdd, hasNoNaNs, setHasNoNaNs); CHECK_FLAG(FAdd, hasNoInfs, setHasNoInfs); CHECK_FLAG(FAdd, hasNoSignedZeros, setHasNoSignedZeros); CHECK_FLAG(FAdd, hasAllowReciprocal, setHasAllowReciprocal); CHECK_FLAG(FAdd, hasAllowContract, setHasAllowContract); CHECK_FLAG(FAdd, hasApproxFunc, setHasApproxFunc); // Check setFastMathFlags(). FastMathFlags OrigFMF = FAdd->getFastMathFlags(); FastMathFlags NewFMF; NewFMF.setAllowReassoc(true); EXPECT_TRUE(NewFMF != OrigFMF); Ctx.save(); FAdd->setFastMathFlags(NewFMF); EXPECT_FALSE(FAdd->getFastMathFlags() != NewFMF); Ctx.revert(); EXPECT_FALSE(FAdd->getFastMathFlags() != OrigFMF); // Check copyFastMathFlags(). Ctx.save(); FAdd->copyFastMathFlags(NewFMF); EXPECT_FALSE(FAdd->getFastMathFlags() != NewFMF); Ctx.revert(); EXPECT_FALSE(FAdd->getFastMathFlags() != OrigFMF); } // IRSnapshotChecker is only defined in debug mode. #ifndef NDEBUG TEST_F(TrackerTest, IRSnapshotCheckerNoChanges) { parseIR(C, R"IR( define i32 @foo(i32 %arg) { %add0 = add i32 %arg, %arg ret i32 %add0 } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); [[maybe_unused]] auto *F = Ctx.createFunction(&LLVMF); sandboxir::IRSnapshotChecker Checker(Ctx); Checker.save(); Checker.expectNoDiff(); } TEST_F(TrackerTest, IRSnapshotCheckerDiesWithUnexpectedChanges) { parseIR(C, R"IR( define i32 @foo(i32 %arg) { %add0 = add i32 %arg, %arg %add1 = add i32 %add0, %arg ret i32 %add1 } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Add1 = &*It++; sandboxir::IRSnapshotChecker Checker(Ctx); Checker.save(); Add1->setOperand(1, Add0); EXPECT_DEATH(Checker.expectNoDiff(), "Found IR difference"); } TEST_F(TrackerTest, IRSnapshotCheckerSaveMultipleTimes) { parseIR(C, R"IR( define i32 @foo(i32 %arg) { %add0 = add i32 %arg, %arg %add1 = add i32 %add0, %arg ret i32 %add1 } )IR"); Function &LLVMF = *M->getFunction("foo"); sandboxir::Context Ctx(C); auto *F = Ctx.createFunction(&LLVMF); auto *BB = &*F->begin(); auto It = BB->begin(); sandboxir::Instruction *Add0 = &*It++; sandboxir::Instruction *Add1 = &*It++; sandboxir::IRSnapshotChecker Checker(Ctx); Checker.save(); Add1->setOperand(1, Add0); // Now IR differs from the last snapshot. Let's take a new snapshot. Checker.save(); // The new snapshot should have replaced the old one, so this should succeed. Checker.expectNoDiff(); } #endif // NDEBUG