//===------------------------ UnitTestRunner.cpp --------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // TO ADD A NEW TEST, search in this file for [new_tests] // // Provides a mechanism for doing faux unit tests. The idea is to emulate the // basic functionality of calling a function and validating its results/effects. // // This is done via the test_specification instruction. Using one or more // instances of it in your function, you can specify which test (i.e. UnitTest // subclass) should be run and what arguments should be provided to it. The // test grabs the arguments it expects out of the test::Arguments instance it is // provided. It calls some function or functions. It then prints out // interesting results. These results can then be FileCheck'd. // // CASE STUDY: // Here's an example of how it works: // 1) A test test/SILOptimizer/interesting_functionality.sil runs this pass: // // RUN: %target-sil-opt -unit-test-runner %s 2>&1 | %FileCheck %s // 2) A function in interesting_functionality.sil contains the // test_specification instruction. // sil @f : $() -> () { // ... // test_specification "my-neato-utility 43 @trace[2] @function[other_fun]" // ... // } // 3) UnitTestRunner sees the name "my-neato-utility", instantiates the // corresponding UnitTest subclass MyNeatoUtilityTest, and calls ::invoke() // on it, passing an test::Arguments that contains // (43 : unsigned long, someValue : SILValue, other_fun : SILFunction *) // // 4) MyNeatoUtilityTest calls takeUInt(), takeValue(), and takeFunction() on // the test::Arguments instance. // auto count = arguments.takeUInt(); // auto target = arguments.takeValue(); // auto callee = arguments.takeFunction(); // 5) MyNeatoUtilityTest calls myNeatoUtility, passing these values along. // myNeatoUtility(count, target, callee); // 6) MyNeatoUtilityTest then dumps out the current function, which was modified // in the process. // getFunction()->dump(); // 7) The test file test/SILOptimizer/interesting_functionality.sil matches the // expected contents of the modified function: // // CHECK-LABEL: sil @f // // CHECK-NOT: function_ref @other_fun // // [new_tests] TESTING MORE FUNCTIONALITY: // // 1) Add a new UnitTest subclass. // - In the section "Unit Tests Subclasses" section ordered alphabetically. // - Add a constructor: NewTest(UnitTestRunner *pass) : UnitTest(pass) {} // - Implement invoke: void invoke(test::Arguments &arguments) override {...} // - Call the take{TYPE} methods to get the arguments you need. // - Call your function with those arguments. // 2) Add a new ADD_UNIT_TEST_SUBCLASS line. // - Ordered alphabetically. // //===----------------------------------------------------------------------===// #include "swift/AST/Type.h" #include "swift/Basic/TaggedUnion.h" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/OSSALifetimeCompletion.h" #include "swift/SIL/OwnershipLiveness.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/PrunedLiveness.h" #include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/SILArgumentArrayRef.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILBridging.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/ScopedAddressUtils.h" #include "swift/SILOptimizer/Analysis/BasicCalleeAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Transforms/SimplifyCFG.h" #include "swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h" #include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "swift/SILOptimizer/Utils/ParseTestSpecification.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace swift; using namespace swift::test; namespace { class UnitTestRunner; class UnitTest { UnitTestRunner *pass; protected: template Analysis *getAnalysis(); UnitTestRunner *getPass() { return pass; } SILFunction *getFunction(); public: UnitTest(UnitTestRunner *pass) : pass(pass){}; virtual ~UnitTest() {} virtual void invoke(Arguments &arguments) = 0; }; class UnitTestRunner : public SILFunctionTransform { void printTestLifetime(bool begin, unsigned testIndex, unsigned testCount, StringRef name, ArrayRef components) { StringRef word = begin ? "\nbegin" : "end"; llvm::errs() << word << " running test " << testIndex + 1 << " of " << testCount << " on " << getFunction()->getName() << ": " << name << " with: "; for (unsigned long index = 0, size = components.size(); index < size; ++index) { auto componentString = components[index].trim(); if (componentString.empty()) continue; llvm::errs() << componentString; if (index != size - 1) { llvm::errs() << ", "; } } llvm::errs() << "\n"; } template void withTest(StringRef name, Doit doit); void runTest(StringRef name, Arguments &arguments) { withTest(name, [&](auto *test) { test->invoke(arguments); }); } void run() override { llvm::SmallVector testSpecifications; getTestSpecifications(getFunction(), testSpecifications); Arguments arguments; SmallVector components; for (unsigned long index = 0, size = testSpecifications.size(); index < size; ++index) { components.clear(); arguments.clear(); auto testSpecification = testSpecifications[index]; test::parseTestArgumentsFromSpecification( getFunction(), testSpecification, arguments, components); auto name = arguments.takeString(); ArrayRef argumentStrings = components; argumentStrings = argumentStrings.drop_front(); printTestLifetime(/*begin=*/true, /*index=*/index, /*size=*/size, name, argumentStrings); runTest(name, arguments); printTestLifetime(/*begin=*/false, /*index=*/index, /*size=*/size, name, argumentStrings); } } friend class UnitTest; }; //===----------------------------------------------------------------------===// // MARK: General Unit Tests //===----------------------------------------------------------------------===// // Arguments: NONE // Dumps: // - the function struct DumpFunction : UnitTest { DumpFunction(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { getFunction()->dump(); } }; // Arguments: NONE // Dumps: the index of the self argument of the current function struct FunctionGetSelfArgumentIndex : UnitTest { FunctionGetSelfArgumentIndex(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto index = BridgedFunction{getFunction()}.getSelfArgumentIndex(); llvm::errs() << "self argument index = " << index << "\n"; } }; // Arguments: // - string: list of characters, each of which specifies subsequent arguments // - A: (block) argument // - F: function // - B: block // - I: instruction // - V: value // - O: operand // - b: boolean // - u: unsigned // - s: string // - ... // - an argument of the type specified in the initial string // - ... // Dumps: // - for each argument (after the initial string) // - its type // - something to identify the instance (mostly this means calling dump) struct TestSpecificationTest : UnitTest { TestSpecificationTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto expectedFields = arguments.takeString(); for (auto expectedField : expectedFields) { switch (expectedField) { case 'A': { auto *argument = arguments.takeBlockArgument(); llvm::errs() << "argument:\n"; argument->dump(); break; } case 'F': { auto *function = arguments.takeFunction(); llvm::errs() << "function: " << function->getName() << "\n"; break; } case 'B': { auto *block = arguments.takeBlock(); llvm::errs() << "block:\n"; block->dump(); break; } case 'I': { auto *instruction = arguments.takeInstruction(); llvm::errs() << "instruction: "; instruction->dump(); break; } case 'V': { auto value = arguments.takeValue(); llvm::errs() << "value: "; value->dump(); break; } case 'O': { auto *operand = arguments.takeOperand(); llvm::errs() << "operand: "; operand->print(llvm::errs()); break; } case 'u': { auto u = arguments.takeUInt(); llvm::errs() << "uint: " << u << "\n"; break; } case 'b': { auto b = arguments.takeBool(); llvm::errs() << "bool: " << b << "\n"; break; } case 's': { auto s = arguments.takeString(); llvm::errs() << "string: " << s << "\n"; break; } default: llvm_unreachable("unknown field type was expected?!"); } } } }; // Arguments: // - value: the value to check for escaping // Dumps: // - the value // - whether it has a pointer escape struct OwnershipUtilsHasPointerEscape : UnitTest { OwnershipUtilsHasPointerEscape(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); auto has = findPointerEscape(value); value->print(llvm::errs()); auto *boolString = has ? "true" : "false"; llvm::errs() << boolString << "\n"; } }; // Arguments: // - value: whose type will be printed // Dumps: // - the type lowering of the type struct PrintTypeLowering : UnitTest { PrintTypeLowering(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); auto ty = value->getType(); getFunction()->getTypeLowering(ty).print(llvm::dbgs()); } }; //===----------------------------------------------------------------------===// // MARK: OSSA Lifetime Unit Tests //===----------------------------------------------------------------------===// // Arguments: // - the lexical borrow to fold // Dumps: // - the function struct LexicalDestroyFoldingTest : UnitTest { LexicalDestroyFoldingTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *dominanceAnalysis = getAnalysis(); DominanceInfo *domTree = dominanceAnalysis->get(getFunction()); auto value = arguments.takeValue(); auto *bbi = cast(value); InstructionDeleter deleter; foldDestroysOfCopiedLexicalBorrow(bbi, *domTree, deleter); getFunction()->dump(); } }; // Arguments: // - variadic list of - instruction: a last user // Dumps: // - the insertion points struct PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest : UnitTest { PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest( UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { PrunedLivenessBoundary boundary; while (arguments.hasUntaken()) { boundary.lastUsers.push_back(arguments.takeInstruction()); } boundary.visitInsertionPoints( [](SILBasicBlock::iterator point) { point->dump(); }); } }; // Arguments: // - SILValue: value to a analyze // Dumps: // - the liveness result and boundary struct SSALivenessTest : UnitTest { SSALivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); assert(!arguments.hasUntaken()); llvm::outs() << "SSA lifetime analysis: " << value; SmallVector discoveredBlocks; SSAPrunedLiveness liveness(value->getFunction(), &discoveredBlocks); liveness.initializeDef(value); LiveRangeSummary summary = liveness.computeSimple(); if (summary.innerBorrowKind == InnerBorrowKind::Reborrowed) llvm::outs() << "Incomplete liveness: Reborrowed inner scope\n"; if (summary.addressUseKind == AddressUseKind::PointerEscape) llvm::outs() << "Incomplete liveness: Escaping address\n"; else if (summary.addressUseKind == AddressUseKind::Unknown) llvm::outs() << "Incomplete liveness: Unknown address use\n"; liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - SILValue: value to a analyze // Dumps: // - the liveness result and boundary struct ScopedAddressLivenessTest : UnitTest { ScopedAddressLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); assert(!arguments.hasUntaken()); llvm::outs() << "Scoped address analysis: " << value; ScopedAddressValue scopedAddress(value); assert(scopedAddress); SmallVector discoveredBlocks; SSAPrunedLiveness liveness(value->getFunction(), &discoveredBlocks); scopedAddress.computeTransitiveLiveness(liveness); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - variadic list of live-range defining values or instructions // Dumps: // - the liveness result and boundary // // Computes liveness for the specified def nodes by finding all their direct SSA // uses. If the def is an instruction, then all results are considered. struct MultiDefLivenessTest : UnitTest { MultiDefLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SmallVector discoveredBlocks; MultiDefPrunedLiveness liveness(getFunction(), &discoveredBlocks); llvm::outs() << "MultiDef lifetime analysis:\n"; while (arguments.hasUntaken()) { auto argument = arguments.takeArgument(); if (isa(argument)) { auto *instruction = cast(argument).getValue(); llvm::outs() << " def instruction: " << instruction; liveness.initializeDef(instruction); } else { SILValue value = cast(argument).getValue(); llvm::outs() << " def value: " << value; liveness.initializeDef(value); } } liveness.computeSimple(); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - the string "defs:" // - list of live-range defining values or instructions // - the string "uses:" // - variadic list of live-range user instructions // Dumps: // - the liveness result and boundary // // Computes liveness for the specified def nodes by considering only the // specified uses. The actual uses of the def nodes are ignored. // // This is useful for testing non-ssa liveness, for example, of memory // locations. In that case, the def nodes may be stores and the uses may be // destroy_addrs. struct MultiDefUseLivenessTest : UnitTest { MultiDefUseLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SmallVector discoveredBlocks; MultiDefPrunedLiveness liveness(getFunction(), &discoveredBlocks); llvm::outs() << "MultiDef lifetime analysis:\n"; if (arguments.takeString() != "defs:") { llvm::report_fatal_error( "test specification expects the 'defs:' label\n"); } while (true) { auto argument = arguments.takeArgument(); if (isa(argument)) { auto *instruction = cast(argument).getValue(); llvm::outs() << " def instruction: " << *instruction; liveness.initializeDef(instruction); continue; } if (isa(argument)) { SILValue value = cast(argument).getValue(); llvm::outs() << " def value: " << value; liveness.initializeDef(value); continue; } if (cast(argument).getValue() != "uses:") { llvm::report_fatal_error( "test specification expects the 'uses:' label\n"); } break; } while (arguments.hasUntaken()) { auto *inst = arguments.takeInstruction(); // lifetimeEnding has no effects on liveness, it's only a cache for the // caller. liveness.updateForUse(inst, /*lifetimeEnding*/false); } liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - value: entity whose fields' livenesses are being computed // - string: "defs:" // - variadic list of triples consisting of // - value: a live-range defining value // - int: the beginning of the range of fields defined by the value // - int: the end of the range of the fields defined by the value // - the string "uses:" // - variadic list of quadruples consisting of // - instruction: a live-range user // - bool: whether the user is lifetime-ending // - int: the beginning of the range of fields used by the instruction // - int: the end of the range of fields used by the instruction // Dumps: // - the liveness result and boundary // // Computes liveness for the specified def nodes by considering the // specified uses. The actual uses of the def nodes are ignored. // // This is useful for testing non-ssa liveness, for example, of memory // locations. In that case, the def nodes may be stores and the uses may be // destroy_addrs. struct FieldSensitiveMultiDefUseLiveRangeTest : UnitTest { FieldSensitiveMultiDefUseLiveRangeTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SmallVector discoveredBlocks; auto value = arguments.takeValue(); FieldSensitiveMultiDefPrunedLiveRange liveness(getFunction(), value, &discoveredBlocks); llvm::outs() << "FieldSensitive MultiDef lifetime analysis:\n"; if (arguments.takeString() != "defs:") { llvm::report_fatal_error( "test specification expects the 'defs:' label\n"); } while (true) { auto argument = arguments.takeArgument(); if (isa(argument)) { if(cast(argument).getValue() != "uses:") { llvm::report_fatal_error( "test specification expects the 'uses:' label\n"); } break; } auto begin = arguments.takeUInt(); auto end = arguments.takeUInt(); TypeTreeLeafTypeRange range(begin, end); if (isa(argument)) { auto *instruction = cast(argument).getValue(); llvm::outs() << " def in range [" << begin << ", " << end << ") instruction: " << *instruction; liveness.initializeDef(instruction, range); continue; } if (isa(argument)) { SILValue value = cast(argument).getValue(); llvm::outs() << " def in range [" << begin << ", " << end << ") value: " << value; liveness.initializeDef(value, range); continue; } llvm::report_fatal_error( "test specification expects the 'uses:' label\n"); } liveness.finishedInitializationOfDefs(); while (arguments.hasUntaken()) { auto *inst = arguments.takeInstruction(); auto lifetimeEnding = arguments.takeBool(); auto begin = arguments.takeUInt(); auto end = arguments.takeUInt(); TypeTreeLeafTypeRange range(begin, end); liveness.updateForUse(inst, range, lifetimeEnding); } liveness.print(llvm::errs()); FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements()); liveness.computeBoundary(boundary); boundary.print(llvm::errs()); } }; // Arguments: // - bool: pruneDebug // - bool: maximizeLifetimes // - bool: "respectAccessScopes", whether to contract lifetimes to end within // access scopes which they previously enclosed but can't be hoisted // before // - SILValue: value to canonicalize // Dumps: // - function after value canonicalization struct CanonicalizeOSSALifetimeTest : UnitTest { CanonicalizeOSSALifetimeTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *accessBlockAnalysis = getAnalysis(); auto *dominanceAnalysis = getAnalysis(); DominanceInfo *domTree = dominanceAnalysis->get(getFunction()); auto *calleeAnalysis = getAnalysis(); auto pruneDebug = arguments.takeBool(); auto maximizeLifetimes = arguments.takeBool(); auto respectAccessScopes = arguments.takeBool(); InstructionDeleter deleter; CanonicalizeOSSALifetime canonicalizer( pruneDebug, maximizeLifetimes, getFunction(), respectAccessScopes ? accessBlockAnalysis : nullptr, domTree, calleeAnalysis, deleter); auto value = arguments.takeValue(); canonicalizer.canonicalizeValueLifetime(value); getFunction()->dump(); } }; // Arguments: // - SILValue: value to canonicalize // Dumps: // - function after value canonicalization struct CanonicalizeBorrowScopeTest : UnitTest { CanonicalizeBorrowScopeTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); auto borrowedValue = BorrowedValue(value); assert(borrowedValue && "specified value isn't a BorrowedValue!?"); InstructionDeleter deleter; CanonicalizeBorrowScope canonicalizer(value->getFunction(), deleter); canonicalizer.canonicalizeBorrowScope(borrowedValue); getFunction()->dump(); } }; // Arguments: // - instruction // Dumps: // - instruction // - whether it's a deinit barrier struct IsDeinitBarrierTest : UnitTest { IsDeinitBarrierTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *instruction = arguments.takeInstruction(); auto *analysis = getAnalysis(); auto isBarrier = isDeinitBarrier(instruction, analysis); instruction->dump(); auto *boolString = isBarrier ? "true" : "false"; llvm::errs() << boolString << "\n"; } }; // Arguments: // - value // Dumps: // - value // - whether it's lexical struct IsLexicalTest : UnitTest { IsLexicalTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); auto isLexical = value->isLexical(); value->dump(); auto *boolString = isLexical ? "true" : "false"; llvm::errs() << boolString << "\n"; } }; struct ShrinkBorrowScopeTest : UnitTest { ShrinkBorrowScopeTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto instruction = arguments.takeValue(); auto expected = arguments.takeBool(); auto *bbi = cast(instruction); auto *analysis = getAnalysis(); SmallVector modifiedCopyValueInsts; InstructionDeleter deleter( InstModCallbacks().onDelete([&](auto *instruction) { llvm::errs() << "DELETED:\n"; instruction->dump(); })); auto shrunk = shrinkBorrowScope(*bbi, deleter, analysis, modifiedCopyValueInsts); unsigned index = 0; for (auto *cvi : modifiedCopyValueInsts) { auto expectedCopy = arguments.takeValue(); llvm::errs() << "rewritten copy " << index << ":\n"; llvm::errs() << "expected:\n"; expectedCopy->print(llvm::errs()); llvm::errs() << "got:\n"; cvi->dump(); assert(cvi == expectedCopy); ++index; } assert(expected == shrunk && "didn't shrink expectedly!?"); } }; // Arguments: // - SILValue: phi // Dumps: // - function // - the adjacent phis struct VisitInnerAdjacentPhisTest : UnitTest { VisitInnerAdjacentPhisTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { getFunction()->dump(); visitInnerAdjacentPhis(cast(arguments.takeValue()), [](auto *argument) -> bool { argument->dump(); return true; }); } }; // Arguments: // - SILValue: value // Dumps: // - function // - the enclosing defs struct FindEnclosingDefsTest : UnitTest { FindEnclosingDefsTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { getFunction()->dump(); llvm::dbgs() << "Enclosing Defs:\n"; visitEnclosingDefs(arguments.takeValue(), [](SILValue def) { def->dump(); return true; }); } }; // Arguments: // - SILValue: value // Dumps: // - function // - the borrow introducers struct FindBorrowIntroducers : UnitTest { FindBorrowIntroducers(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { getFunction()->dump(); llvm::dbgs() << "Introducers:\n"; visitBorrowIntroducers(arguments.takeValue(), [](SILValue def) { def->dump(); return true; }); } }; // Arguments: // - SILValue: value // Dumps: // - function // - the computed pruned liveness // - the liveness boundary struct LinearLivenessTest : UnitTest { LinearLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SILValue value = arguments.takeValue(); getFunction()->dump(); llvm::dbgs() << "Linear liveness: " << value; LinearLiveness liveness(value); liveness.compute(); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.getLiveness().computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - SILValue: value // Dumps: // - function // - the computed pruned liveness // - the liveness boundary struct InteriorLivenessTest : UnitTest { InteriorLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SILValue value = arguments.takeValue(); getFunction()->dump(); llvm::dbgs() << "Interior liveness: " << value; auto *dominanceAnalysis = getAnalysis(); DominanceInfo *domTree = dominanceAnalysis->get(getFunction()); InteriorLiveness liveness(value); auto handleInnerScope = [](SILValue innerBorrow) { llvm::outs() << "Inner scope: " << innerBorrow; }; liveness.compute(domTree, handleInnerScope); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.getLiveness().computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - SILValue: value // Dumps: // - function // - the computed pruned liveness // - the liveness boundary struct ExtendedLinearLivenessTest : UnitTest { ExtendedLinearLivenessTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SILValue value = arguments.takeValue(); getFunction()->dump(); llvm::dbgs() << "Extended liveness: " << value; ExtendedLinearLiveness liveness(value); liveness.compute(); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; liveness.getLiveness().computeBoundary(boundary); boundary.print(llvm::outs()); } }; // Arguments: // - SILValue: value // Dumps: // - function struct OSSALifetimeCompletionTest : UnitTest { OSSALifetimeCompletionTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { SILValue value = arguments.takeValue(); llvm::dbgs() << "OSSA lifetime completion: " << value; OSSALifetimeCompletion completion(getFunction(), /*domInfo*/nullptr); completion.completeOSSALifetime(value); getFunction()->dump(); } }; //===----------------------------------------------------------------------===// // MARK: SimplifyCFG Unit Tests //===----------------------------------------------------------------------===// struct SimplifyCFGSimplifyArgument : UnitTest { SimplifyCFGSimplifyArgument(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); auto *block = arguments.takeBlock(); auto index = arguments.takeUInt(); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifyArgument(block, index); } }; struct SimplifyCFGSimplifyBlockArgs : UnitTest { SimplifyCFGSimplifyBlockArgs(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifyBlockArgs(); } }; struct SimplifyCFGCanonicalizeSwitchEnum : UnitTest { SimplifyCFGCanonicalizeSwitchEnum(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .canonicalizeSwitchEnums(); } }; struct SimplifyCFGSimplifySwitchEnumBlock : UnitTest { SimplifyCFGSimplifySwitchEnumBlock(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifySwitchEnumBlock( cast(arguments.takeInstruction())); } }; struct SimplifyCFGSwitchEnumOnObjcClassOptional : UnitTest { SimplifyCFGSwitchEnumOnObjcClassOptional(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifySwitchEnumOnObjcClassOptional( cast(arguments.takeInstruction())); } }; struct SimplifyCFGSimplifySwitchEnumUnreachableBlocks : UnitTest { SimplifyCFGSimplifySwitchEnumUnreachableBlocks(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifySwitchEnumUnreachableBlocks( cast(arguments.takeInstruction())); } }; struct SimplifyCFGSimplifyTermWithIdenticalDestBlocks : UnitTest { SimplifyCFGSimplifyTermWithIdenticalDestBlocks(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .simplifyTermWithIdenticalDestBlocks(arguments.takeBlock()); } }; struct SimplifyCFGTryJumpThreading : UnitTest { SimplifyCFGTryJumpThreading(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto *passToRun = cast(createSimplifyCFG()); passToRun->injectPassManager(getPass()->getPassManager()); passToRun->injectFunction(getFunction()); SimplifyCFG(*getFunction(), *passToRun, /*VerifyAll=*/false, /*EnableJumpThread=*/false) .tryJumpThreading(cast(arguments.takeInstruction())); } }; //===----------------------------------------------------------------------===// // MARK: AccessPath Unit Tests //===----------------------------------------------------------------------===// struct AccessUseTestVisitor : public AccessUseVisitor { AccessUseTestVisitor() : AccessUseVisitor(AccessUseType::Overlapping, NestedAccessType::IgnoreAccessBegin) {} bool visitUse(Operand *op, AccessUseType useTy) override { switch (useTy) { case AccessUseType::Exact: llvm::errs() << "Exact Use: "; break; case AccessUseType::Inner: llvm::errs() << "Inner Use: "; break; case AccessUseType::Overlapping: llvm::errs() << "Overlapping Use "; break; } llvm::errs() << *op->getUser(); return true; } }; struct AccessPathBaseTest : UnitTest { AccessPathBaseTest(UnitTestRunner *pass) : UnitTest(pass) {} void invoke(Arguments &arguments) override { auto value = arguments.takeValue(); getFunction()->dump(); llvm::outs() << "Access path base: " << value; auto accessPathWithBase = AccessPathWithBase::compute(value); AccessUseTestVisitor visitor; visitAccessPathBaseUses(visitor, accessPathWithBase, getFunction()); } }; /// [new_tests] Add the new UnitTest subclass above this line. //===----------------------------------------------------------------------===// // MARK: Unit Test Registration //===----------------------------------------------------------------------===// template void UnitTestRunner::withTest(StringRef name, Doit doit) { #define ADD_UNIT_TEST_SUBCLASS(STRING, SUBCLASS) \ if (name == STRING) { \ SUBCLASS it{this}; \ doit(&it); \ return; \ } // Alphabetical mapping from string to unit test subclass. ADD_UNIT_TEST_SUBCLASS("accesspath-base", AccessPathBaseTest) ADD_UNIT_TEST_SUBCLASS("canonicalize-ossa-lifetime", CanonicalizeOSSALifetimeTest) ADD_UNIT_TEST_SUBCLASS("canonicalize-borrow-scope", CanonicalizeBorrowScopeTest) ADD_UNIT_TEST_SUBCLASS("dump-function", DumpFunction) ADD_UNIT_TEST_SUBCLASS("extended-liveness", ExtendedLinearLivenessTest) ADD_UNIT_TEST_SUBCLASS("find-borrow-introducers", FindBorrowIntroducers) ADD_UNIT_TEST_SUBCLASS("find-enclosing-defs", FindEnclosingDefsTest) ADD_UNIT_TEST_SUBCLASS("function-get-self-argument-index", FunctionGetSelfArgumentIndex) ADD_UNIT_TEST_SUBCLASS("has-pointer-escape", OwnershipUtilsHasPointerEscape) ADD_UNIT_TEST_SUBCLASS("print-type-lowering", PrintTypeLowering) ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest) ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest) ADD_UNIT_TEST_SUBCLASS("is-lexical", IsLexicalTest) ADD_UNIT_TEST_SUBCLASS("lexical-destroy-folding", LexicalDestroyFoldingTest) ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest) ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest) ADD_UNIT_TEST_SUBCLASS("multidefuse-liveness", MultiDefUseLivenessTest) ADD_UNIT_TEST_SUBCLASS("fieldsensitive-multidefuse-liverange", FieldSensitiveMultiDefUseLiveRangeTest) ADD_UNIT_TEST_SUBCLASS("ossa-lifetime-completion", OSSALifetimeCompletionTest) ADD_UNIT_TEST_SUBCLASS("pruned-liveness-boundary-with-list-of-last-users-insertion-points", PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest) ADD_UNIT_TEST_SUBCLASS("shrink-borrow-scope", ShrinkBorrowScopeTest) // SimplifyCFG unit tests ADD_UNIT_TEST_SUBCLASS("simplify-cfg-simplify-argument", SimplifyCFGSimplifyArgument) ADD_UNIT_TEST_SUBCLASS("simplify-cfg-simplify-block-args", SimplifyCFGSimplifyBlockArgs) ADD_UNIT_TEST_SUBCLASS("simplify-cfg-canonicalize-switch-enum", SimplifyCFGCanonicalizeSwitchEnum) ADD_UNIT_TEST_SUBCLASS("simplify-cfg-simplify-switch-enum-block", SimplifyCFGSimplifySwitchEnumBlock) ADD_UNIT_TEST_SUBCLASS( "simplify-cfg-simplify-switch-enum-unreachable-blocks", SimplifyCFGSimplifySwitchEnumUnreachableBlocks) ADD_UNIT_TEST_SUBCLASS( "simplify-cfg-simplify-switch-enum-on-objc-class-optional", SimplifyCFGSwitchEnumOnObjcClassOptional) ADD_UNIT_TEST_SUBCLASS( "simplify-cfg-simplify-term-with-identical-dest-blocks", SimplifyCFGSimplifyTermWithIdenticalDestBlocks) ADD_UNIT_TEST_SUBCLASS("simplify-cfg-try-jump-threading", SimplifyCFGTryJumpThreading) ADD_UNIT_TEST_SUBCLASS("scoped-address-liveness", ScopedAddressLivenessTest) ADD_UNIT_TEST_SUBCLASS("ssa-liveness", SSALivenessTest) ADD_UNIT_TEST_SUBCLASS("test-specification-parsing", TestSpecificationTest) ADD_UNIT_TEST_SUBCLASS("visit-inner-adjacent-phis", VisitInnerAdjacentPhisTest) /// [new_tests] Add the new mapping from string to subclass above this line. /// Please sort alphabetically by name to help reduce merge /// conflicts. #undef ADD_UNIT_TEST_SUBCLASS llvm::errs() << "No test named: " << name << "\n"; assert(false && "Invalid test name"); } template Analysis *UnitTest::getAnalysis() { return pass->getAnalysis(); } SILFunction *UnitTest::getFunction() { return getPass()->getFunction(); } } // anonymous namespace //===----------------------------------------------------------------------===// // Top Level Entry Point //===----------------------------------------------------------------------===// SILTransform *swift::createUnitTestRunner() { return new UnitTestRunner(); }