|
| 1 | +//===---- Test.h - Testing based on test_specification insts -*- C++ ----*-===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift.org open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | +// |
| 13 | +// TO ADD A NEW TEST, just add a new FunctionTest instance. |
| 14 | +// - In the source file containing the functionality you want to test. |
| 15 | +// - #include "swift/SIL/Test.h" |
| 16 | +// - namespace swift::test { |
| 17 | +// static FunctionTest MyNewTest( |
| 18 | +// "my-new-test", |
| 19 | +// [](auto &function, auto &arguments, auto &test) { |
| 20 | +// }); |
| 21 | +// } // end namespace swift::test |
| 22 | +// |
| 23 | +//===----------------------------------------------------------------------===// |
| 24 | +// |
| 25 | +// Provides a mechanism for writing tests against compiler code in the context |
| 26 | +// of a function. The goal is to get the same effect as calling a function and |
| 27 | +// checking its output. |
| 28 | +// |
| 29 | +// This is done via the test_specification instruction. Using one or more |
| 30 | +// instances of it in your test case's SIL function, you can specify which test |
| 31 | +// (instance of FunctionTest) should be run and what arguments should be |
| 32 | +// provided to it. The test grabs the arguments it expects out of the |
| 33 | +// test::Arguments instance it is provided. It calls some function or |
| 34 | +// functions. It then prints out interesting results. These results can then |
| 35 | +// be FileCheck'd. |
| 36 | +// |
| 37 | +// CASE STUDY: |
| 38 | +// Here's an example of how it works: |
| 39 | +// 0) A source file, NeatUtils.cpp contains |
| 40 | +// |
| 41 | +// static void myNeatoUtility(unsigned, SILValue, SILFunction *) { ... } |
| 42 | +// |
| 43 | +// and |
| 44 | +// |
| 45 | +// static FunctionTest MyNeatoUtilityTest( |
| 46 | +// "my-neato-utility", |
| 47 | +// [](auto *test, auto *function, auto &arguments) { |
| 48 | +// // The code here is described in detail below. |
| 49 | +// // See 4). |
| 50 | +// auto count = arguments.takeUInt(); |
| 51 | +// auto target = arguments.takeValue(); |
| 52 | +// auto callee = arguments.takeFunction(); |
| 53 | +// // See 5) |
| 54 | +// myNeatoUtility(count, target, callee); |
| 55 | +// // See 6) |
| 56 | +// getFunction()->dump(); |
| 57 | +// }); |
| 58 | +// 1) A test test/SILOptimizer/interesting_functionality_unit.sil runs the |
| 59 | +// TestRunner pass: |
| 60 | +// // RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s |
| 61 | +// 2) A function in interesting_functionality_unit.sil contains the |
| 62 | +// test_specification instruction. |
| 63 | +// sil @f : $() -> () { |
| 64 | +// ... |
| 65 | +// test_specification "my-neato-utility 43 @trace[2] @function[other_fun]" |
| 66 | +// ... |
| 67 | +// } |
| 68 | +// 3) TestRunner finds the FunctionTest instance MyNeatoUtilityTest registered |
| 69 | +// under the name "my-neato-utility", and calls ::run() on it, passing an |
| 70 | +// the pass, the function AND most importantly an test::Arguments instance |
| 71 | +// that contains |
| 72 | +// (43 : unsigned long, someValue : SILValue, other_fun : SILFunction *) |
| 73 | +// |
| 74 | +// 4) MyNeatoUtilityTest calls takeUInt(), takeValue(), and takeFunction() on |
| 75 | +// the test::Arguments instance. |
| 76 | +// auto count = arguments.takeUInt(); |
| 77 | +// auto target = arguments.takeValue(); |
| 78 | +// auto callee = arguments.takeFunction(); |
| 79 | +// WARNING: Don't call more than one of these in a single expression! The |
| 80 | +// order of evaluation is implementation defined, but the values |
| 81 | +// must be taken out of the arguments in the order they appear in |
| 82 | +// the test. |
| 83 | +// 5) MyNeatoUtilityTest calls myNeatoUtility, passing these values along. |
| 84 | +// myNeatoUtility(count, target, callee); |
| 85 | +// 6) MyNeatoUtilityTest then dumps out the current function, which was modified |
| 86 | +// in the process. |
| 87 | +// getFunction()->dump(); |
| 88 | +// 7) The test file test/SILOptimizer/interesting_functionality_unit.sil matches |
| 89 | +// the |
| 90 | +// expected contents of the modified function: |
| 91 | +// // CHECK-LABEL: sil @f |
| 92 | +// // CHECK-NOT: function_ref @other_fun |
| 93 | +// |
| 94 | +//===----------------------------------------------------------------------===// |
| 95 | + |
| 96 | +#include "swift/SIL/ParseTestSpecification.h" |
| 97 | +#include "llvm/ADT/STLFunctionalExtras.h" |
| 98 | + |
| 99 | +namespace swift { |
| 100 | + |
| 101 | +class SILFunction; |
| 102 | +class SILFunctionTransform; |
| 103 | +class SILPassManager; |
| 104 | +class DominanceAnalysis; |
| 105 | +class DominanceInfo; |
| 106 | + |
| 107 | +namespace test { |
| 108 | + |
| 109 | +struct Arguments; |
| 110 | +class TestRunner; |
| 111 | + |
| 112 | +/// A test that is run when the corresponding test_specification instruction is |
| 113 | +/// processed by the TestRunner pass. |
| 114 | +/// |
| 115 | +/// Tests are instantiated once at program launch. At that time, they are |
| 116 | +/// stored in a registry name -> test. When an test_specification instruction |
| 117 | +/// naming a particular test is processed, that test is run. |
| 118 | +class FunctionTest final { |
| 119 | +public: |
| 120 | + /// Wraps a test lambda. |
| 121 | + /// |
| 122 | + /// There are three arguments in order of priority: |
| 123 | + /// - SILFunction & - the function with the test_specification instruction |
| 124 | + /// - Arguments & - the resolved list of args specified by the instruction |
| 125 | + /// - FunctionTest & - the test being run; used to find less commonly used |
| 126 | + /// values such as the results of analyses |
| 127 | + using Invocation = |
| 128 | + llvm::function_ref<void(SILFunction &, Arguments &, FunctionTest &)>; |
| 129 | + |
| 130 | +private: |
| 131 | + /// The lambda to be run. |
| 132 | + Invocation invocation; |
| 133 | + |
| 134 | +public: |
| 135 | + /// Creates a test that will run \p invocation and stores it in the global |
| 136 | + /// registry. |
| 137 | + /// |
| 138 | + /// To create a new test, just write |
| 139 | + /// |
| 140 | + /// namespace swift::test { |
| 141 | + /// static FunctionTest myTest("my-test", []( |
| 142 | + /// SILFunction &function, Arguments &arguments, FunctionTest &test){ |
| 143 | + /// // test code |
| 144 | + /// }); |
| 145 | + /// } // end namespace swift::test |
| 146 | + FunctionTest(StringRef name, Invocation invocation); |
| 147 | + |
| 148 | + /// Computes and returns the function's dominance tree. |
| 149 | + DominanceInfo *getDominanceInfo(); |
| 150 | + |
| 151 | + /// Returns the active pass manager. |
| 152 | + SILPassManager *getPassManager(); |
| 153 | + |
| 154 | + /// Returns the indicated analysis. |
| 155 | + /// |
| 156 | + /// NOTE: This function can only be called from files that can import |
| 157 | + /// SILFunctionTransform--those in SILOptimizer and libraries that |
| 158 | + /// depend on it. See `Layering` below for more details. |
| 159 | + template <typename Analysis, typename Transform = SILFunctionTransform> |
| 160 | + Analysis *getAnalysis(); |
| 161 | + |
| 162 | +//===----------------------------------------------------------------------===// |
| 163 | +//=== MARK: Implementation Details === |
| 164 | +//===----------------------------------------------------------------------===// |
| 165 | +// |
| 166 | +// To read, write, and debug function test failures, see above. |
| 167 | +// |
| 168 | +// The following implementation details have to do with executing these tests |
| 169 | +// and handling library layering. |
| 170 | + |
| 171 | +// TestRunner interface: |
| 172 | +private: |
| 173 | + struct Dependencies; |
| 174 | + |
| 175 | + /// Run the stored \p invocation lambda. |
| 176 | + void run(SILFunction &function, Arguments &arguments, |
| 177 | + SILFunctionTransform &pass, Dependencies &dependencies); |
| 178 | + |
| 179 | + /// Retrieve the test with named \p name from the global registry. |
| 180 | + static FunctionTest *get(StringRef name); |
| 181 | + |
| 182 | + /// The instance of the TestRunner pass currently running this test. Only |
| 183 | + /// non-null when FunctionTest::run is executing. |
| 184 | + SILFunctionTransform *pass; |
| 185 | + /// The function which the TestRunner pass is currently processing. Only |
| 186 | + /// non-null when FunctionTest::run is executing. |
| 187 | + SILFunction *function; |
| 188 | + |
| 189 | + friend class TestRunner; |
| 190 | + |
| 191 | +// Layering: |
| 192 | +// |
| 193 | +// Dealing with the differences between the SIL and SILOptimizer libraries. |
| 194 | +// |
| 195 | +// Motivation: 1) Enable writing FunctionTests inline in any source file that |
| 196 | +// can import the SIL library's headers. |
| 197 | +// 2) Allow tests to access the tools that are visible in the source |
| 198 | +// file where they are written. |
| 199 | +// |
| 200 | +// Two examples: |
| 201 | +// A) Tests in the SIL library (or libraries that can import its headers) must |
| 202 | +// be able to access the results of analyses, e.g. DominanceInfo. |
| 203 | +// B) Tests in the SILOptimizer library (or libraries that can import its |
| 204 | +// headers) must be able to access analyses themselves, e.g. |
| 205 | +// DominanceAnalysis. |
| 206 | +// |
| 207 | +// Because analyses aren't part of the SIL library, the code that computes such |
| 208 | +// a result can't called from one of it's source files. For example, this |
| 209 | +// isn't possible in some SIL library cpp file |
| 210 | +// |
| 211 | +// test->getPass()->getAnalysis<DominanceAnalysis>()->get(function); |
| 212 | +// |
| 213 | +// because DominanceAnalysis isn't visible in SIL (it's defined in |
| 214 | +// SILOptimizer). Indeed calling anything on getPass() isn't possible because |
| 215 | +// SILFunctionTransform isn't visible in SIL (again, defined in SILOptimizer). |
| 216 | +// |
| 217 | +// This is further exacerbated by the fact that getting an analysis from a pass |
| 218 | +// requires instantiating a template function. |
| 219 | +// |
| 220 | +// There are two consequences: |
| 221 | +// |
| 222 | +// 1) Dependencies must be provided directly (i.e. not via analyses) to tests |
| 223 | +// in the SIL library. |
| 224 | +// |
| 225 | +// For example ::getDominanceInfo in `struct Dependencies` below. |
| 226 | +// |
| 227 | +// 2) The code that allows tests in the SILOptimizer library to access analyses |
| 228 | +// must not be instantiated when it's imported into the SIL library. |
| 229 | +// |
| 230 | +// Concretely, the "extra" template argument `typename Transform` on |
| 231 | +// getAnalysis and the thunk in the impl:: namespace below. |
| 232 | +private: |
| 233 | + /// Functions for getting tools that are visible in the SIL library. |
| 234 | + /// |
| 235 | + /// The implementation is provided in the SILOptimizer libary where analyses |
| 236 | + /// are visible: TestRunner::FunctionTestDependenciesImpl. |
| 237 | + struct Dependencies { |
| 238 | + virtual DominanceInfo *getDominanceInfo() = 0; |
| 239 | + virtual SILPassManager *getPassManager() = 0; |
| 240 | + virtual ~Dependencies(){}; |
| 241 | + }; |
| 242 | + |
| 243 | + /// The vendor for dependencies provided to the test by TestRunner. Only |
| 244 | + /// non-null when FunctionTest::run is executing. |
| 245 | + Dependencies *dependencies; |
| 246 | +}; |
| 247 | + |
| 248 | +/// Thunks for delaying template instantiation. |
| 249 | +/// |
| 250 | +/// Because C++ lacks "runtime concepts" (Swift's protocols), it's not possible |
| 251 | +/// for the Dependencies struct to have a method like |
| 252 | +/// |
| 253 | +/// template <typename Analysis> |
| 254 | +/// virtual Analysis *getAnalysis() = 0; |
| 255 | +/// |
| 256 | +/// In order to give tests access to analyses, SILFunctionTransform::getAnalysis |
| 257 | +/// template must be instantiated by the test code. This could be done by |
| 258 | +/// making the test-running pass available directly to test code. Here, |
| 259 | +/// instead, it is done by providing a passthrough on test. But this is |
| 260 | +/// complicated by library layering: |
| 261 | +/// |
| 262 | +/// Calling methods directly on `FunctionTest::pass` is illegal from the SIL |
| 263 | +/// library (or any library that can't import SILOptimizer headers, where |
| 264 | +/// SILFunctionTransform is defined): |
| 265 | +/// |
| 266 | +/// member access into incomplete type 'swift::SILFunctionTransform' |
| 267 | +/// |
| 268 | +/// Instead, these thunks are called with the pass as an argument. Because the |
| 269 | +/// static type of pass in these thunks is just "pointer to `typename |
| 270 | +/// Transform`", it's legal to call methods on the pass here: these templates |
| 271 | +/// can only be instantiated in libraries that can import SILOptimizer. |
| 272 | +namespace impl { |
| 273 | +template <typename Analysis, typename Transform> |
| 274 | +Analysis *getAnalysisFromTransform(Transform *pass) { |
| 275 | + return pass->template getAnalysis<Analysis>(); |
| 276 | +} |
| 277 | +} // end namespace impl |
| 278 | + |
| 279 | +template <typename Analysis, typename Transform> |
| 280 | +Analysis *FunctionTest::getAnalysis() { |
| 281 | + return impl::getAnalysisFromTransform<Analysis, Transform>(pass); |
| 282 | +} |
| 283 | +} // namespace test |
| 284 | +} // namespace swift |
0 commit comments