Skip to content

Commit d2c8ede

Browse files
Merge pull request #67069 from nate-chandler/test/20230629/2
[Test] Moved SIL function tests next to tested code.
2 parents 47b73eb + 22df319 commit d2c8ede

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1404
-1099
lines changed

include/swift/ABI/ProtocolDispatchStrategy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#define SWIFT_ABI_PROTOCOLDISPATCHSTRATEGY_H
2121

2222
#include "swift/Basic/Unreachable.h"
23+
#include <cstdint>
2324

2425
namespace swift {
2526

include/swift/SILOptimizer/Utils/ParseTestSpecification.h renamed to include/swift/SIL/ParseTestSpecification.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
//
13-
// This file defines Test::Argument.
13+
// This file defines test::Argument.
1414
//
1515
//===----------------------------------------------------------------------===//
1616

include/swift/SIL/Test.h

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ PASS(SimplifyUnreachableContainingBlocks, "simplify-unreachable-containing-block
435435
"Utility pass. Removes all non-term insts from blocks with unreachable terms")
436436
PASS(SerializeSILPass, "serialize-sil",
437437
"Utility pass. Serializes the current SILModule")
438-
PASS(UnitTestRunner, "unit-test-runner",
438+
PASS(UnitTestRunner, "test-runner",
439439
"Utility pass. Parses arguments and runs code with them.")
440440
PASS(YieldOnceCheck, "yield-once-check",
441441
"Check correct usage of yields in yield-once coroutines")

include/swift/SILOptimizer/PassManager/Transforms.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ namespace swift {
8181
/// Get the transform's name as a C++ identifier.
8282
llvm::StringRef getID() { return PassKindID(getPassKind()); }
8383

84-
protected:
8584
/// Searches for an analysis of type T in the list of registered
8685
/// analysis. If the analysis is not found, the program terminates.
8786
template<typename T>

include/swift/SILOptimizer/Transforms/SimplifyCFG.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
#include "llvm/ADT/SmallVector.h"
2121
#include "llvm/ADT/Statistic.h"
2222

23+
namespace swift {
24+
class DominanceAnalysis;
25+
}
26+
2327
using namespace swift;
2428

2529
struct ThreadInfo;

lib/SIL/IR/SILValue.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "swift/SIL/SILInstruction.h"
1818
#include "swift/SIL/SILModule.h"
1919
#include "swift/SIL/SILVisitor.h"
20+
#include "swift/SIL/Test.h"
2021
#include "llvm/ADT/StringSwitch.h"
2122

2223
using namespace swift;
@@ -154,6 +155,23 @@ bool ValueBase::isLexical() const {
154155
return false;
155156
}
156157

158+
namespace swift::test {
159+
// Arguments:
160+
// - value
161+
// Dumps:
162+
// - value
163+
// - whether it's lexical
164+
static FunctionTest IsLexicalTest("is-lexical", [](auto &function,
165+
auto &arguments,
166+
auto &test) {
167+
auto value = arguments.takeValue();
168+
auto isLexical = value->isLexical();
169+
value->dump();
170+
auto *boolString = isLexical ? "true" : "false";
171+
llvm::errs() << boolString << "\n";
172+
});
173+
} // end namespace swift::test
174+
157175
bool ValueBase::isGuaranteedForwarding() const {
158176
if (getOwnershipKind() != OwnershipKind::Guaranteed) {
159177
return false;

0 commit comments

Comments
 (0)